[FIX] web_editor: reorder the elements of the invisible elements panel

Steps to reproduce the bug:
- Add a form snippet on the footer of the website page.
- Change the visibility of the form snippet to "conditionally".
- On the footer settings, deactivate the "Page Visibility".
=> The form snippet is still present on the "Invisible Elements" panel
but clicking on it has no effects. Indeed, it is inside the footer which
is hidden.

The goal of this commit is to reorganize the "Invisible Elements" panel
in order to better visualize the hierarchy between the different
invisible elements. To do so, the list of the invisible snippet elements
(`[...$invisibleSnippets]`) is scanned. Each invisible snippet that is
the descendant of another is discarded of the list (to only keep the
"root" ones) and a map is created with its `keys` set to invisible
snippets that have invisible descendants. The `value` corresponding to
an invisible snippet element is a list filled with all its descendant
invisible snippets except those that have a closer invisible snippet
ancestor. The list of the root snippets is then scanned. Each root
snippet is inserted in the DOM as well as their descendant snippets
found thanks to the newly created map.

Note that thanks to this commit, another problem is solved:
- Add a cookie bar on the website.
- Add a Text-Image snippet and change its visibility to "conditionally".
- Save and edit again. Note that the "Cookies Bar" is above the "Text-
Image" on the "Invisible Elements" panel.
- Click to display the cookie bar.
=> The order of the "Cookies Bar" and the "Text-Image" is switched on
the "Invisible Elements" panel.
Indeed, before this commit, the order of the snippets was influenced by
the order of their snippet editor creation. Because this order was not
always the same from one call to `_updateInvisibleDOM()` to another, a
glitch could happen when toggling a snippet visibility.
This is now fixed as the order of the invisible snippets in the
"Invisible Elements" panel is determined either by their order in the
list `rootInvisibleSnippetEls` or their order in the lists contained as
value in the map `descendantPerSnippet`.

task-3203914

