From 3859bbe99b0b5a5e3401eac522e63a32b622ea4f Mon Sep 17 00:00:00 2001 From: Guillaume-gdi Date: Tue, 19 Dec 2023 16:29:15 +0100 Subject: [PATCH] [FIX] web_editor, *: prevent blocked UI when original image not found *: website When the user uploads an image and remove the original image, some image options continue to work thanks to a backend fallback on another image. Unfortunately, this fallback is not available for images coming from unsplash. This commit fixes this issue by preventing the UI to be blocked when the original image is not found. Steps to reproduce the bug: - Activate the developer mode - Configure Unsplash in the settings - Add a Cover block on a page - Replace the background image with an unsplash image - Save - Edit the page and via the media dialog, remove the original image => The options on the image cannot start anymore. opw-3636499 opw-3474103 closes odoo/odoo#148197 X-original-commit: 3b0ac160a800f70ae965b60110e83e41655bca5f Signed-off-by: Romain Derie (rde) Signed-off-by: Guillaume Dieleman (gdi) --- .../static/src/js/editor/image_processing.js | 23 +++++++++++++--- .../static/tests/tours/snippet_images_wall.js | 26 ++++++++++++++++++- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/image_processing.js b/addons/web_editor/static/src/js/editor/image_processing.js index 8ecd07370f3..1faca26309b 100644 --- a/addons/web_editor/static/src/js/editor/image_processing.js +++ b/addons/web_editor/static/src/js/editor/image_processing.js @@ -349,12 +349,29 @@ export async function applyModifications(img, dataOptions = {}) { * @param {String} src URL of the image to load * @param {HTMLImageElement} [img] img element in which to load the image * @returns {Promise} Promise that resolves to the loaded img + * or a placeholder image if the src is not found. */ export function loadImage(src, img = new Image()) { + const handleImage = (source, resolve, reject) => { + img.addEventListener("load", () => resolve(img), {once: true}); + img.addEventListener("error", reject, {once: true}); + img.src = source; + }; + // The server will return a placeholder image with the following src. + const placeholderHref = "/web/image/__odoo__unknown__src__/"; + return new Promise((resolve, reject) => { - img.addEventListener('load', () => resolve(img), {once: true}); - img.addEventListener('error', reject, {once: true}); - img.src = src; + fetch(src) + .then(response => { + if (!response.ok) { + src = placeholderHref; + } + handleImage(src, resolve, reject); + }) + .catch(error => { + src = placeholderHref; + handleImage(src, resolve, reject); + }); }); } diff --git a/addons/website/static/tests/tours/snippet_images_wall.js b/addons/website/static/tests/tours/snippet_images_wall.js index 3212cfbff67..2f4885b1747 100644 --- a/addons/website/static/tests/tours/snippet_images_wall.js +++ b/addons/website/static/tests/tours/snippet_images_wall.js @@ -2,6 +2,24 @@ import wTourUtils from "@website/js/tours/tour_utils"; +const wallRaceConditionClass = "image_wall_race_condition"; +const preventRaceConditionSteps = [{ + content: "Wait a few ms to avoid race condition", + // Ensure the class is remove from previous call of those steps + trigger: `body:not(.${wallRaceConditionClass})`, + run() { + setTimeout(() => { + document.body.classList.add(wallRaceConditionClass); + }, 500); + } +}, { + content: "Check the race condition class is added after a few ms", + trigger: `body.${wallRaceConditionClass}`, + run() { + document.body.classList.remove(wallRaceConditionClass); + } +}]; + const selectSignImageStep = { content: "Click on image 14", extra_trigger: ".o_we_customize_panel:not(:has(.snippet-option-GalleryElement))", @@ -9,7 +27,13 @@ const selectSignImageStep = { }; // Without reselecting the image, the tour manages to click on the // move button before the active image is updated. -const reselectSignImageSteps = [{ + +// We need to wait a few ms before clicking on the footer because after +// clicking on reposition option, there may be a delay during the click on +// another block would be ignored. +const reselectSignImageSteps = [ + ...preventRaceConditionSteps, +{ content: "Select footer", trigger: "iframe footer", }, selectSignImageStep];