From 905d18e0445798d4fdac68f5b6f8c144e69d002f Mon Sep 17 00:00:00 2001 From: qsm-odoo <qsm@odoo.com> Date: Fri, 21 Dec 2018 17:08:17 +0000 Subject: [PATCH] [IMP] website, *: add new logo sizing option * portal, web This commit introduces the first of many-to-come non-color website user values: the logo height. Now, the user can choose it in the customize dialog. PR https://github.com/odoo/odoo/pull/29624 task-1904244 --- addons/portal/static/src/scss/portal.scss | 13 +- .../static/src/scss/primary_variables.scss | 2 + addons/web/static/src/scss/utils.scss | 4 +- addons/website/static/src/js/widgets/theme.js | 176 +++++++++++++++++- .../static/src/scss/options/user_values.scss | 7 + .../static/src/scss/primary_variables.scss | 15 ++ .../static/src/scss/secondary_variables.scss | 16 +- .../static/src/scss/website.edit_mode.scss | 51 ++--- addons/website/static/src/scss/website.scss | 46 +++++ .../website/static/src/xml/website.editor.xml | 20 +- addons/website/views/website_templates.xml | 4 + 11 files changed, 325 insertions(+), 29 deletions(-) create mode 100644 addons/website/static/src/scss/options/user_values.scss diff --git a/addons/portal/static/src/scss/portal.scss b/addons/portal/static/src/scss/portal.scss index 350df828cd1d..110164341847 100644 --- a/addons/portal/static/src/scss/portal.scss +++ b/addons/portal/static/src/scss/portal.scss @@ -5,6 +5,7 @@ // ====== Variables ========= $-navbar-height: $nav-link-height !default; +$o-theme-navbar-logo-height: $-navbar-height !default; // Portal toolbar (filters, search bar) $o-portal-mobile-toolbar: true; // Enable/Disable custom design @@ -42,15 +43,23 @@ $o-portal-use-default-colors: $body-bg == $o-portal-default-body-bg; header { .navbar-brand { flex: 0 0 auto; + max-width: 75%; &.logo { padding-top: 0; padding-bottom: 0; img { + // object-fit does not work on IE but is only used as a fallback + object-fit: contain; + display: block; width: auto; - height: $-navbar-height; - max-width: none; + height: $o-theme-navbar-logo-height; + + @include media-breakpoint-down(sm) { + height: auto; + max-height: min($o-theme-navbar-logo-height, 5rem); + } } } } diff --git a/addons/portal/static/src/scss/primary_variables.scss b/addons/portal/static/src/scss/primary_variables.scss index 68a1664e93c6..da994350c975 100644 --- a/addons/portal/static/src/scss/primary_variables.scss +++ b/addons/portal/static/src/scss/primary_variables.scss @@ -1,3 +1,5 @@ $o-portal-default-body-bg: #FCFCFC; +$o-theme-navbar-logo-height: null; + $o-theme-btn-icon-hover-decoration: none; diff --git a/addons/web/static/src/scss/utils.scss b/addons/web/static/src/scss/utils.scss index 5ac917fcd75a..494850670379 100644 --- a/addons/web/static/src/scss/utils.scss +++ b/addons/web/static/src/scss/utils.scss @@ -177,11 +177,11 @@ // Mixin which allows to extend the BS4 bg-variant mixin @mixin o-bg-color-extension($color, $text-color, $with-muted) {} -// Function to remove all falsy values of a map +// Function to remove all null values of a map @function o-map-omit($map) { $-map: (); @each $key, $value in $map { - @if $value { + @if $value != null { $-map: map-merge($-map, ( $key: $value, )); diff --git a/addons/website/static/src/js/widgets/theme.js b/addons/website/static/src/js/widgets/theme.js index e118366d009d..89bab6bb0582 100644 --- a/addons/website/static/src/js/widgets/theme.js +++ b/addons/website/static/src/js/widgets/theme.js @@ -4,6 +4,7 @@ odoo.define('website.theme', function (require) { var config = require('web.config'); var core = require('web.core'); var Dialog = require('web.Dialog'); +var Widget = require('web.Widget'); var weWidgets = require('wysiwyg.widgets'); var ColorpickerDialog = require('wysiwyg.widgets.ColorpickerDialog'); var websiteNavbarData = require('website.navbar'); @@ -12,6 +13,96 @@ var _t = core._t; var templateDef = null; +var QuickEdit = Widget.extend({ + xmlDependencies: ['/website/static/src/xml/website.editor.xml'], + template: 'website.theme_customize_active_input', + events: { + 'keydown input': '_onInputKeydown', + 'click .btn-primary': '_onSaveClick', + 'click .btn-secondary': '_onResetClick', + }, + + /** + * @constructor + */ + init: function (parent, value, unit) { + this._super.apply(this, arguments); + this.value = value; + this.unit = unit; + }, + /** + * @override + */ + start: function () { + this.$input = this.$('input'); + this.$input.select(); + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @param {string} [value] + */ + _save: function (value) { + if (value === undefined) { + value = parseFloat(this.$input.val()); + value = isNaN(value) ? 'null' : (value + this.unit); + } + this.trigger_up('QuickEdit:save', { + value: value, + }); + this.destroy(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {Event} ev + */ + _onInputKeydown: function (ev) { + var inputValue = this.$input.val(); + var value = 0; + if (inputValue !== '') { + value = parseFloat(this.$input.val()); + if (isNaN(value)) { + return; + } + } + switch (ev.which) { + case $.ui.keyCode.UP: + this.$input.val(value + 1); + break; + case $.ui.keyCode.DOWN: + this.$input.val(value - 1); + break; + case $.ui.keyCode.ENTER: + // Do not listen to change events, we want the user to be able + // to confirm in all cases. + this._save(); + break; + } + }, + /** + * @private + */ + _onSaveClick: function () { + this._save(); + }, + /** + * @private + */ + _onResetClick: function () { + this._save('null'); + }, +}); + var ThemeCustomizeDialog = Dialog.extend({ xmlDependencies: (Dialog.prototype.xmlDependencies || []) .concat(['/website/static/src/xml/website.editor.xml']), @@ -60,8 +151,12 @@ var ThemeCustomizeDialog = Dialog.extend({ start: function () { var self = this; + this.PX_BY_REM = parseFloat($(document.documentElement).css('font-size')); + this.$modal.addClass('o_theme_customize_modal'); + this.style = window.getComputedStyle(document.documentElement); + var $tabs; var loadDef = this._loadViews().then(function (data) { self._generateDialogHTML(data); @@ -209,6 +304,7 @@ var ThemeCustomizeDialog = Dialog.extend({ } }); this._setActive(); + this._updateValues(); function _processItems($items, $container) { var optionsName = _.uniqueId('option-'); @@ -412,6 +508,8 @@ var ThemeCustomizeDialog = Dialog.extend({ var self = this; var defs = []; + var $options = $inputs.closest('.o_theme_customize_option'); + // Handle body image changes var $bodyImageInputs = $inputs.filter('[data-xmlid*="website.' + this.CUSTOM_BODY_IMAGE_XML_ID + '"]:checked'); defs = defs.concat(_.map($bodyImageInputs, function () { @@ -419,13 +517,46 @@ var ThemeCustomizeDialog = Dialog.extend({ })); // Handle color changes - var $colors = $inputs.closest('.o_theme_customize_option').find('.o_theme_customize_color'); + var $colors = $options.find('.o_theme_customize_color'); defs = defs.concat(_.map($colors, function (colorElement) { return self._pickColor($(colorElement)); })); + // Handle input changes + var $inputsData = $options.find('.o_theme_customize_input'); + defs = defs.concat(_.map($inputsData, function (inputData, i) { + return self._quickEdit($(inputData)); + })); + return $.when.apply($, defs); }, + /** + * @private + */ + _quickEdit: function ($inputData) { + var text = $inputData.text().trim(); + var value = parseFloat(text) || ''; + var unit = text.match(/([^\s\d]+)$/)[1]; + + var def = $.Deferred(); + var qEdit = new QuickEdit(this, value, unit); + qEdit.on('QuickEdit:save', this, function (ev) { + ev.stopPropagation(); + + var value = ev.data.value; + // Convert back to rem if needed + if ($inputData.data('unit') === 'rem' && unit === 'px' && value !== 'null') { + value = parseFloat(value) / this.PX_BY_REM + 'rem'; + } + + var values = {}; + values[$inputData.data('value')] = value; + this._makeSCSSCusto('/website/static/src/scss/options/user_values.scss', values) + .always(def.resolve.bind(def)); + }); + qEdit.appendTo($inputData.closest('.o_theme_customize_option')); + return def; + }, /** * @private */ @@ -466,6 +597,8 @@ var ThemeCustomizeDialog = Dialog.extend({ * @private */ _updateStyle: function (enable, disable, reload) { + var self = this; + var $loading = $('<i/>', {class: 'fa fa-refresh fa-spin'}); this.$modal.find('.modal-title').append($loading); @@ -509,11 +642,49 @@ var ThemeCustomizeDialog = Dialog.extend({ $links.last().after($newLinks); return linksLoaded; }); - return $.when.apply($, defs).always(function () { $loading.remove(); $allLinks.remove(); }); + }).then(function () { + // Some animations may depend on the variables that were + // customized, so we have to restart them. + self.trigger_up('animation_start_demand'); + }); + }, + /** + * @private + */ + _updateValues: function () { + var self = this; + // Put user values + _.each(this.$('.o_theme_customize_color'), function (el) { + var $el = $(el); + var value = self.style.getPropertyValue('--' + $el.data('color')).trim(); + $el.css('background-color', value); + }); + _.each(this.$('.o_theme_customize_input'), function (el) { + var $el = $(el); + var value = self.style.getPropertyValue('--' + $el.data('value')).trim(); + + // Convert rem values to px values + if (_.str.endsWith(value, 'rem')) { + value = parseFloat(value) * self.PX_BY_REM + 'px'; + } + + var $span = $el.find('span'); + $span.removeClass().text(''); + switch (value) { + case '': + case 'false': + case 'true': + // When null or a boolean value, shows an icon which tells + // the user that there is no numeric/text value + $span.addClass('fa fa-ban text-danger'); + break; + default: + $span.text(value); + } }); }, @@ -572,6 +743,7 @@ var ThemeCustomizeDialog = Dialog.extend({ $option.data('reload') && window.location.href.match(new RegExp($option.data('reload'))) ); }).then(function () { + self._updateValues(); self.$inputs.prop('disabled', false); }); }, diff --git a/addons/website/static/src/scss/options/user_values.scss b/addons/website/static/src/scss/options/user_values.scss new file mode 100644 index 000000000000..3b6899ad0912 --- /dev/null +++ b/addons/website/static/src/scss/options/user_values.scss @@ -0,0 +1,7 @@ +// This file is meant to be edited automatically by the user. The variables it +// contains should not be renamed otherwise it would break existing customers +// customizations. + +$o-user-website-values: map-merge($o-user-website-values, o-map-omit(( + // -- hook -- +))); diff --git a/addons/website/static/src/scss/primary_variables.scss b/addons/website/static/src/scss/primary_variables.scss index 835acc6b3f99..f469cae36add 100644 --- a/addons/website/static/src/scss/primary_variables.scss +++ b/addons/website/static/src/scss/primary_variables.scss @@ -40,6 +40,21 @@ $o-social-colors: ( $o-theme-figcaption-opacity: 0.6; +//------------------------------------------------------------------------------ +// Website customizations +//------------------------------------------------------------------------------ + +$o-website-values-palettes: ( + ( + 'logo-height': null, // Default to navbar height (see portal) + ), +) !default; +$o-website-values-palette-number: 1 !default; + +// By default, all user website values are null. Each null value is +// automatically replaced with corresponsing value of chosen values palette. +$o-user-website-values: () !default; + //------------------------------------------------------------------------------ // Fonts //------------------------------------------------------------------------------ diff --git a/addons/website/static/src/scss/secondary_variables.scss b/addons/website/static/src/scss/secondary_variables.scss index 517a541c99a3..168965c56f11 100644 --- a/addons/website/static/src/scss/secondary_variables.scss +++ b/addons/website/static/src/scss/secondary_variables.scss @@ -34,6 +34,20 @@ $o-theme-color-palettes: append($o-theme-color-palettes, map-merge($-palette-def // Enable last color and theme color palettes, which are now the user customized -// color palettes +// color palettes. $o-color-palette-number: length($o-color-palettes); $o-theme-color-palette-number: length($o-theme-color-palettes); + +//------------------------------------------------------------------------------ +// Website customizations +//------------------------------------------------------------------------------ + +$-website-values-default: nth($o-website-values-palettes, $o-website-values-palette-number); +$o-website-values: map-merge($-website-values-default, o-map-omit($o-user-website-values)) !default; +$o-website-values-palettes: append($o-website-values-palettes, $o-website-values); +$o-website-values-palette-number: length($o-website-values-palettes); +@function o-website-value($key) { + @return map-get($o-website-values, $key); +} + +$o-theme-navbar-logo-height: o-website-value('logo-height') !default; diff --git a/addons/website/static/src/scss/website.edit_mode.scss b/addons/website/static/src/scss/website.edit_mode.scss index 2d0254beb2b0..664dcf037d0e 100644 --- a/addons/website/static/src/scss/website.edit_mode.scss +++ b/addons/website/static/src/scss/website.edit_mode.scss @@ -138,28 +138,37 @@ &::after { background-color: inherit; } + } + + .o_theme_customize_input { + flex: 1 1 auto; + text-align: right; + + .fa-edit { + visibility: hidden; + color: $o-brand-primary; + } + } + .o_theme_customize_option:hover .o_theme_customize_input .fa-edit { + visibility: visible; + } + + .o_theme_customize_active_input { + @include o-position-absolute(0, 0, 0, 0); + width: auto; + padding: 3px; + + > * { + height: 100% !important; + + &.form-control { + background: white !important; + } - $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; + &.form-control, .btn { + padding: 2px 8px !important; + text-align: right !important; + font-size: $font-size-sm !important; } } } diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss index 58b34312a271..706b25ac34a7 100644 --- a/addons/website/static/src/scss/website.scss +++ b/addons/website/static/src/scss/website.scss @@ -19,6 +19,41 @@ $-font-numbers: ( } } +:root { + // The theme customize modal JS will need to know the value of some scss + // variables used to render the user website, and those may have been + // customized by themes, the user or anything else (so there is no file to + // parse to get them). Those will be printed here as CSS variables. + + // 1) The values in the $theme-colors map are already printed by Bootstrap. + + // 2) The values in the $colors map are also printed by Bootstrap. + + // 3) The Odoo values map, $o-website-values, must be printed. + @each $key, $value in $o-website-values { + --#{$key}: #{$value}; + } + + // Handle keys which can be null but default to other values + $-h1-color: if($headings-color == inherit, $body-color, $headings-color); + $-h2-color: if(color('h2'), color('h2'), $-h1-color); + $-h3-color: if(color('h3'), color('h3'), $-h2-color); + $-h4-color: if(color('h4'), color('h4'), $-h3-color); + $-h5-color: if(color('h5'), color('h5'), $-h4-color); + $-h6-color: if(color('h6'), color('h6'), $-h5-color); + + --body: #{$body-bg}; + --text: #{$body-color}; + --h1: #{$-h1-color}; + --h2: #{$-h2-color}; + --h3: #{$-h3-color}; + --h4: #{$-h4-color}; + --h5: #{$-h5-color}; + --h6: #{$-h6-color}; + + --logo-height: #{$o-theme-navbar-logo-height}; +} + #wrapwrap { background-size: cover; background-repeat: no-repeat; @@ -64,6 +99,17 @@ $-font-numbers: ( } } +@if $o-theme-navbar-logo-height { + // With default values, this makes it slightly bigger than standard + // navbar-brand, which is what we want + header .navbar-brand { + font-size: $o-theme-navbar-logo-height / $line-height-base; + $-logo-padding-y: max(0, $nav-link-height - $o-theme-navbar-logo-height) / 2; + padding-top: $-logo-padding-y; + padding-bottom: $-logo-padding-y; + } +} + .o_footer { @if color('footer') { @include o-bg-color(color('footer')); diff --git a/addons/website/static/src/xml/website.editor.xml b/addons/website/static/src/xml/website.editor.xml index d02d82eb5aa6..2f8473d90c38 100644 --- a/addons/website/static/src/xml/website.editor.xml +++ b/addons/website/static/src/xml/website.editor.xml @@ -49,10 +49,28 @@ </div> </t> <t t-name="website.theme_customize_widget_color"> - <div t-attf-class="bg-#{color} o_theme_customize_color o_theme_customize_color_#{color}" + <div t-attf-class="o_theme_customize_color o_theme_customize_color_#{color}" t-att-data-color="color" t-att-data-color-type="colorType"/> </t> + <t t-name="website.theme_customize_widget_input"> + <div t-attf-class="o_theme_customize_input o_theme_customize_input_#{value}" + t-att-data-value="value" + t-att-data-unit="unit"> + <i class="fa fa-edit"/> + <span/> + </div> + </t> + <t t-name="website.theme_customize_active_input"> + <div class="input-group input-group-sm align-items-center o_theme_customize_active_input"> + <input type="text" class="form-control" t-att-value="widget.value"/> + <div class="input-group-append"> + <div t-if="widget.unit" class="input-group-text"><t t-esc="widget.unit"/></div> + <button type="button" class="btn btn-primary fa fa-check" title="Save"/> + <button type="button" class="btn btn-secondary fa fa-undo" title="Reset"/> + </div> + </div> + </t> <t t-extend="wysiwyg.widgets.link"> <t t-jquery="#o_link_dialog_url_input" t-operation="after"> diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml index cf437b281efb..d8650d7656d3 100644 --- a/addons/website/views/website_templates.xml +++ b/addons/website/views/website_templates.xml @@ -4,6 +4,9 @@ <template id="_assets_primary_variables" inherit_id="portal._assets_primary_variables"> <xpath expr="//link[last()]" position="after"> <link rel="stylesheet" type="text/scss" href="/website/static/src/scss/primary_variables.scss"/> + + <!-- Those files will be automatically edited by users --> + <link rel="stylesheet" type="text/scss" href="/website/static/src/scss/options/user_values.scss"/> <link rel="stylesheet" type="text/scss" href="/website/static/src/scss/options/colors/user_color_palette.scss"/> <link rel="stylesheet" type="text/scss" href="/website/static/src/scss/options/colors/user_theme_color_palette.scss"/> </xpath> @@ -524,6 +527,7 @@ </list> <list string="Logo"> <checkbox><opt data-xmlid="website.layout_logo_show" data-reload="/"/></checkbox> + <opt data-widget="input" data-unit="rem" data-value="logo-height" string="Logo Height"/> </list> </content> -- GitLab