X-original-commit: 548e95b3dc9bf642c5eea3fc43b83f7ce282be0b
Part-of: odoo/odoo#123027
This commit is contained in:
Louis (loco)
2023-06-01 09:14:04 +02:00
parent 432f7d4e9d
commit f45a6ea385
2 changed files with 96 additions and 27 deletions
@@ -2488,16 +2488,70 @@ var SnippetsMenu = Widget.extend({
$invisibleDOMPanelEl.toggleClass('d-none', !$invisibleSnippets.length);
const proms = Array.from($invisibleSnippets).map(async (el) => {
const editor = await this._createSnippetEditor($(el));
const $invisEntry = $('<div/>', {
class: 'o_we_invisible_entry d-flex align-items-center justify-content-between',
text: editor.getName(),
}).append($('<i/>', {class: `fa ${editor.isTargetVisible() ? 'fa-eye' : 'fa-eye-slash'} ms-2`}));
$invisibleDOMPanelEl.append($invisEntry);
this.invisibleDOMMap.set($invisEntry[0], el);
// descendantPerSnippet: a map with its keys set to invisible
// snippets that have invisible descendants. The value corresponding
// to an invisible snippet element is a list filled with all its
// descendant invisible snippets except those that have a closer
// invisible snippet ancestor.
const descendantPerSnippet = new Map();
// Filter the "$invisibleSnippets" to only keep the root snippets
// and create the map ("descendantPerSnippet") of the snippets and
// their descendant snippets.
const rootInvisibleSnippetEls = [...$invisibleSnippets].filter(invisibleSnippetEl => {
const ancestorInvisibleEl = invisibleSnippetEl
.parentElement.closest(".o_snippet_invisible");
if (!ancestorInvisibleEl) {
return true;
}
const descendantSnippets = descendantPerSnippet.get(ancestorInvisibleEl) || [];
descendantPerSnippet.set(ancestorInvisibleEl,
[...descendantSnippets, invisibleSnippetEl]);
return false;
});
return Promise.all(proms);
// Insert an invisible snippet in its "parentEl" element.
const createInvisibleElement = async (invisibleSnippetEl, isRootParent, isDescendant,
parentEl) => {
const editor = await this._createSnippetEditor($(invisibleSnippetEl));
const invisibleEntryEl = document.createElement("div");
invisibleEntryEl.className = `${isRootParent ? "o_we_invisible_root_parent" : ""}`;
invisibleEntryEl.classList.add("o_we_invisible_entry", "d-flex",
"align-items-center", "justify-content-between");
invisibleEntryEl.classList.toggle("o_we_sublevel_1", isDescendant);
const titleEl = document.createElement("we-title");
titleEl.textContent = editor.getName();
invisibleEntryEl.appendChild(titleEl);
const iconEl = document.createElement("i");
const eyeIconClass = editor.isTargetVisible() ? "fa-eye" : "fa-eye-slash";
iconEl.classList.add("fa", "ms-2", eyeIconClass);
invisibleEntryEl.appendChild(iconEl);
parentEl.appendChild(invisibleEntryEl);
this.invisibleDOMMap.set(invisibleEntryEl, invisibleSnippetEl);
};
// Insert all the invisible snippets contained in "snippetEls" as
// well as their descendants in the "parentEl" element. If
// "snippetEls" is set to "rootInvisibleSnippetEls" and "parentEl"
// is set to "$invisibleDOMPanelEl[0]", then fills the right
// invisible panel like this:
// rootInvisibleSnippet
// └ descendantInvisibleSnippet
// └ descendantOfDescendantInvisibleSnippet
// └ etc...
const createInvisibleElements = async (snippetEls, isDescendant, parentEl) => {
for (const snippetEl of snippetEls) {
const descendantSnippetEls = descendantPerSnippet.get(snippetEl);
// An element is considered as "RootParent" if it has one or
// more invisible descendants but is not a descendant.
await createInvisibleElement(snippetEl,
!isDescendant && !!descendantSnippetEls, isDescendant, parentEl);
if (descendantSnippetEls) {
// Insert all the descendant snippets in a list.
const listEntryEl = document.createElement("ul");
await createInvisibleElements(descendantSnippetEls, true, listEntryEl);
parentEl.appendChild(listEntryEl);
}
}
};
return createInvisibleElements(rootInvisibleSnippetEls, false, $invisibleDOMPanelEl[0]);
}, false);
},
/**
@@ -270,6 +270,24 @@
}
}
%o_we_sublevel > we-title::before {
content: "└"; // TODO The size and look of this depends on the
// browser default font, we should use a SVG instead.
display: inline-block;
margin-right: 0.4em;
}
@for $level from 1 through 3 {
.o_we_sublevel_#{$level} {
@extend %o_we_sublevel;
@if $level > 1 {
> we-title {
padding-left: ($level - 1) * 0.6em;
}
}
}
}
#snippets_menu {
flex: 0 0 auto;
display: flex;
@@ -1709,24 +1727,6 @@
}
}
%o_we_sublevel > we-title::before {
content: "└"; // TODO The size and look of this depends on the
// browser default font, we should use a SVG instead.
display: inline-block;
margin-right: 0.4em;
}
@for $level from 1 through 3 {
.o_we_sublevel_#{$level} {
@extend %o_we_sublevel;
@if $level > 1 {
> we-title {
padding-left: ($level - 1) * 0.6em;
}
}
}
}
.o_we_image_weight {
margin-left: $o-we-sidebar-content-field-label-spacing * 2;
}
@@ -1765,6 +1765,21 @@
background-color: $o-we-sidebar-bg;
}
}
div.o_we_invisible_root_parent {
padding-bottom: 3px;
}
ul {
list-style: none;
padding-inline-start: 15px;
margin-bottom: $o-we-sidebar-content-field-spacing - 3px;
div.o_we_invisible_entry {
padding-top: 3px;
padding-bottom: 3px;
}
}
}
&.o_we_backdrop {