[IMP] web: simplify owl_compatibility
This commit tried to remove owl_compatibility completely but its last uses are either in refactoring or a bit complex to migrate. Instead of removing the helpers completely, this commit simplifies them a lot and makes them more readable and safe. closes odoo/odoo#134295 Task: 3439226 Related: odoo/enterprise#46899 Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ComponentWrapper } from "@web/legacy/js/owl_compatibility";
|
||||
import { attachComponent } from "@web/legacy/utils";
|
||||
import { Wysiwyg } from "@web_editor/js/wysiwyg/wysiwyg";
|
||||
import { closestElement } from "@web_editor/js/editor/odoo-editor/src/OdooEditor";
|
||||
import { Toolbar } from "@web_editor/js/editor/toolbar";
|
||||
@@ -46,15 +46,13 @@ export class MassMailingWysiwyg extends Wysiwyg {
|
||||
if (fold) {
|
||||
this.snippetsMenu.setFolded(true);
|
||||
if (!this.floatingToolbar) {
|
||||
// Instantiate and configure new toolbar.
|
||||
const toolbarWrapper = new ComponentWrapper({}, Toolbar, this.state.toolbarProps);
|
||||
|
||||
// The wysiwyg can be instanciated inside an iframe. The dialog
|
||||
// component is mounted on the global document.
|
||||
const toolbarWrapperElement = document.createElement('div');
|
||||
toolbarWrapperElement.style.display = 'contents';
|
||||
document.body.append(toolbarWrapperElement);
|
||||
await toolbarWrapper.mount(toolbarWrapperElement);
|
||||
// Instantiate and configure new toolbar.
|
||||
await attachComponent({}, toolbarWrapperElement, Toolbar, this.state.toolbarProps);
|
||||
this.toolbarEl = toolbarWrapperElement.firstChild;
|
||||
this.floatingToolbarEl = this.toolbarEl;
|
||||
|
||||
|
||||
@@ -69,8 +69,6 @@
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/libs/owl.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
@@ -162,7 +160,6 @@
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/env.js',
|
||||
'web/static/src/legacy/js/owl_compatibility.js',
|
||||
|
||||
'mrp_subcontracting/static/src/subcontracting_portal/*',
|
||||
'web/static/src/start.js',
|
||||
|
||||
@@ -142,7 +142,6 @@
|
||||
# FIXME POSREF legacy dependencies to remove
|
||||
'web/static/lib/moment/moment.js',
|
||||
'web/static/src/legacy/js/promise_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
],
|
||||
# Bundle that starts the pos, loaded on /pos/ui
|
||||
'point_of_sale.assets_prod': [
|
||||
|
||||
@@ -120,8 +120,6 @@
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/libs/owl.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
@@ -215,7 +213,6 @@
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/env.js',
|
||||
'web/static/src/legacy/js/owl_compatibility.js',
|
||||
|
||||
'web_editor/static/src/js/editor/odoo-editor/src/base_style.scss',
|
||||
'web_editor/static/lib/vkbeautify/**/*',
|
||||
|
||||
@@ -69,8 +69,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/legacy/js/core/cookie_utils.js',
|
||||
|
||||
'web/static/lib/moment/moment.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
@@ -151,7 +149,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/legacy/backend_utils.js',
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/env.js',
|
||||
'web/static/src/legacy/js/owl_compatibility.js',
|
||||
|
||||
'web/static/src/legacy/xml/base.xml',
|
||||
# Don't include dark mode files in light mode
|
||||
@@ -206,8 +203,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/libs/owl.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
@@ -266,7 +261,6 @@ This module provides the core of the Odoo Web Client.
|
||||
'web/static/src/libs/pdfjs.js',
|
||||
|
||||
'web/static/src/legacy/utils.js',
|
||||
'web/static/src/legacy/js/owl_compatibility.js',
|
||||
'web/static/src/legacy/js/public/public_env.js',
|
||||
'web/static/src/legacy/js/public/public_root.js',
|
||||
'web/static/src/legacy/js/public/public_root_instance.js',
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
(function () {
|
||||
|
||||
owl.Component.env = {};
|
||||
|
||||
/**
|
||||
* Symbol used in ComponentWrapper to redirect Owl events to Odoo legacy
|
||||
* events.
|
||||
*/
|
||||
odoo.widgetSymbol = Symbol('widget');
|
||||
})();
|
||||
@@ -1,744 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
import { templates } from "@web/core/assets";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
|
||||
/**
|
||||
* This file defines the necessary tools for the transition phase where Odoo
|
||||
* legacy widgets and Owl components will coexist. There are two possible
|
||||
* scenarios:
|
||||
* 1) An Owl component has to instantiate legacy widgets
|
||||
* 2) A legacy widget has to instantiate Owl components
|
||||
*/
|
||||
|
||||
const {
|
||||
App,
|
||||
Component,
|
||||
onMounted,
|
||||
onWillStart,
|
||||
onWillUnmount,
|
||||
onPatched,
|
||||
onWillUpdateProps,
|
||||
onWillDestroy,
|
||||
useSubEnv,
|
||||
xml,
|
||||
status,
|
||||
} = owl;
|
||||
|
||||
const widgetSymbol = odoo.widgetSymbol;
|
||||
const children = new WeakMap(); // associates legacy widgets with their Owl children
|
||||
|
||||
const templateForLegacy = xml`<t/>`;
|
||||
const templateForOwl = xml`<t t-component="props.Component" t-props="childProps" />`;
|
||||
/**
|
||||
* Case 1) An Owl component has to instantiate legacy widgets
|
||||
* ----------------------------------------------------------
|
||||
*
|
||||
* The ComponentAdapter is an Owl component meant to be used as universal
|
||||
* adapter for Owl components that embed Odoo legacy widgets (or dynamically
|
||||
* both Owl components and Odoo legacy widgets), e.g.:
|
||||
*
|
||||
* Owl Component
|
||||
* |
|
||||
* ComponentAdapter (Owl component)
|
||||
* |
|
||||
* Legacy Widget(s) (or Owl component(s))
|
||||
*
|
||||
*
|
||||
* The adapter takes the component/widget class as 'Component' prop, and the
|
||||
* arguments (except first arg 'parent') to initialize it as props.
|
||||
* For instance:
|
||||
* <ComponentAdapter Component="LegacyWidget" params="params"/>
|
||||
* will be translated to:
|
||||
* const LegacyWidget = this.props.Component;
|
||||
* const legacyWidget = new LegacyWidget(this, this.props.params);
|
||||
*
|
||||
* If more than one argument (in addition to 'parent') is given to initialize
|
||||
* the legacy widget, the arguments order (to initialize the sub widget) has
|
||||
* to be somehow specified. There are two alternatives. One can either (1)
|
||||
* specify the prop 'widgetArgs', corresponding to the array of arguments,
|
||||
* otherwise (2) a subclass of ComponentAdapter has to be defined. This
|
||||
* subclass must override the 'widgetArgs' getter to translate arguments
|
||||
* received as props to an array of arguments for the call to init.
|
||||
* For instance:
|
||||
* (1) <ComponentAdapter Component="LegacyWidget" firstArg="a" secondArg="b" widgetsArgs="[a, b]"/>
|
||||
* (2) class SpecificAdapter extends ComponentAdapter {
|
||||
* get widgetArgs() {
|
||||
* return [this.props.firstArg, this.props.secondArg];
|
||||
* }
|
||||
* }
|
||||
* <SpecificAdapter Component="LegacyWidget" firstArg="a" secondArg="b"/>
|
||||
*
|
||||
* If the legacy widget has to be updated when props change, one must define
|
||||
* a subclass of ComponentAdapter to override 'updateWidget' and 'renderWidget'. The
|
||||
* 'updateWidget' function takes the nextProps as argument, and should update the
|
||||
* internal state of the widget (might be async, and return a Promise).
|
||||
* However, to ensure that the DOM is updated all at once, it shouldn't do
|
||||
* a re-rendering. This is the role of function 'renderWidget', which will be
|
||||
* called just before patching the DOM, and which thus must be synchronous.
|
||||
* For instance:
|
||||
* class SpecificAdapter extends ComponentAdapter {
|
||||
* updateWidget(nextProps) {
|
||||
* return this.widget.updateState(nextProps);
|
||||
* }
|
||||
* renderWidget() {
|
||||
* return this.widget.render();
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export class ComponentAdapter extends LegacyComponent {
|
||||
/**
|
||||
* Creates the template on-the-fly, depending on the type of Component
|
||||
* (legacy widget or Owl component).
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
constructor(props) {
|
||||
if (!props.Component) {
|
||||
throw Error(`ComponentAdapter: 'Component' prop is missing.`);
|
||||
}
|
||||
let template;
|
||||
if (!(props.Component.prototype instanceof Component)) {
|
||||
template = templateForLegacy;
|
||||
} else {
|
||||
template = templateForOwl;
|
||||
}
|
||||
ComponentAdapter.template = template;
|
||||
super(...arguments);
|
||||
this.template = template;
|
||||
|
||||
this.widget = null; // widget instance, if Component is a legacy widget
|
||||
}
|
||||
|
||||
setup() {
|
||||
onWillUpdateProps((nextProps) => {
|
||||
if (this.widget) {
|
||||
return this.updateWidget(nextProps);
|
||||
}
|
||||
});
|
||||
|
||||
let widgetIsAttached = false;
|
||||
const insertWidget = () => {
|
||||
this.removeEl();
|
||||
if (!this.widget || !this.widget.el) {
|
||||
return;
|
||||
}
|
||||
const node = this.__owl__.firstNode();
|
||||
node.parentNode.insertBefore(this.widget.el, node);
|
||||
this.widgetEl = this.widget.el;
|
||||
widgetIsAttached = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
insertWidget();
|
||||
if (this.widget && this.widget.on_attach_callback) {
|
||||
this.widget.on_attach_callback();
|
||||
}
|
||||
});
|
||||
|
||||
onPatched(() => {
|
||||
if (widgetIsAttached) {
|
||||
this.renderWidget();
|
||||
} else {
|
||||
insertWidget();
|
||||
}
|
||||
});
|
||||
|
||||
onWillDestroy(() => this.__destroy(this.__owl__.parent.component));
|
||||
|
||||
onWillStart(this.onWillStart);
|
||||
onWillUnmount(this.onWillUnmount);
|
||||
}
|
||||
|
||||
onWillStart() {
|
||||
if (!(this.props.Component.prototype instanceof Component)) {
|
||||
this.widget = new this.props.Component(this, ...this.widgetArgs);
|
||||
return this.widget._widgetRenderAndInsert(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
onWillUnmount() {
|
||||
if (this.widget && this.widget.on_detach_callback) {
|
||||
this.widget.on_detach_callback();
|
||||
}
|
||||
this.removeEl();
|
||||
}
|
||||
|
||||
removeEl() {
|
||||
if (this.widgetEl) {
|
||||
this.widgetEl.remove();
|
||||
this.widgetEl = null;
|
||||
}
|
||||
}
|
||||
|
||||
__destroy() {
|
||||
this.removeEl();
|
||||
if (this.widget) {
|
||||
this.widget.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
get childProps() {
|
||||
if (!this._childProps) {
|
||||
this._childProps = Object.assign({}, this.props);
|
||||
delete this._childProps.Component;
|
||||
}
|
||||
return this._childProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter that translates the props (except 'Component') into the array
|
||||
* of arguments used to initialize the legacy widget.
|
||||
*
|
||||
* Must be overriden if at least two props (other that Component) are
|
||||
* given.
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
get widgetArgs() {
|
||||
if (this.props.widgetArgs) {
|
||||
return this.props.widgetArgs;
|
||||
}
|
||||
const args = Object.keys(this.props);
|
||||
args.splice(args.indexOf('Component'), 1);
|
||||
if (args.length > 1) {
|
||||
throw new Error(`ComponentAdapter has more than 1 argument, 'widgetArgs' must be overriden.`);
|
||||
}
|
||||
return args.map(a => this.props[a]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overriden to update the internal state of the widget when props
|
||||
* change. To ensure that the DOM is updated at once, this function should
|
||||
* not do a re-rendering (which should be done by 'render' instead).
|
||||
*
|
||||
* @param {Object} nextProps
|
||||
* @returns {Promise}
|
||||
*/
|
||||
updateWidget(/*nextProps*/) {
|
||||
if (odoo.debug.includes("assets")) {
|
||||
console.warn(`ComponentAdapter: Widget could not be updated, maybe override 'updateWidget' function?`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be overriden to re-render the widget after an update. This
|
||||
* function will be called just before patchin the DOM, s.t. the DOM is
|
||||
* updated at once. It must be synchronous
|
||||
*/
|
||||
renderWidget() {
|
||||
if (odoo.debug.includes("assets")) {
|
||||
console.warn(`ComponentAdapter: Widget could not be re-rendered, maybe override 'renderWidget' function?`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mocks _trigger_up to redirect Odoo legacy events to OWL events.
|
||||
*
|
||||
* @private
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_trigger_up(ev) {
|
||||
const evType = ev.name;
|
||||
const payload = ev.data;
|
||||
if (evType === 'call_service') {
|
||||
let args = payload.args || [];
|
||||
if (payload.service === 'ajax' && payload.method === 'rpc') {
|
||||
// ajax service uses an extra 'target' argument for rpc
|
||||
args = args.concat(ev.target);
|
||||
}
|
||||
const service = this.env.services[payload.service];
|
||||
//If the service doesn't exist it means that it was translated to Owl
|
||||
if (service) {
|
||||
const result = service[payload.method].apply(service, args);
|
||||
payload.callback(result);
|
||||
} else {
|
||||
throw new Error(
|
||||
`The service "${payload.service}" is not present in the legacy owl environment.
|
||||
You should probably create a mapper in @web/legacy/utils`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
payload.__targetWidget = ev.target;
|
||||
this.trigger(evType.replace(/_/g, '-'), payload);
|
||||
}
|
||||
}
|
||||
|
||||
get el() {
|
||||
if (this.widget) {
|
||||
return this.widget.el;
|
||||
}
|
||||
return super.el;
|
||||
}
|
||||
}
|
||||
|
||||
const bodyRef = { get el() { return document.body } };
|
||||
export function standaloneAdapter(props = {}, ref = bodyRef) {
|
||||
const env = owl.Component.env;
|
||||
const app = new App(null, {
|
||||
templates,
|
||||
env,
|
||||
dev: !!odoo.debug,
|
||||
translatableAttributes: ["data-tooltip"],
|
||||
translateFn: _t,
|
||||
});
|
||||
if (!("Component" in props)) {
|
||||
props.Component = owl.Component;
|
||||
}
|
||||
const component = app.makeNode(ComponentAdapter, props).component;
|
||||
Object.defineProperty(component, "el", {
|
||||
get() {
|
||||
return ref.el;
|
||||
}
|
||||
});
|
||||
return component;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case 2) A legacy widget has to instantiate Owl components
|
||||
* ---------------------------------------------------------
|
||||
*
|
||||
* The WidgetAdapterMixin and the ComponentWrapper are meant to be used
|
||||
* together when an Odoo legacy widget needs to instantiate Owl components.
|
||||
* In this case, the widgets/components hierarchy would look like:
|
||||
*
|
||||
* Legacy Widget + WidgetAdapterMixin
|
||||
* |
|
||||
* ComponentWrapper (Owl component)
|
||||
* |
|
||||
* Owl Component
|
||||
*
|
||||
* In this case, the parent legacy widget must use the WidgetAdapterMixin,
|
||||
* which ensures that Owl hooks (mounted, willUnmount, destroy...) are
|
||||
* properly called on the sub components. Moreover, it must instantiate a
|
||||
* ComponentWrapper, and provide it the Owl component class to use alongside
|
||||
* its props. This wrapper will ensure that the Owl component will be
|
||||
* correctly updated (with willUpdateProps) like it would be if it was embed
|
||||
* in an Owl hierarchy. Moreover, this wrapper automatically redirects all
|
||||
* events triggered by the Owl component (or its descendants) to legacy
|
||||
* custom events (trigger_up) on the parent legacy widget.
|
||||
|
||||
* For example:
|
||||
* class MyComponent extends Component {}
|
||||
* MyComponent.template = xml`<div>Owl component with value <t t-esc="props.value"/></div>`;
|
||||
* const MyWidget = Widget.extend(WidgetAdapterMixin, {
|
||||
* start() {
|
||||
* this.component = new ComponentWrapper(this, MyComponent, {value: 44});
|
||||
* return this.component.mount(this.el);
|
||||
* },
|
||||
* update() {
|
||||
* return this.component.update({value: 45});
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export const WidgetAdapterMixin = {
|
||||
/**
|
||||
* Calls on_attach_callback on each child ComponentWrapper, which will
|
||||
* call __callMounted on each sub component (recursively), to mark them
|
||||
* as mounted.
|
||||
*/
|
||||
on_attach_callback() {
|
||||
for (const component of children.get(this) || []) {
|
||||
component.on_attach_callback();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Calls on_detach_callback on each child ComponentWrapper, which will
|
||||
* call __callWillUnmount to mark itself and its children as no longer
|
||||
* mounted.
|
||||
*/
|
||||
on_detach_callback() {
|
||||
for (const component of children.get(this) || []) {
|
||||
component.on_detach_callback();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Destroys each sub component when the widget is destroyed. We call the
|
||||
* private __destroy function as there is no need to remove the el from
|
||||
* the DOM (will be removed alongside this widget).
|
||||
*/
|
||||
destroy() {
|
||||
for (const wrapper of children.get(this) || []) {
|
||||
wrapper.destroy();
|
||||
}
|
||||
children.delete(this);
|
||||
},
|
||||
};
|
||||
|
||||
//----------------------------------//
|
||||
// Low-level coordination functions //
|
||||
//----------------------------------//
|
||||
|
||||
/**
|
||||
* Calls "callback" recursively on a ComponentNode's hierarchy
|
||||
*
|
||||
* @param {ComponentNode}
|
||||
* @param {Boolean} childrenFirst whether to execute on the bottom-most child first
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function recursiveCall(node, childrenFirst = false, callback) {
|
||||
if (!childrenFirst) {
|
||||
callback(node);
|
||||
}
|
||||
for (const child of Object.values(node.children)) {
|
||||
recursiveCall(child, childrenFirst, callback);
|
||||
}
|
||||
if (childrenFirst) {
|
||||
callback(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the node able to distinguish between
|
||||
* mounting in the DOM and mounting outside of if.
|
||||
* @param {ComponentNode} node
|
||||
*/
|
||||
function prepareForFinish(node) {
|
||||
const fiber = node.fiber;
|
||||
const complete = fiber.complete;
|
||||
fiber.complete = function () {
|
||||
// if target is not in dom
|
||||
// just trigger mounted hooks on the Proxy, not on any other node
|
||||
if (!this.target.ownerDocument.contains(this.target)) {
|
||||
this.mounted = [this];
|
||||
// We skipped a bunch of mounted calls.
|
||||
// Following calls to patched may crash because of this.
|
||||
// (e.g. useEffect dependencies set in mounted and used in patched)
|
||||
}
|
||||
complete.call(this);
|
||||
};
|
||||
}
|
||||
|
||||
const nodesToRemount = new WeakMap();
|
||||
/**
|
||||
* Pushed a node into the nodesToRemount WeakMap.
|
||||
* The value is a callback that sets the node for remounting
|
||||
* @param {ComponentNode} node
|
||||
* @param {function} updateAndRender The original node's prototype's
|
||||
*/
|
||||
function setToRemount(node, updateAndRender) {
|
||||
let toRemount = true;
|
||||
|
||||
if (!node.isPatched) {
|
||||
node.isPatched = true;
|
||||
node.mounted.push(() => {
|
||||
toRemount = false;
|
||||
});
|
||||
node.willUpdateProps.push(() => {
|
||||
const rootMounted = node.fiber.root.mounted;
|
||||
if (toRemount && !rootMounted.includes(node.fiber)) {
|
||||
rootMounted.push(node.fiber);
|
||||
}
|
||||
});
|
||||
}
|
||||
return () => toRemount = true;
|
||||
}
|
||||
/**
|
||||
* Make the node able to remount its children nodes correctly
|
||||
* Typically, in that case we don't call willPatch and patched hooks,
|
||||
* rather, we want to call the mounted hooks
|
||||
* @param {ComponentNode} mainNode
|
||||
*/
|
||||
function prepareForRemount(mainNode) {
|
||||
const updateAndRender = mainNode.updateAndRender;
|
||||
recursiveCall(mainNode, false, (node) => {
|
||||
if (mainNode === node) {
|
||||
return;
|
||||
}
|
||||
if (nodesToRemount.has(node)) {
|
||||
nodesToRemount.get(node)();
|
||||
return;
|
||||
} else {
|
||||
nodesToRemount.set(node, setToRemount(node, updateAndRender));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a wrapper instance as a child of the given parent in the
|
||||
* 'children' weakMap.
|
||||
*
|
||||
* @private
|
||||
* @param {Widget} parent
|
||||
*/
|
||||
function registerWrapper(parent, wrapper) {
|
||||
let parentChildren = children.get(parent);
|
||||
if (!parentChildren) {
|
||||
parentChildren = [];
|
||||
children.set(parent, parentChildren);
|
||||
}
|
||||
parentChildren.push(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* The component class that will be instanciated between a legacy and an OWL 2 layer.
|
||||
*/
|
||||
class ProxyComponent extends LegacyComponent {
|
||||
setup() {
|
||||
for (const [hookName, cb] of Object.entries(this.props.hooks)) {
|
||||
owl[hookName](cb);
|
||||
}
|
||||
onWillUnmount(() => {
|
||||
// The current el will be change if we remount after unmounting
|
||||
this._handledEvents = new Set();
|
||||
});
|
||||
this.parentWidget = this.props.parentWidget;
|
||||
this._handledEvents = new Set();
|
||||
useSubEnv({
|
||||
[widgetSymbol]: this._addListener.bind(this)
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Adds an event handler that will redirect the given Owl event to an
|
||||
* Odoo legacy event. This function is called just before the event is
|
||||
* actually triggered.
|
||||
*
|
||||
* @private
|
||||
* @param {string} evType
|
||||
*/
|
||||
_addListener(evType) {
|
||||
if (this.parentWidget && !this._handledEvents.has(evType) && status(this) === "mounted") {
|
||||
this._handledEvents.add(evType);
|
||||
this.el.addEventListener(evType, ev => {
|
||||
// as the WrappeComponent has the same root node as the
|
||||
// actual sub Component, we have to check that the event
|
||||
// hasn't been stopped by that component (it would naturally
|
||||
// call stopPropagation, whereas it should actually call
|
||||
// stopImmediatePropagation to prevent from getting here)
|
||||
if (!ev.cancelBubble) {
|
||||
ev.stopPropagation();
|
||||
const detail = Object.assign({}, ev.detail, {
|
||||
__originalComponent: ev.originalComponent,
|
||||
});
|
||||
this.parentWidget.trigger_up(ev.type.replace(/-/g, '_'), detail);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
ProxyComponent.template = xml`<t t-component="props.Component" t-props="props.props"/>`;
|
||||
|
||||
export class ComponentWrapper {
|
||||
constructor(parent, Component, props) {
|
||||
if (parent instanceof Component) {
|
||||
throw new Error("ComponentWrapper must be used with a legacy Widget as parent");
|
||||
}
|
||||
this.setParent(parent);
|
||||
const _env = props.env;
|
||||
delete props.env;
|
||||
this.props = props;
|
||||
|
||||
this.Component = Component;
|
||||
|
||||
const env = _env || owl.Component.env;
|
||||
const appConfig = {
|
||||
env,
|
||||
templates,
|
||||
dev: env.debug,
|
||||
translatableAttributes: ["data-tooltip"],
|
||||
translateFn: _t,
|
||||
};
|
||||
const app = new App(null, appConfig);
|
||||
this.app = app;
|
||||
this.node = this._makeOwlNode();
|
||||
this.env = this.node.component.env;
|
||||
app.root = this.node;
|
||||
this.__owl__ = Object.create(this.node);
|
||||
this.componentRef = { comp: null };
|
||||
this.status = "new";
|
||||
this.setup();
|
||||
}
|
||||
|
||||
_makeOwlNode() {
|
||||
const resolveRender = () => {
|
||||
if (this.renderResolve) {
|
||||
this.renderResolve();
|
||||
}
|
||||
};
|
||||
|
||||
const props = {
|
||||
props: this.props,
|
||||
hooks: {
|
||||
onMounted: resolveRender,
|
||||
onPatched: resolveRender,
|
||||
onRendered: () => {
|
||||
this.componentRef.comp = Object.values(this.node.children)[0].component;
|
||||
},
|
||||
},
|
||||
Component: this.Component,
|
||||
parentWidget: this.parentWidget,
|
||||
};
|
||||
return this.app.makeNode(ProxyComponent, props);
|
||||
}
|
||||
|
||||
setup() { }
|
||||
|
||||
get el() {
|
||||
return this.node.component.el;
|
||||
}
|
||||
|
||||
//------------------//
|
||||
// OWL 1 - like API //
|
||||
//------------------//
|
||||
|
||||
async mount(target, options) {
|
||||
if (this.status === "mounted" || this.status === "willMount") {
|
||||
return this.render();
|
||||
} else if (this.status === "destroyed") {
|
||||
return;
|
||||
}
|
||||
if (target) {
|
||||
this.target = target;
|
||||
}
|
||||
if (options) {
|
||||
this.mountOptions = options;
|
||||
}
|
||||
const remount = this.status === "unmounted";
|
||||
if (remount) {
|
||||
prepareForRemount(this.node);
|
||||
}
|
||||
this.status = "willMount";
|
||||
const prom = this.app.mountNode(this.node, this.target, this.mountOptions);
|
||||
if (remount) {
|
||||
this.node.fiber.deep = true;
|
||||
}
|
||||
prepareForFinish(this.node);
|
||||
await prom;
|
||||
if (this.target.ownerDocument.contains(this.target)) {
|
||||
this.status = "mounted";
|
||||
}
|
||||
this.node.willStart = [];
|
||||
// remove the promise.resolve from mounted callbacks
|
||||
const mounted = this.node.mounted;
|
||||
this.node.mounted = mounted.slice(0, mounted.length - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
unmount() {
|
||||
this.on_detach_callback();
|
||||
this.el.remove();
|
||||
this.node.bdom = null;
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.status !== "mounted") {
|
||||
return;
|
||||
}
|
||||
if (this.renderProm) {
|
||||
this.node.render(true);
|
||||
return this.renderProm;
|
||||
}
|
||||
this.renderProm = new Promise((resolve, reject) => {
|
||||
this.renderResolve = resolve;
|
||||
this.renderReject = reject;
|
||||
}).then(() => {
|
||||
this.renderProm = null;
|
||||
this.renderResolve = null;
|
||||
this.renderReject = null;
|
||||
});
|
||||
this.node.render(true);
|
||||
return this.renderProm;
|
||||
}
|
||||
|
||||
trigger() {
|
||||
return this.node.component.trigger(...arguments);
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (["willMount", "unmounted"].includes(this.status)) {
|
||||
recursiveCall(this.node, false, (node) => {
|
||||
node.willUnmount = [];
|
||||
});
|
||||
}
|
||||
this.app.destroy();
|
||||
this.status = "destroyed";
|
||||
}
|
||||
|
||||
//---------------------//
|
||||
// API from legacy POV //
|
||||
//---------------------//
|
||||
|
||||
get $el() {
|
||||
return $(this.el);
|
||||
}
|
||||
|
||||
on_attach_callback() {
|
||||
if (!this.el || !this.el.ownerDocument.contains(this.el)) {
|
||||
return;
|
||||
}
|
||||
if (this.status === "mounted") {
|
||||
return;
|
||||
}
|
||||
recursiveCall(this.node, true, (node) => {
|
||||
for (const cb of node.mounted) {
|
||||
cb();
|
||||
}
|
||||
});
|
||||
this.node.status = 1;
|
||||
this.status = "mounted";
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls willUnmount to notify the component it will be unmounted.
|
||||
*/
|
||||
on_detach_callback() {
|
||||
if (this.status === "unmounted") {
|
||||
return;
|
||||
}
|
||||
recursiveCall(this.node, false, (node) => {
|
||||
// node.status might be "new" (0) here, if the component is
|
||||
// currently being re-rendered, and a new component has just
|
||||
// been instantiated, but as the rendering isn't completed, it
|
||||
// isn't mounted yet
|
||||
if (node.status === 1) {
|
||||
const component = node.component;
|
||||
for (const cb of node.willUnmount) {
|
||||
cb.call(component);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.node.status = 0;
|
||||
this.status = "unmounted";
|
||||
}
|
||||
|
||||
async update(nextProps) {
|
||||
if (this.status === "destroyed") {
|
||||
return;
|
||||
}
|
||||
const props = this.node.component.props.props;
|
||||
const nextComponentProps = Object.assign({}, props, nextProps);
|
||||
this.node.component.props.props = nextComponentProps;
|
||||
this.props = nextComponentProps;
|
||||
if (this.status === "unmounted") {
|
||||
return this.mount(this.target);
|
||||
} else {
|
||||
return this.render();
|
||||
}
|
||||
}
|
||||
|
||||
setParent(parent) {
|
||||
if (parent instanceof Component) {
|
||||
throw new Error('ComponentWrapper must be used with a legacy Widget as parent');
|
||||
}
|
||||
if (parent) {
|
||||
registerWrapper(parent, this);
|
||||
}
|
||||
if (this.parentWidget) {
|
||||
const parentChildren = children.get(this.parentWidget);
|
||||
parentChildren.splice(parentChildren.indexOf(this), 1);
|
||||
}
|
||||
|
||||
this.parentWidget = parent;
|
||||
if (this.node) {
|
||||
this.node.component.parentWidget = parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
ComponentAdapter,
|
||||
ComponentWrapper,
|
||||
WidgetAdapterMixin,
|
||||
standaloneAdapter,
|
||||
};
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
makeLegacyDialogMappingService,
|
||||
mapLegacyEnvToWowlEnv,
|
||||
makeLegacyRPCService,
|
||||
createWidgetParent,
|
||||
} from "../../utils";
|
||||
import { standaloneAdapter } from "@web/legacy/js/owl_compatibility";
|
||||
|
||||
import { makeEnv, startServices } from "@web/env";
|
||||
import { loadJS, templates } from '@web/core/assets';
|
||||
@@ -24,7 +24,7 @@ import { jsonrpc } from '@web/core/network/rpc_service';
|
||||
import { renderToString } from "@web/core/utils/render";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
import { Component, App, whenReady } from "@odoo/owl";
|
||||
import { App, whenReady } from "@odoo/owl";
|
||||
import { getOrigin } from '@web/core/utils/urls';
|
||||
|
||||
const { Settings } = luxon;
|
||||
@@ -395,8 +395,7 @@ export async function createPublicRoot(RootWidget) {
|
||||
|
||||
await startServices(wowlEnv);
|
||||
mapLegacyEnvToWowlEnv(legacyEnv, wowlEnv);
|
||||
// The root widget's parent is a standalone adapter so that it has _trigger_up
|
||||
const publicRoot = new RootWidget(standaloneAdapter({ Component }));
|
||||
const publicRoot = new RootWidget(createWidgetParent(legacyEnv));
|
||||
const app = new App(MainComponentsContainer, {
|
||||
templates,
|
||||
env: wowlEnv,
|
||||
|
||||
@@ -1,95 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* This component SHOULD NOT be used!
|
||||
* It's used in legacy when components should work like in owl 1.
|
||||
*
|
||||
* If you need to get the component's `el` then use `useRef`
|
||||
* and if you need to send info to the component's parent then use props.
|
||||
*
|
||||
* e.g.
|
||||
* // OWL 1
|
||||
* class Component_OWL1 extends owl.Component {
|
||||
* static template = owl.tags.xml`
|
||||
* <button t-on-click="() => this.trigger('up')">click me</button>
|
||||
* `;
|
||||
* mounted() {
|
||||
* this.el.style = "color: red";
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // OWL 2
|
||||
* class Component_OWL2 extends owl.Component {
|
||||
* static template = owl.xml`
|
||||
* <button t-ref="btn" t-on-click="() => props.up()">click me</button>
|
||||
* `;
|
||||
* setup() {
|
||||
* this.btnRef = useRef("btn");
|
||||
* owl.onMounted(() => {
|
||||
* this.btnRef.el.style = "color: red";
|
||||
* });
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export class LegacyComponent extends owl.Component {
|
||||
get el() {
|
||||
const bdom = this.__owl__.bdom;
|
||||
if (!bdom) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const el = (bdom.component && bdom.component.el) || bdom.firstNode();
|
||||
return el.nodeType === Node.ELEMENT_NODE ? el : null;
|
||||
}
|
||||
/**
|
||||
* Add a new method to owl Components to ensure that no performed RPC is
|
||||
* resolved/rejected when the component is destroyed.
|
||||
*/
|
||||
rpc() {
|
||||
return new Promise((resolve, reject) => {
|
||||
return this.env.services
|
||||
.rpc(...arguments)
|
||||
.then((result) => {
|
||||
if (owl.status(this) !== "destroyed") {
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
.catch((reason) => {
|
||||
if (owl.status(this) !== "destroyed") {
|
||||
reject(reason);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit a custom event of type 'eventType' with the given 'payload' on the
|
||||
* component's el, if it exists. However, note that the event will only bubble
|
||||
* up to the parent DOM nodes. Thus, it must be called between mounted() and
|
||||
* willUnmount().
|
||||
*/
|
||||
trigger(eventType, payload) {
|
||||
this.__trigger(eventType, payload);
|
||||
}
|
||||
/**
|
||||
* Private trigger method, allows to choose the component which triggered
|
||||
* the event in the first place
|
||||
*/
|
||||
__trigger(eventType, payload) {
|
||||
if (this.env[odoo.widgetSymbol]) {
|
||||
this.env[odoo.widgetSymbol](eventType);
|
||||
}
|
||||
if (this.el) {
|
||||
const ev = new CustomEvent(eventType, {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
detail: payload,
|
||||
});
|
||||
this.el.dispatchEvent(ev);
|
||||
}
|
||||
}
|
||||
}
|
||||
LegacyComponent.props = {
|
||||
"*": true,
|
||||
};
|
||||
@@ -2,8 +2,20 @@
|
||||
|
||||
import { browser } from "../core/browser/browser";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { Component, useComponent, xml } from "@odoo/owl";
|
||||
import { loadJS } from "@web/core/assets";
|
||||
import {
|
||||
App,
|
||||
Component,
|
||||
onMounted,
|
||||
onWillStart,
|
||||
onWillUnmount,
|
||||
useComponent,
|
||||
useEnv,
|
||||
useRef,
|
||||
useState,
|
||||
xml,
|
||||
} from "@odoo/owl";
|
||||
import { loadJS, templates } from "@web/core/assets";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import {
|
||||
ConnectionAbortedError,
|
||||
ConnectionLostError,
|
||||
@@ -234,3 +246,82 @@ export function useWowlService(serviceName) {
|
||||
component.env = env;
|
||||
return service;
|
||||
}
|
||||
|
||||
export function createWidgetParent(env) {
|
||||
return {
|
||||
env,
|
||||
_trigger_up: (ev) => {
|
||||
if (ev.name === "call_service") {
|
||||
let args = ev.data.args || [];
|
||||
if (ev.data.service === "ajax" && ev.data.method === "rpc") {
|
||||
// ajax service uses an extra 'target' argument for rpc
|
||||
args = args.concat(ev.target);
|
||||
}
|
||||
const service = env.services[ev.data.service];
|
||||
const result = service[ev.data.method].apply(service, args);
|
||||
ev.data.callback(result);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function useWidget(refName, widgetClass, params = []) {
|
||||
const ref = useRef(refName);
|
||||
const env = useEnv();
|
||||
|
||||
const parent = createWidgetParent(env);
|
||||
const widget = new widgetClass(parent, ...params);
|
||||
|
||||
onWillStart(() => {
|
||||
return widget._widgetRenderAndInsert(() => {});
|
||||
});
|
||||
onMounted(() => {
|
||||
ref.el.append(widget.el);
|
||||
});
|
||||
onWillUnmount(() => {
|
||||
widget.destroy();
|
||||
});
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
const rootTemplate = xml`<SubComp t-props="state"/>`;
|
||||
export async function attachComponent(parent, element, componentClass, props = {}) {
|
||||
class Root extends Component {
|
||||
static template = rootTemplate;
|
||||
static components = { SubComp: componentClass };
|
||||
state = useState(props);
|
||||
}
|
||||
|
||||
const env = Component.env;
|
||||
const app = new App(Root, {
|
||||
env,
|
||||
templates,
|
||||
dev: env.debug,
|
||||
translatableAttributes: ["data-tooltip"],
|
||||
translateFn: _t,
|
||||
});
|
||||
|
||||
if (parent.__parentedMixin) {
|
||||
parent.__parentedChildren.push({
|
||||
get $el() {
|
||||
return $(app.root.el);
|
||||
},
|
||||
destroy() {
|
||||
app.destroy();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const component = await app.mount(element);
|
||||
const subComp = Object.values(component.__owl__.children)[0].component;
|
||||
return {
|
||||
component: subComp,
|
||||
destroy() {
|
||||
app.destroy();
|
||||
},
|
||||
update(props) {
|
||||
Object.assign(component.state, props);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import makeTestEnvironment from "@web/../tests/legacy/helpers/test_env";
|
||||
import testUtils from "@web/../tests/legacy/helpers/test_utils";
|
||||
import { destroy, getFixture, mount } from "@web/../tests/helpers/utils";
|
||||
import { LegacyComponent } from "@web/legacy/legacy_component";
|
||||
|
||||
const { xml } = owl;
|
||||
|
||||
let target;
|
||||
QUnit.module("web", { beforeEach() { target = getFixture(); }}, function () {
|
||||
QUnit.module("Component Extension");
|
||||
|
||||
QUnit.test("Component destroyed while performing successful RPC", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class Parent extends LegacyComponent {}
|
||||
Parent.template = xml`<div/>`;
|
||||
|
||||
const env = makeTestEnvironment({}, () => Promise.resolve());
|
||||
|
||||
const parent = await mount(Parent, target, { env });
|
||||
|
||||
parent.rpc({}).then(() => { throw new Error(); });
|
||||
destroy(parent);
|
||||
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.ok(true, "Promise should still be pending");
|
||||
});
|
||||
|
||||
QUnit.test("Component destroyed while performing failed RPC", async function (assert) {
|
||||
assert.expect(1);
|
||||
|
||||
class Parent extends LegacyComponent {}
|
||||
Parent.template = xml`<div/>`;
|
||||
|
||||
const env = makeTestEnvironment({}, () => Promise.reject());
|
||||
const parent = await mount(Parent, target, { env });
|
||||
|
||||
parent.rpc({}).catch(() => { throw new Error(); });
|
||||
destroy(parent);
|
||||
|
||||
await testUtils.nextTick();
|
||||
|
||||
assert.ok(true, "Promise should still be pending");
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,6 @@ Odoo Web Editor widget.
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/libs/owl.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
|
||||
@@ -17,7 +17,7 @@ import { debounce, throttleForAnimation } from "@web/core/utils/timing";
|
||||
import { uniqueId } from "@web/core/utils/functions";
|
||||
import { sortBy, unique } from "@web/core/utils/arrays";
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { ComponentWrapper } from "@web/legacy/js/owl_compatibility";
|
||||
import { attachComponent } from "@web/legacy/utils";
|
||||
import { Toolbar } from "@web_editor/js/editor/toolbar";
|
||||
import {
|
||||
Component,
|
||||
@@ -4297,14 +4297,13 @@ var SnippetsMenu = Widget.extend({
|
||||
wysiwygState: Object,
|
||||
};
|
||||
}
|
||||
const toolbarWrapper = new ComponentWrapper(this, WebsiteToolbar, {
|
||||
wysiwygState: this.options.wysiwyg.state,
|
||||
});
|
||||
// Add the toolbarWrapperEl to the dom for owl to properly mount the
|
||||
// Toolbar.
|
||||
document.body.append(this._toolbarWrapperEl);
|
||||
this._toolbarWrapperEl.style.display = 'none';
|
||||
await toolbarWrapper.mount(this._toolbarWrapperEl);
|
||||
await attachComponent(this, this._toolbarWrapperEl, WebsiteToolbar, {
|
||||
wysiwygState: this.options.wysiwyg.state,
|
||||
});
|
||||
this._toolbarWrapperEl.style.display = 'contents';
|
||||
|
||||
const toolbarEl = this._toolbarWrapperEl.firstChild;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ComponentWrapper } from "@web/legacy/js/owl_compatibility";
|
||||
import { attachComponent } from "@web/legacy/utils";
|
||||
import { MediaDialog } from "@web_editor/components/media_dialog/media_dialog";
|
||||
import Dialog from "@web/legacy/js/core/dialog";
|
||||
import dom from "@web/legacy/js/core/dom";
|
||||
@@ -1413,6 +1413,8 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
const _super = this._super.bind(this);
|
||||
const args = arguments;
|
||||
|
||||
this.resetTabCount = 0;
|
||||
|
||||
if (!this.options.dataAttributes.lazyPalette === 'true') {
|
||||
await this._renderColorPalette();
|
||||
}
|
||||
@@ -1444,7 +1446,7 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
this.colorPaletteWrapper?.update({
|
||||
selectedCC: this._ccValue,
|
||||
selectedColor: this._value,
|
||||
resetTabCount: this.colorPaletteWrapper.node.component.props.props.resetTabCount + 1,
|
||||
resetTabCount: ++this.resetTabCount,
|
||||
});
|
||||
} else {
|
||||
// TODO review in master, this does async stuff. Maybe the open
|
||||
@@ -1591,9 +1593,10 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_renderColorPalette: function () {
|
||||
_renderColorPalette: async function () {
|
||||
this.resetTabCount = 0;
|
||||
const options = {
|
||||
resetTabCount: 0,
|
||||
resetTabCount: this.resetTabCount,
|
||||
selectedCC: this._ccValue,
|
||||
selectedColor: this._value,
|
||||
onSetColorNames: (colorNames) => {
|
||||
@@ -1636,12 +1639,8 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
options.document = this.$target[0].ownerDocument;
|
||||
options.getTemplate = wysiwyg.getColorpickerTemplate.bind(wysiwyg);
|
||||
}
|
||||
if (this.colorPaletteWrapper) {
|
||||
this.colorPaletteWrapper.destroy();
|
||||
}
|
||||
this.colorPaletteWrapper = new ComponentWrapper(this, ColorPalette, options);
|
||||
|
||||
return this.colorPaletteWrapper.mount(this.colorPaletteEl);
|
||||
this.colorPaletteWrapper?.destroy();
|
||||
this.colorPaletteWrapper = await attachComponent(this, this.colorPaletteEl, ColorPalette, options);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -6020,15 +6019,14 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
this.trigger_up('disable_loading_effect');
|
||||
const img = this._getImg();
|
||||
const document = this.$el[0].ownerDocument;
|
||||
const imageCropWrapper = new ComponentWrapper(this, ImageCrop, {
|
||||
const imageCropWrapperElement = document.createElement('div');
|
||||
document.body.append(imageCropWrapperElement);
|
||||
const imageCropWrapper = await attachComponent(this, imageCropWrapperElement, ImageCrop, {
|
||||
rpc: this._rpc.bind(this),
|
||||
activeOnStart: true,
|
||||
media: img,
|
||||
mimetype: this._getImageMimetype(img),
|
||||
});
|
||||
const imageCropWrapperElement = document.createElement('div');
|
||||
document.body.append(imageCropWrapperElement);
|
||||
await imageCropWrapper.mount(imageCropWrapperElement);
|
||||
|
||||
await new Promise(resolve => {
|
||||
this.$target.one('image_cropper_destroyed', async () => {
|
||||
@@ -6037,9 +6035,10 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
}
|
||||
await this._reapplyCurrentShape();
|
||||
resolve();
|
||||
imageCropWrapperElement.remove();
|
||||
});
|
||||
});
|
||||
imageCropWrapperElement.remove();
|
||||
imageCropWrapper.destroy();
|
||||
this.trigger_up('enable_loading_effect');
|
||||
},
|
||||
/**
|
||||
@@ -6083,18 +6082,17 @@ registry.ImageTools = ImageHandlerOption.extend({
|
||||
// Mount the ImageCrop to call the reset method. As we need the state of
|
||||
// the component to be mounted before calling reset, mount it
|
||||
// temporarily into the body.
|
||||
const imageCropWrapper = new ComponentWrapper(this, ImageCrop, {
|
||||
const imageCropWrapperElement = document.createElement('div');
|
||||
document.body.append(imageCropWrapperElement);
|
||||
const imageCropWrapper = await attachComponent(this, imageCropWrapperElement, ImageCrop, {
|
||||
rpc: this._rpc.bind(this),
|
||||
activeOnStart: true,
|
||||
media: img,
|
||||
mimetype: this._getImageMimetype(img),
|
||||
});
|
||||
const imageCropWrapperElement = document.createElement('div');
|
||||
document.body.append(imageCropWrapperElement);
|
||||
await imageCropWrapper.mount(imageCropWrapperElement);
|
||||
await imageCropWrapper.componentRef.comp.mountedPromise;
|
||||
await imageCropWrapper.componentRef.comp.reset();
|
||||
imageCropWrapper.unmount();
|
||||
await imageCropWrapper.component.mountedPromise;
|
||||
await imageCropWrapper.component.reset();
|
||||
imageCropWrapper.destroy();
|
||||
imageCropWrapperElement.remove();
|
||||
|
||||
await this._reapplyCurrentShape();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { loadBundle } from "@web/core/assets";
|
||||
import { useWowlService } from '@web/legacy/utils';
|
||||
import { useWowlService, attachComponent } from '@web/legacy/utils';
|
||||
import { requireLegacyModule } from '@web_editor/js/frontend/loader';
|
||||
|
||||
export async function loadWysiwygFromTextarea(parent, textarea, options) {
|
||||
@@ -16,7 +16,6 @@ export async function loadWysiwygFromTextarea(parent, textarea, options) {
|
||||
currentOptions.value = '<p><br></p>';
|
||||
}
|
||||
|
||||
const { ComponentWrapper } = await requireLegacyModule('@web/legacy/js/owl_compatibility')
|
||||
const { Wysiwyg } = await requireLegacyModule('@web_editor/js/wysiwyg/wysiwyg', async () => {
|
||||
await loadBundle("web_editor.assets_wysiwyg");
|
||||
});
|
||||
@@ -40,11 +39,10 @@ export async function loadWysiwygFromTextarea(parent, textarea, options) {
|
||||
$form.append($textarea);
|
||||
$wysiwygWrapper.html('');
|
||||
const wysiwygWrapper = $wysiwygWrapper[0];
|
||||
const componentWrapper = new ComponentWrapper(parent, LegacyWysiwyg, {
|
||||
await attachComponent(parent, wysiwygWrapper, LegacyWysiwyg, {
|
||||
options: currentOptions,
|
||||
editingValue: currentOptions.value,
|
||||
});
|
||||
componentWrapper.mount(wysiwygWrapper);
|
||||
|
||||
$form.find('.note-editable').data('wysiwyg', wysiwyg);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import {ComponentAdapter} from '@web/legacy/js/owl_compatibility';
|
||||
import { useWidget } from "@web/legacy/utils";
|
||||
import {useService} from '@web/core/utils/hooks';
|
||||
import AceEditor from '@web_editor/js/common/ace';
|
||||
|
||||
const {Component} = owl;
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
|
||||
export const WebsiteAceEditor = AceEditor.extend({
|
||||
|
||||
@@ -37,12 +37,7 @@ export const WebsiteAceEditor = AceEditor.extend({
|
||||
// specific or because its parent was edited too and the view
|
||||
// got copy/unlink).
|
||||
const selectedView = Object.values(this.views).find(view => view.id === this._getSelectedResource());
|
||||
let context;
|
||||
this.trigger_up('context_get', {
|
||||
callback: (ctx) => {
|
||||
context = ctx;
|
||||
},
|
||||
});
|
||||
const context = this.options.getContext();
|
||||
defs.push(this._rpc({
|
||||
model: 'ir.ui.view',
|
||||
method: 'search_read',
|
||||
@@ -97,37 +92,19 @@ export const WebsiteAceEditor = AceEditor.extend({
|
||||
* @override
|
||||
*/
|
||||
_rpc(options) {
|
||||
let context;
|
||||
this.trigger_up('context_get', {
|
||||
callback: (ctx) => {
|
||||
context = ctx;
|
||||
},
|
||||
});
|
||||
return this._super({...options, context: context});
|
||||
return this._super({ ...options, context: this.options.getContext() });
|
||||
},
|
||||
});
|
||||
|
||||
export class AceEditorAdapterComponent extends ComponentAdapter {
|
||||
setup() {
|
||||
super.setup();
|
||||
export class AceEditorAdapterComponent extends Component {
|
||||
static template = xml`<div style="display: contents;" t-ref="wrapper"/>`;
|
||||
|
||||
setup() {
|
||||
this.website = useService("website");
|
||||
this.user = useService("user");
|
||||
this.env = Component.env;
|
||||
}
|
||||
|
||||
_trigger_up(event) {
|
||||
if (event.name === 'context_get') {
|
||||
return event.data.callback({...this.user.context, website_id: this.website.currentWebsite.id});
|
||||
}
|
||||
super._trigger_up(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
get widgetArgs() {
|
||||
return [
|
||||
useWidget("wrapper", WebsiteAceEditor, [
|
||||
this.website.pageDocument && this.website.pageDocument.documentElement.dataset.viewXmlid,
|
||||
{
|
||||
toggleAceEditor: () => this.website.context.showAceEditor = false,
|
||||
@@ -139,10 +116,11 @@ export class AceEditorAdapterComponent extends ComponentAdapter {
|
||||
reload: () => {
|
||||
this.website.contentWindow.location.reload();
|
||||
},
|
||||
}
|
||||
];
|
||||
getContext: () => ({
|
||||
...this.user.context,
|
||||
website_id: this.website.currentWebsite.id,
|
||||
}),
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
AceEditorAdapterComponent.defaultProps = {
|
||||
Component: WebsiteAceEditor,
|
||||
};
|
||||
|
||||
@@ -157,8 +157,6 @@ Featuring
|
||||
'web/static/lib/owl/owl.js',
|
||||
'web/static/lib/owl/odoo_module.js',
|
||||
'web/static/src/libs/owl.js',
|
||||
'web/static/src/legacy/js/component_extension.js',
|
||||
'web/static/src/legacy/legacy_component.js',
|
||||
'web/static/lib/jquery/jquery.js',
|
||||
'web/static/lib/jquery.ui/jquery-ui.js',
|
||||
'web/static/lib/jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js',
|
||||
|
||||
Reference in New Issue
Block a user