[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
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
$o-portal-default-body-bg: #FCFCFC;
|
||||
|
||||
$o-theme-navbar-logo-height: null;
|
||||
|
||||
$o-theme-btn-icon-hover-decoration: none;
|
||||
|
||||
@@ -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,
|
||||
));
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
},
|
||||
|
||||
@@ -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 --
|
||||
)));
|
||||
@@ -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
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -138,28 +138,37 @@
|
||||
&::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;
|
||||
.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;
|
||||
}
|
||||
|
||||
&.form-control, .btn {
|
||||
padding: 2px 8px !important;
|
||||
text-align: right !important;
|
||||
font-size: $font-size-sm !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user