diff --git a/web/static/src/ts/main.ts b/web/static/src/ts/main.ts index c9e204dd..ea5d9410 100644 --- a/web/static/src/ts/main.ts +++ b/web/static/src/ts/main.ts @@ -30,16 +30,3 @@ document.addEventListener("DOMContentLoaded", async function() { // For debugging purpose, we keep a reference to the root widget in odoo (window).odoo.rootWidget = rootWidget; }); - -let testMixin = Base => - class extends Base { - calc() { - return 32; - } - }; - -class Foo {} - -class Bar extends testMixin(Foo) {} - -console.log(new Bar().calc()); diff --git a/web/static/src/ts/store/action_manager_mixin.ts b/web/static/src/ts/store/action_manager_mixin.ts index 83f54501..452883d5 100644 --- a/web/static/src/ts/store/action_manager_mixin.ts +++ b/web/static/src/ts/store/action_manager_mixin.ts @@ -49,7 +49,13 @@ export type ActionDescription = | ActWindowActionDescription; export type ActionInfo = ClientActionInfo | ActWindowInfo; -export type ActionStack = ActionInfo[]; + +export interface Action { + id: number; + widget?: Widget; + executor(parent: Widget): Promise | null>; + activate(): void; +} //------------------------------------------------------------------------------ // Action Manager Mixin @@ -60,55 +66,61 @@ export function actionManagerMixin>( ) { return class extends Base { actionCache: { [key: number]: Promise } = {}; + currentAction?: Action; + lastAction?: Action; async doAction(request: ActionRequest) { + const self = this; const descr = await this.loadAction(request); + let executor; switch (descr.type) { case "ir.actions.client": - return this.doClientAction(descr); + executor = this.doClientAction(descr); + break; case "ir.actions.act_window": - return this.doActWindowAction(descr); + executor = this.doActWindowAction(descr); + break; default: throw new Error("unhandled action"); } + const action: Action = { + id: this.generateID(), + executor, + activate() { + if (self.currentAction && self.currentAction.widget) { + self.currentAction.widget.destroy(); + } + self.currentAction = action; + self.update({ + inHome: false + }); + document.title = descr.name + " - Odoo"; + } + }; + self.lastAction = action; + this.trigger("update_action", action); } doActWindowAction(descr: ActWindowActionDescription) { - let title = descr.name; - this.update({ - inHome: false, - stack: [ - { - id: 1, - context: {}, - target: "current", - type: "act_window", - title, - Widget: View - } - ] - }); - document.title = descr.name + " - Odoo"; + return async function executor(this: Action, parent: Widget) { + const widget = new View(parent, {}); + const div = document.createElement("div"); + await widget.mount(div); + this.widget = widget; + return widget; + }; } doClientAction(descr: ClientActionDescription) { let key = descr.tag; - let title = descr.name; let Widget = this.actionRegistry.get(key); - this.update({ - inHome: false, - stack: [ - { - id: 1, - context: {}, - target: "current", - type: "client", - title, - Widget: Widget - } - ] - }); - document.title = descr.name + " - Odoo"; + return async function executor(this: Action, parent: Widget) { + const widget = new Widget(parent, {}); + const div = document.createElement("div"); + await widget.mount(div); + this.widget = widget; + return widget; + }; } loadAction(id: number): Promise { diff --git a/web/static/src/ts/store/notification_mixin.ts b/web/static/src/ts/store/notification_mixin.ts index 4fdd4c35..59d04077 100644 --- a/web/static/src/ts/store/notification_mixin.ts +++ b/web/static/src/ts/store/notification_mixin.ts @@ -1,6 +1,5 @@ import { Type } from "../core/component"; import { BaseStore } from "./store"; -import { idGenerator } from "../core/utils"; //------------------------------------------------------------------------------ // Notifications Mixin @@ -15,8 +14,6 @@ export interface INotification { export function notificationMixin>(Base: T) { return class extends Base { - generateID = idGenerator(); - addNotification(notif: Partial): number { const id = this.generateID(); const defaultVals = { diff --git a/web/static/src/ts/store/store.ts b/web/static/src/ts/store/store.ts index aa0331e3..df00c900 100644 --- a/web/static/src/ts/store/store.ts +++ b/web/static/src/ts/store/store.ts @@ -1,12 +1,9 @@ import { EventBus } from "../core/event_bus"; import { Registry } from "../core/registry"; +import { idGenerator } from "../core/utils"; import { RPC } from "../services/ajax"; import { IRouter, Query } from "../services/router"; -import { - actionManagerMixin, - ActionStack, - ActionWidget -} from "./action_manager_mixin"; +import { actionManagerMixin, ActionWidget } from "./action_manager_mixin"; import { notificationMixin } from "./notification_mixin"; import { rpcMixin } from "./rpc_mixin"; import { MenuItem } from "./store"; @@ -15,7 +12,7 @@ import { MenuItem } from "./store"; // Types //------------------------------------------------------------------------------ -export { ActionStack, ActionWidget } from "./action_manager_mixin"; +export { ActionWidget } from "./action_manager_mixin"; export { INotification } from "./notification_mixin"; export { RPC } from "./rpc_mixin"; @@ -40,7 +37,6 @@ export interface MenuInfo { } export interface State { - stack: ActionStack; inHome: boolean; currentApp: MenuItem | null; } @@ -55,7 +51,6 @@ export interface Services { //------------------------------------------------------------------------------ export class BaseStore extends EventBus { state: State = { - stack: [], inHome: false, currentApp: null }; @@ -63,6 +58,7 @@ export class BaseStore extends EventBus { services: Services; actionRegistry: Registry; currentQuery: Query; + generateID = idGenerator(); constructor( services: Services, @@ -115,7 +111,6 @@ export class Store extends actionManagerMixin( this.state.inHome = true; this.services.router.navigate({ home: true }); } - this.services.router.on("query_changed", this, this.updateAction); this.updateAction(this.services.router.getQuery()); } diff --git a/web/static/src/ts/widgets/action_container.ts b/web/static/src/ts/widgets/action_container.ts deleted file mode 100644 index 9ad3fde3..00000000 --- a/web/static/src/ts/widgets/action_container.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { ActionStack } from "../store/store"; -import { Widget } from "./widget"; - -//------------------------------------------------------------------------------ -// Types -//------------------------------------------------------------------------------ - -export interface Props { - stack: ActionStack; -} - -//------------------------------------------------------------------------------ -// Action Container -//------------------------------------------------------------------------------ - -export class ActionContainer extends Widget { - template = "web.action_container"; - currentWidget: any; - - willStart() { - return this.setContentWidget(); - } - - mounted() { - if (this.currentWidget && this.currentWidget.el) { - this.el!.appendChild(this.currentWidget.el); - this.currentWidget.__mount(); - } - } - - shouldUpdate(nextProps: Props) { - if (nextProps.stack !== this.props.stack) { - this.props = nextProps; - this.setContentWidget(); - } - return false; - } - - async setContentWidget() { - const info = this.props.stack[this.props.stack.length - 1]; - if (info) { - const Widget = info.Widget; - let widget = new Widget(this, {}); - await widget.mount(this.el || document.createElement("div")); - if (this.currentWidget) { - this.currentWidget.destroy(); - } - this.currentWidget = widget; - } - } -} diff --git a/web/static/src/ts/widgets/root.ts b/web/static/src/ts/widgets/root.ts index 2aba1659..552daf10 100644 --- a/web/static/src/ts/widgets/root.ts +++ b/web/static/src/ts/widgets/root.ts @@ -1,11 +1,11 @@ import { debounce } from "../core/utils"; import { Env } from "../env"; import { State, Store } from "../store/store"; -import { ActionContainer } from "./action_container"; import { HomeMenu } from "./home_menu"; import { Navbar } from "./navbar"; import { Notification } from "./notification"; import { Widget } from "./widget"; +import { Action } from "../store/action_manager_mixin"; //------------------------------------------------------------------------------ // Root Widget @@ -13,7 +13,7 @@ import { Widget } from "./widget"; export class Root extends Widget { template = "web.web_client"; - widgets = { Navbar, HomeMenu, ActionContainer }; + widgets = { Navbar, HomeMenu }; notifications: { [id: number]: Notification } = {}; store: Store; @@ -53,5 +53,21 @@ export class Root extends Widget { this.render(); } }, 50)); + + // actions + this.store.on("update_action", this, this.applyAction); + if (this.store.lastAction) { + this.applyAction(this.store.lastAction); + } + } + + async applyAction(action: Action) { + const widget = await action.executor(this); + if (widget) { + // to do: call some public method of widget instead... + (this.refs.content).appendChild(widget.el!); + widget.__mount(); + action.activate(); + } } } diff --git a/web/static/src/xml/templates.xml b/web/static/src/xml/templates.xml index f5579cc5..86321cad 100644 --- a/web/static/src/xml/templates.xml +++ b/web/static/src/xml/templates.xml @@ -4,9 +4,7 @@
- - - +
Loading
diff --git a/web/static/tests/store/store.test.ts b/web/static/tests/store/store.test.ts index 1f6aaa66..1e4d33b3 100644 --- a/web/static/tests/store/store.test.ts +++ b/web/static/tests/store/store.test.ts @@ -113,6 +113,7 @@ describe("state transitions", () => { expect(store.services.router.getQuery()).toEqual({ home: true }); await promise; + store.lastAction!.activate(); expect(store.state.inHome).toBe(false); expect(store.services.router.getQuery()).toEqual({ action_id: "131", @@ -138,6 +139,8 @@ describe("state transitions", () => { const promise = store.activateMenuItem(96); expect(document.title).toBe("Odoo"); await promise; + store.lastAction!.activate(); + expect(document.title).toBe("Discuss - Odoo"); store.toggleHomeMenu(); diff --git a/web/static/tests/widgets/__snapshots__/action_container.test.ts.snap b/web/static/tests/widgets/__snapshots__/action_container.test.ts.snap deleted file mode 100644 index 134e526b..00000000 --- a/web/static/tests/widgets/__snapshots__/action_container.test.ts.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`can be rendered with a non empty stack 1`] = `"
some client action
"`; - -exports[`can be rendered with an empty stack 1`] = `"
"`; - -exports[`content is updated properly when new props are given 1`] = `"
some client action
"`; diff --git a/web/static/tests/widgets/__snapshots__/root.test.ts.snap b/web/static/tests/widgets/__snapshots__/root.test.ts.snap index 5f8b0dc7..2ba189cd 100644 --- a/web/static/tests/widgets/__snapshots__/root.test.ts.snap +++ b/web/static/tests/widgets/__snapshots__/root.test.ts.snap @@ -26,6 +26,7 @@ exports[`can be rendered (in home menu) 1`] = `
+
Loading
" @@ -46,7 +47,7 @@ exports[`if url has action_id, will render action and navigate to proper menu_id -
+
DISCUSS!! @@ -73,7 +74,6 @@ exports[`if url has action_id, will render action and navigate to proper menu_id
-
Loading
" @@ -94,7 +94,7 @@ exports[`start with no action => clicks on client action => discuss is rendered
-
+
DISCUSS!! @@ -121,7 +121,6 @@ exports[`start with no action => clicks on client action => discuss is rendered
-
Loading
" diff --git a/web/static/tests/widgets/action_container.test.ts b/web/static/tests/widgets/action_container.test.ts deleted file mode 100644 index 3928f521..00000000 --- a/web/static/tests/widgets/action_container.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { Env, makeEnv } from "../../src/ts/env"; -import { ActionStack, Store } from "../../src/ts/store/store"; -import { ActionContainer, Props } from "../../src/ts/widgets/action_container"; -import { Widget } from "../../src/ts/widgets/widget"; -import * as helpers from "../helpers"; - -//------------------------------------------------------------------------------ -// Setup and helpers -//------------------------------------------------------------------------------ - -let fixture: HTMLElement; -let store: Store; -let env: Env; -let props: Props; -let templates: string; - -beforeAll(async () => { - templates = await helpers.loadTemplates(); -}); - -beforeEach(() => { - fixture = helpers.makeTestFixture(); - store = helpers.makeTestStore(); - env = makeEnv(store, templates); - props = { stack: [] }; -}); - -afterEach(() => { - fixture.remove(); -}); - -class ClientAction extends Widget<{}, {}> { - inlineTemplate = "
some client action
"; -} - -const demoStack: ActionStack = [ - { - id: 33, - context: {}, - title: "some title", - target: "new", - type: "client", - Widget: ClientAction - } -]; - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -test("can be rendered with an empty stack", async () => { - const container = new ActionContainer(env, props); - await container.mount(fixture); - expect(fixture.innerHTML).toMatchSnapshot(); -}); - -test("can be rendered with a non empty stack", async () => { - props.stack = demoStack; - const container = new ActionContainer(env, props); - await container.mount(fixture); - expect(fixture.innerHTML).toMatchSnapshot(); -}); - -test("content is updated properly when new props are given", async () => { - const container = new ActionContainer(env, props); - await container.mount(fixture); - await container.updateProps({ stack: demoStack }); - await helpers.nextTick(); - expect(fixture.innerHTML).toMatchSnapshot(); -}); diff --git a/web/static/tests/widgets/root.test.ts b/web/static/tests/widgets/root.test.ts index 37c4a26e..7ded0172 100644 --- a/web/static/tests/widgets/root.test.ts +++ b/web/static/tests/widgets/root.test.ts @@ -45,12 +45,12 @@ test("if url has action_id, will render action and navigate to proper menu_id", const root = new Root(env, store); await root.mount(fixture); + await helpers.nextTick(); expect(env.services.router.getQuery()).toEqual({ action_id: "131", menu_id: "96" }); expect(fixture.innerHTML).toMatchSnapshot(); - // we check here that the url was changed to set app id as menu_id }); test("start with no action => clicks on client action => discuss is rendered", async () => {