From fa233f804249ddad5cb242594d1369a92fcc08a2 Mon Sep 17 00:00:00 2001
From: Benoit Socias <bso@odoo.com>
Date: Wed, 18 Jan 2023 08:16:23 +0000
Subject: [PATCH] [FIX] website: not move images twice within image wall

Since [1] the "Images Add/Remove All" buttons are on top of the
background options in order to appear as the first option for the
"Image Wall" snippet.
This makes the JS related to the handling of the options of that
snippet created twice: once for the buttons above and once for the
options below.
Because of this, events are registered by both instances and they both
get notified on option update. Therefore, when an image is moved, it is
moved twice instead of once.

This commit differentiates both instances, and makes the one that
handles the "Add" and "Remove All" buttons be the only one that works on
the images.
In stable the differentiation is done with conditional statements.
In master the instances should be of distinct classes.

[1]: https://github.com/odoo/odoo/commit/b6494fcf284edcddaa36b5fcfd407a6d7186fbd7

task-2990053

closes odoo/odoo#110242

Signed-off-by: Guillaume-gdi <gdi@odoo.com>
---
 .../src/snippets/s_image_gallery/options.js   | 29 +++++++++---
 .../static/tests/tours/snippet_images_wall.js | 44 +++++++++++++++++++
 addons/website/tests/test_snippets.py         |  3 ++
 3 files changed, 71 insertions(+), 5 deletions(-)
 create mode 100644 addons/website/static/tests/tours/snippet_images_wall.js

diff --git a/addons/website/static/src/snippets/s_image_gallery/options.js b/addons/website/static/src/snippets/s_image_gallery/options.js
index ff7526ea4e1f..b0298cfc9890 100644
--- a/addons/website/static/src/snippets/s_image_gallery/options.js
+++ b/addons/website/static/src/snippets/s_image_gallery/options.js
@@ -16,6 +16,18 @@ options.registry.gallery = options.Class.extend({
      */
     start: function () {
         var self = this;
+        // TODO In master: define distinct classes.
+        // Differentiate both instances of this class: we want to avoid
+        // registering the same event listener twice.
+        this.hasAddImages = this.el.querySelector("we-button[data-add-images]");
+
+        if (!this.hasAddImages) {
+            const containerEl = this.$target[0].querySelector(":scope > .container, :scope > .container-fluid, :scope > .o_container_small");
+            if (containerEl.querySelector(":scope > *:not(div)")) {
+                self.mode(null, self.getMode());
+            }
+            return this._super.apply(this, arguments);
+        }
 
         // Make sure image previews are updated if images are changed
         this.$target.on('image_changed.gallery', 'img', function (ev) {
@@ -43,11 +55,6 @@ options.registry.gallery = options.Class.extend({
             }
         });
 
-        const $container = this.$('> .container, > .container-fluid, > .o_container_small');
-        if ($container.find('> *:not(div)').length) {
-            self.mode(null, self.getMode());
-        }
-
         return this._super.apply(this, arguments);
     },
     /**
@@ -302,12 +309,24 @@ options.registry.gallery = options.Class.extend({
      */
     notify: function (name, data) {
         this._super(...arguments);
+        // TODO Remove in master.
+        if (!this.hasAddImages) {
+            // In stable, the widget is instanciated twice. We do not want
+            // operations, especially moves, to be performed twice.
+            // We therefore ignore the requests from one of the instances.
+            return;
+        }
         if (name === 'image_removed') {
             data.$image.remove(); // Force the removal of the image before reset
             this.mode('reset', this.getMode());
         } else if (name === 'image_index_request') {
             var imgs = this._getImages();
             var position = _.indexOf(imgs, data.$image[0]);
+            if (position === 0 && data.position === "prev") {
+                data.position = "last";
+            } else if (position === imgs.length - 1 && data.position === "next") {
+                data.position = "first";
+            }
             imgs.splice(position, 1);
             switch (data.position) {
                 case 'first':
diff --git a/addons/website/static/tests/tours/snippet_images_wall.js b/addons/website/static/tests/tours/snippet_images_wall.js
new file mode 100644
index 000000000000..423410117907
--- /dev/null
+++ b/addons/website/static/tests/tours/snippet_images_wall.js
@@ -0,0 +1,44 @@
+/** @odoo-module **/
+
+import tour from "web_tour.tour";
+import wTourUtils from "website.tour_utils";
+
+tour.register("snippet_images_wall", {
+    test: true,
+    url: "/",
+}, [
+    ...wTourUtils.clickOnEditAndWaitEditMode(),
+    wTourUtils.dragNDrop({
+        id: "s_images_wall",
+        name: "Images Wall",
+}), wTourUtils.clickOnSnippet({
+    id: "s_image_gallery",
+    name: "Images Wall",
+}), {
+    // Prefixing selectors with #wrap to avoid matching droppable block.
+    content: "Click on third image",
+    trigger: "#wrap .s_image_gallery img[data-index='2']",
+}, {
+    content: "Click on move to previous",
+    trigger: ".snippet-option-gallery_img we-button[data-position='prev']",
+}, {
+    content: "Click on move to first",
+    extra_trigger: "#wrap .s_image_gallery .o_masonry_col:nth-child(2):has(img[data-index='1'][data-original-src*='sign'])",
+    trigger: ".snippet-option-gallery_img we-button[data-position='first']",
+}, {
+    content: "Click on move to previous",
+    extra_trigger: "#wrap .s_image_gallery .o_masonry_col:nth-child(1):has(img[data-index='0'][data-original-src*='sign'])",
+    trigger: ".snippet-option-gallery_img we-button[data-position='prev']",
+}, {
+    content: "Click on move to next",
+    extra_trigger: "#wrap .s_image_gallery .o_masonry_col:nth-child(3):has(img[data-index='5'][data-original-src*='sign'])",
+    trigger: ".snippet-option-gallery_img we-button[data-position='next']",
+}, {
+    content: "Click on move to last",
+    extra_trigger: "#wrap .s_image_gallery .o_masonry_col:nth-child(1):has(img[data-index='0'][data-original-src*='sign'])",
+    trigger: ".snippet-option-gallery_img we-button[data-position='last']",
+}, {
+    content: "Check layout",
+    trigger: "#wrap .s_image_gallery .o_masonry_col:nth-child(3):has(img[data-index='5'][data-original-src*='sign'])",
+    run: () => {}, // This is a check.
+}]);
diff --git a/addons/website/tests/test_snippets.py b/addons/website/tests/test_snippets.py
index a4b5bb380596..ad650f7a258d 100644
--- a/addons/website/tests/test_snippets.py
+++ b/addons/website/tests/test_snippets.py
@@ -53,3 +53,6 @@ class TestSnippets(odoo.tests.HttpCase):
             'url': base + '/web/image/website.s_banner_default_image.jpg',
         })
         self.start_tour("/", "snippet_image_gallery", login='admin')
+
+    def test_07_snippet_images_wall(self):
+        self.start_tour('/', 'snippet_images_wall', login='admin')
-- 
GitLab