From 983db5868f90b4e1259892a5d01beebd591ed34b Mon Sep 17 00:00:00 2001 From: "Louis (loco)" Date: Mon, 14 Apr 2025 14:50:11 +0200 Subject: [PATCH 1/3] Update option on image changed --- .../src/core/utils/update_on_img_changed.js | 22 +++++++++++ .../src/core/utils/update_on_img_changed.xml | 14 +++++++ .../plugins/options/card_image_option.js | 39 +++++++++++++++---- .../plugins/options/card_option.js | 2 + .../plugins/options/card_option.xml | 4 +- 5 files changed, 72 insertions(+), 9 deletions(-) create mode 100644 addons/html_builder/static/src/core/utils/update_on_img_changed.js create mode 100644 addons/html_builder/static/src/core/utils/update_on_img_changed.xml diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.js b/addons/html_builder/static/src/core/utils/update_on_img_changed.js new file mode 100644 index 0000000000000..5d4beddf14508 --- /dev/null +++ b/addons/html_builder/static/src/core/utils/update_on_img_changed.js @@ -0,0 +1,22 @@ +import { BuilderComponent } from "../building_blocks/builder_component"; +import { useDomState } from "../utils"; + +export class UpdateOptionOnImgChanged extends BuilderComponent { + static template = "html_builder.UpdateOptionOnImgChanged"; + static props = {}; + + setup() { + super.setup(); + let boolean = true; + this.state = useDomState((editingElement) => { + const imageEl = editingElement.querySelector("img"); + if (imageEl && !imageEl.complete) { + // Rerender the slot if the image is not loaded + boolean = !boolean; + } + return { + bool: boolean, + }; + }); + } +} diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.xml b/addons/html_builder/static/src/core/utils/update_on_img_changed.xml new file mode 100644 index 0000000000000..725e48b4c628d --- /dev/null +++ b/addons/html_builder/static/src/core/utils/update_on_img_changed.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js b/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js index ed431f254b315..80a246bb8ec37 100644 --- a/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js +++ b/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js @@ -1,4 +1,6 @@ import { BaseOptionComponent, useDomState } from "@html_builder/core/utils"; +import { onWillStart } from "@odoo/owl"; +import { Deferred } from "@web/core/utils/concurrency"; export class CardImageOption extends BaseOptionComponent { static template = "html_builder.CardImageOption"; @@ -6,15 +8,36 @@ export class CardImageOption extends BaseOptionComponent { setup() { super.setup(); - this.state = useDomState((editingElement) => { - const imageToWrapperRatio = this.getImageToWrapperRatio(editingElement); - return { - hasCoverImage: !!editingElement.querySelector(".o_card_img_wrapper"), - hasSquareRatio: imageToWrapperRatio === 1, - imageToWrapperRatio, - hasShape: !!editingElement.querySelector(".o_card_img[data-shape]"), - }; + const deferred = new Deferred(); + onWillStart(async () => { + const editingElements = this.env.getEditingElements(); + const promises = []; + for (const editingEl of editingElements) { + const imageEl = editingEl.querySelector(".o_card_img"); + if (!imageEl || imageEl.complete) { + continue; + } + promises.push( + new Promise((resolve) => { + imageEl.addEventListener("load", () => resolve()); + }) + ); + } + await Promise.all(promises); + deferred.resolve(); }); + this.state = useDomState( + (editingElement) => { + const imageToWrapperRatio = this.getImageToWrapperRatio(editingElement); + return { + hasCoverImage: !!editingElement.querySelector(".o_card_img_wrapper"), + hasSquareRatio: imageToWrapperRatio === 1, + imageToWrapperRatio, + hasShape: !!editingElement.querySelector(".o_card_img[data-shape]"), + }; + }, + { onReady: deferred } + ); } /** diff --git a/addons/html_builder/static/src/website_builder/plugins/options/card_option.js b/addons/html_builder/static/src/website_builder/plugins/options/card_option.js index 96c2329150b89..4525a667da4fc 100644 --- a/addons/html_builder/static/src/website_builder/plugins/options/card_option.js +++ b/addons/html_builder/static/src/website_builder/plugins/options/card_option.js @@ -3,6 +3,7 @@ import { WebsiteBackgroundOption } from "@html_builder/website_builder/plugins/o import { CardImageOption } from "./card_image_option"; import { BorderConfigurator } from "@html_builder/plugins/border_configurator_option"; import { ShadowOption } from "@html_builder/plugins/shadow_option"; +import { UpdateOptionOnImgChanged } from "@html_builder/core/utils/update_on_img_changed"; export class CardOption extends BaseOptionComponent { static template = "website.CardOption"; @@ -11,6 +12,7 @@ export class CardOption extends BaseOptionComponent { WebsiteBackgroundOption, BorderConfigurator, ShadowOption, + UpdateOptionOnImgChanged, }; static props = { disableWidth: { type: Boolean, optional: true }, diff --git a/addons/html_builder/static/src/website_builder/plugins/options/card_option.xml b/addons/html_builder/static/src/website_builder/plugins/options/card_option.xml index 4332aa361e8aa..37f702adb8b1e 100644 --- a/addons/html_builder/static/src/website_builder/plugins/options/card_option.xml +++ b/addons/html_builder/static/src/website_builder/plugins/options/card_option.xml @@ -11,7 +11,9 @@ - + + + From 1b882ec7a1e684f9956470133947332c81ffe95d Mon Sep 17 00:00:00 2001 From: "Louis (loco)" Date: Wed, 16 Apr 2025 15:46:42 +0200 Subject: [PATCH 2/3] Introduce LoadImgComponent --- .../src/core/utils/load_img_component.js | 28 +++++++++++++ .../src/core/utils/load_img_component.xml | 7 ++++ .../src/core/utils/update_on_img_changed.js | 15 ++++--- .../src/core/utils/update_on_img_changed.xml | 12 +++--- .../plugins/options/card_image_option.js | 39 ++++--------------- 5 files changed, 60 insertions(+), 41 deletions(-) create mode 100644 addons/html_builder/static/src/core/utils/load_img_component.js create mode 100644 addons/html_builder/static/src/core/utils/load_img_component.xml diff --git a/addons/html_builder/static/src/core/utils/load_img_component.js b/addons/html_builder/static/src/core/utils/load_img_component.js new file mode 100644 index 0000000000000..8b690d05ed5ef --- /dev/null +++ b/addons/html_builder/static/src/core/utils/load_img_component.js @@ -0,0 +1,28 @@ +import { onWillStart } from "@odoo/owl"; +import { BuilderComponent } from "../building_blocks/builder_component"; + +export class LoadImgComponent extends BuilderComponent { + static template = "html_builder.LoadImgComponent"; + static props = { slots: { type: Object } }; + + setup() { + super.setup(); + onWillStart(async () => { + const editingElements = this.env.getEditingElements(); + const promises = []; + for (const editingEl of editingElements) { + const imageEls = editingEl.querySelectorAll("img"); + for (const imageEl of imageEls) { + if (!imageEl.complete) { + promises.push( + new Promise((resolve) => { + imageEl.addEventListener("load", () => resolve()); + }) + ); + } + } + } + await Promise.all(promises); + }); + } +} diff --git a/addons/html_builder/static/src/core/utils/load_img_component.xml b/addons/html_builder/static/src/core/utils/load_img_component.xml new file mode 100644 index 0000000000000..f29bf8640d5f6 --- /dev/null +++ b/addons/html_builder/static/src/core/utils/load_img_component.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.js b/addons/html_builder/static/src/core/utils/update_on_img_changed.js index 5d4beddf14508..d329d3609f973 100644 --- a/addons/html_builder/static/src/core/utils/update_on_img_changed.js +++ b/addons/html_builder/static/src/core/utils/update_on_img_changed.js @@ -1,18 +1,23 @@ import { BuilderComponent } from "../building_blocks/builder_component"; import { useDomState } from "../utils"; +import { LoadImgComponent } from "./load_img_component"; export class UpdateOptionOnImgChanged extends BuilderComponent { static template = "html_builder.UpdateOptionOnImgChanged"; - static props = {}; + static props = { slots: { type: Object } }; + static components = { LoadImgComponent }; setup() { super.setup(); let boolean = true; this.state = useDomState((editingElement) => { - const imageEl = editingElement.querySelector("img"); - if (imageEl && !imageEl.complete) { - // Rerender the slot if the image is not loaded - boolean = !boolean; + const imageEls = editingElement.querySelectorAll("img"); + for (const imageEl of imageEls) { + if (!imageEl.complete) { + // Rerender the slot if an image is not loaded + boolean = !boolean; + break; + } } return { bool: boolean, diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.xml b/addons/html_builder/static/src/core/utils/update_on_img_changed.xml index 725e48b4c628d..b9a6677fe218a 100644 --- a/addons/html_builder/static/src/core/utils/update_on_img_changed.xml +++ b/addons/html_builder/static/src/core/utils/update_on_img_changed.xml @@ -4,11 +4,13 @@ - - - - + + + + + + - + diff --git a/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js b/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js index 80a246bb8ec37..ed431f254b315 100644 --- a/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js +++ b/addons/html_builder/static/src/website_builder/plugins/options/card_image_option.js @@ -1,6 +1,4 @@ import { BaseOptionComponent, useDomState } from "@html_builder/core/utils"; -import { onWillStart } from "@odoo/owl"; -import { Deferred } from "@web/core/utils/concurrency"; export class CardImageOption extends BaseOptionComponent { static template = "html_builder.CardImageOption"; @@ -8,36 +6,15 @@ export class CardImageOption extends BaseOptionComponent { setup() { super.setup(); - const deferred = new Deferred(); - onWillStart(async () => { - const editingElements = this.env.getEditingElements(); - const promises = []; - for (const editingEl of editingElements) { - const imageEl = editingEl.querySelector(".o_card_img"); - if (!imageEl || imageEl.complete) { - continue; - } - promises.push( - new Promise((resolve) => { - imageEl.addEventListener("load", () => resolve()); - }) - ); - } - await Promise.all(promises); - deferred.resolve(); + this.state = useDomState((editingElement) => { + const imageToWrapperRatio = this.getImageToWrapperRatio(editingElement); + return { + hasCoverImage: !!editingElement.querySelector(".o_card_img_wrapper"), + hasSquareRatio: imageToWrapperRatio === 1, + imageToWrapperRatio, + hasShape: !!editingElement.querySelector(".o_card_img[data-shape]"), + }; }); - this.state = useDomState( - (editingElement) => { - const imageToWrapperRatio = this.getImageToWrapperRatio(editingElement); - return { - hasCoverImage: !!editingElement.querySelector(".o_card_img_wrapper"), - hasSquareRatio: imageToWrapperRatio === 1, - imageToWrapperRatio, - hasShape: !!editingElement.querySelector(".o_card_img[data-shape]"), - }; - }, - { onReady: deferred } - ); } /** From fd6817ccf575032c23a817c64930c0356b751f6e Mon Sep 17 00:00:00 2001 From: "Louis (loco)" Date: Thu, 17 Apr 2025 13:34:49 +0200 Subject: [PATCH 3/3] put all LoadImgComponent and UpdateOptionOnImgChanged in one file --- .../src/core/utils/load_img_component.js | 28 ---------- .../src/core/utils/load_img_component.xml | 7 --- .../src/core/utils/update_on_img_changed.js | 54 ++++++++++++++++--- .../src/core/utils/update_on_img_changed.xml | 16 ------ 4 files changed, 48 insertions(+), 57 deletions(-) delete mode 100644 addons/html_builder/static/src/core/utils/load_img_component.js delete mode 100644 addons/html_builder/static/src/core/utils/load_img_component.xml delete mode 100644 addons/html_builder/static/src/core/utils/update_on_img_changed.xml diff --git a/addons/html_builder/static/src/core/utils/load_img_component.js b/addons/html_builder/static/src/core/utils/load_img_component.js deleted file mode 100644 index 8b690d05ed5ef..0000000000000 --- a/addons/html_builder/static/src/core/utils/load_img_component.js +++ /dev/null @@ -1,28 +0,0 @@ -import { onWillStart } from "@odoo/owl"; -import { BuilderComponent } from "../building_blocks/builder_component"; - -export class LoadImgComponent extends BuilderComponent { - static template = "html_builder.LoadImgComponent"; - static props = { slots: { type: Object } }; - - setup() { - super.setup(); - onWillStart(async () => { - const editingElements = this.env.getEditingElements(); - const promises = []; - for (const editingEl of editingElements) { - const imageEls = editingEl.querySelectorAll("img"); - for (const imageEl of imageEls) { - if (!imageEl.complete) { - promises.push( - new Promise((resolve) => { - imageEl.addEventListener("load", () => resolve()); - }) - ); - } - } - } - await Promise.all(promises); - }); - } -} diff --git a/addons/html_builder/static/src/core/utils/load_img_component.xml b/addons/html_builder/static/src/core/utils/load_img_component.xml deleted file mode 100644 index f29bf8640d5f6..0000000000000 --- a/addons/html_builder/static/src/core/utils/load_img_component.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.js b/addons/html_builder/static/src/core/utils/update_on_img_changed.js index d329d3609f973..04606f19cf4e1 100644 --- a/addons/html_builder/static/src/core/utils/update_on_img_changed.js +++ b/addons/html_builder/static/src/core/utils/update_on_img_changed.js @@ -1,17 +1,59 @@ -import { BuilderComponent } from "../building_blocks/builder_component"; +import { Component, onWillStart, xml } from "@odoo/owl"; import { useDomState } from "../utils"; -import { LoadImgComponent } from "./load_img_component"; -export class UpdateOptionOnImgChanged extends BuilderComponent { - static template = "html_builder.UpdateOptionOnImgChanged"; +class LoadImgComponent extends Component { + static template = xml` + + `; + static props = { slots: { type: Object } }; + + setup() { + onWillStart(async () => { + const editingElements = this.env.getEditingElements(); + const promises = []; + for (const editingEl of editingElements) { + const imageEls = editingEl.matches("img") + ? [editingEl] + : editingEl.querySelectorAll("img"); + for (const imageEl of imageEls) { + if (!imageEl.complete) { + promises.push( + new Promise((resolve) => { + imageEl.addEventListener("load", () => resolve()); + }) + ); + } + } + } + await Promise.all(promises); + }); + } +} + +/** + * In Chrome, when replacing an image on the DOM, some image properties are not + * available even if the image has been loaded beforehand. This is a problem if + * an option is using one of those property at each DOM change (useDomState). + * To solve the problem, this component reloads the option (and waits for the + * images to be loaded) each time an image has been modified inside its editing + * element. + */ +export class UpdateOptionOnImgChanged extends Component { + // TODO: this is a hack until is + // fixed in OWL. + static template = xml` + + + `; static props = { slots: { type: Object } }; static components = { LoadImgComponent }; setup() { - super.setup(); let boolean = true; this.state = useDomState((editingElement) => { - const imageEls = editingElement.querySelectorAll("img"); + const imageEls = editingElement.matches("img") + ? [editingElement] + : editingElement.querySelectorAll("img"); for (const imageEl of imageEls) { if (!imageEl.complete) { // Rerender the slot if an image is not loaded diff --git a/addons/html_builder/static/src/core/utils/update_on_img_changed.xml b/addons/html_builder/static/src/core/utils/update_on_img_changed.xml deleted file mode 100644 index b9a6677fe218a..0000000000000 --- a/addons/html_builder/static/src/core/utils/update_on_img_changed.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - -