From 45a54104bfdee9f0459bc35d7da15c6e586b4982 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Tue, 18 Dec 2018 16:08:31 +0000 Subject: [PATCH] [REF] website: unify the theme customize dialog color options Before this commit, color options in the theme customize were treated as big exception in the system. The whole color panel XML was defined client-side and used custom design. Now, all colors are defined as options in the "server-side" template, allowing for more customizations and better maintenance. This commit also makes the color saving system generic to allow for more new options in the future (like customizing the logo height or the font size, which is the next task). PR https://github.com/odoo/odoo/pull/29624 task-1904244 --- addons/website/static/src/js/widgets/theme.js | 319 ++++++++++-------- .../static/src/scss/website.edit_mode.scss | 109 +++--- .../website/static/src/xml/website.editor.xml | 47 +-- addons/website/views/website_templates.xml | 30 +- 4 files changed, 276 insertions(+), 229 deletions(-) diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js index dd32defb40f..380a19d936a 100644 --- a/addons/website/static/src/js/widgets/theme.js +++ b/addons/website/static/src/js/widgets/theme.js @@ -3,9 +3,9 @@ odoo.define('website.theme', function (require) { var config = require('web.config'); var core = require('web.core'); -var ColorpickerDialog = require('wysiwyg.widgets.ColorpickerDialog'); var Dialog = require('web.Dialog'); var weWidgets = require('wysiwyg.widgets'); +var ColorpickerDialog = require('wysiwyg.widgets.ColorpickerDialog'); var websiteNavbarData = require('website.navbar'); var _t = core._t; @@ -19,10 +19,11 @@ var ThemeCustomizeDialog = Dialog.extend({ template: 'website.theme_customize', events: { 'change .o_theme_customize_option_input': '_onChange', - 'click .o_theme_customize_option_input[type="radio"]:checked': '_onChange', - 'click .o_theme_customize_color': '_onColorClick', + 'click .checked .o_theme_customize_option_input[type="radio"]': '_onChange', }, + CUSTOM_BODY_IMAGE_XML_ID: 'option_custom_body_image', + /** * @constructor */ @@ -76,21 +77,31 @@ var ThemeCustomizeDialog = Dialog.extend({ this.opened().then(function () { $tabs.eq(self.defaultTab).tab('show'); - var $colorPreview = self.$('.o_theme_customize_color_previews:visible'); - var $primary = $colorPreview.find('.o_theme_customize_color[data-color="primary"]'); - var $alpha = $colorPreview.find('.o_theme_customize_color[data-color="alpha"]'); - var $secondary = $colorPreview.find('.o_theme_customize_color[data-color="secondary"]'); - var $beta = $colorPreview.find('.o_theme_customize_color[data-color="beta"]'); - var sameAlphaPrimary = $primary.find('.o_color_preview').css('background-color') === $alpha.find('.o_color_preview').css('background-color'); - var sameBetaSecondary = $secondary.find('.o_color_preview').css('background-color') === $beta.find('.o_color_preview').css('background-color'); + // 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.find('.o_color_name').text(_t("Extra Color")); - $primary.removeClass('d-none').addClass('d-flex'); + $alpha.prev().text(_t("Extra Color")); } if (!sameBetaSecondary) { - $beta.find('.o_color_name').text(_t("Extra Color")); - $secondary.removeClass('d-none').addClass('d-flex'); + $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) { @@ -105,6 +116,44 @@ var ThemeCustomizeDialog = Dialog.extend({ // Private //-------------------------------------------------------------------------- + /** + * @private + */ + _chooseBodyCustomImage: function () { + var self = this; + var def = $.Deferred(); + var $image = $(''); + var editor = new weWidgets.MediaDialog(this, { + onlyImages: true, + firstFilters: ['background'], + }, $image[0]); + + editor.on('save', this, function (media) { // TODO use scss customization instead (like for user colors) + var src = $(media).attr('src'); + self._rpc({ + model: 'ir.model.data', + method: 'get_object_reference', + args: ['website', this.CUSTOM_BODY_IMAGE_XML_ID], + }).then(function (data) { + return self._rpc({ + model: 'ir.ui.view', + method: 'save', + args: [ + data[1], + '#wrapwrap { background-image: url("' + src + '"); }', + '//style', + ], + }); + }).always(def.resolve.bind(def)); + }); + editor.on('cancel', this, function () { + def.resolve(); + }); + + editor.open(); + + return def; + }, /** * @private * @param {Object} data - @see this._loadViews @@ -150,7 +199,7 @@ var ThemeCustomizeDialog = Dialog.extend({ this.$('[title]').tooltip(); - this.$inputs = self.$('[data-xmlid], [data-enable], [data-disable]'); + this.$inputs = self.$('.o_theme_customize_option_input'); // Enable data-xmlid="" inputs if none of their neighbors were enabled _.each(this.$inputs.filter('[data-xmlid=""]'), function (input) { var $input = $(input); @@ -175,19 +224,20 @@ var ThemeCustomizeDialog = Dialog.extend({ var xmlid = $item.data('xmlid'); - // Build the options template - var $option = $(core.qweb.render('website.theme_customize_modal_option', { - alone: alone, - name: optionsName, - id: $item.attr('id') || _.uniqueId('o_theme_customize_input_id_'), - checked: xmlid && (!_.difference(self._getXMLIDs($item), data.enabled).length), - + var renderingOptions = _.extend({ string: $item.attr('string') || data.names[xmlid.split(',')[0].trim()], icon: $item.data('icon'), font: $item.data('font'), + }, $item.data()); + // Build the options template + var $option = $(core.qweb.render('website.theme_customize_modal_option', _.extend({ + alone: alone, + name: xmlid === undefined ? _.uniqueId('option-') : optionsName, + id: $item.attr('id') || _.uniqueId('o_theme_customize_input_id_'), + checked: xmlid === undefined || xmlid && (!_.difference(self._getXMLIDs($item), data.enabled).length), widget: widgetName, - })); + }, renderingOptions))); $option.find('input') .addClass('o_theme_customize_option_input') .attr({ @@ -198,8 +248,8 @@ var ThemeCustomizeDialog = Dialog.extend({ }); if (widgetName) { - var $widget = $(core.qweb.render('website.theme_customize_' + widgetName)); - $option.append($widget); + var $widget = $(core.qweb.render('website.theme_customize_widget_' + widgetName, renderingOptions)); + $option.find('label').append($widget); } if ($container.hasClass('form-row')) { @@ -267,51 +317,113 @@ var ThemeCustomizeDialog = Dialog.extend({ /** * @private */ - _processChange: function ($inputs) { + _makeSCSSCusto: function (url, values) { var self = this; - this.$modal.addClass('o_theme_customize_loading'); + return this._rpc({ // TODO improve to be more efficient + route: '/web_editor/get_assets_editor_resources', + params: { + 'key': 'website.layout', + 'get_views': false, + 'get_scss': true, + 'get_js': false, + 'bundles': false, + 'bundles_restriction': [], + 'only_user_custom_files': false, + }, + }).then(function (data) { + var file = _.find(data.scss[0][1], function (file) { + return file.url === url; + }); - var bodyCustomImageXMLID = 'option_custom_body_image'; - var $inputBodyCustomImage = $inputs.filter('[data-xmlid*="website.' + bodyCustomImageXMLID + '"]:checked'); - if (!$inputBodyCustomImage.length) { - return $.when(); - } + var updatedFileContent = file.arch; + _.each(values, function (value, name) { + var pattern = _.str.sprintf("'%s': %%s,\n", name); + var regex = new RegExp(_.str.sprintf(pattern, ".+")); + var replacement = _.str.sprintf(pattern, value); + if (regex.test(updatedFileContent)) { + updatedFileContent = updatedFileContent + .replace(regex, replacement); + } else { + updatedFileContent = updatedFileContent + .replace(/( *)(.*hook.*)/, _.str.sprintf('$1%s$1$2', replacement)); + } + }); - var def = $.Deferred(); - var $image = $(''); - var editor = new weWidgets.MediaDialog(this, { - onlyImages: true, - firstFilters: ['background'], - }, $image[0]); - - editor.on('save', this, function (media) { // TODO use scss customization instead (like for user colors) - var src = $(media).attr('src'); - self._rpc({ - model: 'ir.model.data', - method: 'get_object_reference', - args: ['website', bodyCustomImageXMLID], - }).then(function (data) { - return self._rpc({ - model: 'ir.ui.view', - method: 'save', - args: [ - data[1], - '#wrapwrap { background-image: url("' + src + '"); }', - '//style', - ], - }); - }).then(function () { - def.resolve(); + return self._rpc({ + route: '/web_editor/save_scss_or_js', + params: { + 'url': file.url, + 'bundle_xmlid': 'web.assets_common', + 'content': updatedFileContent, + 'file_type': 'scss', + }, }); }); - editor.on('cancel', this, function () { - def.resolve(); - }); + }, + /** + * @private + */ + _pickColor: function (colorElement) { + var self = this; + var $color = $(colorElement); + var colorName = $color.data('color'); + var colorType = $color.data('colorType'); - editor.open(); + var def = $.Deferred(); + + var colorpicker = new ColorpickerDialog(this, { + defaultColor: $color.css('background-color'), + }); + var chosenColor = undefined; + colorpicker.on('colorpicker:saved', this, function (ev) { + ev.stopPropagation(); + chosenColor = ev.data.cssColor; + }); + colorpicker.on('closed', this, function (ev) { + if (chosenColor === undefined) { + def.resolve(); + return; + } + + var baseURL = '/website/static/src/scss/options/colors/'; + var url = _.str.sprintf('%suser_%scolor_palette.scss', baseURL, (colorType ? (colorType + '_') : '')); + + var colors = {}; + colors[colorName] = chosenColor; + if (colorName === 'alpha') { + colors['beta'] = 'null'; + colors['gamma'] = 'null'; + colors['delta'] = 'null'; + colors['epsilon'] = 'null'; + } + + self._makeSCSSCusto(url, colors).always(def.resolve.bind(def)); + }); + colorpicker.open(); return def; }, + /** + * @private + */ + _processChange: function ($inputs) { + var self = this; + var defs = []; + + // Handle body image changes + var $bodyImageInputs = $inputs.filter('[data-xmlid*="website.' + this.CUSTOM_BODY_IMAGE_XML_ID + '"]:checked'); + defs = defs.concat(_.map($bodyImageInputs, function () { + return self._chooseBodyCustomImage(); + })); + + // Handle color changes + var $colors = $inputs.closest('.o_theme_customize_option').find('.o_theme_customize_color'); + defs = defs.concat(_.map($colors, function (colorElement) { + return self._pickColor($(colorElement)); + })); + + return $.when.apply($, defs); + }, /** * @private */ @@ -319,7 +431,7 @@ var ThemeCustomizeDialog = Dialog.extend({ var self = this; // Look at all options to see if they are enabled or disabled - var $enable = this.$inputs.filter('[data-xmlid]:checked'); + var $enable = this.$inputs.filter(':checked'); // Mark the labels as checked accordingly this.$('label').removeClass('checked'); @@ -362,7 +474,6 @@ var ThemeCustomizeDialog = Dialog.extend({ return $.Deferred(); } - var self = this; return this._rpc({ route: '/website/theme_customize', params: { @@ -394,9 +505,7 @@ var ThemeCustomizeDialog = Dialog.extend({ }); }); - return $.when.apply($, defs).then(function () { - self.$modal.removeClass('o_theme_customize_loading'); - }); + return $.when.apply($, defs); }); }, @@ -443,87 +552,17 @@ var ThemeCustomizeDialog = Dialog.extend({ this._setActive(); // Update the style according to the whole set of options + this.$modal.addClass('o_theme_customize_loading'); self._processChange($options).then(function () { - self._updateStyle( + return self._updateStyle( self._getXMLIDs($enable), self._getXMLIDs($disable), $option.data('reload') && window.location.href.match(new RegExp($option.data('reload'))) ); + }).always(function () { + self.$modal.removeClass('o_theme_customize_loading'); }); }, - /** - * @private - * @param {Event} ev - */ - _onColorClick: function (ev) { - var self = this; - var $color = $(ev.currentTarget); - var colorName = $color.data('color'); - var colorType = $color.data('colorType'); - - var colorpicker = new ColorpickerDialog(this, { - defaultColor: $color.find('.o_color_preview').css('background-color'), - }); - colorpicker.on('colorpicker:saved', this, function (ev) { - ev.stopPropagation(); - - // TODO improve to be more efficient - self._rpc({ - route: '/web_editor/get_assets_editor_resources', - params: { - 'key': 'website.layout', - 'get_views': false, - 'get_scss': true, - 'get_js': false, - 'bundles': false, - 'bundles_restriction': [], - 'only_user_custom_files': false, - }, - }).then(function (data) { - var files = data.scss[0][1]; - var file = _.find(files, function (file) { - var baseURL = '/website/static/src/scss/options/colors/'; - return file.url === _.str.sprintf('%suser_%scolor_palette.scss', baseURL, (colorType ? (colorType + '_') : '')); - }); - - var colors = {}; - colors[colorName] = ev.data.cssColor; - if (colorName === 'alpha') { - colors['beta'] = 'null'; - colors['gamma'] = 'null'; - colors['delta'] = 'null'; - colors['epsilon'] = 'null'; - } - - var updatedFileContent = file.arch; - _.each(colors, function (colorValue, colorName) { - var pattern = _.str.sprintf("'%s': %%s,\n", colorName); - var regex = new RegExp(_.str.sprintf(pattern, ".+")); - var replacement = _.str.sprintf(pattern, colorValue); - if (regex.test(updatedFileContent)) { - updatedFileContent = updatedFileContent - .replace(regex, replacement); - } else { - updatedFileContent = updatedFileContent - .replace(/( *)(.*hook.*)/, _.str.sprintf('$1%s$1$2', replacement)); - } - }); - - return self._rpc({ - route: '/web_editor/save_scss_or_js', - params: { - 'url': file.url, - 'bundle_xmlid': 'web.assets_common', - 'content': updatedFileContent, - 'file_type': 'scss', - }, - }); - }).then(function () { - return self._updateStyle(); - }); - }); - colorpicker.open(); - }, }); var ThemeCustomizeMenu = websiteNavbarData.WebsiteNavbarActionWidget.extend({ diff --git a/addons/website/static/src/scss/website.edit_mode.scss b/addons/website/static/src/scss/website.edit_mode.scss index 85ee8dc8fd4..60536b74bed 100644 --- a/addons/website/static/src/scss/website.edit_mode.scss +++ b/addons/website/static/src/scss/website.edit_mode.scss @@ -71,25 +71,40 @@ + label { @include o-position-absolute(0, 0, 0, 0); width: auto; - background: transparent; + background: transparent !important; } } label { + display: flex; + align-items: center; + justify-content: center; width: 100%; min-height: 24px; margin: 0; padding: 8px; border: 2px solid transparent; background-color: $o-brand-lightsecondary; + text-align: center; font-size: 14px; line-height: 1; cursor: pointer; + &:hover::after { + content: ""; + pointer-events: none; + @include o-position-absolute(0, 0, 0, 0); + background-color: rgba(0, 32, 64, 0.03); + } + &.checked { border-color: $o-brand-odoo; color: $o-brand-odoo; } + + &.o_switch { + justify-content: flex-start; + } } $i: 1; @@ -103,57 +118,59 @@ } $i: $i + 1; } - } - .o_theme_customize_color_previews { - h6, a { - font-size: 12px; - margin: 4px 0 0; - } + &.o_theme_customize_with_widget { + label { + justify-content: flex-start; + text-align: left; - .o_theme_customize_color { - > .o_color_preview { - position: relative; - display: inline-block; - width: 48px; - height: 27px; - border: 1px solid black; - - &::before, &::after { - content: ""; - @include o-position-absolute(0, 0, 0, 0); - } - &::before { - background-image: url('/web_editor/static/src/img/transparent.png'); - } - &::after { - background-color: inherit; + &.checked { + border-color: transparent; + color: inherit; } } + } + } - $palette: nth($o-color-palettes, $o-color-palette-number); - $-text-color: if(map-get($palette, 'text'), map-get($palette, 'text'), #212529); // BS default as fallback (TODO find real value) - $-h1-color: if(map-get($palette, 'h1'), map-get($palette, 'h1'), $-text-color); - $-h2-color: if(map-get($palette, 'h2'), map-get($palette, 'h2'), $-h1-color); - $-h3-color: if(map-get($palette, 'h3'), map-get($palette, 'h3'), $-h2-color); - $-h4-color: if(map-get($palette, 'h4'), map-get($palette, 'h4'), $-h3-color); - $-h5-color: if(map-get($palette, 'h5'), map-get($palette, 'h5'), $-h4-color); - $-h6-color: if(map-get($palette, 'h6'), map-get($palette, 'h6'), $-h5-color); - $-defaults: ( - 'body': white, // BS default as fallback (TODO find real value) - 'text': $-text-color, - 'h1': $-h1-color, - 'h2': $-h2-color, - 'h3': $-h3-color, - 'h4': $-h4-color, - 'h5': $-h5-color, - 'h6': $-h6-color, - ); + .o_theme_customize_color { + position: relative; + width: 30px; + height: 20px; + margin-left: auto; + border: 1px solid black; - @each $name, $value in map-merge($-defaults, o-map-omit($palette)) { - &.o_theme_customize_color_#{$name} > .o_color_preview { - background-color: $value; - } + &::before, &::after { + content: ""; + @include o-position-absolute(0, 0, 0, 0); + } + &::before { + background-image: url('/web_editor/static/src/img/transparent.png'); + } + &::after { + background-color: inherit; + } + + $palette: nth($o-color-palettes, $o-color-palette-number); + $-text-color: if(map-get($palette, 'text'), map-get($palette, 'text'), #212529); // BS default as fallback (TODO find real value) + $-h1-color: if(map-get($palette, 'h1'), map-get($palette, 'h1'), $-text-color); + $-h2-color: if(map-get($palette, 'h2'), map-get($palette, 'h2'), $-h1-color); + $-h3-color: if(map-get($palette, 'h3'), map-get($palette, 'h3'), $-h2-color); + $-h4-color: if(map-get($palette, 'h4'), map-get($palette, 'h4'), $-h3-color); + $-h5-color: if(map-get($palette, 'h5'), map-get($palette, 'h5'), $-h4-color); + $-h6-color: if(map-get($palette, 'h6'), map-get($palette, 'h6'), $-h5-color); + $-defaults: ( + 'body': white, // BS default as fallback (TODO find real value) + 'text': $-text-color, + 'h1': $-h1-color, + 'h2': $-h2-color, + 'h3': $-h3-color, + 'h4': $-h4-color, + 'h5': $-h5-color, + 'h6': $-h6-color, + ); + @each $name, $value in map-merge($-defaults, o-map-omit($palette)) { + &.o_theme_customize_color_#{$name} { + background-color: $value; } } } diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index 293b68a93a6..d02d82eb5aa 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -37,58 +37,21 @@
-
+
- -
- -
-
-
Main colors
- - - - - -
-
-
Background colors
- - - -
-
-
Text colors
- - - -
-
-
State colors
- - - - - -
-
-
- - + t-att-data-color-type="colorType"/> diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index 9bcdc071f6c..cf437b281ef 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -485,7 +485,35 @@
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + +