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 @@