mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
work on menu data/home menu/navbar/routing
This commit is contained in:
@@ -174,6 +174,9 @@ export class Widget<T extends WEnv, Props> 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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export const makeEnvironment = memoize(async function(): Promise<Env> {
|
||||
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
|
||||
|
||||
@@ -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[] = (<any>window).odoo.menus;
|
||||
const menuItems: BaseMenuItem[] = (<any>window).odoo.menus;
|
||||
const menuInfo = processMenuItems(menuItems);
|
||||
delete (<any>window).odoo.menus; // overkill?
|
||||
|
||||
|
||||
@@ -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 }, <MenuItem>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(
|
||||
(<string>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;
|
||||
}
|
||||
|
||||
@@ -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<Type<Widget<Env, any>>>;
|
||||
stack: ActionStack;
|
||||
|
||||
constructor(router: IRouter, registry: Registry<Type<Widget<Env, any>>>) {
|
||||
constructor(registry: Registry<Type<Widget<Env, any>>>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Env, Props> {
|
||||
@@ -15,6 +17,6 @@ export class Navbar extends Widget<Env, Props> {
|
||||
|
||||
toggleHome(ev: MouseEvent) {
|
||||
ev.preventDefault();
|
||||
this.trigger("toggle-home-menu");
|
||||
this.trigger("toggle_home_menu");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Env, Props> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Env, Props> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<div t-name="web.web_client" class="o_web_client">
|
||||
<t t-widget="Navbar" t-props="{inHome:state.inHome,app:state.currentApp}" t-on-toggle-home-menu="toggleHome"/>
|
||||
<t t-widget="Navbar" t-props="{inHome:state.inHome,app:state.currentApp}" t-on-toggle_home_menu="toggleHome"/>
|
||||
<t t-if="state.inHome">
|
||||
<t t-widget="HomeMenu" t-keep-alive="1" t-props="{menuInfo:props.menuInfo}"/>
|
||||
<t t-widget="HomeMenu" t-keep-alive="1" t-props="{menuInfo:props.menuInfo}" t-on-app_opened="openApp"/>
|
||||
</t>
|
||||
<t t-else="1">
|
||||
<t t-widget="ActionContainer" t-props="{stack:state.stack}" t-keep-alive="1"/>
|
||||
@@ -18,7 +18,10 @@
|
||||
|
||||
<div t-name="web.navbar" class="o_navbar" t-att-class="props.inHome ? 'o_navbar o_in_home' : 'o_navbar'">
|
||||
<a aria-label="Applications" class="o_title fa fa-th" href="#" title="Applications" accesskey="h" t-on-click="toggleHome"/>
|
||||
<ul t-if="!props.inHome">
|
||||
<ul t-if="!props.inHome && props.app">
|
||||
<a class="o_menu_brand" href="#" role="button">
|
||||
<t t-esc="props.app.name"/>
|
||||
</a>
|
||||
<li t-foreach="env.menus" t-as="menu">
|
||||
<a t-att-href="getUrl(menu)">
|
||||
<t t-esc="menu.title"/>
|
||||
@@ -30,7 +33,7 @@
|
||||
|
||||
<div t-name="web.home_menu" class="o_home_menu">
|
||||
<div class="o_apps">
|
||||
<a t-foreach="apps" t-as="app" href="#" class="o_app" t-on-click="openApp(app.id)">
|
||||
<a t-foreach="apps" t-as="app" t-att-href="'#menu_id=' + app.menuId + '&action_id=' + app.actionId" class="o_app" t-on-click="openApp(app)">
|
||||
<i t-att-class="app.icon + ' o_app_icon fa-3x fa-fw'"/>
|
||||
<div class="o_caption">
|
||||
<t t-esc="app.name"/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`can be rendered 1`] = `
|
||||
"<div class=\\"o_home_menu\\">
|
||||
<div class=\\"o_apps\\">
|
||||
<a href=\\"#\\" class=\\"o_app\\">
|
||||
<a href=\\"#menu_id=14&action_id=43\\" class=\\"o_app\\">
|
||||
<i class=\\"fa fa-test o_app_icon fa-3x fa-fw\\"></i>
|
||||
<div class=\\"o_caption\\">
|
||||
Demo
|
||||
|
||||
@@ -3,33 +3,20 @@
|
||||
exports[`can be rendered 1`] = `
|
||||
"<div class=\\"o_navbar\\">
|
||||
<a aria-label=\\"Applications\\" class=\\"o_title fa fa-th\\" href=\\"#\\" title=\\"Applications\\" accesskey=\\"h\\"></a>
|
||||
<ul>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`can render one menu item 1`] = `
|
||||
"<div class=\\"o_navbar\\">
|
||||
<a aria-label=\\"Applications\\" class=\\"o_title fa fa-th\\" href=\\"#\\" title=\\"Applications\\" accesskey=\\"h\\"></a>
|
||||
<ul>
|
||||
<li>
|
||||
<a href=\\"\\">
|
||||
menu
|
||||
</a>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
||||
</div>"
|
||||
`;
|
||||
|
||||
exports[`mobile mode: navbar is different 1`] = `
|
||||
"<div class=\\"o_navbar\\">
|
||||
<a aria-label=\\"Applications\\" class=\\"o_title fa fa-th\\" href=\\"#\\" title=\\"Applications\\" accesskey=\\"h\\"></a>
|
||||
<ul>
|
||||
|
||||
<li>MOBILEMODE</li>
|
||||
</ul>
|
||||
|
||||
</div>"
|
||||
`;
|
||||
|
||||
@@ -28,7 +28,9 @@ beforeEach(() => {
|
||||
parent_id: false,
|
||||
action: false,
|
||||
icon: "fa fa-test",
|
||||
children: []
|
||||
children: [],
|
||||
menuId: 14,
|
||||
actionId: 43
|
||||
}
|
||||
},
|
||||
roots: [14]
|
||||
|
||||
@@ -19,7 +19,7 @@ beforeEach(() => {
|
||||
fixture = makeTestFixture();
|
||||
env = makeTestEnv();
|
||||
env.qweb.loadTemplates(templates);
|
||||
props = { inHome: false };
|
||||
props = { inHome: false, app: null };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
Reference in New Issue
Block a user