diff --git a/addons/web/static/src/core/effects/effect_container.js b/addons/web/static/src/core/effects/effect_container.js
index e57753bec98e1..e4b40d4dcd450 100644
--- a/addons/web/static/src/core/effects/effect_container.js
+++ b/addons/web/static/src/core/effects/effect_container.js
@@ -1,25 +1,24 @@
/** @odoo-module **/
-import { RainbowMan } from "./rainbow_man";
-
const { Component, tags } = owl;
export class EffectContainer extends Component {
setup() {
- this.rainbowProps = {};
+ this.effect = null;
this.props.bus.on("UPDATE", this, (effect) => {
- this.rainbowProps = effect;
+ this.effect = effect;
this.render();
});
}
- closeRainbowMan() {
- this.rainbowProps = {};
+ removeEffect() {
+ this.effect = null;
this.render();
}
}
EffectContainer.template = tags.xml`
-
+
+
+
`;
-EffectContainer.components = { RainbowMan };
diff --git a/addons/web/static/src/core/effects/effect_service.js b/addons/web/static/src/core/effects/effect_service.js
index ba8d8352bb6b3..5f2401b7884f1 100644
--- a/addons/web/static/src/core/effects/effect_service.js
+++ b/addons/web/static/src/core/effects/effect_service.js
@@ -2,28 +2,78 @@
import { registry } from "../registry";
import { EffectContainer } from "./effect_container";
+import { RainbowMan } from "./rainbow_man";
const { EventBus } = owl.core;
-export function convertRainBowMessage(message) {
+const effectRegistry = registry.category("effects");
+
+// -----------------------------------------------------------------------------
+// RainbowMan effect
+// -----------------------------------------------------------------------------
+
+/**
+ * Handles effect of type "rainbow_man". If the effects aren't disabled, returns
+ * the RainbowMan component to instantiate and its props. If the effects are
+ * disabled, displays the message in a notification.
+ *
+ * @param {Object} env
+ * @param {Object} [params={}]
+ * @param {string} [params.message="Well Done"]
+ * The message in the notice the rainbowman holds or the content of the notification if effects are disabled
+ * Can be a simple a string
+ * Can be a string representation of html (prefer component if you want interactions in the DOM)
+ * @param {boolean} [params.img_url="/web/static/img/smile.svg"]
+ * The url of the image to display inside the rainbow
+ * @param {boolean} [params.messageIsHtml]
+ * Set to true if the message encodes html, s.t. it will be correctly inserted into the DOM.
+ * @param {"slow"|"medium"|"fast"|"no"} [params.fadeout="medium"]
+ * Delay for rainbowman to disappear
+ * 'fast' will make rainbowman dissapear quickly
+ * 'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer)
+ * 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman
+ * @param {owl.Component} [params.Component=RainbowMan]
+ * Component class to instantiate (if effects aren't disabled)
+ * @param {Object} [params.props]
+ * If params.Component is given, its props can be passed with this argument
+ */
+function rainbowMan(env, params = {}) {
+ let message = params.message;
if (message instanceof jQuery) {
console.log(
- "Providing jQuery to an effect is deprecated. Note that all event handlers will be lost."
+ "Providing a jQuery element to an effect is deprecated. Note that all event handlers will be lost."
);
- return message.html();
+ message = message.html();
} else if (message instanceof Element) {
console.log(
- "Providing HTML to an effect is deprecated. Note that all event handlers will be lost."
+ "Providing an HTML element to an effect is deprecated. Note that all event handlers will be lost."
);
- return message.outerHTML;
- } else if (typeof message === "string") {
- return message;
+ message = message.outerHTML;
+ } else if (!message) {
+ message = env._t("well Done!");
+ }
+ if (env.services.user.showEffect) {
+ return {
+ Component: params.Component || RainbowMan,
+ props: {
+ imgUrl: params.img_url || "/web/static/img/smile.svg",
+ fadeout: params.fadeout,
+ message,
+ messageIsHtml: params.messageIsHtml || false,
+ ...params.props,
+ },
+ };
}
+ env.services.notification.add(message);
}
+effectRegistry.add("rainbow_man", rainbowMan);
+
+// -----------------------------------------------------------------------------
+// Effect service
+// -----------------------------------------------------------------------------
export const effectService = {
- dependencies: ["notification", "user"],
- start(env, { notification, user }) {
+ start(env) {
const bus = new EventBus();
registry.category("main_components").add("EffectContainer", {
Component: EffectContainer,
@@ -32,74 +82,15 @@ export const effectService = {
let effectId = 0;
/**
- * This private method checks if the effects are disabled.
- * If so, it makes a notification from the message attribute of an effect and
- * doesn't trigger the effect at all.
- * @param {Object} effect The effect to display
- */
- function applyEffect(effect) {
- if (!user.showEffect) {
- notification.add(effect.message, { sticky: false });
- } else {
- bus.trigger("UPDATE", effect);
- }
- }
-
- /**
- * Display a rainbowman effect
- *
- * @param {Object} [params={}]
- * @param {string|Function} [params.message="Well Done"]
- * The message in the notice the rainbowman holds or the content of the notification if effects are disabled
- * Can be a simple a string
- * Can be a string representation of html (prefer component if you want interactions in the DOM)
- * Can be a function returning a string (like _t)
- * @param {boolean} [params.messageIsHtml]
- * The message can be a string representation of html but it needs to be marked as well
- * @param {"slow"|"medium"|"fast"|"no"} [params.fadeout="medium"]
- * Delay for rainbowman to disappear
- * 'fast' will make rainbowman dissapear quickly
- * 'medium' and 'slow' will wait little longer before disappearing (can be used when options.message is longer)
- * 'no' will keep rainbowman on screen until user clicks anywhere outside rainbowman
- * @param {owl.Component} [params.Component]
- * Component class to instantiate
- * It this option is set, the message option is ignored unless effect are disbled
- * Then, the message param is used to display a notification
- * @param {Object} [params.props]
- * If a component is used, its props can be passed with this argument
- */
- function rainbowMan(params = {}) {
- const effect = Object.assign({
- imgUrl: "/web/static/img/smile.svg",
- fadeout: params.fadeout,
- id: ++effectId,
- message: convertRainBowMessage(params.message) || env._t("Well Done!"),
- messageIsHtml: params.messageIsHtml || false,
- Component: params.Component,
- props: params.props,
- });
- applyEffect(effect);
- }
-
- /**
- * Display an effect
- * This is a dispatcher for the effect. Usefull if the request for effect comes
- * from the server.
- * In the weblient, use the more specific effect functions.
- *
- * @param {string} type
- * What effect to create
- * - rainbowman
- * @param {Object} [params={}]
- * All the options for the effect.
- * The options get passed to the more specific effect methods.
+ * @param {Object} params various params depending on the type of effect
+ * @param {string} [params.type="rainbow_man"] the effect to display
*/
- function add(type, params = {}) {
- switch (type.replace("_", "").toLowerCase()) {
- case "rainbowman":
- return rainbowMan(params);
- default:
- throw new Error("NON_IMPLEMENTED_EFFECT");
+ function add(params) {
+ const type = params.type || "rainbow_man";
+ const effect = effectRegistry.get(type);
+ const { Component, props } = effect(env, params) || {};
+ if (Component) {
+ bus.trigger("UPDATE", { Component, props, id: effectId++ });
}
}
diff --git a/addons/web/static/src/core/effects/rainbow_man.js b/addons/web/static/src/core/effects/rainbow_man.js
index d082e8345d4b5..a8aae22ac0947 100644
--- a/addons/web/static/src/core/effects/rainbow_man.js
+++ b/addons/web/static/src/core/effects/rainbow_man.js
@@ -50,7 +50,7 @@ export class RainbowMan extends Component {
}
closeRainbowMan() {
- this.trigger("close-rainbowman");
+ this.props.close();
}
}
RainbowMan.template = "web.RainbowMan";
diff --git a/addons/web/static/src/legacy/legacy_service_provider.js b/addons/web/static/src/legacy/legacy_service_provider.js
index b8efe0125b13f..a776135a7b87e 100644
--- a/addons/web/static/src/legacy/legacy_service_provider.js
+++ b/addons/web/static/src/legacy/legacy_service_provider.js
@@ -11,10 +11,10 @@ export const legacyServiceProvider = {
dependencies: ["effect", "action"],
start({ services }) {
browser.addEventListener("show-effect", (ev) => {
- services.effect.add(ev.detail.type, ev.detail);
+ services.effect.add(ev.detail);
});
bus.on("show-effect", this, (payload) => {
- services.effect.add(payload.type, payload);
+ services.effect.add(payload);
});
browser.addEventListener("do-action", (ev) => {
diff --git a/addons/web/static/src/legacy/utils.js b/addons/web/static/src/legacy/utils.js
index bc5b548aa0292..75b0c3303051d 100644
--- a/addons/web/static/src/legacy/utils.js
+++ b/addons/web/static/src/legacy/utils.js
@@ -296,7 +296,7 @@ export function makeLegacyRainbowManService(legacyEnv) {
dependencies: ["effect"],
start(env, { effect }) {
legacyEnv.bus.on("show-effect", null, (payload) => {
- effect.add(payload.type, payload);
+ effect.add(payload);
});
},
};
diff --git a/addons/web/static/src/webclient/actions/action_service.js b/addons/web/static/src/webclient/actions/action_service.js
index 0fb7454d12da3..a6bf736bfbe22 100644
--- a/addons/web/static/src/webclient/actions/action_service.js
+++ b/addons/web/static/src/webclient/actions/action_service.js
@@ -1153,7 +1153,7 @@ function makeActionManager(env) {
await _executeCloseAction();
}
if (effect) {
- env.services.effect.add(effect.type, effect);
+ env.services.effect.add(effect);
}
}
diff --git a/addons/web/static/tests/core/effects/effect_service_tests.js b/addons/web/static/tests/core/effects/effect_service_tests.js
new file mode 100644
index 0000000000000..ae673191e8554
--- /dev/null
+++ b/addons/web/static/tests/core/effects/effect_service_tests.js
@@ -0,0 +1,126 @@
+/** @odoo-module **/
+
+import { notificationService } from "@web/core/notifications/notification_service";
+import { registry } from "@web/core/registry";
+import { effectService } from "@web/core/effects/effect_service";
+import { RainbowMan } from "@web/core/effects/rainbow_man";
+import { userService } from "@web/core/user_service";
+import { session } from "@web/session";
+import { makeTestEnv } from "../../helpers/mock_env";
+import { makeFakeLocalizationService } from "../../helpers/mock_services";
+import { click, getFixture, nextTick, patchWithCleanup } from "../../helpers/utils";
+import { registerCleanup } from "../../helpers/cleanup";
+
+const { Component, mount, tags } = owl;
+const serviceRegistry = registry.category("services");
+const mainComponentRegistry = registry.category("main_components");
+
+class Parent extends Component {
+ setup() {
+ this.EffectContainer = mainComponentRegistry.get("EffectContainer");
+ this.NotificationContainer = mainComponentRegistry.get("NotificationContainer");
+ }
+}
+Parent.template = tags.xml`
+
+
+
+
+ `;
+
+async function makeParent() {
+ const env = await makeTestEnv({ serviceRegistry });
+ const target = getFixture();
+ const parent = await mount(Parent, { env, target });
+ registerCleanup(() => parent.destroy());
+ return parent;
+}
+
+QUnit.module("Effect Service", (hooks) => {
+ let effectParams;
+ hooks.beforeEach(() => {
+ effectParams = {
+ message: "Congrats!
",
+ messageIsHtml: true,
+ fadeout: "nextTick",
+ };
+
+ patchWithCleanup(session, { show_effect: true }); // enable effects
+
+ serviceRegistry.add("user", userService);
+ serviceRegistry.add("effect", effectService);
+ serviceRegistry.add("notification", notificationService);
+ serviceRegistry.add("localization", makeFakeLocalizationService());
+ });
+
+ QUnit.test("effect service displays a rainbowman by default", async function (assert) {
+ const parent = await makeParent();
+
+ parent.env.services.effect.add({ message: "Hello", fadeout: "no" });
+ await nextTick();
+
+ assert.containsOnce(parent.el, ".o_reward");
+ assert.strictEqual(parent.el.querySelector(".o_reward").innerText, "Hello");
+ });
+
+ QUnit.test("rainbowman effect with show_effect: false", async function (assert) {
+ patchWithCleanup(session, { show_effect: false });
+
+ const parent = await makeParent();
+
+ parent.env.services.effect.add({ type: "rainbow_man", message: "", fadeout: "no" });
+ await nextTick();
+
+ assert.containsNone(parent.el, ".o_reward");
+ assert.containsOnce(parent.el, ".o_notification");
+ });
+
+ QUnit.test("rendering a rainbowman destroy after animation", async function (assert) {
+ patchWithCleanup(RainbowMan, {
+ rainbowFadeouts: { nextTick: 0 },
+ });
+
+ const parent = await makeParent();
+ parent.env.services.effect.add(effectParams);
+ await nextTick();
+
+ assert.containsOnce(parent, ".o_reward");
+ assert.containsOnce(parent, ".o_reward_rainbow");
+ assert.strictEqual(
+ parent.el.querySelector(".o_reward_msg_content").innerHTML,
+ "Congrats!
"
+ );
+
+ const ev = new AnimationEvent("animationend", { animationName: "reward-fading-reverse" });
+ parent.el.querySelector(".o_reward").dispatchEvent(ev);
+ await nextTick();
+ assert.containsNone(parent, ".o_reward");
+ });
+
+ QUnit.test("rendering a rainbowman destroy on click", async function (assert) {
+ const parent = await makeParent();
+
+ parent.env.services.effect.add({ ...effectParams, fadeout: "no" });
+ await nextTick();
+
+ assert.containsOnce(parent.el, ".o_reward");
+ assert.containsOnce(parent.el, ".o_reward_rainbow");
+
+ await click(parent.el);
+ assert.containsNone(parent, ".o_reward");
+ });
+
+ QUnit.test("rendering a rainbowman with an escaped message", async function (assert) {
+ const parent = await makeParent();
+
+ parent.env.services.effect.add({ ...effectParams, messageIsHtml: false });
+ await nextTick();
+
+ assert.containsOnce(parent.el, ".o_reward");
+ assert.containsOnce(parent.el, ".o_reward_rainbow");
+ assert.strictEqual(
+ parent.el.querySelector(".o_reward_msg_content").textContent,
+ "Congrats!
"
+ );
+ });
+});
diff --git a/addons/web/static/tests/webclient/actions/effects_tests.js b/addons/web/static/tests/webclient/actions/effects_tests.js
index 607131d144177..cba1f3ad67881 100644
--- a/addons/web/static/tests/webclient/actions/effects_tests.js
+++ b/addons/web/static/tests/webclient/actions/effects_tests.js
@@ -27,7 +27,7 @@ QUnit.module("ActionManager", (hooks) => {
await doAction(webClient, 1);
assert.containsOnce(webClient.el, ".o_kanban_view");
assert.containsNone(webClient.el, ".o_reward");
- webClient.env.services.effect.add("rainbowman", { message: "", fadeout: "no" });
+ webClient.env.services.effect.add({ type: "rainbow_man", message: "", fadeout: "no" });
await nextTick();
await legacyExtraNextTick();
assert.containsOnce(webClient.el, ".o_reward");
@@ -36,7 +36,7 @@ QUnit.module("ActionManager", (hooks) => {
await legacyExtraNextTick();
assert.containsNone(webClient.el, ".o_reward");
assert.containsOnce(webClient.el, ".o_kanban_view");
- webClient.env.services.effect.add("rainbowman", { message: "", fadeout: "no" });
+ webClient.env.services.effect.add({ type: "rainbow_man", message: "", fadeout: "no" });
await nextTick();
await legacyExtraNextTick();
assert.containsOnce(webClient.el, ".o_reward");
@@ -48,22 +48,6 @@ QUnit.module("ActionManager", (hooks) => {
assert.containsOnce(webClient.el, ".o_list_view");
});
- QUnit.test("show effect notification instead of rainbow man", async function (assert) {
- assert.expect(6);
-
- const webClient = await createWebClient({ serverData });
- await doAction(webClient, 1);
- assert.containsOnce(webClient.el, ".o_kanban_view");
- assert.containsNone(webClient.el, ".o_reward");
- assert.containsNone(webClient.el, ".o_notification");
- webClient.env.services.effect.add("rainbowman", { message: "", fadeout: "no" });
- await nextTick();
- await legacyExtraNextTick();
- assert.containsOnce(webClient.el, ".o_kanban_view");
- assert.containsNone(webClient.el, ".o_reward");
- assert.containsOnce(webClient.el, ".o_notification");
- });
-
QUnit.test("on close with effect from server", async function (assert) {
assert.expect(1);
patchWithCleanup(session, { show_effect: true });
@@ -89,14 +73,14 @@ QUnit.module("ActionManager", (hooks) => {
QUnit.test("on close with effect in xml", async function (assert) {
assert.expect(2);
serverData.views["partner,false,form"] = `
- `;
+ `;
patchWithCleanup(session, { show_effect: true });
const mockRPC = async (route) => {
if (route === "/web/dataset/call_button") {
diff --git a/addons/web/static/tests/webclient/effects/rainbow_man_tests.js b/addons/web/static/tests/webclient/effects/rainbow_man_tests.js
deleted file mode 100644
index 91283070d3f8e..0000000000000
--- a/addons/web/static/tests/webclient/effects/rainbow_man_tests.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/** @odoo-module **/
-
-import { notificationService } from "@web/core/notifications/notification_service";
-import { registry } from "@web/core/registry";
-import { effectService } from "@web/core/effects/effect_service";
-import { RainbowMan } from "@web/core/effects/rainbow_man";
-import { userService } from "@web/core/user_service";
-import { makeTestEnv } from "../../helpers/mock_env";
-import { click, getFixture, nextTick, patchWithCleanup } from "../../helpers/utils";
-import { session } from "@web/session";
-
-const { Component, mount, tags } = owl;
-const serviceRegistry = registry.category("services");
-
-class Parent extends Component {
- setup() {
- this.RainbowMgr = registry.category("main_components").get("EffectContainer");
- }
-}
-Parent.template = tags.xml`
-
-
-
- `;
-
-QUnit.module("RainbowMan", (hooks) => {
- let rainbowManDefault, target;
- hooks.beforeEach(async () => {
- rainbowManDefault = {
- message: "Congrats!
",
- messageIsHtml: true,
- fadeout: "nextTick",
- };
- target = getFixture();
- patchWithCleanup(session, { show_effect: true });
- serviceRegistry.add("user", userService);
- serviceRegistry.add("effect", effectService);
- serviceRegistry.add("notification", notificationService);
- });
-
- QUnit.test("rendering a rainbowman destroy after animation", async function (assert) {
- assert.expect(4);
- const _delays = RainbowMan.rainbowFadeouts;
- RainbowMan.rainbowFadeouts = { nextTick: 0 };
- const env = await makeTestEnv({ serviceRegistry });
- const parent = await mount(Parent, { env, target });
- env.services.effect.add("rainbowman", rainbowManDefault);
- await nextTick();
- assert.containsOnce(target, ".o_reward");
- assert.containsOnce(parent.el, ".o_reward_rainbow");
- assert.strictEqual(
- parent.el.querySelector(".o_reward_msg_content").innerHTML,
- "Congrats!
"
- );
-
- const ev = new AnimationEvent("animationend", { animationName: "reward-fading-reverse" });
- target.querySelector(".o_reward").dispatchEvent(ev);
- await nextTick();
- assert.containsNone(target, ".o_reward");
- RainbowMan.rainbowFadeouts = _delays;
- parent.destroy();
- });
-
- QUnit.test("rendering a rainbowman destroy on click", async function (assert) {
- assert.expect(3);
- rainbowManDefault.fadeout = "no";
- const env = await makeTestEnv({ serviceRegistry });
- const parent = await mount(Parent, { env, target });
- env.services.effect.add("rainbowman", rainbowManDefault);
- await nextTick();
- assert.containsOnce(parent.el, ".o_reward");
- assert.containsOnce(parent.el, ".o_reward_rainbow");
- await click(target);
- assert.containsNone(target, ".o_reward");
- parent.destroy();
- });
-
- QUnit.test("rendering a rainbowman with an escaped message", async function (assert) {
- assert.expect(3);
- const env = await makeTestEnv({ serviceRegistry });
- const parent = await mount(Parent, { env, target });
- env.services.effect.add("rainbowman", { ...rainbowManDefault, messageIsHtml: false });
- await nextTick();
- assert.containsOnce(parent.el, ".o_reward");
- assert.containsOnce(parent.el, ".o_reward_rainbow");
- assert.strictEqual(
- parent.el.querySelector(".o_reward_msg_content").textContent,
- "Congrats!
"
- );
- parent.destroy();
- });
-});