diff --git a/web/static/src/ts/core/Widget.ts b/web/static/src/ts/core/Widget.ts index 0eb93031..acc7f7b3 100644 --- a/web/static/src/ts/core/Widget.ts +++ b/web/static/src/ts/core/Widget.ts @@ -174,6 +174,9 @@ export class Widget extends EventBus { * case, it will simply update the state and will not rerender */ async updateState(nextState: Object) { + if (Object.keys(nextState).length === 0) { + return; + } for (let key in nextState) { if (key in this.state) { this.state[key] = nextState[key]; diff --git a/web/static/src/ts/core/router.ts b/web/static/src/ts/core/router.ts index d4b00d2a..f321c824 100644 --- a/web/static/src/ts/core/router.ts +++ b/web/static/src/ts/core/router.ts @@ -58,7 +58,7 @@ export class Router extends EventBus implements IRouter { getQuery(): Query { const query = {}; - for (let part of window.location.hash.slice(1).split("?")) { + for (let part of window.location.hash.slice(1).split("&")) { let [key, value] = part.split("="); query[key] = value; } diff --git a/web/static/src/ts/env.ts b/web/static/src/ts/env.ts index ae7a1fa4..241101c2 100644 --- a/web/static/src/ts/env.ts +++ b/web/static/src/ts/env.ts @@ -65,7 +65,7 @@ export const makeEnvironment = memoize(async function(): Promise { const qweb = new QWeb(); const router = new Router(); const ajax = new Ajax(); - const actionManager = new ActionManager(router, actionRegistry); + const actionManager = new ActionManager(actionRegistry); const notifications = new NotificationManager(); // demo data diff --git a/web/static/src/ts/main.ts b/web/static/src/ts/main.ts index fab9ca11..a9d07f65 100644 --- a/web/static/src/ts/main.ts +++ b/web/static/src/ts/main.ts @@ -3,7 +3,7 @@ import { debounce } from "./core/utils"; import { makeEnvironment } from "./env"; import { Root } from "./widgets/root"; -import { MenuItem, processMenuItems } from "./misc/menu_helpers"; +import { BaseMenuItem, processMenuItems } from "./misc/menu_helpers"; //------------------------------------------------------------------------------ // Application bootstrapping @@ -13,7 +13,7 @@ document.addEventListener("DOMContentLoaded", async function() { const env = await makeEnvironment(); // menu processing - const menuItems: MenuItem[] = (window).odoo.menus; + const menuItems: BaseMenuItem[] = (window).odoo.menus; const menuInfo = processMenuItems(menuItems); delete (window).odoo.menus; // overkill? diff --git a/web/static/src/ts/misc/menu_helpers.ts b/web/static/src/ts/misc/menu_helpers.ts index 5c111edd..9a7459ea 100644 --- a/web/static/src/ts/misc/menu_helpers.ts +++ b/web/static/src/ts/misc/menu_helpers.ts @@ -1,3 +1,5 @@ +import { findInTree } from "../core/utils"; + //------------------------------------------------------------------------------ // Types //------------------------------------------------------------------------------ @@ -8,18 +10,17 @@ export interface BaseMenuItem { parent_id: number | false; action: string | false; icon: string | false; + children: BaseMenuItem[]; } export interface MenuItem extends BaseMenuItem { + menuId: number; + actionId: number; children: MenuItem[]; } -export interface ProcessedMenuItem extends BaseMenuItem { - children: number[]; -} - export interface MenuInfo { - menuMap: { [key: number]: ProcessedMenuItem | undefined }; + menuMap: { [key: number]: MenuItem | undefined }; roots: number[]; } @@ -27,28 +28,90 @@ export interface MenuInfo { // Helpers //------------------------------------------------------------------------------ -export function processMenuItems(items: MenuItem[]): MenuInfo { +export function processMenuItems(items: BaseMenuItem[]): MenuInfo { const menuMap: MenuInfo["menuMap"] = {}; const roots: number[] = []; + // build MenuItems for (let root of items) { roots.push(root.id); - addToMap(root); + addToMap(root, root.id); } - function addToMap(m: MenuItem) { - let processedItem: ProcessedMenuItem = Object.assign({}, m, { - children: m.children.map(c => c.id) - }); - menuMap[processedItem.id] = processedItem; - m.children.forEach(addToMap); + function addToMap(m: BaseMenuItem, menuId: number) { + let item: MenuItem = Object.assign({ menuId, actionId: -1 }, m); + menuMap[item.id] = item; + m.children.forEach(c => addToMap(c, menuId)); + } + + // add proper actionId to every menuitems + for (let menuId in menuMap) { + const menu = menuMap[menuId]!; + let menuWithAction = menu && findInTree(menu, m => Boolean(m.action)); + if (menuWithAction) { + menu.actionId = parseInt( + (menuWithAction.action).split(",")[1], + 10 + ); + } } return { menuMap, roots }; } -// todo +export function getAppAndAction( + info: MenuInfo, + query: { menu_id?: string; action_id?: string } +): { app: MenuItem | null; actionId: number | null } { + let app = findApp(info, query.menu_id, query.action_id); + let actionId = findAction(info, query.menu_id, query.action_id); -// menu -> formatURL(menuID, menus), -// findApp(menuID, menus), -// findMenu + return { app, actionId }; +} + +function findApp( + info: MenuInfo, + menu_id?: string, + action_id?: string +): MenuItem | null { + if (menu_id) { + const menuID = parseInt(menu_id, 10); + if (info.roots.indexOf(menuID) > -1) { + const menu = info.menuMap[menuID]; + if (menu) { + return menu; + } + } + } + if (action_id) { + for (let itemID in info.menuMap) { + let menu = info.menuMap[itemID]; + const actionId = menu && menu.actionId; + if (actionId && String(actionId) === action_id) { + if (menu) { + let topMenu = info.menuMap[menu.menuId]; + if (topMenu) { + return topMenu; + } + } + } + } + } + return null; +} + +function findAction( + info: MenuInfo, + menu_id?: string, + action_id?: string +): number | null { + if (action_id) { + return parseInt(action_id, 10); + } + if (menu_id) { + const menuID = parseInt(menu_id, 10); + const menu = info.menuMap[menuID]!; + return menu.actionId; + } + return null; +} diff --git a/web/static/src/ts/services/action_manager.ts b/web/static/src/ts/services/action_manager.ts index 4d11da5f..5ec80570 100644 --- a/web/static/src/ts/services/action_manager.ts +++ b/web/static/src/ts/services/action_manager.ts @@ -1,8 +1,7 @@ import { EventBus } from "../core/event_bus"; -import { Widget, Type } from "../core/widget"; -import { Env } from "../env"; import { Registry } from "../core/registry"; -import { IRouter, Query } from "../core/router"; +import { Type, Widget } from "../core/widget"; +import { Env } from "../env"; //------------------------------------------------------------------------------ // Types @@ -38,7 +37,6 @@ export type ActionEvent = "action_stack_updated"; type Callback = (stack: ActionStack) => void; export interface IActionManager { - activate(): void; doAction(request: ActionRequest): void; on(event: ActionEvent, owner: any, callback: Callback): void; getStack(): ActionStack; @@ -49,22 +47,15 @@ export interface IActionManager { //------------------------------------------------------------------------------ export class ActionManager extends EventBus implements IActionManager { - router: IRouter; registry: Registry>>; stack: ActionStack; - constructor(router: IRouter, registry: Registry>>) { + constructor(registry: Registry>>) { super(); - this.router = router; this.registry = registry; this.stack = []; } - activate() { - this.router.on("query_changed", this, this.update); - this.update(this.router.getQuery()); - } - doAction(request: ActionRequest) { if (typeof request === "number") { // this is an action ID @@ -89,11 +80,4 @@ export class ActionManager extends EventBus implements IActionManager { getStack(): ActionStack { return []; } - - update(query: Query) { - if ("action_id" in query) { - const actionID = parseInt(query.action_id); - this.doAction(actionID); - } - } } diff --git a/web/static/src/ts/widgets/Navbar.ts b/web/static/src/ts/widgets/Navbar.ts index afa5ddb6..f8fb8c21 100644 --- a/web/static/src/ts/widgets/Navbar.ts +++ b/web/static/src/ts/widgets/Navbar.ts @@ -1,8 +1,10 @@ import { Widget } from "../core/widget"; import { Env, Menu } from "../env"; +import { MenuItem } from "../misc/menu_helpers"; export interface Props { inHome: boolean; + app: MenuItem | null; } export class Navbar extends Widget { @@ -15,6 +17,6 @@ export class Navbar extends Widget { toggleHome(ev: MouseEvent) { ev.preventDefault(); - this.trigger("toggle-home-menu"); + this.trigger("toggle_home_menu"); } } diff --git a/web/static/src/ts/widgets/home_menu.ts b/web/static/src/ts/widgets/home_menu.ts index 04ac6d1a..aba94737 100644 --- a/web/static/src/ts/widgets/home_menu.ts +++ b/web/static/src/ts/widgets/home_menu.ts @@ -1,6 +1,6 @@ import { Widget } from "../core/widget"; import { Env } from "../env"; -import { MenuInfo, ProcessedMenuItem } from "../misc/menu_helpers"; +import { MenuInfo, MenuItem } from "../misc/menu_helpers"; //------------------------------------------------------------------------------ // Types @@ -17,12 +17,13 @@ export interface Props { export class HomeMenu extends Widget { template = "web.home_menu"; - openApp(appId: number) { - debugger; - } - - get apps(): ProcessedMenuItem[] { + get apps(): MenuItem[] { const info = this.props.menuInfo; return info.roots.map(root => info.menuMap[root]!); } + + openApp(app: MenuItem, event: MouseEvent) { + event.preventDefault(); + this.trigger("app_opened", app); + } } diff --git a/web/static/src/ts/widgets/root.ts b/web/static/src/ts/widgets/root.ts index e04e4fcb..02cc30ea 100644 --- a/web/static/src/ts/widgets/root.ts +++ b/web/static/src/ts/widgets/root.ts @@ -1,12 +1,13 @@ import { INotification } from "../core/notifications"; import { Widget } from "../core/widget"; import { Env } from "../env"; -import { MenuInfo } from "../misc/menu_helpers"; +import { MenuInfo, MenuItem, getAppAndAction } from "../misc/menu_helpers"; import { ActionStack } from "../services/action_manager"; import { ActionContainer } from "./action_container"; import { HomeMenu } from "./home_menu"; import { Navbar } from "./navbar"; import { Notification } from "./notification"; +import { Query } from "../core/router"; //------------------------------------------------------------------------------ // Types @@ -16,6 +17,7 @@ interface State { notifications: INotification[]; stack: ActionStack; inHome: boolean; + currentApp: MenuItem | null; } interface Props { @@ -33,20 +35,61 @@ export class Root extends Widget { state: State = { notifications: [], stack: [], - inHome: false + inHome: false, + currentApp: null }; + constructor(env: Env, props: Props) { + super(env, props); + const query = this.env.router.getQuery(); + let { app, actionId } = getAppAndAction(props.menuInfo, query); + this.state.currentApp = app; + if (!actionId) { + this.state.inHome = true; + } + } mounted() { + // notifications this.env.notifications.on("notifications_updated", this, notifs => this.updateState({ notifications: notifs }) ); + + // actions this.env.actionManager.on("action_stack_updated", this, stack => - this.updateState({ stack }) + this.updateState({ stack, inHome: false }) ); - this.env.actionManager.activate(); + this.env.router.on("query_changed", this, this.updateAction); + this.updateAction(this.env.router.getQuery()); + } + + private updateAction(query: Query) { + let { app, actionId } = getAppAndAction(this.props.menuInfo, query); + this.updateAppState(app, actionId); + } + + private updateAppState(app: MenuItem | null, actionId: number | null) { + const newApp = app || this.state.currentApp; + if (actionId) { + const query: Query = { action_id: String(actionId) }; + const menuId = newApp ? newApp.menuId : false; + if (menuId) { + query.menu_id = String(menuId); + } + if (app) { + this.updateState({ currentApp: app }); + } + this.env.router.navigate(query); + this.env.actionManager.doAction(actionId); + } else { + this.updateState({ inHome: true, currentApp: newApp }); + } } toggleHome() { this.updateState({ inHome: !this.state.inHome }); } + + openApp(app: MenuItem) { + this.updateAppState(app, app.actionId); + } } diff --git a/web/static/src/xml/templates.xml b/web/static/src/xml/templates.xml index ecda302e..b11e223e 100644 --- a/web/static/src/xml/templates.xml +++ b/web/static/src/xml/templates.xml @@ -2,9 +2,9 @@
- + - + @@ -18,7 +18,10 @@
-