[IMP] web: use dialog service on action service
Until now, the action container opened/closed an action dialog directly on the template, and relayed on the bus to communicate with the action service. Now the action service opens/closes the action dialogs using the action service. To do this, a new close method is created on the action service. This commit also separates the code of dialog service and dialog container to be more standard. closes odoo-dev/odoo#900 Related: odoo-dev/enterprise#156 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
committed by
Aaron Bohy
parent
1092475171
commit
d73309de28
@@ -0,0 +1,60 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "../registry";
|
||||
import { useService } from "../service_hook";
|
||||
|
||||
const { Component, tags } = owl;
|
||||
|
||||
class ErrorHandler extends Component {
|
||||
catchError(error) {
|
||||
this.props.callback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
ErrorHandler.template = tags.xml`<t t-component="props.dialog.class" t-props="props.dialog.props" />`;
|
||||
|
||||
export class DialogContainer extends Component {
|
||||
setup() {
|
||||
this.dialogs = {};
|
||||
const { bus } = useService("dialog");
|
||||
bus.on("ADD", this, (dialog) => {
|
||||
this.dialogs[dialog.id] = dialog;
|
||||
this.render();
|
||||
});
|
||||
bus.on("CLOSE", this, (id) => {
|
||||
this.closeDialog(id);
|
||||
});
|
||||
}
|
||||
|
||||
onDialogClosed(id) {
|
||||
this.closeDialog(id);
|
||||
}
|
||||
|
||||
closeDialog(id) {
|
||||
if (this.dialogs[id].options && this.dialogs[id].options.onCloseCallback) {
|
||||
this.dialogs[id].options.onCloseCallback();
|
||||
}
|
||||
delete this.dialogs[id];
|
||||
this.render();
|
||||
}
|
||||
|
||||
errorCallBack(id) {
|
||||
return () => this.closeDialog(id);
|
||||
}
|
||||
|
||||
__destroy() {
|
||||
this.env.bus.off("ADD", this);
|
||||
this.env.bus.off("CLOSE", this);
|
||||
super.__destroy();
|
||||
}
|
||||
}
|
||||
DialogContainer.components = { ErrorHandler };
|
||||
DialogContainer.template = tags.xml`
|
||||
<div class="o_dialog_manager">
|
||||
<t t-foreach="Object.values(dialogs)" t-as="dialog" t-key="dialog.id">
|
||||
<ErrorHandler dialog="dialog" t-on-dialog-closed="onDialogClosed(dialog.id)" callback="errorCallBack(dialog.id)" />
|
||||
</t>
|
||||
</div>
|
||||
`;
|
||||
|
||||
registry.category("main_components").add("DialogContainer", DialogContainer);
|
||||
@@ -1,76 +1,33 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { registry } from "../registry";
|
||||
import { useService } from "../service_hook";
|
||||
import { Dialog } from "./dialog";
|
||||
|
||||
const { Component, core, tags, useState } = owl;
|
||||
const { core } = owl;
|
||||
const { EventBus } = core;
|
||||
|
||||
class ErrorHandler extends Component {
|
||||
catchError(error) {
|
||||
this.props.callback();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
ErrorHandler.template = tags.xml`<t t-component="props.dialog.class" t-props="props.dialog.props" />`;
|
||||
|
||||
export class DialogContainer extends Component {
|
||||
setup() {
|
||||
this.dialogs = useState({});
|
||||
this.dialogId = 1;
|
||||
const { bus } = useService("dialog");
|
||||
bus.on("UPDATE", this, (dialogClass, props, options) => {
|
||||
this.addDialog(dialogClass, props, options);
|
||||
});
|
||||
}
|
||||
|
||||
addDialog(dialogClass, props, options) {
|
||||
if (!(dialogClass.prototype instanceof Dialog)) {
|
||||
throw new Error(dialogClass.name + " must be a subclass of Dialog");
|
||||
}
|
||||
const id = this.dialogId++;
|
||||
this.dialogs[id] = {
|
||||
id,
|
||||
class: dialogClass,
|
||||
props,
|
||||
options,
|
||||
};
|
||||
}
|
||||
|
||||
onDialogClosed(id) {
|
||||
this.doCloseDialog(id);
|
||||
}
|
||||
|
||||
doCloseDialog(id) {
|
||||
if (this.dialogs[id].options && this.dialogs[id].options.onCloseCallback) {
|
||||
this.dialogs[id].options.onCloseCallback();
|
||||
}
|
||||
delete this.dialogs[id];
|
||||
}
|
||||
|
||||
errorCallBack(id) {
|
||||
return () => this.doCloseDialog(id);
|
||||
}
|
||||
}
|
||||
DialogContainer.components = { ErrorHandler };
|
||||
DialogContainer.template = tags.xml`
|
||||
<div class="o_dialog_manager">
|
||||
<t t-foreach="Object.values(dialogs)" t-as="dialog" t-key="dialog.id">
|
||||
<ErrorHandler dialog="dialog" t-on-dialog-closed="onDialogClosed(dialog.id)" callback="errorCallBack(dialog.id)" />
|
||||
</t>
|
||||
</div>
|
||||
`;
|
||||
|
||||
registry.category("main_components").add("DialogContainer", DialogContainer);
|
||||
|
||||
export const dialogService = {
|
||||
start(env) {
|
||||
start() {
|
||||
const bus = new EventBus();
|
||||
let dialogId = 0;
|
||||
function open(dialogClass, props, options) {
|
||||
bus.trigger("UPDATE", dialogClass, props, options);
|
||||
if (!(dialogClass.prototype instanceof Dialog)) {
|
||||
throw new Error(dialogClass.name + " must be a subclass of Dialog");
|
||||
}
|
||||
const id = ++dialogId;
|
||||
const dialog = {
|
||||
id,
|
||||
class: dialogClass,
|
||||
props,
|
||||
options,
|
||||
};
|
||||
bus.trigger("ADD", dialog);
|
||||
return id;
|
||||
}
|
||||
return { open, bus };
|
||||
function close(id) {
|
||||
bus.trigger("CLOSE", id);
|
||||
}
|
||||
return { open, close, bus };
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import Context from "web.Context";
|
||||
import core from "web.core";
|
||||
import { ComponentAdapter } from "web.OwlCompatibility";
|
||||
import { objectToQuery } from "../core/browser/router_service";
|
||||
import { useBus } from "../core/bus_hook";
|
||||
import { useDebugMenu } from "../core/debug/debug_menu";
|
||||
import { Dialog } from "../core/dialog/dialog";
|
||||
import { useEffect } from "../core/effect_hook";
|
||||
@@ -39,34 +38,35 @@ class ActionAdapter extends ComponentAdapter {
|
||||
// This is protected in legacy (backward compatibility) but should not e supported in Wowl
|
||||
this.tempQuery = {};
|
||||
let originalUpdateControlPanel;
|
||||
useEffect(() => {
|
||||
this.title.setParts({ action: this.widget.getTitle() });
|
||||
const query = objectToQuery(this.widget.getState());
|
||||
Object.assign(query, this.tempQuery);
|
||||
this.tempQuery = null;
|
||||
this.__widget = this.widget;
|
||||
if (!this.wowlEnv.inDialog) {
|
||||
this.router.pushState(query);
|
||||
}
|
||||
this.wowlEnv.bus.on("ACTION_MANAGER:UPDATE", this, (info) => {
|
||||
if (info.type === "MAIN") {
|
||||
this.env.bus.trigger("close_dialogs");
|
||||
useEffect(
|
||||
() => {
|
||||
this.title.setParts({ action: this.widget.getTitle() });
|
||||
const query = objectToQuery(this.widget.getState());
|
||||
Object.assign(query, this.tempQuery);
|
||||
this.tempQuery = null;
|
||||
this.__widget = this.widget;
|
||||
if (!this.wowlEnv.inDialog) {
|
||||
this.router.pushState(query);
|
||||
}
|
||||
cleanDomFromBootstrap();
|
||||
});
|
||||
originalUpdateControlPanel = this.__widget.updateControlPanel.bind(this.__widget);
|
||||
this.__widget.updateControlPanel = (newProps) => {
|
||||
this.wowlEnv.bus.on("ACTION_MANAGER:UPDATE", this, () => {
|
||||
this.env.bus.trigger("close_dialogs");
|
||||
cleanDomFromBootstrap();
|
||||
});
|
||||
originalUpdateControlPanel = this.__widget.updateControlPanel.bind(this.__widget);
|
||||
this.__widget.updateControlPanel = (newProps) => {
|
||||
this.trigger("controller-title-updated", this.__widget.getTitle());
|
||||
return originalUpdateControlPanel(newProps);
|
||||
};
|
||||
this.trigger("controller-title-updated", this.__widget.getTitle());
|
||||
return originalUpdateControlPanel(newProps);
|
||||
};
|
||||
this.trigger("controller-title-updated", this.__widget.getTitle());
|
||||
core.bus.trigger("DOM_updated");
|
||||
core.bus.trigger("DOM_updated");
|
||||
|
||||
return () => {
|
||||
this.__widget.updateControlPanel = originalUpdateControlPanel;
|
||||
this.wowlEnv.bus.off("ACTION_MANAGER:UPDATE", this);
|
||||
};
|
||||
}, () => []);
|
||||
return () => {
|
||||
this.__widget.updateControlPanel = originalUpdateControlPanel;
|
||||
this.wowlEnv.bus.off("ACTION_MANAGER:UPDATE", this);
|
||||
};
|
||||
},
|
||||
() => []
|
||||
);
|
||||
}
|
||||
|
||||
_trigger_up(ev) {
|
||||
@@ -188,34 +188,37 @@ function useMagicLegacyReload() {
|
||||
let legacyReloadProm = null;
|
||||
const getReloadProm = () => legacyReloadProm;
|
||||
let manualReload;
|
||||
useEffect(() => {
|
||||
const widget = comp.widget;
|
||||
const controllerReload = widget.reload;
|
||||
widget.reload = function (...args) {
|
||||
manualReload = true;
|
||||
legacyReloadProm = controllerReload.call(widget, ...args);
|
||||
return legacyReloadProm.then(() => {
|
||||
if (manualReload) {
|
||||
legacyReloadProm = null;
|
||||
manualReload = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
const controllerUpdate = widget.update;
|
||||
widget.update = function (...args) {
|
||||
const updateProm = controllerUpdate.call(widget, ...args);
|
||||
const manualUpdate = !manualReload;
|
||||
if (manualUpdate) {
|
||||
legacyReloadProm = updateProm;
|
||||
}
|
||||
return updateProm.then(() => {
|
||||
useEffect(
|
||||
() => {
|
||||
const widget = comp.widget;
|
||||
const controllerReload = widget.reload;
|
||||
widget.reload = function (...args) {
|
||||
manualReload = true;
|
||||
legacyReloadProm = controllerReload.call(widget, ...args);
|
||||
return legacyReloadProm.then(() => {
|
||||
if (manualReload) {
|
||||
legacyReloadProm = null;
|
||||
manualReload = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
const controllerUpdate = widget.update;
|
||||
widget.update = function (...args) {
|
||||
const updateProm = controllerUpdate.call(widget, ...args);
|
||||
const manualUpdate = !manualReload;
|
||||
if (manualUpdate) {
|
||||
legacyReloadProm = null;
|
||||
legacyReloadProm = updateProm;
|
||||
}
|
||||
});
|
||||
};
|
||||
widget[magicReloadSymbol] = getReloadProm;
|
||||
}, () => []);
|
||||
return updateProm.then(() => {
|
||||
if (manualUpdate) {
|
||||
legacyReloadProm = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
widget[magicReloadSymbol] = getReloadProm;
|
||||
},
|
||||
() => []
|
||||
);
|
||||
return getReloadProm;
|
||||
}
|
||||
|
||||
@@ -226,7 +229,6 @@ export class ViewAdapter extends ActionAdapter {
|
||||
this.vm = useService("view");
|
||||
this.shouldUpdateWidget = true;
|
||||
this.magicReload = useMagicLegacyReload();
|
||||
const envWowl = this.env;
|
||||
useDebugMenu("action", {
|
||||
action: this.props.viewParams.action,
|
||||
component: this,
|
||||
@@ -235,23 +237,6 @@ export class ViewAdapter extends ActionAdapter {
|
||||
if (this.props.viewInfo.type === "form") {
|
||||
useDebugMenu("form");
|
||||
}
|
||||
if (!envWowl.inDialog) {
|
||||
useBus(envWowl.bus, "ACTION_MANAGER:UPDATE", (info) => {
|
||||
switch (info.type) {
|
||||
case "OPEN_DIALOG": {
|
||||
// we are a main action, and a dialog is going to open:
|
||||
// we should not reload
|
||||
this.shouldUpdateWidget = false;
|
||||
break;
|
||||
}
|
||||
case "CLOSE_DIALOG": {
|
||||
this.shouldUpdateWidget = false;
|
||||
info.closingProms.push(() => this.magicReload());
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
this.env = Component.env;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,36 +9,9 @@ const { Component, tags } = owl;
|
||||
// -----------------------------------------------------------------------------
|
||||
export class ActionContainer extends Component {
|
||||
setup() {
|
||||
this.main = {};
|
||||
this.dialog = {};
|
||||
this.info = {};
|
||||
this.env.bus.on("ACTION_MANAGER:UPDATE", this, (info) => {
|
||||
switch (info.type) {
|
||||
case "MAIN":
|
||||
this.main = info;
|
||||
break;
|
||||
case "OPEN_DIALOG": {
|
||||
const { onClose } = this.dialog;
|
||||
this.dialog = {
|
||||
id: info.id,
|
||||
props: info.props,
|
||||
onClose: onClose || info.onClose,
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "CLOSE_DIALOG": {
|
||||
let onClose;
|
||||
if (this.dialog.id) {
|
||||
onClose = this.dialog.onClose;
|
||||
} else {
|
||||
onClose = info.onClose;
|
||||
}
|
||||
if (onClose) {
|
||||
onClose(info.onCloseInfo);
|
||||
}
|
||||
this.dialog = {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.info = info;
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
@@ -47,17 +20,11 @@ export class ActionContainer extends Component {
|
||||
this.env.bus.off("ACTION_MANAGER:UPDATE", this);
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
onDialogClosed() {
|
||||
this.dialog = {};
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
ActionContainer.components = { ActionDialog };
|
||||
ActionContainer.template = tags.xml`
|
||||
<t t-name="web.ActionContainer">
|
||||
<div class="o_action_manager">
|
||||
<t t-if="main.Component" t-component="main.Component" t-props="main.componentProps" t-key="main.id"/>
|
||||
<ActionDialog t-if="dialog.id" t-props="dialog.props" t-key="dialog.id" t-on-dialog-closed="onDialogClosed"/>
|
||||
<t t-if="info.Component" t-component="info.Component" t-props="info.componentProps" t-key="info.id"/>
|
||||
</div>
|
||||
</t>`;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { KeyNotFoundError } from "@web/core/registry";
|
||||
import { cleanDomFromBootstrap } from "@web/legacy/utils";
|
||||
import { browser } from "../../core/browser/browser";
|
||||
import { useBus } from "../../core/bus_hook";
|
||||
import { makeContext } from "../../core/context";
|
||||
@@ -9,6 +10,7 @@ import { evaluateExpr } from "../../core/py_js/py";
|
||||
import { registry } from "../../core/registry";
|
||||
import { KeepLast } from "../../core/utils/concurrency";
|
||||
import { sprintf } from "../../core/utils/strings";
|
||||
import { ActionDialog } from "./action_dialog";
|
||||
|
||||
/**
|
||||
* @typedef {"current" | "fullscreen" | "new" | "self" | "inline"} ActionMode */
|
||||
@@ -57,6 +59,7 @@ function makeActionManager(env) {
|
||||
let controllerStack = [];
|
||||
let dialogCloseProm;
|
||||
let actionCache = {};
|
||||
let dialog = {};
|
||||
|
||||
// The state action (or default user action if none) is loaded as soon as possible
|
||||
// so that the next "doAction" will have its action ready when needed.
|
||||
@@ -536,6 +539,7 @@ function makeActionManager(env) {
|
||||
ControllerComponent.Component = controller.Component;
|
||||
|
||||
if (action.target === "new") {
|
||||
cleanDomFromBootstrap();
|
||||
const actionDialogProps = {
|
||||
// TODO add size
|
||||
ActionComponent: ControllerComponent,
|
||||
@@ -544,12 +548,16 @@ function makeActionManager(env) {
|
||||
if (action.name) {
|
||||
actionDialogProps.title = action.name;
|
||||
}
|
||||
env.bus.trigger("ACTION_MANAGER:UPDATE", {
|
||||
type: "OPEN_DIALOG",
|
||||
id: ++id,
|
||||
props: actionDialogProps,
|
||||
onClose: options.onClose,
|
||||
});
|
||||
|
||||
const { onClose } = dialog;
|
||||
if (dialog.id) {
|
||||
env.services.dialog.close(dialog.id);
|
||||
}
|
||||
const dialogId = env.services.dialog.open(ActionDialog, actionDialogProps);
|
||||
dialog = {
|
||||
id: dialogId,
|
||||
onClose: onClose || options.onClose,
|
||||
};
|
||||
return currentActionProm;
|
||||
}
|
||||
|
||||
@@ -571,7 +579,6 @@ function makeActionManager(env) {
|
||||
controller.props.breadcrumbs = _getBreadcrumbs(nextStack.slice(0, nextStack.length - 1));
|
||||
const closingProm = _executeCloseAction();
|
||||
env.bus.trigger("ACTION_MANAGER:UPDATE", {
|
||||
type: "MAIN",
|
||||
id: ++id,
|
||||
Component: ControllerComponent,
|
||||
componentProps: controller.props,
|
||||
@@ -903,13 +910,20 @@ function makeActionManager(env) {
|
||||
}
|
||||
|
||||
async function _executeCloseAction(params = {}) {
|
||||
const closingProms = [];
|
||||
env.bus.trigger("ACTION_MANAGER:UPDATE", {
|
||||
type: "CLOSE_DIALOG",
|
||||
closingProms,
|
||||
...params,
|
||||
});
|
||||
await Promise.all([dialogCloseProm].concat(closingProms.map((fn) => fn())));
|
||||
cleanDomFromBootstrap();
|
||||
let onClose;
|
||||
if (dialog.id) {
|
||||
onClose = dialog.onClose;
|
||||
env.services.dialog.close(dialog.id);
|
||||
} else {
|
||||
onClose = params.onClose;
|
||||
}
|
||||
dialog = {};
|
||||
if (onClose) {
|
||||
await onClose(params.onCloseInfo);
|
||||
}
|
||||
|
||||
return dialogCloseProm;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { DialogContainer, dialogService } from "@web/core/dialog/dialog_service";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_container";
|
||||
import { dialogService } from "@web/core/dialog/dialog_service";
|
||||
import { ErrorDialog } from "@web/core/errors/error_dialogs";
|
||||
import { errorService } from "@web/core/errors/error_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
@@ -19,6 +19,7 @@ import { effectService } from "@web/webclient/effects/effect_service";
|
||||
import { hotkeyService } from "@web/webclient/hotkeys/hotkey_service";
|
||||
import { menuService } from "@web/webclient/menu_service";
|
||||
import { WebClient } from "@web/webclient/webclient";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_container";
|
||||
// This import is needed because of it's sideeffects, for exemple :
|
||||
// web.test_utils easyload xml templates at line : 124:130.
|
||||
// Also it set the autocomplete delay time for the field Many2One at 0 for the tests at line : 132:137
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_service";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_container";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { NotificationContainer } from "@web/core/notifications/notification_container";
|
||||
import testUtils from "web.test_utils";
|
||||
|
||||
@@ -8,6 +8,7 @@ import { clearRegistryWithCleanup } from "../../helpers/mock_env";
|
||||
import { makeFakeNotificationService, makeFakeUserService } from "../../helpers/mock_services";
|
||||
import { createWebClient, doAction, getActionManagerTestConfig } from "./helpers";
|
||||
import { mockDownload } from "@web/../tests/helpers/utils";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_container";
|
||||
|
||||
let testConfig;
|
||||
|
||||
@@ -18,6 +19,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
hooks.beforeEach(() => {
|
||||
testConfig = getActionManagerTestConfig();
|
||||
clearRegistryWithCleanup(mainComponentRegistry);
|
||||
mainComponentRegistry.add("DialogContainer", DialogContainer);
|
||||
});
|
||||
|
||||
QUnit.module("Report actions");
|
||||
@@ -284,7 +286,7 @@ QUnit.module("ActionManager", (hooks) => {
|
||||
};
|
||||
const webClient = await createWebClient({ testConfig, mockRPC });
|
||||
let customHandlerCalled = false;
|
||||
registry.category("ir.actions.report handlers").add("custom_handler", async action => {
|
||||
registry.category("ir.actions.report handlers").add("custom_handler", async (action) => {
|
||||
if (action.id === 7 && !customHandlerCalled) {
|
||||
customHandlerCalled = true;
|
||||
assert.step("calling custom handler");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { debugService } from "@web/core/debug/debug_service";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_service";
|
||||
import { DialogContainer } from "@web/core/dialog/dialog_container";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { editView } from "@web/legacy/debug_manager";
|
||||
import { clearUncommittedChanges } from "@web/webclient/actions/action_service";
|
||||
|
||||
Reference in New Issue
Block a user