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 b8ab92a3d8d..50a14ae6cba 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -779,6 +779,7 @@ var SnippetsMenu = Widget.extend({ cacheSnippetTemplate: {}, events: { 'click .o_install_btn': '_onInstallBtnClick', + 'click .o_we_add_snippet_btn': '_onBlocksTabClick', 'click .o_we_invisible_entry': '_onInvisibleEntryClick', 'click #snippet_custom .o_delete_btn': '_onDeleteBtnClick', }, @@ -804,6 +805,11 @@ var SnippetsMenu = Widget.extend({ 'user_value_widget_opening': '_onUserValueWidgetOpening', 'reload_snippet_template': '_onReloadSnippetTemplate', }, + // enum of the SnippetsMenu's tabs. + tabs: { + BLOCKS: 'blocks', + OPTIONS: 'options', + }, /** * @param {Widget} parent @@ -896,10 +902,6 @@ var SnippetsMenu = Widget.extend({ }); var $target = $(srcElement); - if ($target.closest('#snippets_menu').length) { - this._activateSnippet(false); - return; - } if (!$target.closest('body > *').length) { return; } @@ -1833,6 +1835,32 @@ var SnippetsMenu = Widget.extend({ return this.nodeType === 3 && this.textContent.match(/\S/); }).parent().addClass('o_default_snippet_text'); }, + /** + * Changes the content of the left panel and selects a tab. + * + * @private + * @param {htmlString | Element | Text | Array | jQuery} [content] + * the new content of the customizePanel + * @param {this.tabs.VALUE} [tab='blocks'] - the tab to select + */ + _updateLeftPanelContent: function ({content, tab}) { + this._closeWidgets(); + + tab = tab || this.tabs.BLOCKS; + + while (this.customizePanel.firstChild) { + this.customizePanel.removeChild(this.customizePanel.firstChild); + } + if (content) { + $(this.customizePanel).append(content); + } + + this.$('#o_scroll').toggleClass('d-none', tab !== this.tabs.BLOCKS); + this.customizePanel.classList.toggle('d-none', tab === this.tabs.BLOCKS); + + this.$('.o_we_add_snippet_btn').toggleClass('active', tab === this.tabs.BLOCKS); + this.$('.o_we_customize_snippet_btn').toggleClass('active', tab === this.tabs.OPTIONS); + }, //-------------------------------------------------------------------------- // Handlers @@ -2000,6 +2028,16 @@ var SnippetsMenu = Widget.extend({ .toggleClass('fa-eye-slash', !isVisible); return this._activateSnippet(isVisible ? $snippet : false); }, + /** + * @private + */ + _onBlocksTabClick: function (ev) { + this._activateSnippet(false); + this._updateLeftPanelContent({ + content: [], + tab: this.tabs.BLOCKS, + }); + }, /** * @private */ @@ -2099,18 +2137,10 @@ var SnippetsMenu = Widget.extend({ * @param {OdooEvent} ev */ _onUpdateCustomizeElements: function (ev) { - this._closeWidgets(); - while (this.customizePanel.firstChild) { - this.customizePanel.removeChild(this.customizePanel.firstChild); - } - ev.data.customize$Elements.forEach($el => { - this.customizePanel.appendChild($el[0]); + this._updateLeftPanelContent({ + content: ev.data.customize$Elements, + tab: ev.data.customize$Elements.length ? this.tabs.OPTIONS : this.tabs.BLOCKS, }); - var customize = !!ev.data.customize$Elements.length; - this.$('#o_scroll').toggleClass('d-none', customize); - this.$('.o_we_add_snippet_btn').toggleClass('active', !customize); - this.customizePanel.classList.toggle('d-none', !customize); - this.$('.o_we_customize_snippet_btn').toggleClass('active', customize); }, /** * Called when an user value widget is being opened -> close all the other diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py index 83c0df51ff8..2ebc4900a6c 100644 --- a/addons/website/controllers/main.py +++ b/addons/website/controllers/main.py @@ -384,11 +384,6 @@ class Website(Home): return True - @http.route(['/website/theme_customize_reload'], type='http', auth="public", website=True) - def theme_customize_reload(self, href, enable, disable, tab=0, **kwargs): - self.theme_customize(enable and enable.split(",") or [], disable and disable.split(",") or []) - return request.redirect(href + ("&theme=true" if "#" in href else "#theme=true") + ("&tab=" + tab)) - @http.route(['/website/make_scss_custo'], type='json', auth='user', website=True) def make_scss_custo(self, url, values): """ diff --git a/addons/website/static/src/js/editor/snippets.editor.js b/addons/website/static/src/js/editor/snippets.editor.js new file mode 100644 index 00000000000..b7776a3df13 --- /dev/null +++ b/addons/website/static/src/js/editor/snippets.editor.js @@ -0,0 +1,52 @@ +odoo.define('website.snippet.editor', function (require) { +'use strict'; + +const weSnippetEditor = require('web_editor.snippet.editor'); +const ThemeCustomizationMenu = require('website.theme'); + +weSnippetEditor.Class.include({ + events: _.extend({}, weSnippetEditor.Class.prototype.events, { + 'click .o_we_customize_theme_btn': '_onThemeTabClick', + }), + tabs: _.extend({}, weSnippetEditor.Class.prototype.tabs, { + THEME: 'theme', + }), + + /** + * @override + */ + start: function () { + const prom1 = this._super(...arguments); + this.themeCustomizationMenu = new ThemeCustomizationMenu(this); + const prom2 = this.themeCustomizationMenu.appendTo(document.createDocumentFragment()); + return Promise.all([prom1, prom2]); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _updateLeftPanelContent: function ({content, tab}) { + this._super(...arguments); + this.$('.o_we_customize_theme_btn').toggleClass('active', tab === this.tabs.THEME); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + */ + _onThemeTabClick: function (ev) { + this._activateSnippet(false); + this._updateLeftPanelContent({ + content: this.themeCustomizationMenu.el, + tab: this.tabs.THEME, + }); + }, +}); +}); diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js index ab05221e8b0..7d976ac4136 100644 --- a/addons/website/static/src/js/widgets/theme.js +++ b/addons/website/static/src/js/widgets/theme.js @@ -6,7 +6,6 @@ var core = require('web.core'); var Dialog = require('web.Dialog'); var Widget = require('web.Widget'); var weWidgets = require('wysiwyg.widgets'); -var websiteNavbarData = require('website.navbar'); var ColorPaletteDialog = require('web_editor.ColorPalette').ColorPaletteDialog; const ColorpickerDialog = require('web.ColorpickerDialog'); @@ -107,11 +106,10 @@ var QuickEdit = Widget.extend({ }, }); -var ThemeCustomizeDialog = Dialog.extend({ - xmlDependencies: (Dialog.prototype.xmlDependencies || []) - .concat(['/website/static/src/xml/website.editor.xml']), +var ThemeCustomizationMenu = Widget.extend({ + xmlDependencies: ['/website/static/src/xml/website.editor.xml'], - template: 'website.theme_customize', + className: 'd-flex flex-column align-items-start o_theme_customization_menu p-2', events: { 'change .o_theme_customize_option_input': '_onChange', 'click .checked .o_theme_customize_option_input[type="radio"]': '_onChange', @@ -124,14 +122,8 @@ var ThemeCustomizeDialog = Dialog.extend({ /** * @constructor */ - init: function (parent, options) { - options = options || {}; - this._super(parent, _.extend({ - title: _t("Customize Theme"), - buttons: [], - }, options)); - - this.defaultTab = options.tab || 0; + init: function (parent) { + this._super(...arguments); this.fontVariables = []; }, /** @@ -156,69 +148,19 @@ var ThemeCustomizeDialog = Dialog.extend({ * @override */ start: function () { - var self = this; - this.PX_BY_REM = parseFloat($(document.documentElement).css('font-size')); - this.$modal.addClass('o_theme_customize_modal'); - this.$modal.find('.modal-footer').append($('', { - href: '/web#action=website.theme_install_kanban_action', - text: _t("Choose another theme..."), - })); - this.style = window.getComputedStyle(document.documentElement); this.nbFonts = parseInt(this.style.getPropertyValue('--number-of-fonts')); var googleFontsProperty = this.style.getPropertyValue('--google-fonts').trim(); this.googleFonts = googleFontsProperty ? googleFontsProperty.split(/\s*,\s*/g) : []; - var $tabs; - var loadDef = this._loadViews().then(function (data) { - self._generateDialogHTML(data); - $tabs = self.$('[data-toggle="tab"]'); - - // Hide the tab navigation if only one tab - if ($tabs.length <= 1) { - $tabs.closest('.nav').addClass('d-none'); - } + var loadDef = this._loadViews().then(data => { + this._generateDialogHTML(data); + this._removeDuplicateColors(); }); - // Enable the first option tab or the given default tab - this.opened().then(function () { - $tabs.eq(self.defaultTab).tab('show'); - - // Hack to hide primary/secondary if they are equal to alpha/beta - // (this is the case with default values but not in some themes). - var $primary = self.$('.o_theme_customize_color[data-color="primary"]'); - var $alpha = self.$('.o_theme_customize_color[data-color="alpha"]'); - var $secondary = self.$('.o_theme_customize_color[data-color="secondary"]'); - var $beta = self.$('.o_theme_customize_color[data-color="beta"]'); - - var sameAlphaPrimary = $primary.css('background-color') === $alpha.css('background-color'); - var sameBetaSecondary = $secondary.css('background-color') === $beta.css('background-color'); - - if (!sameAlphaPrimary) { - $alpha.prev().text(_t("Extra Color")); - } - if (!sameBetaSecondary) { - $beta.prev().text(_t("Extra Color")); - } - - $primary = $primary.closest('.o_theme_customize_option'); - $alpha = $alpha.closest('.o_theme_customize_option'); - $secondary = $secondary.closest('.o_theme_customize_option'); - $beta = $beta.closest('.o_theme_customize_option'); - - $primary.toggleClass('d-none', sameAlphaPrimary); - $secondary.toggleClass('d-none', sameBetaSecondary); - - if (!sameAlphaPrimary && sameBetaSecondary) { - $beta.insertBefore($alpha); - } else if (sameAlphaPrimary && !sameBetaSecondary) { - $secondary.insertAfter($alpha); - } - }); - - return Promise.all([this._super.apply(this, arguments), loadDef]); + return Promise.all([this._super(...arguments), loadDef]); }, //-------------------------------------------------------------------------- @@ -270,38 +212,21 @@ var ThemeCustomizeDialog = Dialog.extend({ */ _generateDialogHTML: function (data) { var self = this; - var $contents = this.$el.children('content'); + var $contents = $(core.qweb.render('website.theme_customize')).children('content'); if ($contents.length === 0) { return; } - $contents.remove(); - this.$el.append(core.qweb.render('website.theme_customize_modal_layout')); - var $navLinksContainer = this.$('.nav'); - var $navContents = this.$('.tab-content'); - _.each($contents, function (content) { - var $content = $(content); + const $content = $(content); + const contentID = _.uniqueId('content-'); - var contentID = _.uniqueId('content-'); - - // Build the nav tab for the content - $navLinksContainer.append($('
', { - class: 'nav-item mb-1', - }).append($('', { - href: '#' + contentID, - class: 'nav-link', - 'data-toggle': 'tab', - text: $content.attr('string'), - }))); - - // Build the tab pane for the content - var $navContent = $(core.qweb.render('website.theme_customize_modal_content', { + const $menuSection = $(core.qweb.render('website.theme_customize_section', { id: contentID, - title: $content.attr('title'), + title: $content.attr('string'), })); - $navContents.append($navContent); - var $optionsContainer = $navContent.find('.o_options_container'); + self.$el.append($menuSection); + var $optionsContainer = $menuSection.find('.o_options_container'); // Process content items _processItems($content.children(), $optionsContainer, false); @@ -350,7 +275,7 @@ var ThemeCustomizeDialog = Dialog.extend({ } // Build the options template - $element = $(core.qweb.render('website.theme_customize_modal_option', _.extend({ + $element = $(core.qweb.render('website.theme_customize_menu_option', _.extend({ alone: alone, name: xmlid === undefined && widgetName !== 'auto' ? _.uniqueId('option-') : optionsName, id: $item.attr('id') || _.uniqueId('o_theme_customize_input_id_'), @@ -380,7 +305,7 @@ var ThemeCustomizeDialog = Dialog.extend({ break; case 'LIST': - $element = $('', {class: 'py-1 px-2 o_theme_customize_option_list'}); + $element = $('', {class: 'py-1 o_theme_customize_option_list'}); _processItems($item.children(), $element, false); break; @@ -418,6 +343,21 @@ var ThemeCustomizeDialog = Dialog.extend({ }))); break; + case 'A': + $element = $item.clone(); + $element.on('click', (ev) => { + ev.preventDefault(); + Dialog.confirm(self, _t("Switching theme cannot be done directly from edit mode, click ok to save your current changes and go to the theme selection page."), { + confirm_callback: () => self.trigger_up('request_save', { + reload: false, + onSuccess: function () { + window.location.href = ev.target.href; + }, + }), + }); + }); + break; + default: _processItems($item.children(), $container, false); return; @@ -425,7 +365,7 @@ var ThemeCustomizeDialog = Dialog.extend({ if ($container.hasClass('form-row')) { var $col = $('', { - class: _.str.sprintf('col-%s', $item.data('col') || 6), + class: _.str.sprintf('col-%s', $item.data('col') || 12), }); if (item.tagName === 'LIST') { @@ -620,6 +560,42 @@ var ThemeCustomizeDialog = Dialog.extend({ qEdit.appendTo($inputData.closest('.o_theme_customize_option')); }); }, + /** + * Hides primary/secondary if they are equal to alpha/beta + * (this is the case with default values but not in some themes). + * + * @private + */ + _removeDuplicateColors: function () { + var $primary = this.$el.find('.o_theme_customize_color[data-color="primary"]'); + var $alpha = this.$el.find('.o_theme_customize_color[data-color="alpha"]'); + var $secondary = this.$el.find('.o_theme_customize_color[data-color="secondary"]'); + var $beta = this.$el.find('.o_theme_customize_color[data-color="beta"]'); + + var sameAlphaPrimary = $primary.css('background-color') === $alpha.css('background-color'); + var sameBetaSecondary = $secondary.css('background-color') === $beta.css('background-color'); + + if (!sameAlphaPrimary) { + $alpha.prev().text(_t("Extra Color")); + } + if (!sameBetaSecondary) { + $beta.prev().text(_t("Extra Color")); + } + + $primary = $primary.closest('.o_theme_customize_option'); + $alpha = $alpha.closest('.o_theme_customize_option'); + $secondary = $secondary.closest('.o_theme_customize_option'); + $beta = $beta.closest('.o_theme_customize_option'); + + $primary.toggleClass('d-none', sameAlphaPrimary); + $secondary.toggleClass('d-none', sameBetaSecondary); + + if (!sameAlphaPrimary && sameBetaSecondary) { + $beta.insertBefore($alpha); + } else if (sameAlphaPrimary && !sameBetaSecondary) { + $secondary.insertAfter($alpha); + } + }, /** * @private */ @@ -711,17 +687,37 @@ var ThemeCustomizeDialog = Dialog.extend({ _updateStyle: function (enable, disable, reload) { var self = this; - var $loading = $('', {class: 'fa fa-refresh fa-spin'}); - this.$modal.find('.modal-title').append($loading); - if (reload || config.isDebug('assets')) { - window.location.href = $.param.querystring('/website/theme_customize_reload', { - href: window.location.href, - enable: (enable || []).join(','), - disable: (disable || []).join(','), - tab: this.$('.nav-link.active').parent().index(), + return new Promise(resolve => { + const confirm = Dialog.confirm(self, _t("This change needs to reload the page, this will save all your changes and reload the page, are you sure you want to proceed?") + + (config.isDebug('assets') ? _t(" It appears you are in debug=assets mode, all theme customization options require a page reload in this mode.") : ''), { + confirm_callback: () => resolve(true), + }); + confirm.on('closed', self, () => resolve(false)); + }).then(save => { + if (!save) { + if (this.$lastToggled.attr('type') === 'checkbox') { + this.$lastToggled.prop('checked', !this.$lastToggled.prop('checked')); + } + return; + } + self.trigger_up('request_save', { + reload: false, + onSuccess: () => { + this._rpc({ + route: '/website/theme_customize', + params: { + 'enable': enable, + 'disable': disable, + 'get_bundle': true, + }, + }).then(() => { + window.location.href = window.location.origin + window.location.pathname + '?enable_editor=1'; + }); + }, + }); + return new Promise(resolve => {}); }); - return Promise.resolve(); } return this._rpc({ @@ -762,10 +758,8 @@ var ThemeCustomizeDialog = Dialog.extend({ return linksLoaded; }); return Promise.all(defs).then(function () { - $loading.remove(); $allLinks.remove(); }).guardedCatch(function () { - $loading.remove(); $allLinks.remove(); }); }).then(function () { @@ -838,6 +832,7 @@ var ThemeCustomizeDialog = Dialog.extend({ * @param {Event} ev */ _onChange: function (ev) { + this.$lastToggled = $(ev.target); var self = this; // Checkout the option that changed @@ -957,43 +952,5 @@ var ThemeCustomizeDialog = Dialog.extend({ }, }); -var ThemeCustomizeMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ - actions: _.extend({}, websiteNavbarData.WebsiteNavbarActionWidget.prototype.actions || {}, { - 'customize_theme': '_openThemeCustomizeDialog', - }), - - /** - * Automatically opens the theme customization dialog if the corresponding - * hash is in the page URL. - * - * @override - */ - start: function () { - if ((window.location.hash || '').indexOf('theme=true') > 0) { - var tab = window.location.hash.match(/tab=(\d+)/); - this._openThemeCustomizeDialog(tab ? tab[1] : false); - window.location.hash = ''; - } - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Actions - //-------------------------------------------------------------------------- - - /** - * Instantiates and opens the theme customization dialog. - * - * @private - * @param {string} tab - * @returns {Promise} - */ - _openThemeCustomizeDialog: function (tab) { - return new ThemeCustomizeDialog(this, {tab: tab}).open(); - }, -}); - -websiteNavbarData.websiteNavbarRegistry.add(ThemeCustomizeMenu, '#theme_customize'); - -return ThemeCustomizeDialog; +return ThemeCustomizationMenu; }); diff --git a/addons/website/static/src/scss/website.editor.ui.scss b/addons/website/static/src/scss/website.editor.ui.scss index 1d1728714dc..f7c516dbb59 100644 --- a/addons/website/static/src/scss/website.editor.ui.scss +++ b/addons/website/static/src/scss/website.editor.ui.scss @@ -4,48 +4,7 @@ } } -.modal.o_theme_customize_modal { - .modal-dialog { - @include o-position-absolute(0, 2%); - width: 96%; - max-width: 550px; - } - .modal-header { - background-color: $o-brand-odoo; - color: white; - border-radius: 0; - - * { - color: inherit; - } - } - - .close:hover, .close:focus { - color: white; - } - - .nav { - padding: 0; - - .nav-item { - background-color: $o-brand-lightsecondary; - - .nav-link { - color: $o-main-text-color; - - &.active { - font-weight: bold; - background-color: white; - border: 1px solid #d9d7d7; - border-left: 1px solid #333333; - } - &:hover, &:focus { - text-decoration: none; - } - } - } - } - +.o_theme_customization_menu { .dropdown-menu { position: static !important; transform: none !important; @@ -73,7 +32,6 @@ width: 100%; min-height: 24px; margin: 0; - padding: 8px; text-align: center; font-size: 14px; line-height: 1; @@ -90,14 +48,9 @@ justify-content: flex-start; } } - &:not(.dropdown-item) label { - border: 2px solid transparent; - background-color: $o-brand-lightsecondary; - - &.checked { - border-color: $o-brand-odoo; - color: $o-brand-odoo; - } + &:not(.dropdown-item) label.checked { + border: 2px solid $o-brand-odoo; + color: $o-brand-odoo; } &.o_theme_customize_with_widget { @@ -115,10 +68,9 @@ .o_theme_customize_color { position: relative; - width: 30px; - height: 20px; + width: 1rem; + height: 1rem; margin-left: auto; - border: 1px solid black; &::before, &::after { content: ""; @@ -171,10 +123,6 @@ pointer-events: none; } - .o_theme_customize_option_list { - box-shadow: 0 0 40px 0 rgba(0, 0, 0, 0.05); - } - .o_theme_customize_dropdown_btn { background-color: $o-brand-lightsecondary; } diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 31ffb28704d..eb19b27e7af 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -28,15 +28,11 @@ -