work on menu data/home menu/navbar/routing

This commit is contained in:
Géry Debongnie
2019-02-04 13:23:41 +01:00
parent b292a66523
commit 70b25fc7b8
14 changed files with 162 additions and 74 deletions
+3
View File
@@ -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];
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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?
+80 -17
View File
@@ -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;
}
+3 -19
View File
@@ -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);
}
}
}
+3 -1
View File
@@ -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");
}
}
+7 -6
View File
@@ -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);
}
}
+47 -4
View File
@@ -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);
}
}
+7 -4
View File
@@ -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 &amp;&amp; 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 + '&amp;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&amp;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>"
`;
+3 -1
View File
@@ -28,7 +28,9 @@ beforeEach(() => {
parent_id: false,
action: false,
icon: "fa fa-test",
children: []
children: [],
menuId: 14,
actionId: 43
}
},
roots: [14]
+1 -1
View File
@@ -19,7 +19,7 @@ beforeEach(() => {
fixture = makeTestFixture();
env = makeTestEnv();
env.qweb.loadTemplates(templates);
props = { inHome: false };
props = { inHome: false, app: null };
});
afterEach(() => {