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