From 62b4e968c9858651a7a50ca20d5e3b28110bb9c5 Mon Sep 17 00:00:00 2001 From: Benoit Socias Date: Thu, 28 Jan 2021 13:17:22 +0000 Subject: [PATCH] [FIX] website, web_editor: ensure the loader appears on click options Before this commit the loader did not always get displayed when switching to the options tab. After this commit the loader always gets displayed ASAP when switching to the options tab until the tab is ready and displayed. task-2439295 https://github.com/odoo/odoo/pull/65223 X-original-commit: f2fa17540e130b5cf3f70570a3a97495cfbb7499 Co-authored-by: Samuel Degueldre --- .../static/src/js/editor/snippets.editor.js | 15 +++- .../static/src/js/editor/snippets.editor.js | 73 ++++++++++++------- 2 files changed, 57 insertions(+), 31 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 4b416d96c0c..fbad9839dde 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -2214,22 +2214,29 @@ var SnippetsMenu = Widget.extend({ async _execWithLoadingEffect(action, contentLoading = true, delay = 500) { const mutexExecResult = this._mutex.exec(action); if (!this.loadingTimers[contentLoading]) { - this.loadingTimers[contentLoading] = setTimeout(() => { + const addLoader = () => { this.loadingElements[contentLoading] = this._createLoadingElement(); if (contentLoading) { this.$snippetEditorArea.append(this.loadingElements[contentLoading]); } else { this.el.appendChild(this.loadingElements[contentLoading]); } - }, delay); + }; + if (delay) { + this.loadingTimers[contentLoading] = setTimeout(addLoader, delay); + } else { + addLoader(); + } this._mutex.getUnlockedDef().then(() => { // Note: we remove the loading element at the end of the // execution queue *even if subsequent actions are content // related or not*. This is a limitation of the loading feature, // the goal is still to limit the number of elements in that // queue anyway. - clearTimeout(this.loadingTimers[contentLoading]); - this.loadingTimers[contentLoading] = undefined; + if (delay) { + clearTimeout(this.loadingTimers[contentLoading]); + this.loadingTimers[contentLoading] = undefined; + } if (this.loadingElements[contentLoading]) { this.loadingElements[contentLoading].remove(); diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js index c5d64e11d54..77229934eac 100644 --- a/addons/website/static/src/js/editor/snippets.editor.js +++ b/addons/website/static/src/js/editor/snippets.editor.js @@ -154,37 +154,56 @@ weSnippetEditor.Class.include({ */ async _onThemeTabClick(ev) { // Note: nothing async here but start the loading effect asap - this._execWithLoadingEffect(async () => new Promise(resolve => setTimeout(() => resolve(), 0)), false, 0); + let releaseLoader; + try { + const promise = new Promise(resolve => releaseLoader = resolve); + this._execWithLoadingEffect(() => promise, false, 0); + // loader is added to the DOM synchronously + await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); + // ensure loader is rendered: first call asks for the (already done) DOM update, + // second call happens only after rendering the first "updates" - if (!this.topFakeOptionEl) { - let el; - for (const [elementName, title] of this.optionsTabStructure) { - const newEl = document.createElement(elementName); - newEl.dataset.name = title; - if (el) { - el.appendChild(newEl); - } else { - this.topFakeOptionEl = newEl; + if (!this.topFakeOptionEl) { + let el; + for (const [elementName, title] of this.optionsTabStructure) { + const newEl = document.createElement(elementName); + newEl.dataset.name = title; + if (el) { + el.appendChild(newEl); + } else { + this.topFakeOptionEl = newEl; + } + el = newEl; } - el = newEl; + this.bottomFakeOptionEl = el; + this.el.appendChild(this.topFakeOptionEl); } - this.bottomFakeOptionEl = el; - this.el.appendChild(this.topFakeOptionEl); + + // Need all of this in that order so that: + // - the element is visible and can be enabled and the onFocus method is + // called each time. + // - the element is hidden afterwards so it does not take space in the + // DOM, same as the overlay which may make a scrollbar appear. + this.topFakeOptionEl.classList.remove('d-none'); + const editorPromise = this._activateSnippet($(this.bottomFakeOptionEl)); + releaseLoader(); // because _activateSnippet uses the same mutex as the loader + releaseLoader = undefined; + const editor = await editorPromise; + this.topFakeOptionEl.classList.add('d-none'); + editor.toggleOverlay(false); + + this._updateLeftPanelContent({ + tab: this.tabs.THEME, + }); + } catch (e) { + // Normally the loading effect is removed in case of error during the action but here + // the actual activity is happening outside of the action, the effect must therefore + // be cleared in case of error as well + if (releaseLoader) { + releaseLoader(); + } + throw e; } - - // Need all of this in that order so that: - // - the element is visible and can be enabled and the onFocus method is - // called each time. - // - the element is hidden afterwards so it does not take space in the - // DOM, same as the overlay which may make a scrollbar appear. - this.topFakeOptionEl.classList.remove('d-none'); - const editor = await this._activateSnippet($(this.bottomFakeOptionEl)); - this.topFakeOptionEl.classList.add('d-none'); - editor.toggleOverlay(false); - - this._updateLeftPanelContent({ - tab: this.tabs.THEME, - }); }, });