work on initialization

This commit is contained in:
Géry Debongnie
2019-02-18 15:34:23 +01:00
parent 7c54787c32
commit 3446c5b504
20 changed files with 220 additions and 194 deletions
+71
View File
@@ -0,0 +1,71 @@
import { QWeb } from "./core/qweb_vdom";
import { idGenerator, memoize } from "./core/utils";
import { TemplateLoader } from "./loaders/templates";
import { actionRegistry } from "./registries";
import { RPC } from "./services/ajax";
import { ActionManager } from "./store/action_manager";
import { Ajax } from "./store/ajax";
import { NotificationManager } from "./store/notifications";
import { Router } from "./store/router";
import { Env } from "./widgets/widget";
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
interface Loaders {
loadTemplates: TemplateLoader;
}
interface Services {
rpc: RPC;
}
type EnvBuilder = (loaders: Loaders, services: Services) => Promise<Env>;
//------------------------------------------------------------------------------
// Environment
//------------------------------------------------------------------------------
/**
* init returns the main environment for the application.
*
* Note that it does not make much sense (except for tests) to have more than
* one environment. For example, with two environment, the router code in one
* environment will probably interfere with the code from the other environment.
*
* For this reason, the result of init is memoized: every call to
* this function will actually return the same environment.
*/
export const makeEnv: EnvBuilder = memoize(_makeEnv);
async function _makeEnv(loaders: Loaders, services: Services): Promise<Env> {
// services
const qweb = new QWeb();
const router = new Router();
const ajax = new Ajax(services.rpc);
const actionManager = new ActionManager(actionRegistry, ajax);
const notifications = new NotificationManager();
// templates
const templates = await loaders.loadTemplates();
qweb.addTemplate("default", "<div/>");
qweb.loadTemplates(templates);
const env: Env = {
// Base widget requirements
qweb,
getID: idGenerator(),
actionManager,
ajax,
notifications,
actionRegistry,
router,
rpc: ajax.rpc,
debug: false,
isMobile: window.innerWidth <= 768
};
return env;
}
-153
View File
@@ -1,153 +0,0 @@
import { Ajax } from "./store/ajax";
import { NotificationManager } from "./core/notifications";
import { QWeb } from "./core/qweb_vdom";
import { Router } from "./core/router";
import { findInTree, idGenerator, memoize } from "./core/utils";
import { actionRegistry } from "./registries";
import { ActionManager } from "./store/action_manager";
import { MenuInfo, MenuItem } from "./widgets/root";
import { Env } from "./widgets/widget";
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
interface InitializedData {
env: Env;
menuInfo: MenuInfo;
}
/**
* init returns the main environment for the application.
*
* Note that it does not make much sense (except for tests) to have more than
* one environment. For example, with two environment, the router code in one
* environment will probably interfere with the code from the other environment.
*
* For this reason, the result of init is memoized: every call to
* this function will actually return the same environment.
*/
export const init = memoize(async function(): Promise<InitializedData> {
// services
const qweb = new QWeb();
const router = new Router();
const ajax = new Ajax(ajaxFetch);
const actionManager = new ActionManager(actionRegistry, ajax);
const notifications = new NotificationManager();
// templates
const templates = await loadTemplates();
qweb.addTemplate("default", "<div/>");
qweb.loadTemplates(templates);
const menuInfo = loadMenus();
const env: Env = {
// Base widget requirements
qweb,
getID: idGenerator(),
actionManager,
ajax,
notifications,
actionRegistry,
router,
rpc: ajax.rpc,
debug: false,
isMobile: window.innerWidth <= 768
};
return { env, menuInfo };
});
//------------------------------------------------------------------------------
// Adapters
//------------------------------------------------------------------------------
function ajaxFetch(route: string, params: any): Promise<any> {
console.log("RPC", route, params);
const delay = Math.random() * 150;
return new Promise(resolve => setTimeout(resolve, delay));
}
/**
* Load xml templates as a string.
*/
async function loadTemplates(): Promise<string> {
const result = await fetch("templates.xml");
if (!result.ok) {
throw new Error("Error while fetching xml templates");
}
return result.text();
}
/**
* Load all menu items
*/
function loadMenus(): MenuInfo {
const menuItems: BaseMenuItem[] = (<any>window).odoo.menus;
if (!menuItems) {
throw new Error("Cannot find menus description");
}
const menuInfo = getMenuInfo(menuItems);
delete (<any>window).odoo.menus; // overkill?
return menuInfo;
}
interface BaseMenuItem {
id: number;
name: string;
parent_id: number | false;
action: string | false;
icon: string | false;
children: BaseMenuItem[];
}
/**
* Generate a valid MenuInfo object from a list of BaseMenuItems. This function
* is supposed to be called once at startup.
*/
export function getMenuInfo(items: BaseMenuItem[]): MenuInfo {
const menus: { [key: number]: MenuItem | undefined } = {};
const actionMap: { [id: number]: MenuItem | undefined } = {};
const roots: number[] = [];
// build MenuItems
for (let root of items) {
roots.push(root.id);
addToMap(root);
}
function addToMap(m: BaseMenuItem, app?: MenuItem): MenuItem {
const item: Partial<MenuItem> = {
id: m.id,
name: m.name,
parentId: m.parent_id,
action: m.action,
icon: m.icon,
actionId: -1 // will be filled later on with correct id
};
item.app = app || (item as MenuItem);
item.children = m.children.map(c => addToMap(c, item.app as MenuItem));
menus[item.id!] = item as MenuItem;
return item as MenuItem;
}
// add proper actionId to every menuitems
for (let menuId in menus) {
const menu = menus[menuId]!;
let menuWithAction = menu && findInTree(menu, m => Boolean(m.action));
if (menuWithAction) {
menu.actionId = parseInt(
(<string>menuWithAction.action).split(",")[1],
10
);
if (menuWithAction === menu) {
actionMap[menu.actionId] = menu;
}
}
}
return { menus, roots, actionMap };
}
+99
View File
@@ -0,0 +1,99 @@
import { findInTree } from "../core/utils";
//------------------------------------------------------------------------------
// Types
//------------------------------------------------------------------------------
export interface MenuItem {
id: number;
name: string;
parentId: number | false;
action: string | false;
icon: string | false;
// root menu id
app: MenuItem;
actionId: number;
children: MenuItem[];
}
export interface MenuInfo {
menus: { [key: number]: MenuItem | undefined };
actionMap: { [id: number]: MenuItem | undefined };
roots: number[];
}
interface BaseMenuItem {
id: number;
name: string;
parent_id: number | false;
action: string | false;
icon: string | false;
children: BaseMenuItem[];
}
//------------------------------------------------------------------------------
// Code
//------------------------------------------------------------------------------
/**
* Load all menu items
*/
export function loadMenus(): MenuInfo {
const menuItems: BaseMenuItem[] = (<any>window).odoo.menus;
if (!menuItems) {
throw new Error("Cannot find menus description");
}
const menuInfo = getMenuInfo(menuItems);
delete (<any>window).odoo.menus; // overkill?
return menuInfo;
}
/**
* Generate a valid MenuInfo object from a list of BaseMenuItems. This function
* is supposed to be called once at startup.
*/
export function getMenuInfo(items: BaseMenuItem[]): MenuInfo {
const menus: { [key: number]: MenuItem | undefined } = {};
const actionMap: { [id: number]: MenuItem | undefined } = {};
const roots: number[] = [];
// build MenuItems
for (let root of items) {
roots.push(root.id);
addToMap(root);
}
function addToMap(m: BaseMenuItem, app?: MenuItem): MenuItem {
const item: Partial<MenuItem> = {
id: m.id,
name: m.name,
parentId: m.parent_id,
action: m.action,
icon: m.icon,
actionId: -1 // will be filled later on with correct id
};
item.app = app || (item as MenuItem);
item.children = m.children.map(c => addToMap(c, item.app as MenuItem));
menus[item.id!] = item as MenuItem;
return item as MenuItem;
}
// add proper actionId to every menuitems
for (let menuId in menus) {
const menu = menus[menuId]!;
let menuWithAction = menu && findInTree(menu, m => Boolean(m.action));
if (menuWithAction) {
menu.actionId = parseInt(
(<string>menuWithAction.action).split(",")[1],
10
);
if (menuWithAction === menu) {
actionMap[menu.actionId] = menu;
}
}
}
return { menus, roots, actionMap };
}
+12
View File
@@ -0,0 +1,12 @@
export type TemplateLoader = () => Promise<string>;
/**
* Load xml templates as a string.
*/
export const loadTemplates: TemplateLoader = async function(): Promise<string> {
const result = await fetch("templates.xml");
if (!result.ok) {
throw new Error("Error while fetching xml templates");
}
return result.text();
};
+12 -2
View File
@@ -1,6 +1,9 @@
///<amd-module name="main" />
import { init } from "./init";
import { makeEnv } from "./env";
import { rpc } from "./services/ajax";
import { loadMenus } from "./loaders/menus";
import { loadTemplates } from "./loaders/templates";
import { Root } from "./widgets/root";
//------------------------------------------------------------------------------
@@ -8,7 +11,14 @@ import { Root } from "./widgets/root";
//------------------------------------------------------------------------------
document.addEventListener("DOMContentLoaded", async function() {
const { env, menuInfo } = await init();
const services = {
rpc
};
const loaders = {
loadTemplates
};
const env = await makeEnv(loaders, services);
const menuInfo = loadMenus();
// Creating root widget
const rootWidget = new Root(env, { menuInfo });
+1 -1
View File
@@ -1,5 +1,5 @@
import { Registry } from "./core/registry";
import { ActionWidget } from "./services/action_manager";
import { ActionWidget } from "./store/action_manager";
import { CRM } from "./widgets/crm";
import { Discuss } from "./widgets/discuss";
+7
View File
@@ -0,0 +1,7 @@
export type RPC = (route: string, params: any) => Promise<any>;
export const rpc: RPC = async function(route, params) {
console.log("RPC", route, params);
const delay = Math.random() * 150;
return new Promise(resolve => setTimeout(resolve, delay));
};
+1 -1
View File
@@ -1,4 +1,4 @@
import { MenuItem } from "./root";
import { MenuItem } from "../loaders/menus";
import { PureWidget } from "./widget";
//------------------------------------------------------------------------------
@@ -1,4 +1,4 @@
import { ActionStack } from "../services/action_manager";
import { ActionStack } from "../store/action_manager";
import { Widget } from "./widget";
//------------------------------------------------------------------------------
+1 -1
View File
@@ -1,4 +1,4 @@
import { MenuInfo, MenuItem } from "../widgets/root";
import { MenuInfo, MenuItem } from "../loaders/menus";
import { Widget } from "./widget";
//------------------------------------------------------------------------------
+1 -1
View File
@@ -1,4 +1,4 @@
import { INotification } from "../core/notifications";
import { INotification } from "../store/notifications";
import { Widget } from "./widget";
export class Notification extends Widget<INotification, {}> {
+3 -22
View File
@@ -1,6 +1,7 @@
import { Query } from "../core/router";
import { Query } from "../store/router";
import { debounce } from "../core/utils";
import { ActionStack } from "../services/action_manager";
import { MenuInfo, MenuItem } from "../loaders/menus";
import { ActionStack } from "../store/action_manager";
import { ActionContainer } from "./action_container";
import { HomeMenu } from "./home_menu";
import { Navbar } from "./navbar";
@@ -11,26 +12,6 @@ import { Env, Widget } from "./widget";
// Types
//------------------------------------------------------------------------------
export interface MenuItem {
id: number;
name: string;
parentId: number | false;
action: string | false;
icon: string | false;
// root menu id
app: MenuItem;
actionId: number;
children: MenuItem[];
}
export interface MenuInfo {
menus: { [key: number]: MenuItem | undefined };
actionMap: { [id: number]: MenuItem | undefined };
roots: number[];
}
export interface Props {
menuInfo: MenuInfo;
}
+4 -4
View File
@@ -1,9 +1,9 @@
import { IAjax } from "../services/ajax";
import { IAjax } from "../store/ajax";
import { Component, PureComponent, WEnv } from "../core/component";
import { INotificationManager } from "../core/notifications";
import { INotificationManager } from "../store/notifications";
import { Registry } from "../core/registry";
import { IRouter } from "../core/router";
import { ActionWidget, IActionManager } from "../services/action_manager";
import { IRouter } from "../store/router";
import { ActionWidget, IActionManager } from "../store/action_manager";
//------------------------------------------------------------------------------
// Types
+1 -1
View File
@@ -1,4 +1,4 @@
import { NotificationManager } from "../../src/ts/core/notifications";
import { NotificationManager } from "../../src/ts/store/notifications";
test("can subscribe and add notification", () => {
let n = 0;
+3 -4
View File
@@ -1,15 +1,14 @@
import { readFile } from "fs";
import { WEnv } from "../src/ts/core/component";
import { Callback } from "../src/ts/core/event_bus";
import { NotificationManager } from "../src/ts/core/notifications";
import { NotificationManager } from "../src/ts/store/notifications";
import { QWeb } from "../src/ts/core/qweb_vdom";
import { IRouter, Query, RouterEvent } from "../src/ts/core/router";
import { IRouter, Query, RouterEvent } from "../src/ts/store/router";
import { idGenerator } from "../src/ts/core/utils";
import { getMenuInfo } from "../src/ts/init";
import { getMenuInfo, MenuInfo } from "../src/ts/loaders/menus";
import { actionRegistry } from "../src/ts/registries";
import { ActionManager } from "../src/ts/store/action_manager";
import { Ajax } from "../src/ts/store/ajax";
import { MenuInfo } from "../src/ts/widgets/root";
import { Env } from "../src/ts/widgets/widget";
export function makeTestFixture() {
@@ -1,4 +1,4 @@
import { ActionStack } from "../../src/ts/services/action_manager";
import { ActionStack } from "../../src/ts/store/action_manager";
import { ActionContainer, Props } from "../../src/ts/widgets/action_container";
import { Widget } from "../../src/ts/widgets/widget";
import * as helpers from "../helpers";
+1 -1
View File
@@ -1,6 +1,6 @@
import { Navbar, Props } from "../../src/ts/widgets/navbar";
import * as helpers from "../helpers";
import { MenuInfo } from "../../src/ts/widgets/root";
import { MenuInfo } from "../../src/ts/loaders/menus";
//------------------------------------------------------------------------------
// Setup and helpers
@@ -1,4 +1,4 @@
import { INotification } from "../../src/ts/core/notifications";
import { INotification } from "../../src/ts/store/notifications";
import { Notification } from "../../src/ts/widgets/notification";
import { makeTestEnv, makeTestFixture, loadTemplates } from "../helpers";