[IMP] website, web_editor, *: allow to use pre-configured gradients
*: web After this commit pre-configured gradients can be used as snippet backgrounds, snippet filters or as text effects / highlight effects. The background colors (and now gradients) are now possible to add *alongside* a color combination class (editing one does not remove the other). Background colors and gradients are mutually exclusive. Part of https://github.com/odoo/odoo/pull/73611 task-2599770 closes odoo/odoo#73611 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com> Co-authored-by: Benoit Socias <bso@odoo.com>
This commit is contained in:
co-authored by
Benoit Socias
parent
38754b2485
commit
a48a30f954
@@ -449,4 +449,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
getListMode,
|
||||
getOuid,
|
||||
insertText,
|
||||
isColorGradient,
|
||||
nodeSize,
|
||||
preserveCursor,
|
||||
setCursor,
|
||||
@@ -999,27 +1000,73 @@ export class OdooEditor extends EventTarget {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the text colors (foreground ink and background highlight)
|
||||
* based on the current text cursor position. For gradients, displays
|
||||
* the average color of the gradient.
|
||||
*
|
||||
* @param {object} [params]
|
||||
* @param {string} [params.foreColor] - forces the 'foreColor' in the
|
||||
* toolbar instead of determining it from the cursor position
|
||||
* @param {string} [params.hiliteColor] - forces the 'hiliteColor' in the
|
||||
* toolbar instead of determining it from the cursor position
|
||||
*/
|
||||
updateColorpickerLabels(params = {}) {
|
||||
const foreColor = params.foreColor || rgbToHex(document.queryCommandValue('foreColor'));
|
||||
function hexFromColor(color) {
|
||||
if (isColorGradient(color)) {
|
||||
// For gradients, compute the average color
|
||||
color = color.match(/gradient(.*)/)[0];
|
||||
let r = 0, g = 0, b = 0, count = 0;
|
||||
for (const entry of color.matchAll(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)/g)) {
|
||||
count++;
|
||||
r += parseInt(entry[1], 10);
|
||||
g += parseInt(entry[2], 10);
|
||||
b += parseInt(entry[3], 10);
|
||||
}
|
||||
color = `rgb(${Math.round(r / count)}, ${Math.round(g / count)}, ${Math.round(b / count)})`;
|
||||
}
|
||||
return rgbToHex(color);
|
||||
}
|
||||
let foreColor = params.foreColor;
|
||||
let hiliteColor = params.hiliteColor;
|
||||
|
||||
// Determine colors at cursor position
|
||||
const sel = this.document.getSelection();
|
||||
if (sel.rangeCount && (!foreColor || !hiliteColor)) {
|
||||
const endContainer = closestElement(sel.getRangeAt(0).endContainer);
|
||||
const computedStyle = getComputedStyle(endContainer);
|
||||
const backgroundImage = computedStyle.backgroundImage;
|
||||
const hasGradient = isColorGradient(backgroundImage);
|
||||
const hasTextGradientClass = endContainer.classList.contains('text-gradient');
|
||||
if (!foreColor) {
|
||||
if (hasGradient && hasTextGradientClass) {
|
||||
foreColor = backgroundImage;
|
||||
} else {
|
||||
foreColor = document.queryCommandValue('foreColor');
|
||||
}
|
||||
}
|
||||
if (!hiliteColor) {
|
||||
if (hasGradient && !hasTextGradientClass) {
|
||||
hiliteColor = backgroundImage;
|
||||
} else {
|
||||
hiliteColor = computedStyle.backgroundColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// display colors in toolbar buttons
|
||||
foreColor = hexFromColor(foreColor);
|
||||
this.toolbar.style.setProperty('--fore-color', foreColor);
|
||||
const foreColorInput = this.toolbar.querySelector('#foreColor input');
|
||||
if (foreColorInput) {
|
||||
foreColorInput.value = foreColor;
|
||||
}
|
||||
|
||||
let hiliteColor = params.hiliteColor;
|
||||
if (!hiliteColor) {
|
||||
const sel = this.document.getSelection();
|
||||
if (sel.rangeCount) {
|
||||
const endContainer = closestElement(sel.getRangeAt(0).endContainer);
|
||||
const hiliteColorRgb = getComputedStyle(endContainer).backgroundColor;
|
||||
hiliteColor = rgbToHex(hiliteColorRgb);
|
||||
}
|
||||
}
|
||||
hiliteColor = hexFromColor(hiliteColor);
|
||||
this.toolbar.style.setProperty('--hilite-color', hiliteColor);
|
||||
const hiliteColorInput = this.toolbar.querySelector('#hiliteColor input');
|
||||
if (hiliteColorInput) {
|
||||
hiliteColorInput.value = hiliteColor.length <= 7 ? hiliteColor : rgbToHex(hiliteColor);
|
||||
hiliteColorInput.value = hiliteColor.length <= 7 ? hiliteColor : hexFromColor(hiliteColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
insertText,
|
||||
isBlock,
|
||||
isBold,
|
||||
isColorGradient,
|
||||
isContentTextNode,
|
||||
isShrunkBlock,
|
||||
isVisible,
|
||||
@@ -138,11 +139,25 @@ function align(editor, mode) {
|
||||
function colorElement(element, color, mode) {
|
||||
const newClassName = element.className
|
||||
.replace(mode === 'color' ? TEXT_CLASSES_REGEX : BG_CLASSES_REGEX, '')
|
||||
.replace(/\btext-gradient\b/g, '') // cannot be combined with setting a background
|
||||
.replace(/\s+/, ' ');
|
||||
element.className !== newClassName && (element.className = newClassName);
|
||||
element.style['background-image'] = '';
|
||||
if (mode === 'backgroundColor') {
|
||||
element.style['background'] = '';
|
||||
}
|
||||
if (color.startsWith('text') || color.startsWith('bg-')) {
|
||||
element.style[mode] = '';
|
||||
element.className += ' ' + color;
|
||||
element.classList.add(color);
|
||||
} else if (isColorGradient(color)) {
|
||||
element.style[mode] = '';
|
||||
if (mode === 'color') {
|
||||
element.style['background'] = '';
|
||||
element.style['background-image'] = color;
|
||||
element.classList.add('text-gradient');
|
||||
} else {
|
||||
element.style['background-image'] = color;
|
||||
}
|
||||
} else {
|
||||
element.style[mode] = color;
|
||||
}
|
||||
@@ -160,6 +175,17 @@ function hasColor(element, mode) {
|
||||
const style = element.style;
|
||||
const parent = element.parentNode;
|
||||
const classRegex = mode === 'color' ? TEXT_CLASSES_REGEX : BG_CLASSES_REGEX;
|
||||
if (isColorGradient(style['background-image'])) {
|
||||
if (element.classList.contains('text-gradient')) {
|
||||
if (mode === 'color') {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (mode !== 'color') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
(style[mode] && style[mode] !== 'inherit' && style[mode] !== parent.style[mode]) ||
|
||||
(classRegex.test(element.className) &&
|
||||
|
||||
@@ -1216,6 +1216,15 @@ export function isShrunkBlock(blockEl) {
|
||||
return isEmptyBlock(blockEl) && !blockEl.querySelector('br');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} [value]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isColorGradient(value) {
|
||||
// FIXME duplicated in @web_editor/utils.js
|
||||
return value && value.includes('-gradient(');
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// DOM Modification
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -159,6 +159,31 @@ function _areCssValuesEqual(value1, value2, cssProp, $target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// They may be gradients
|
||||
const value1IsGradient = _isColorGradient(value1);
|
||||
const value2IsGradient = _isColorGradient(value2);
|
||||
if (value1IsGradient !== value2IsGradient) {
|
||||
return false;
|
||||
}
|
||||
if (value1IsGradient) {
|
||||
// Kinda hacky and probably inneficient but probably the easiest way:
|
||||
// applied the value as background-image of two fakes elements and
|
||||
// compare their computed value.
|
||||
const temp1El = document.createElement('div');
|
||||
temp1El.style.backgroundImage = value1;
|
||||
document.body.appendChild(temp1El);
|
||||
value1 = getComputedStyle(temp1El).backgroundImage;
|
||||
document.body.removeChild(temp1El);
|
||||
|
||||
const temp2El = document.createElement('div');
|
||||
temp2El.style.backgroundImage = value2;
|
||||
document.body.appendChild(temp2El);
|
||||
value2 = getComputedStyle(temp2El).backgroundImage;
|
||||
document.body.removeChild(temp2El);
|
||||
|
||||
return value1 === value2;
|
||||
}
|
||||
|
||||
// Convert the second value in the unit of the first one and compare
|
||||
// floating values
|
||||
const data = _getNumericAndUnit(value1);
|
||||
@@ -237,7 +262,8 @@ function _normalizeColor(color) {
|
||||
* @returns {string|false} the src of the image or false if not parsable
|
||||
*/
|
||||
function _getBgImageURL(el) {
|
||||
const string = $(el).css('background-image');
|
||||
const parts = _backgroundImageCssToParts($(el).css('background-image'));
|
||||
const string = parts.url || '';
|
||||
const match = string.match(/^url\((['"])(.*?)\1\)$/);
|
||||
if (!match) {
|
||||
return '';
|
||||
@@ -250,6 +276,47 @@ function _getBgImageURL(el) {
|
||||
}
|
||||
return matchedURL;
|
||||
}
|
||||
/**
|
||||
* Extracts url and gradient parts from the background-image CSS property.
|
||||
*
|
||||
* @param {string} CSS 'background-image' property value
|
||||
* @returns {Object} contains the separated 'url' and 'gradient' parts
|
||||
*/
|
||||
function _backgroundImageCssToParts(css) {
|
||||
const parts = {};
|
||||
css = css || '';
|
||||
if (css.startsWith('url(')) {
|
||||
const urlEnd = css.indexOf(')') + 1;
|
||||
parts.url = css.substring(0, urlEnd).trim();
|
||||
const commaPos = css.indexOf(',', urlEnd);
|
||||
css = commaPos > 0 ? css.substring(commaPos + 1) : '';
|
||||
}
|
||||
if (_isColorGradient(css)) {
|
||||
parts.gradient = css.trim();
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
/**
|
||||
* Combines url and gradient parts into a background-image CSS property value
|
||||
*
|
||||
* @param {Object} contains the separated 'url' and 'gradient' parts
|
||||
* @returns {string} CSS 'background-image' property value
|
||||
*/
|
||||
function _backgroundImagePartsToCss(parts) {
|
||||
let css = parts.url || '';
|
||||
if (parts.gradient) {
|
||||
css += (css ? ', ' : '') + parts.gradient;
|
||||
}
|
||||
return css || 'none';
|
||||
}
|
||||
/**
|
||||
* @param {string} [value]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function _isColorGradient(value) {
|
||||
// FIXME duplicated in odoo-editor/utils.js
|
||||
return value && value.includes('-gradient(');
|
||||
}
|
||||
|
||||
return {
|
||||
CSS_SHORTHANDS: CSS_SHORTHANDS,
|
||||
@@ -261,9 +328,12 @@ return {
|
||||
getNumericAndUnit: _getNumericAndUnit,
|
||||
areCssValuesEqual: _areCssValuesEqual,
|
||||
isColorCombinationName: _isColorCombinationName,
|
||||
isColorGradient: _isColorGradient,
|
||||
computeColorClasses: _computeColorClasses,
|
||||
getCSSVariableValue: _getCSSVariableValue,
|
||||
normalizeColor: _normalizeColor,
|
||||
getBgImageURL: _getBgImageURL,
|
||||
backgroundImageCssToParts: _backgroundImageCssToParts,
|
||||
backgroundImagePartsToCss: _backgroundImagePartsToCss,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ const weUtils = require('web_editor.utils');
|
||||
const {
|
||||
normalizeColor,
|
||||
getBgImageURL,
|
||||
backgroundImageCssToParts,
|
||||
backgroundImagePartsToCss,
|
||||
DEFAULT_PALETTE,
|
||||
} = weUtils;
|
||||
var weWidgets = require('wysiwyg.widgets');
|
||||
@@ -480,6 +482,13 @@ const UserValueWidget = Widget.extend({
|
||||
const uniqArr = arr.filter((v, i, arr) => i === arr.indexOf(v));
|
||||
this._methodsParams.optionsPossibleValues[methodName] = uniqArr;
|
||||
}
|
||||
|
||||
// Method names come from the widget's dataset whose keys' order cannot
|
||||
// be relied on. We explicitely sort them by alphabetical order allowing
|
||||
// consistent behavior, while relying on order for such methods should
|
||||
// not be done when possible (the methods should be independent from
|
||||
// each other when possible).
|
||||
this._methodsNames.sort();
|
||||
},
|
||||
/**
|
||||
* @param {boolean} [previewMode=false]
|
||||
@@ -1329,7 +1338,7 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
*/
|
||||
open: function () {
|
||||
if (this.colorPalette.setSelectedColor) {
|
||||
this.colorPalette.setSelectedColor(this._value);
|
||||
this.colorPalette.setSelectedColor(this._ccValue, this._value);
|
||||
} else {
|
||||
// TODO review in master, this does async stuff. Maybe the open
|
||||
// method should now be async. This is not really robust as the
|
||||
@@ -1363,14 +1372,23 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
* @override
|
||||
*/
|
||||
getValue: function (methodName) {
|
||||
if (typeof this._previewColor === 'string') {
|
||||
return this._previewColor;
|
||||
}
|
||||
if (typeof this._customColorValue === 'string') {
|
||||
return this._customColorValue;
|
||||
}
|
||||
const isCCMethod = (this._methodsParams.withCombinations === methodName);
|
||||
let value = this._super(...arguments);
|
||||
if (isCCMethod) {
|
||||
value = this._ccValue;
|
||||
} else if (typeof this._customColorValue === 'string') {
|
||||
value = this._customColorValue;
|
||||
}
|
||||
|
||||
// TODO strange there is some processing below for the normal value but
|
||||
// not for the preview value? To check in older stable versions as well.
|
||||
if (typeof this._previewColor === 'string') {
|
||||
return isCCMethod ? this._previewCC : this._previewColor;
|
||||
}
|
||||
|
||||
if (value) {
|
||||
// TODO probably something to be done to handle gradients properly
|
||||
// in this code.
|
||||
const useCssColor = this.options.dataAttributes.hasOwnProperty('useCssColor');
|
||||
const cssCompatible = this.options.dataAttributes.hasOwnProperty('cssCompatible');
|
||||
if ((useCssColor || cssCompatible) && !ColorpickerWidget.isCSSColor(value)) {
|
||||
@@ -1393,34 +1411,46 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
* @override
|
||||
*/
|
||||
isActive: function () {
|
||||
return !weUtils.areCssValuesEqual(this._value, 'rgba(0, 0, 0, 0)');
|
||||
return !!this._ccValue
|
||||
|| !weUtils.areCssValuesEqual(this._value, 'rgba(0, 0, 0, 0)');
|
||||
},
|
||||
/**
|
||||
* Updates the color preview + re-render the whole color palette widget.
|
||||
*
|
||||
* @override
|
||||
*/
|
||||
async setValue(color) {
|
||||
await this._super(...arguments);
|
||||
async setValue(color, methodName, ...rest) {
|
||||
// The colorpicker widget can hold two values: a color combination and
|
||||
// a normal color or a gradient. The base `_value` will hold the normal
|
||||
// color or the gradient value. The color combination one will be
|
||||
// available in `_ccValue`.
|
||||
const isCCMethod = (this._methodsParams.withCombinations === methodName);
|
||||
// Always call _super but don't change _value if meant for the CC value.
|
||||
await this._super(isCCMethod ? this._value : color, methodName, ...rest);
|
||||
if (isCCMethod) {
|
||||
this._ccValue = color;
|
||||
}
|
||||
|
||||
await this._colorPaletteRenderPromise;
|
||||
|
||||
const classes = weUtils.computeColorClasses(this.colorPalette.getColorNames());
|
||||
this.colorPreviewEl.classList.remove(...classes);
|
||||
this.colorPreviewEl.style.removeProperty('background-color');
|
||||
|
||||
this.colorPreviewEl.style.removeProperty('background-image');
|
||||
if (this._ccValue) {
|
||||
this.colorPreviewEl.classList.add('o_cc', `o_cc${this._ccValue}`);
|
||||
}
|
||||
if (this._value) {
|
||||
if (ColorpickerWidget.isCSSColor(this._value)) {
|
||||
this.colorPreviewEl.style.backgroundColor = this._value;
|
||||
} else if (weUtils.isColorCombinationName(this._value)) {
|
||||
this.colorPreviewEl.classList.add('o_cc', `o_cc${this._value}`);
|
||||
} else {
|
||||
} else if (!weUtils.isColorGradient(this._value)) {
|
||||
this.colorPreviewEl.classList.add(`bg-${this._value}`);
|
||||
} else {
|
||||
this.colorPreviewEl.style.backgroundImage = this._value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -1431,14 +1461,21 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
*/
|
||||
_renderColorPalette: function () {
|
||||
const options = {
|
||||
selectedCC: this._ccValue,
|
||||
selectedColor: this._value,
|
||||
};
|
||||
if (this.options.dataAttributes.excluded) {
|
||||
options.excluded = this.options.dataAttributes.excluded.replace(/ /g, '').split(',');
|
||||
}
|
||||
if (this.options.dataAttributes.opacity) {
|
||||
options.opacity = this.options.dataAttributes.opacity;
|
||||
}
|
||||
if (this.options.dataAttributes.withCombinations) {
|
||||
options.withCombinations = !!this.options.dataAttributes.withCombinations;
|
||||
}
|
||||
if (this.options.dataAttributes.withGradients) {
|
||||
options.withGradients = !!this.options.dataAttributes.withGradients;
|
||||
}
|
||||
if (this.options.dataAttributes.selectedTab) {
|
||||
options.selectedTab = this.options.dataAttributes.selectedTab;
|
||||
}
|
||||
@@ -1479,9 +1516,13 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onColorPicked: function (ev) {
|
||||
this._previewCC = false;
|
||||
this._previewColor = false;
|
||||
this._customColorValue = false;
|
||||
|
||||
this._ccValue = ev.data.ccValue;
|
||||
this._value = ev.data.color;
|
||||
|
||||
this._onUserValueChange(ev);
|
||||
},
|
||||
/**
|
||||
@@ -1491,6 +1532,7 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onColorHovered: function (ev) {
|
||||
this._previewCC = ev.data.ccValue;
|
||||
this._previewColor = ev.data.color;
|
||||
this._onUserValuePreview(ev);
|
||||
},
|
||||
@@ -1501,6 +1543,7 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onColorLeft: function (ev) {
|
||||
this._previewCC = false;
|
||||
this._previewColor = false;
|
||||
this._onUserValueReset(ev);
|
||||
},
|
||||
@@ -3031,31 +3074,42 @@ const SnippetOptionWidget = Widget.extend({
|
||||
this.$target.trigger('background-color-event', previewMode);
|
||||
}
|
||||
|
||||
const cssProps = weUtils.CSS_SHORTHANDS[params.cssProperty] || [params.cssProperty];
|
||||
// Always reset the inline style first to not put inline style on an
|
||||
// element which already have this style through css stylesheets.
|
||||
let cssProps = weUtils.CSS_SHORTHANDS[params.cssProperty] || [params.cssProperty];
|
||||
for (const cssProp of cssProps) {
|
||||
// Always reset the inline style first to not put inline style on an
|
||||
// element which already have this style through css stylesheets.
|
||||
this.$target[0].style.setProperty(cssProp, '');
|
||||
}
|
||||
if (params.extraClass) {
|
||||
this.$target.removeClass(params.extraClass);
|
||||
}
|
||||
// Plain color and gradient are mutually exclusive as background so in
|
||||
// case we edit a background-color we also have to reset the gradient
|
||||
// part of the background-image property (the opposite is handled by the
|
||||
// fact that editing a gradient as background is done by calling this
|
||||
// method with background-color as property too, so it is automatically
|
||||
// reset anyway).
|
||||
let bgImageParts = undefined;
|
||||
if (params.cssProperty === 'background-color') {
|
||||
const styles = getComputedStyle(this.$target[0]);
|
||||
bgImageParts = backgroundImageCssToParts(styles['background-image']);
|
||||
delete bgImageParts.gradient;
|
||||
const combined = backgroundImagePartsToCss(bgImageParts);
|
||||
this.$target[0].style.setProperty('background-image', '');
|
||||
applyCSS.call(this, 'background-image', combined, styles);
|
||||
}
|
||||
|
||||
// Only allow to use a color name as a className if we know about the
|
||||
// other potential color names (to remove) and if we know about a prefix
|
||||
// (otherwise we suppose that we should use the actual related color).
|
||||
// Note: color combinations classes are handled by a dedicated method,
|
||||
// as they can be combined with normal classes.
|
||||
if (params.colorNames && params.colorPrefix) {
|
||||
const classes = weUtils.computeColorClasses(params.colorNames, params.colorPrefix);
|
||||
const colorNames = params.colorNames.filter(name => !weUtils.isColorCombinationName(name));
|
||||
const classes = weUtils.computeColorClasses(colorNames, params.colorPrefix);
|
||||
this.$target[0].classList.remove(...classes);
|
||||
|
||||
if (weUtils.isColorCombinationName(widgetValue)) {
|
||||
// Those are the special color combinations classes. Just have
|
||||
// to add it (and adding the potential extra class) then leave.
|
||||
this.$target[0].classList.add('o_cc', `o_cc${widgetValue}`, params.extraClass);
|
||||
_restoreTransitions();
|
||||
return;
|
||||
}
|
||||
if (params.colorNames.includes(widgetValue)) {
|
||||
if (colorNames.includes(widgetValue)) {
|
||||
const originalCSSValue = window.getComputedStyle(this.$target[0])[cssProps[0]];
|
||||
const className = params.colorPrefix + widgetValue;
|
||||
this.$target[0].classList.add(className);
|
||||
@@ -3074,6 +3128,8 @@ const SnippetOptionWidget = Widget.extend({
|
||||
}
|
||||
}
|
||||
|
||||
const styles = window.getComputedStyle(this.$target[0]);
|
||||
|
||||
// At this point, the widget value is either a property/color name or
|
||||
// an actual css property value. If it is a property/color name, we will
|
||||
// apply a css variable as style value.
|
||||
@@ -3082,6 +3138,21 @@ const SnippetOptionWidget = Widget.extend({
|
||||
widgetValue = `var(--${widgetValue})`;
|
||||
}
|
||||
|
||||
// In case of background-color edition, we could receive a gradient, in
|
||||
// which case the value has to be combined with the potential background
|
||||
// image (real image).
|
||||
if (params.cssProperty === 'background-color' && weUtils.isColorGradient(widgetValue)) {
|
||||
cssProps = ['background-image'];
|
||||
bgImageParts.gradient = widgetValue;
|
||||
widgetValue = backgroundImagePartsToCss(bgImageParts);
|
||||
|
||||
// Also force the background-color to transparent as otherwise it
|
||||
// won't act as a "gradient replacing the color combination
|
||||
// background" but be applied over it (which would be the opposite
|
||||
// of what happens when editing the background color).
|
||||
applyCSS.call(this, 'background-color', 'rgba(0, 0, 0, 0)', styles);
|
||||
}
|
||||
|
||||
// replacing ', ' by ',' to prevent attributes with internal space separators from being split:
|
||||
// eg: "rgba(55, 12, 47, 1.9) 47px" should be split as ["rgba(55,12,47,1.9)", "47px"]
|
||||
const values = widgetValue.replace(/,\s/g, ',').split(/\s+/g);
|
||||
@@ -3102,7 +3173,6 @@ const SnippetOptionWidget = Widget.extend({
|
||||
}
|
||||
}
|
||||
|
||||
const styles = window.getComputedStyle(this.$target[0]);
|
||||
let hasUserValue = false;
|
||||
for (let i = cssProps.length - 1; i > 0; i--) {
|
||||
hasUserValue = applyCSS.call(this, cssProps[i], values.pop(), styles) || hasUserValue;
|
||||
@@ -3123,6 +3193,22 @@ const SnippetOptionWidget = Widget.extend({
|
||||
|
||||
_restoreTransitions();
|
||||
},
|
||||
/**
|
||||
* Sets a color combination.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
async selectColorCombination(previewMode, widgetValue, params) {
|
||||
if (params.colorNames) {
|
||||
const names = params.colorNames.filter(weUtils.isColorCombinationName);
|
||||
const classes = weUtils.computeColorClasses(names);
|
||||
this.$target[0].classList.remove(...classes);
|
||||
|
||||
if (widgetValue) {
|
||||
this.$target[0].classList.add('o_cc', `o_cc${widgetValue}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
@@ -3398,10 +3484,15 @@ const SnippetOptionWidget = Widget.extend({
|
||||
return attrValue || params.attributeDefaultValue || '';
|
||||
}
|
||||
case 'selectStyle': {
|
||||
let usedCC = undefined;
|
||||
if (params.colorPrefix && params.colorNames) {
|
||||
for (const c of params.colorNames) {
|
||||
const className = weUtils.computeColorClasses([c], params.colorPrefix)[0];
|
||||
if (this.$target[0].classList.contains(className)) {
|
||||
if (weUtils.isColorCombinationName(c)) {
|
||||
usedCC = c;
|
||||
continue;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -3414,6 +3505,18 @@ const SnippetOptionWidget = Widget.extend({
|
||||
const _restoreTransitions = () => this.$target[0].classList.remove('o_we_force_no_transition');
|
||||
|
||||
const styles = window.getComputedStyle(this.$target[0]);
|
||||
|
||||
if (params.cssProperty === 'background-color') {
|
||||
// Check if there is a gradient, in that case this is the
|
||||
// value to be returned, we normally not allow color and
|
||||
// gradient at the same time (the option would remove one
|
||||
// if editing the other).
|
||||
const parts = backgroundImageCssToParts(styles['background-image']);
|
||||
if (parts.gradient) {
|
||||
return parts.gradient;
|
||||
}
|
||||
}
|
||||
|
||||
const cssProps = weUtils.CSS_SHORTHANDS[params.cssProperty] || [params.cssProperty];
|
||||
const cssValues = cssProps.map(cssProp => {
|
||||
let value = styles[cssProp].trim();
|
||||
@@ -3438,7 +3541,44 @@ const SnippetOptionWidget = Widget.extend({
|
||||
|
||||
_restoreTransitions();
|
||||
|
||||
return cssValues.join(' ');
|
||||
const value = cssValues.join(' ');
|
||||
|
||||
if (params.cssProperty === 'background-color' && params.withCombinations) {
|
||||
if (usedCC) {
|
||||
const ccValue = weUtils.getCSSVariableValue(`o-cc${usedCC}-bg`).trim();
|
||||
if (weUtils.areCssValuesEqual(value, ccValue)) {
|
||||
// Prevent to consider that a color is used as CC
|
||||
// override in case that color is the same as the
|
||||
// one used in that CC.
|
||||
return '';
|
||||
}
|
||||
} else {
|
||||
const rgba = ColorpickerWidget.convertCSSColorToRgba(value);
|
||||
if (rgba && rgba.opacity < 0.001) {
|
||||
// Prevent to consider a transparent color is
|
||||
// applied as background unless it is to override a
|
||||
// CC. Simply allows to add a CC on a transparent
|
||||
// snippet in the first place.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
case 'selectColorCombination': {
|
||||
if (params.colorNames) {
|
||||
for (const c of params.colorNames) {
|
||||
if (!weUtils.isColorCombinationName(c)) {
|
||||
continue;
|
||||
}
|
||||
const className = weUtils.computeColorClasses([c])[0];
|
||||
if (this.$target[0].classList.contains(className)) {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5038,7 +5178,10 @@ registry.BackgroundOptimize = ImageHandlerOption.extend({
|
||||
* @override
|
||||
*/
|
||||
_applyImage(img) {
|
||||
this.$target.css('background-image', `url('${img.getAttribute('src')}')`);
|
||||
const parts = backgroundImageCssToParts(this.$target.css('background-image'));
|
||||
parts.url = `url('${img.getAttribute('src')}')`;
|
||||
const combined = backgroundImagePartsToCss(parts);
|
||||
this.$target.css('background-image', combined);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -5060,20 +5203,6 @@ registry.BackgroundOptimize = ImageHandlerOption.extend({
|
||||
});
|
||||
|
||||
registry.BackgroundToggler = SnippetOptionWidget.extend({
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start() {
|
||||
this.$target.on('content_changed.BackgroundToggler', this._onExternalUpdate.bind(this));
|
||||
return this._super(...arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
destroy() {
|
||||
this._super(...arguments);
|
||||
this.$target.off('.BackgroundToggler');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
@@ -5086,6 +5215,7 @@ registry.BackgroundToggler = SnippetOptionWidget.extend({
|
||||
*/
|
||||
toggleBgImage(previewMode, widgetValue, params) {
|
||||
if (!widgetValue) {
|
||||
this.$target.find('> .o_we_bg_filter').remove();
|
||||
// TODO: use setWidgetValue instead of calling background directly when possible
|
||||
const [bgImageWidget] = this._requestUserValueWidgets('bg_image_opt');
|
||||
const bgImageOpt = bgImageWidget.getParent();
|
||||
@@ -5108,23 +5238,39 @@ registry.BackgroundToggler = SnippetOptionWidget.extend({
|
||||
return shapeOption._toggleShape();
|
||||
},
|
||||
/**
|
||||
* Toggles background filter on or off.
|
||||
* Sets a color filter.
|
||||
*
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
toggleBgFilter(previewMode, widgetValue, params) {
|
||||
if (widgetValue) {
|
||||
const bgFilterEl = document.createElement('div');
|
||||
bgFilterEl.classList.add('o_we_bg_filter', 'bg-black-50');
|
||||
async selectFilterColor(previewMode, widgetValue, params) {
|
||||
// Find the filter element.
|
||||
let filterEl = this.$target[0].querySelector(':scope > .o_we_bg_filter');
|
||||
|
||||
// If the filter would be transparent, remove it / don't create it.
|
||||
const rgba = widgetValue && ColorpickerWidget.convertCSSColorToRgba(widgetValue);
|
||||
if (!widgetValue || rgba && rgba.opacity < 0.001) {
|
||||
if (filterEl) {
|
||||
filterEl.remove();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the filter if necessary.
|
||||
if (!filterEl) {
|
||||
filterEl = document.createElement('div');
|
||||
filterEl.classList.add('o_we_bg_filter');
|
||||
const lastBackgroundEl = this._getLastPreFilterLayerElement();
|
||||
if (lastBackgroundEl) {
|
||||
$(lastBackgroundEl).after(bgFilterEl);
|
||||
$(lastBackgroundEl).after(filterEl);
|
||||
} else {
|
||||
this.$target.prepend(bgFilterEl);
|
||||
this.$target.prepend(filterEl);
|
||||
}
|
||||
} else {
|
||||
this.$target.find('.o_we_bg_filter').remove();
|
||||
}
|
||||
|
||||
// Apply the color on the filter.
|
||||
const obj = createPropertyProxy(this, '$target', $(filterEl));
|
||||
params.cssProperty = 'background-color';
|
||||
return this.selectStyle.call(obj, previewMode, widgetValue, params);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -5141,14 +5287,20 @@ registry.BackgroundToggler = SnippetOptionWidget.extend({
|
||||
const bgImageOpt = bgImageWidget.getParent();
|
||||
return !!bgImageOpt._computeWidgetState('background', bgImageWidget.getMethodsParams('background'));
|
||||
}
|
||||
case 'toggleBgFilter': {
|
||||
return this._hasBgFilter();
|
||||
}
|
||||
case 'toggleBgShape': {
|
||||
const [shapeWidget] = this._requestUserValueWidgets('bg_shape_opt');
|
||||
const shapeOption = shapeWidget.getParent();
|
||||
return !!shapeOption._computeWidgetState('shape', shapeWidget.getMethodsParams('shape'));
|
||||
}
|
||||
case 'selectFilterColor': {
|
||||
const filterEl = this.$target[0].querySelector(':scope > .o_we_bg_filter');
|
||||
if (!filterEl) {
|
||||
return '';
|
||||
}
|
||||
const obj = createPropertyProxy(this, '$target', $(filterEl));
|
||||
params.cssProperty = 'background-color';
|
||||
return this._computeWidgetState.call(obj, 'selectStyle', params);
|
||||
}
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
@@ -5158,32 +5310,6 @@ registry.BackgroundToggler = SnippetOptionWidget.extend({
|
||||
_getLastPreFilterLayerElement() {
|
||||
return null;
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @returns {Boolean}
|
||||
*/
|
||||
_hasBgFilter() {
|
||||
return !!this.$target.find('> .o_we_bg_filter').length;
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onExternalUpdate() {
|
||||
if (this._hasBgFilter()
|
||||
&& !this._getLastPreFilterLayerElement()
|
||||
&& !getBgImageURL(this.$target)) {
|
||||
// No 'pre-filter' background layout anymore and no more background
|
||||
// image: remove the background filter option.
|
||||
// TODO there probably is a better system to implement to do that
|
||||
const widget = this._requestUserValueWidgets('bg_filter_toggle_opt')[0];
|
||||
widget.enable();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -5235,14 +5361,14 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
|
||||
this.previousSrc = currentSrc;
|
||||
break;
|
||||
case 'reset':
|
||||
this.$target.css('background-image', `url('${this.previousSrc}')`);
|
||||
this._setBackground(this.previousSrc);
|
||||
return;
|
||||
}
|
||||
const newURL = new URL(currentSrc, window.location.origin);
|
||||
newURL.searchParams.set(params.colorName, normalizeColor(widgetValue));
|
||||
const src = newURL.pathname + newURL.search;
|
||||
await loadImage(src);
|
||||
this.$target.css('background-image', `url('${src}')`);
|
||||
this._setBackground(src);
|
||||
if (!previewMode) {
|
||||
this.previousSrc = src;
|
||||
}
|
||||
@@ -5313,13 +5439,16 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
|
||||
* @param {string} backgroundURL
|
||||
*/
|
||||
_setBackground(backgroundURL) {
|
||||
const parts = backgroundImageCssToParts(this.$target.css('background-image'));
|
||||
if (backgroundURL) {
|
||||
this.$target.css('background-image', `url('${backgroundURL}')`);
|
||||
parts.url = `url('${backgroundURL}')`;
|
||||
this.$target.addClass('oe_img_bg o_bg_img_center');
|
||||
} else {
|
||||
this.$target.css('background-image', '');
|
||||
delete parts.url;
|
||||
this.$target.removeClass('oe_img_bg o_bg_img_center');
|
||||
}
|
||||
const combined = backgroundImagePartsToCss(parts);
|
||||
this.$target.css('background-image', combined);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
* @param {boolean} [options.resetButton=true] Whether to display or not the reset button.
|
||||
* @param {string[]} [options.excluded=[]] Sections not to display.
|
||||
* @param {string[]} [options.excludeSectionOf] Extra section to exclude: the one containing the named color.
|
||||
* @param {boolean} [options.withCombinations=false] Enable color combinations selection.
|
||||
* @param {float} [options.opacity=1] Specify a default opacity (predefined gradients & color).
|
||||
* @param {string} [options.selectedTab='theme-colors'] Tab initially selected.
|
||||
* @param {boolean} [options.withGradients=false] Enable gradient selection.
|
||||
* @param {JQuery} [options.$editable=$()] Editable content from which the custom colors are retrieved.
|
||||
*/
|
||||
init: function (parent, options) {
|
||||
@@ -46,9 +50,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
excludeSectionOf: null,
|
||||
$editable: $(),
|
||||
withCombinations: false,
|
||||
opacity: 1,
|
||||
selectedTab: 'theme-colors',
|
||||
withGradients: false,
|
||||
}, options || {});
|
||||
|
||||
this.selectedColor = '';
|
||||
this.resetButton = this.options.resetButton;
|
||||
this.withCombinations = this.options.withCombinations;
|
||||
@@ -69,6 +74,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
'transparent_grayscale',
|
||||
'common_grays',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'gradients',
|
||||
pickers: this.options.withGradients ? ['predefined_gradients'] : [],
|
||||
}];
|
||||
|
||||
this.sections = {};
|
||||
@@ -118,15 +127,18 @@ const ColorPaletteWidget = Widget.extend({
|
||||
pickerEl.dataset.name = 'custom';
|
||||
break;
|
||||
default:
|
||||
pickerEl = colorpickerEl.querySelector(`[data-name="${pickerId}"]`).cloneNode(true);
|
||||
pickerEl = colorpickerEl.querySelector(`[data-name="${pickerId}"]`);
|
||||
pickerEl = pickerEl && pickerEl.cloneNode(true);
|
||||
}
|
||||
sectionEl.appendChild(pickerEl);
|
||||
if (pickerEl) {
|
||||
sectionEl.appendChild(pickerEl);
|
||||
|
||||
if (!this.options.excluded.includes(pickerId)) {
|
||||
sectionIsEmpty = false;
|
||||
if (!this.options.excluded.includes(pickerId)) {
|
||||
sectionIsEmpty = false;
|
||||
}
|
||||
|
||||
this.pickers[pickerId] = pickerEl;
|
||||
}
|
||||
|
||||
this.pickers[pickerId] = pickerEl;
|
||||
});
|
||||
|
||||
// If the section is empty, hide it and
|
||||
@@ -141,6 +153,16 @@ const ColorPaletteWidget = Widget.extend({
|
||||
this.sections[tab.id] = sectionEl;
|
||||
});
|
||||
|
||||
// Predefined gradient opacity
|
||||
if (this.options.withGradients && this.options.opacity !== 1) {
|
||||
this.pickers['predefined_gradients'].querySelectorAll('button').forEach(elem => {
|
||||
let gradient = elem.dataset.color;
|
||||
gradient = gradient.replaceAll(/rgba?(\(\s*\d+\s*,\s*\d+\s*,\s*\d+)(?:\s*,.+?)?\)/g,
|
||||
`rgba$1, ${this.options.opacity})`);
|
||||
elem.dataset.color = gradient.replaceAll(/\s+/g, '');
|
||||
});
|
||||
}
|
||||
|
||||
// Switch to the correct tab
|
||||
const selectedButtonIndex = this.tabs.map(tab => tab.id).indexOf(this.options.selectedTab);
|
||||
this._selectTabFromButton(this.el.querySelectorAll('button')[selectedButtonIndex]);
|
||||
@@ -183,6 +205,9 @@ const ColorPaletteWidget = Widget.extend({
|
||||
this.colorToColorNames = {};
|
||||
this.el.querySelectorAll('button[data-color]').forEach(elem => {
|
||||
const colorName = elem.dataset.color;
|
||||
if (weUtils.isColorGradient(colorName)) {
|
||||
return;
|
||||
}
|
||||
const $color = $(elem);
|
||||
const isCCName = weUtils.isColorCombinationName(colorName);
|
||||
if (isCCName) {
|
||||
@@ -199,6 +224,9 @@ const ColorPaletteWidget = Widget.extend({
|
||||
|
||||
// Select selected Color and build customColors.
|
||||
// If no color is selected selectedColor is an empty string (transparent is interpreted as no color)
|
||||
if (this.options.selectedCC) {
|
||||
this.selectedCC = this.options.selectedCC;
|
||||
}
|
||||
if (this.options.selectedColor) {
|
||||
let selectedColor = this.options.selectedColor;
|
||||
if (compatibilityColorNames.includes(selectedColor)) {
|
||||
@@ -218,12 +246,14 @@ const ColorPaletteWidget = Widget.extend({
|
||||
if (defaultColor && !ColorpickerWidget.isCSSColor(defaultColor)) {
|
||||
defaultColor = weUtils.getCSSVariableValue(defaultColor, this.style);
|
||||
}
|
||||
if (!defaultColor && this.options.opacity !== 1) {
|
||||
defaultColor = 'rgba(0, 0, 0, ' + this.options.opacity + ')';
|
||||
}
|
||||
this.colorPicker = new ColorpickerWidget(this, {
|
||||
defaultColor: defaultColor,
|
||||
});
|
||||
await this.colorPicker.appendTo(this.sections['custom-colors']);
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
/**
|
||||
@@ -233,12 +263,33 @@ const ColorPaletteWidget = Widget.extend({
|
||||
return this.colorNames;
|
||||
},
|
||||
/**
|
||||
* Sets the currently selected color
|
||||
* Gets the currently selected colors.
|
||||
*
|
||||
* @returns {Object} ccValue and color (plain color or gradient).
|
||||
*/
|
||||
getSelectedColors() {
|
||||
return {
|
||||
ccValue: this.selectedCC,
|
||||
color: this.selectedColor,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Sets the currently selected colors
|
||||
*
|
||||
* @param {string|number} ccValue
|
||||
* @param {string} color rgb[a]
|
||||
*/
|
||||
setSelectedColor: function (color) {
|
||||
this._selectColor({color: color});
|
||||
setSelectedColor: function (ccValue, color) {
|
||||
if (color === 'rgba(0, 0, 0, 0)' && this.options.opacity !== 1) {
|
||||
color = 'rgba(0, 0, 0, ' + this.options.opacity + ')';
|
||||
}
|
||||
this._selectColor({
|
||||
ccValue: ccValue,
|
||||
color: color,
|
||||
});
|
||||
// This is called on open, restore default tab selection
|
||||
const selectedButtonIndex = this.tabs.map(tab => tab.id).indexOf(this.options.selectedTab);
|
||||
this._selectTabFromButton(this.el.querySelectorAll('button')[selectedButtonIndex]);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -330,18 +381,30 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_getButtonInfo: function (buttonEl) {
|
||||
const bgColor = buttonEl.style.backgroundColor;
|
||||
return {
|
||||
color: bgColor ? ColorpickerWidget.normalizeCSSColor(bgColor) : buttonEl.dataset.color || '',
|
||||
const value = buttonEl.dataset.color || (bgColor && bgColor !== 'initial' ? ColorpickerWidget.normalizeCSSColor(bgColor) : '') || '';
|
||||
const info = {
|
||||
target: buttonEl,
|
||||
};
|
||||
if (!value) {
|
||||
info.ccValue = '';
|
||||
info.color = '';
|
||||
} else if (weUtils.isColorCombinationName(value)) {
|
||||
info.ccValue = value;
|
||||
} else {
|
||||
info.color = value;
|
||||
}
|
||||
return info;
|
||||
},
|
||||
/**
|
||||
* Set the selectedColor and trigger an event
|
||||
*
|
||||
* @param {Object} color
|
||||
* @param {Object} colorInfo
|
||||
* @param {string} [colorInfo.ccValue]
|
||||
* @param {string} [colorInfo.color]
|
||||
* @param {string} [eventName]
|
||||
*/
|
||||
_selectColor: function (colorInfo, eventName) {
|
||||
this.selectedCC = colorInfo.ccValue;
|
||||
this.selectedColor = colorInfo.color = this.colorToColorNames[colorInfo.color] || colorInfo.color;
|
||||
if (eventName) {
|
||||
this.trigger_up(eventName, colorInfo);
|
||||
@@ -353,15 +416,18 @@ const ColorPaletteWidget = Widget.extend({
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Mark the selected color
|
||||
* Marks the selected colors.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
_markSelectedColor: function () {
|
||||
this.el.querySelectorAll('button.selected').forEach(el => el.classList.remove('selected'));
|
||||
const buttonToSelectEls = this.el.querySelectorAll(`button[data-color="${this.selectedColor}"], button[style*="background-color:${this.selectedColor};"]`);
|
||||
for (const buttonToSelectEl of buttonToSelectEls) {
|
||||
buttonToSelectEl.classList.add('selected');
|
||||
for (const buttonEl of this.el.querySelectorAll('button')) {
|
||||
// TODO buttons should only be search by data-color value
|
||||
// instead of style but seems necessary for custom colors right
|
||||
// now...
|
||||
const value = buttonEl.dataset.color || buttonEl.style.backgroundColor;
|
||||
buttonEl.classList.toggle('selected', value
|
||||
&& (this.selectedCC === value || weUtils.areCssValuesEqual(this.selectedColor, value)));
|
||||
}
|
||||
},
|
||||
/**
|
||||
@@ -370,7 +436,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
* @private
|
||||
* @param {HTMLElement} buttonEl
|
||||
*/
|
||||
_selectTabFromButton(buttonEl) {
|
||||
_selectTabFromButton(buttonEl) {
|
||||
this.el.querySelectorAll('.o_we_colorpicker_switch_pane_btn').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
buttonEl.classList.add('active');
|
||||
this.el.querySelectorAll('.o_colorpicker_sections').forEach(el => {
|
||||
el.classList.toggle('d-none', el.dataset.colorTab !== buttonEl.dataset.target);
|
||||
@@ -389,7 +458,7 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_onColorButtonClick: function (ev) {
|
||||
const buttonEl = ev.currentTarget;
|
||||
const colorInfo = this._getButtonInfo(buttonEl);
|
||||
const colorInfo = Object.assign(this.getSelectedColors(), this._getButtonInfo(buttonEl));
|
||||
this._selectColor(colorInfo, 'color_picked');
|
||||
},
|
||||
/**
|
||||
@@ -400,7 +469,7 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_onColorButtonEnter: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.trigger_up('color_hover', this._getButtonInfo(ev.currentTarget));
|
||||
this.trigger_up('color_hover', Object.assign(this.getSelectedColors(), this._getButtonInfo(ev.currentTarget)));
|
||||
},
|
||||
/**
|
||||
* Called when a color button is left the data color is the color currently selected.
|
||||
@@ -410,10 +479,9 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_onColorButtonLeave: function (ev) {
|
||||
ev.stopPropagation();
|
||||
this.trigger_up('color_leave', {
|
||||
color: this.selectedColor,
|
||||
this.trigger_up('color_leave', Object.assign(this.getSelectedColors(), {
|
||||
target: ev.target,
|
||||
});
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Called when an update is made on the colorpicker.
|
||||
@@ -422,10 +490,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onColorPickerPreview: function (ev) {
|
||||
this.trigger_up('color_hover', {
|
||||
this.trigger_up('color_hover', Object.assign(this.getSelectedColors(), {
|
||||
color: ev.data.cssColor,
|
||||
target: this.colorPicker.el,
|
||||
});
|
||||
}));
|
||||
},
|
||||
/**
|
||||
* Called when a color is selected on the colorpicker (mouseup).
|
||||
@@ -434,10 +502,10 @@ const ColorPaletteWidget = Widget.extend({
|
||||
* @param {Event} ev
|
||||
*/
|
||||
_onColorPickerSelect: function (ev) {
|
||||
this._selectColor({
|
||||
this._selectColor(Object.assign(this.getSelectedColors(), {
|
||||
color: ev.data.cssColor,
|
||||
target: this.colorPicker.el,
|
||||
}, 'custom_color_picked');
|
||||
}), 'custom_color_picked');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -445,9 +513,6 @@ const ColorPaletteWidget = Widget.extend({
|
||||
*/
|
||||
_onSwitchPaneButtonClick(ev) {
|
||||
ev.stopPropagation();
|
||||
this.el.querySelectorAll('.o_we_colorpicker_switch_pane_btn').forEach(el => {
|
||||
el.classList.remove('active');
|
||||
});
|
||||
this._selectTabFromButton(ev.currentTarget);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ const snippetsEditor = require('web_editor.snippet.editor');
|
||||
const Toolbar = require('web_editor.toolbar');
|
||||
const weWidgets = require('wysiwyg.widgets');
|
||||
const wysiwygUtils = require('@web_editor/js/wysiwyg/wysiwyg_utils');
|
||||
const weUtils = require('web_editor.utils');
|
||||
|
||||
var _t = core._t;
|
||||
|
||||
@@ -393,7 +394,10 @@ const Wysiwyg = Widget.extend({
|
||||
},
|
||||
});
|
||||
if (isBackground) {
|
||||
$(el).css('background-image', `url('${newAttachmentSrc}')`);
|
||||
const parts = weUtils.backgroundImageCssToParts($(el).css('background-image'));
|
||||
parts.url = `url('${newAttachmentSrc}')`;
|
||||
const combined = weUtils.backgroundImagePartsToCss(parts);
|
||||
$(el).css('background-image', combined);
|
||||
delete el.dataset.bgSrc;
|
||||
} else {
|
||||
el.setAttribute('src', newAttachmentSrc);
|
||||
@@ -832,10 +836,19 @@ const Wysiwyg = Widget.extend({
|
||||
const targetElement = targetNode && targetNode.nodeType === Node.ELEMENT_NODE
|
||||
? targetNode
|
||||
: targetNode && targetNode.parentNode;
|
||||
const backgroundImage = $(targetElement).css('background-image');
|
||||
let backgroundGradient = false;
|
||||
if (weUtils.isColorGradient(backgroundImage)) {
|
||||
const textGradient = targetElement.classList.contains('text-gradient');
|
||||
if (eventName === "foreColor" && textGradient || eventName !== "foreColor" && !textGradient) {
|
||||
backgroundGradient = backgroundImage;
|
||||
}
|
||||
}
|
||||
colorpicker = new ColorPaletteWidget(this, {
|
||||
excluded: ['transparent_grayscale'],
|
||||
$editable: $(this.odooEditor.editable), // Our parent is the root widget, we can't retrieve the editable section from it...
|
||||
selectedColor: $(targetElement).css(eventName === "foreColor" ? 'color' : 'backgroundColor'),
|
||||
selectedColor: backgroundGradient || $(targetElement).css(eventName === "foreColor" ? 'color' : 'backgroundColor'),
|
||||
withGradients: true,
|
||||
});
|
||||
colorpicker.on('custom_color_picked color_picked', null, ev => {
|
||||
this._processAndApplyColor(eventName, ev.data.color);
|
||||
@@ -869,7 +882,7 @@ const Wysiwyg = Widget.extend({
|
||||
_processAndApplyColor: function (eventName, color) {
|
||||
if (!color) {
|
||||
color = 'inherit';
|
||||
} else if (!ColorpickerWidget.isCSSColor(color)) {
|
||||
} else if (!ColorpickerWidget.isCSSColor(color) && !weUtils.isColorGradient(color)) {
|
||||
color = (eventName === "foreColor" ? 'text-' : 'bg-') + color;
|
||||
}
|
||||
this.odooEditor.execCommand('applyColor', color, eventName === 'foreColor' ? 'color' : 'backgroundColor', this.lastMediaClicked);
|
||||
@@ -881,6 +894,9 @@ const Wysiwyg = Widget.extend({
|
||||
_colorToHex: function (color) {
|
||||
if (color.startsWith('#')) {
|
||||
return color;
|
||||
} else if (weUtils.isColorGradient(color)) {
|
||||
// return gradient the way it is: updateColorpickerLabels will handle it
|
||||
return color;
|
||||
} else {
|
||||
let rgbColor;
|
||||
if (color.startsWith('rgb')) {
|
||||
|
||||
@@ -21,6 +21,7 @@ $o-color-palette: map-get($o-color-palettes, $o-color-palette-name) or ();
|
||||
// Original color palette can contain override of the default combinations (so keep 'null' values for this merge)
|
||||
$o-color-palette: map-merge($-combination-additions, $o-color-palette);
|
||||
$o-color-palette: map-merge($o-base-color-palette, o-map-omit($o-color-palette));
|
||||
$o-color-palette: o-map-force-nulls($o-color-palette);
|
||||
|
||||
// Theme colors
|
||||
$o-theme-color-palette: map-get($o-theme-color-palettes, $o-theme-color-palette-name) or () !default;
|
||||
@@ -42,6 +43,7 @@ $o-theme-color-palette: map-merge((
|
||||
'dark': darken(desaturate($-main-color, 80%), min(70%, max(0%, $-main-color-lightness - 10%))), // Does not lower under 10% lightness
|
||||
), o-map-omit($o-theme-color-palette));
|
||||
$o-theme-color-palette: map-merge($o-base-theme-color-palette, o-map-omit($o-theme-color-palette));
|
||||
$o-theme-color-palette: o-map-force-nulls($o-theme-color-palette);
|
||||
|
||||
// Gray colors
|
||||
// Extend grays with transparent ones (for some reason, BS4 create black-50 and
|
||||
@@ -49,6 +51,7 @@ $o-theme-color-palette: map-merge($o-base-theme-color-palette, o-map-omit($o-the
|
||||
$o-gray-color-palette: map-get($o-gray-color-palettes, $o-gray-color-palette-name) or () !default;
|
||||
$o-gray-color-palette: map-merge($o-transparent-grays, o-map-omit($o-gray-color-palette));
|
||||
$o-gray-color-palette: map-merge($o-base-gray-color-palette, o-map-omit($o-gray-color-palette));
|
||||
$o-gray-color-palette: o-map-force-nulls($o-gray-color-palette);
|
||||
|
||||
$o-color-system-initialized: false;
|
||||
|
||||
|
||||
@@ -787,3 +787,11 @@ section, .oe_img_bg, [data-oe-shape-data] {
|
||||
.o_we_bg_filter {
|
||||
@extend %o-we-background-layer;
|
||||
}
|
||||
|
||||
// Gradient
|
||||
// TODO should be in the editor lib since it is handled there... but could not
|
||||
// find the right place for it.
|
||||
.text-gradient {
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
@@ -446,6 +446,21 @@ $o-we-sidebar-content-field-toggle-control-shadow: 0 2px 3px 0 $o-we-bg-darkest
|
||||
// Edited content
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Useful to force null values (in website, null values set by the user
|
||||
// are removed to act as "removing the user custo and resetting to theme
|
||||
// default": ideally, we should review that system to actually removing the
|
||||
// custos in that case instead of setting a null value but this cannot be
|
||||
// migrated at the moment).
|
||||
@function o-map-force-nulls($map) {
|
||||
$-map: ();
|
||||
@each $key, $value in $map {
|
||||
$-map: map-merge($-map, (
|
||||
$key: if($value == 'NULL', null, $value),
|
||||
));
|
||||
}
|
||||
@return $-map;
|
||||
}
|
||||
|
||||
$o-support-13-0-color-system: false !default;
|
||||
|
||||
$o-checklist-margin-left: 20px;
|
||||
@@ -482,10 +497,6 @@ $o-color-palettes: (
|
||||
'o-color-3': #F8F9FA,
|
||||
'o-color-4': #FFFFFF,
|
||||
'o-color-5': #343a40,
|
||||
|
||||
'menu': 2,
|
||||
'footer': 2,
|
||||
'copyright': 5,
|
||||
),
|
||||
) !default;
|
||||
$o-color-palette-name: 'base-1' !default;
|
||||
|
||||
@@ -117,8 +117,15 @@ $o-we-zindex: $o-we-overlay-zindex + 1 !default;
|
||||
}
|
||||
|
||||
// Specific elements
|
||||
#colorInputButtonGroup label:last-of-type .btn {
|
||||
margin: 0 1px 0 -1px;
|
||||
#colorInputButtonGroup {
|
||||
label:last-of-type .btn {
|
||||
margin: 0 1px 0 -1px;
|
||||
}
|
||||
|
||||
.note-back-color-preview.dropup .dropdown-menu {
|
||||
// Highlight palette alignment
|
||||
left: -52px;
|
||||
}
|
||||
}
|
||||
.oe-tablepicker {
|
||||
margin: 1px 2px -8px 2px !important;
|
||||
|
||||
@@ -51,12 +51,17 @@
|
||||
<span>Theme</span>
|
||||
</button>
|
||||
<button type="button" tabindex="2" class="o_we_colorpicker_switch_pane_btn" data-target="custom-colors">
|
||||
<span>Custom</span>
|
||||
<span>Solid</span>
|
||||
</button>
|
||||
<button t-if="widget.resetButton" type="button" class="my-1 ml-auto o_we_color_btn o_colorpicker_reset">
|
||||
<t t-if="widget.withCombinations">None</t>
|
||||
<t t-else="">Reset</t>
|
||||
<button type="button" tabindex="3" class="o_we_colorpicker_switch_pane_btn" data-target="gradients">
|
||||
<span>Gradient</span>
|
||||
</button>
|
||||
<t t-if="widget.resetButton">
|
||||
<t t-set="trash_title"><t t-if="widget.withCombinations">None</t><t t-else="">Reset</t></t>
|
||||
<button type="button" class="my-1 ml-auto o_we_color_btn o_colorpicker_reset" t-att-title="trash_title">
|
||||
<i class="fa fa-trash"/>
|
||||
</button>
|
||||
</t>
|
||||
</div>
|
||||
<div class="o_colorpicker_sections pt-2 px-2 pb-3 bg-black-25" data-color-tab="color-combinations">
|
||||
<!-- List all Presets -->
|
||||
@@ -68,6 +73,7 @@
|
||||
</div>
|
||||
<div class="o_colorpicker_sections py-3 px-2 bg-black-25" data-color-tab="theme-colors"/>
|
||||
<div class="o_colorpicker_sections py-3 px-2 bg-black-25" data-color-tab="custom-colors"/>
|
||||
<div class="o_colorpicker_sections py-3 px-2 bg-black-25" data-color-tab="gradients"/>
|
||||
</div>
|
||||
<t t-name="web_editor.colorpalette.combination.btn">
|
||||
<button type="button" class="o_we_color_btn o_we_color_combination_btn"
|
||||
|
||||
@@ -297,7 +297,7 @@ QUnit.module('web_editor', {}, function () {
|
||||
await testUtils.dom.click($('#toolbar .note-back-color-preview .o_we_color_btn.bg-o-color-3'));
|
||||
|
||||
assert.strictEqual($field.find('.note-editable').html(),
|
||||
'<p>t<font style="background-color: rgb(0, 255, 255);">oto t</font><font style="" class=" bg-o-color-3">oto to</font>to</p><p>tata</p>',
|
||||
'<p>t<font style="background-color: rgb(0, 255, 255);">oto t</font><font style="" class="bg-o-color-3">oto to</font>to</p><p>tata</p>',
|
||||
"should have rendered the field correctly in edit");
|
||||
|
||||
form.destroy();
|
||||
|
||||
@@ -60,6 +60,21 @@
|
||||
<button data-color="white-75"></button>
|
||||
<button data-color="white-85"></button>
|
||||
</div>
|
||||
<div class="o_colorpicker_section" data-name="predefined_gradients">
|
||||
<t t-set="gradients" t-value="[
|
||||
'linear-gradient(135deg, rgb(255, 204, 51) 0%, rgb(226, 51, 255) 100%)',
|
||||
'linear-gradient(135deg, rgb(102, 153, 255) 0%, rgb(255, 51, 102) 100%)',
|
||||
'linear-gradient(135deg, rgb(47, 128, 237) 0%, rgb(178, 255, 218) 100%)',
|
||||
'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',
|
||||
'linear-gradient(135deg, rgb(214, 255, 127) 0%, rgb(0, 179, 204) 100%)',
|
||||
'linear-gradient(135deg, rgb(255, 222, 69) 0%, rgb(69, 33, 0) 100%)',
|
||||
'linear-gradient(135deg, rgb(222, 222, 222) 0%, rgb(69, 69, 69) 100%)',
|
||||
'linear-gradient(135deg, rgb(255, 222, 202) 0%, rgb(202, 115, 69) 100%)'
|
||||
]"/>
|
||||
<t t-foreach="gradients" t-as="gradient">
|
||||
<button class="w-50" t-attf-style="background-image: #{gradient};" t-att-data-color="gradient"/>
|
||||
</t>
|
||||
</div>
|
||||
</colorpicker>
|
||||
</template>
|
||||
</odoo>
|
||||
|
||||
@@ -123,10 +123,12 @@
|
||||
<template id="snippet_options_background_color_widget">
|
||||
<we-colorpicker title="Color"
|
||||
data-name="bg_color_opt"
|
||||
t-att-data-select-color-combination="'' if with_color_combinations else None"
|
||||
data-select-style=""
|
||||
data-css-property="background-color"
|
||||
data-color-prefix="bg-"
|
||||
t-att-data-with-combinations="with_color_combinations and 'true' or None"
|
||||
t-att-data-with-combinations="with_color_combinations and 'selectColorCombination' or None"
|
||||
t-att-data-with-gradients="with_gradients and 'true' or None"
|
||||
t-att-data-css-compatible="css_compatible and 'true' or None"/>
|
||||
</template>
|
||||
|
||||
@@ -145,13 +147,6 @@
|
||||
data-no-preview="true">
|
||||
<i class="fa fa-fw fa-camera"/>
|
||||
</we-button>
|
||||
<we-button title="Filter"
|
||||
data-name="bg_filter_toggle_opt"
|
||||
data-dependencies="bg_image_toggle_opt"
|
||||
data-toggle-bg-filter="true"
|
||||
data-no-preview="true">
|
||||
<i class="fa fa-fw fa-paint-brush"/>
|
||||
</we-button>
|
||||
<we-button title="Shape"
|
||||
data-toggle-bg-shape="true"
|
||||
t-att-data-dependencies="images_dependencies"
|
||||
@@ -210,15 +205,22 @@
|
||||
</div>
|
||||
|
||||
<!-- Color filter -->
|
||||
<div data-option-name="colorFilter"
|
||||
<div t-att-data-js="with_colors and with_color_combinations and 'ColoredLevelBackground' or 'BackgroundToggler'"
|
||||
t-att-data-selector="selector"
|
||||
t-att-data-exclude="exclude"
|
||||
t-att-data-target="target">
|
||||
<we-colorpicker string="⌙ Color filter"
|
||||
data-select-style="true"
|
||||
data-css-property="background-color"
|
||||
<t t-set="color_filter_dependencies" t-valuef="bg_image_toggle_opt"/>
|
||||
<!--   -->
|
||||
<we-colorpicker string=" ⌙ Color filter"
|
||||
t-att-data-dependencies="color_filter_dependencies"
|
||||
data-name="bg_filter_opt"
|
||||
data-select-filter-color=""
|
||||
data-color-prefix="bg-"
|
||||
data-apply-to=".o_we_bg_filter"/>
|
||||
data-opacity="0.5"
|
||||
data-with-gradients="1"
|
||||
data-selected-tab="gradients"
|
||||
data-excluded="theme, common"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-js="BackgroundShape" string="Shape"
|
||||
|
||||
@@ -35,6 +35,13 @@ class Assets(models.AbstractModel):
|
||||
'warning': 'null',
|
||||
'danger': 'null',
|
||||
})
|
||||
# Also reset gradients which are in the "website" values palette
|
||||
self.make_scss_customization('/website/static/src/scss/options/user_values.scss', {
|
||||
'menu-gradient': 'null',
|
||||
'header-boxed-gradient': 'null',
|
||||
'footer-gradient': 'null',
|
||||
'copyright-gradient': 'null',
|
||||
})
|
||||
|
||||
custom_url = self.make_custom_asset_file_url(url, 'web.assets_common')
|
||||
updatedFileContent = self.get_asset_content(custom_url) or self.get_asset_content(url)
|
||||
|
||||
@@ -384,6 +384,7 @@ options.Class.include({
|
||||
custom_events: _.extend({}, options.Class.prototype.custom_events || {}, {
|
||||
'google_fonts_custo_request': '_onGoogleFontsCustoRequest',
|
||||
}),
|
||||
specialCheckAndReloadMethodsNames: ['customizeWebsiteViews', 'customizeWebsiteVariable', 'customizeWebsiteColor'],
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Options
|
||||
@@ -422,19 +423,27 @@ options.Class.include({
|
||||
}
|
||||
for (const widget of widgets) {
|
||||
const methodsNames = widget.getMethodsNames();
|
||||
if (!methodsNames.includes('customizeWebsiteViews')
|
||||
&& !methodsNames.includes('customizeWebsiteVariable')
|
||||
&& !methodsNames.includes('customizeWebsiteColor')) {
|
||||
const specialMethodsNames = [];
|
||||
for (const methodName of methodsNames) {
|
||||
if (this.specialCheckAndReloadMethodsNames.includes(methodName)) {
|
||||
specialMethodsNames.push(methodName);
|
||||
}
|
||||
}
|
||||
if (!specialMethodsNames.length) {
|
||||
continue;
|
||||
}
|
||||
let paramsReload = false;
|
||||
if (widget.getMethodsParams('customizeWebsiteViews').reload
|
||||
|| widget.getMethodsParams('customizeWebsiteVariable').reload
|
||||
|| widget.getMethodsParams('customizeWebsiteColor').reload) {
|
||||
paramsReload = true;
|
||||
const isDebugAssets = config.isDebug('assets');
|
||||
let paramsReload = isDebugAssets;
|
||||
if (!isDebugAssets) {
|
||||
for (const methodName of specialMethodsNames) {
|
||||
if (widget.getMethodsParams(methodName).reload) {
|
||||
paramsReload = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (paramsReload || config.isDebug('assets')) {
|
||||
return (config.isDebug('assets') ? _t("It appears you are in debug=assets mode, all theme customization options require a page reload in this mode.") : true);
|
||||
if (paramsReload) {
|
||||
return (isDebugAssets ? _t("It appears you are in debug=assets mode, all theme customization options require a page reload in this mode.") : true);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@@ -498,7 +507,7 @@ options.Class.include({
|
||||
}
|
||||
}
|
||||
|
||||
if (params.reload || config.isDebug('assets')) {
|
||||
if (params.reload || config.isDebug('assets') || params.noBundleReload) {
|
||||
// Caller will reload the page, nothing needs to be done anymore.
|
||||
return;
|
||||
}
|
||||
@@ -543,7 +552,7 @@ options.Class.include({
|
||||
}
|
||||
}
|
||||
}
|
||||
return this._makeSCSSCusto(url, finalColors);
|
||||
return this._makeSCSSCusto(url, finalColors, params.nullValue);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -551,7 +560,7 @@ options.Class.include({
|
||||
_customizeWebsiteVariable: async function (value, params) {
|
||||
return this._makeSCSSCusto('/website/static/src/scss/options/user_values.scss', {
|
||||
[params.variable]: value,
|
||||
});
|
||||
}, params.nullValue);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -582,12 +591,12 @@ options.Class.include({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_makeSCSSCusto: async function (url, values) {
|
||||
_makeSCSSCusto: async function (url, values, defaultValue = 'null') {
|
||||
return this._rpc({
|
||||
route: '/website/make_scss_custo',
|
||||
params: {
|
||||
'url': url,
|
||||
'values': _.mapObject(values, v => v || 'null'),
|
||||
'values': _.mapObject(values, v => v || defaultValue),
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -700,6 +709,7 @@ options.registry.BackgroundToggler.include({
|
||||
*/
|
||||
toggleBgVideo(previewMode, widgetValue, params) {
|
||||
if (!widgetValue) {
|
||||
this.$target.find('> .o_we_bg_filter').remove();
|
||||
// TODO: use setWidgetValue instead of calling background directly when possible
|
||||
const [bgVideoWidget] = this._requestUserValueWidgets('bg_video_opt');
|
||||
const bgVideoOpt = bgVideoWidget.getParent();
|
||||
@@ -1946,6 +1956,54 @@ options.registry.collapse = options.Class.extend({
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.WebsiteLevelColor = options.Class.extend({
|
||||
specialCheckAndReloadMethodsNames: options.Class.prototype.specialCheckAndReloadMethodsNames
|
||||
.concat(['customizeWebsiteLayer2Color']),
|
||||
|
||||
/**
|
||||
* @see this.selectClass for parameters
|
||||
*/
|
||||
async customizeWebsiteLayer2Color(previewMode, widgetValue, params) {
|
||||
if (previewMode) {
|
||||
return;
|
||||
}
|
||||
params.color = params.layerColor;
|
||||
params.variable = params.layerGradient;
|
||||
let color = undefined;
|
||||
let gradient = undefined;
|
||||
if (weUtils.isColorGradient(widgetValue)) {
|
||||
color = '';
|
||||
gradient = widgetValue;
|
||||
} else {
|
||||
color = widgetValue;
|
||||
gradient = '';
|
||||
}
|
||||
await this.customizeWebsiteVariable(previewMode, gradient, params);
|
||||
params.noBundleReload = false;
|
||||
return this.customizeWebsiteColor(previewMode, color, params);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
async _computeWidgetState(methodName, params) {
|
||||
if (methodName === 'customizeWebsiteLayer2Color') {
|
||||
params.variable = params.layerGradient;
|
||||
const gradient = await this._computeWidgetState('customizeWebsiteVariable', params);
|
||||
if (gradient) {
|
||||
return gradient.substring(1, gradient.length - 1); // Unquote
|
||||
}
|
||||
params.color = params.layerColor;
|
||||
return this._computeWidgetState('customizeWebsiteColor', params);
|
||||
}
|
||||
return this._super(...arguments);
|
||||
},
|
||||
});
|
||||
|
||||
options.registry.HeaderNavbar = options.Class.extend({
|
||||
/**
|
||||
* Particular case: we want the option to be associated on the header navbar
|
||||
@@ -2633,10 +2691,23 @@ options.registry.CoverProperties = options.Class.extend({
|
||||
name: 'bg_color_opt',
|
||||
onSuccess: _widget => colorPickerWidget = _widget,
|
||||
});
|
||||
const color = colorPickerWidget._value;
|
||||
const isCSSColor = ColorpickerWidget.isCSSColor(color);
|
||||
this.$target[0].dataset.bgColorClass = isCSSColor ? '' : weUtils.computeColorClasses([color])[0];
|
||||
this.$target[0].dataset.bgColorStyle = isCSSColor ? `background-color: ${color};` : '';
|
||||
// TODO there is probably a better way and this should be refactored to
|
||||
// use more standard colorpicker+imagepicker structure
|
||||
const ccValue = colorPickerWidget._ccValue;
|
||||
const colorOrGradient = colorPickerWidget._value;
|
||||
const isGradient = weUtils.isColorGradient(colorOrGradient);
|
||||
const isCSSColor = !isGradient && ColorpickerWidget.isCSSColor(colorOrGradient);
|
||||
const colorNames = [];
|
||||
if (ccValue) {
|
||||
colorNames.push(ccValue);
|
||||
}
|
||||
if (!isGradient && !isCSSColor) {
|
||||
colorNames.push(colorOrGradient);
|
||||
}
|
||||
this.$target[0].dataset.bgColorClass = weUtils.computeColorClasses(colorNames).join(' ');
|
||||
this.$target[0].dataset.bgColorStyle =
|
||||
isCSSColor ? `background-color: ${colorOrGradient};` :
|
||||
isGradient ? `background-color: rgba(0, 0, 0, 0); background-image: ${colorOrGradient};` : '';
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -6,10 +6,14 @@
|
||||
$o-base-color-palette: map-merge($o-base-color-palette, (
|
||||
'body': $o-portal-default-body-bg,
|
||||
'menu': 1, // o_cc1
|
||||
'menu-custom': null,
|
||||
'menu-border-color': null, // Default to classes used on the template
|
||||
'header-boxed': '200',
|
||||
'header-boxed': null,
|
||||
'header-boxed-custom': '200',
|
||||
'footer': 5, // o_cc5
|
||||
'copyright': 'black-15',
|
||||
'footer-custom': null,
|
||||
'copyright': null,
|
||||
'copyright-custom': null,
|
||||
));
|
||||
|
||||
// By default, all user color palette values are null. Each null value is
|
||||
@@ -42,6 +46,14 @@ $o-theme-figcaption-opacity: 0.6;
|
||||
|
||||
$o-color-palettes: map-merge($o-color-palettes,
|
||||
(
|
||||
'base-1': map-merge(map-get($o-color-palettes, 'base-1'), (
|
||||
'copyright-custom': 'black-15',
|
||||
)),
|
||||
'base-2': map-merge(map-get($o-color-palettes, 'base-2'), (
|
||||
'menu': 2,
|
||||
'footer': 2,
|
||||
'copyright': 5,
|
||||
)),
|
||||
'generic-1': (
|
||||
'o-color-1': #984c46,
|
||||
'o-color-2': #23323b,
|
||||
@@ -51,6 +63,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 3,
|
||||
'footer': 3,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-2': (
|
||||
'o-color-1': #B99932,
|
||||
@@ -68,6 +81,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-3': #fdf8ef,
|
||||
'o-color-4': #FFFFFF,
|
||||
'o-color-5': #212c39,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-4': (
|
||||
'o-color-1': #6E7993,
|
||||
@@ -77,6 +92,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #313347,
|
||||
|
||||
'menu': 5,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-5': (
|
||||
'o-color-1': #F7CF41,
|
||||
@@ -87,6 +103,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 3,
|
||||
'footer': 3,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-6': (
|
||||
'o-color-1': #45859A,
|
||||
@@ -139,6 +156,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #000000,
|
||||
|
||||
'menu': 5,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-10': (
|
||||
'o-color-1': #b56355,
|
||||
@@ -148,6 +166,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #343733,
|
||||
|
||||
'footer': 2,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-11': (
|
||||
'o-color-1': #01ACAB,
|
||||
@@ -157,6 +176,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #000000,
|
||||
|
||||
'footer': 1,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-12': (
|
||||
'o-color-1': #926190,
|
||||
@@ -172,6 +192,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-cc5-headings': 'o-color-4',
|
||||
'o-cc5-link': 'o-color-4',
|
||||
'o-cc5-text': rgba(#fff, .6),
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'generic-13': (
|
||||
'o-color-1': #478FA2,
|
||||
@@ -240,6 +262,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 5,
|
||||
'footer': 5,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'anelusia-1': (
|
||||
'o-color-1': #000000,
|
||||
@@ -284,6 +307,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'o-cc5-link': #218bdf,
|
||||
'o-cc5-btn-primary': #218bdf,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'anelusia-4': (
|
||||
'o-color-1': #dc143c,
|
||||
@@ -291,6 +316,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-3': #faf7e1,
|
||||
'o-color-4': #ffffff,
|
||||
'o-color-5': #222222,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'anelusia-5': (
|
||||
'o-color-1': #32c0c9,
|
||||
@@ -300,6 +327,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #2e2e2e,
|
||||
|
||||
'footer': 1,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'anelusia-6': (
|
||||
'o-color-1': #e27b92,
|
||||
@@ -313,6 +341,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 2,
|
||||
'footer': 2,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'artists-1': (
|
||||
'o-color-1': #ab44ff,
|
||||
@@ -324,6 +353,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-cc5-text': 'o-color-3',
|
||||
|
||||
'menu': 5,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'artists-2': (
|
||||
'o-color-1': #1ad68f,
|
||||
@@ -333,6 +363,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #000000,
|
||||
|
||||
'menu': 2,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'artists-3': (
|
||||
'o-color-1': #007882,
|
||||
@@ -355,6 +386,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 2,
|
||||
'footer': 2,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'artists-5': (
|
||||
'o-color-1': #f48847,
|
||||
@@ -364,6 +396,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #000000,
|
||||
|
||||
'footer': 3,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'artists-6': (
|
||||
'o-color-1': #95d2e5,
|
||||
@@ -374,6 +407,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 2,
|
||||
'footer': 2,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'avantgarde-1': (
|
||||
'o-color-1': #ee4980,
|
||||
@@ -403,6 +437,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 4,
|
||||
'footer': 4,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'avantgarde-3': (
|
||||
'o-color-1': #f08e80,
|
||||
@@ -465,6 +500,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-cc2-headings': 'o-color-2',
|
||||
'o-cc3-headings': 'o-color-3',
|
||||
'o-cc5-headings': 'o-color-1',
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'avantgarde-7': (
|
||||
'o-color-1': #f48847,
|
||||
@@ -475,6 +512,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'o-cc2-headings': 'o-color-1',
|
||||
'o-cc5-headings': 'o-color-1',
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'avantgarde-8': (
|
||||
'o-color-1': #278f84,
|
||||
@@ -485,6 +524,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'o-cc2-headings': 'o-color-1',
|
||||
'o-cc5-headings': 'o-color-1',
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'beauty-1': (
|
||||
'o-color-1': #df699c,
|
||||
@@ -553,6 +594,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #112625,
|
||||
|
||||
'o-cc2-link': 'o-color-5',
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'bewise-1': (
|
||||
'o-color-1': #162238,
|
||||
@@ -861,7 +904,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'header': 1,
|
||||
'footer': 3,
|
||||
'copyright': 'black-15',
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'enark-1': (
|
||||
'o-color-1': #7b97af,
|
||||
@@ -873,6 +916,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-cc1-text': 'o-color-2',
|
||||
'o-cc1-btn-primary': 'o-color-2',
|
||||
'o-cc5-link': 'o-color-4',
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'enark-2': (
|
||||
'o-color-1': #41cb7a,
|
||||
@@ -936,7 +981,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'body': 'black-15',
|
||||
'menu': 1,
|
||||
'footer': 5,
|
||||
'copyright': 'black-25',
|
||||
'copyright-custom': 'black-25',
|
||||
),
|
||||
'graphene-2': (
|
||||
'o-color-1': #c8a47e,
|
||||
@@ -950,6 +995,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-cc3-headings': 'o-color-4',
|
||||
'o-cc4-headings': 'o-color-5',
|
||||
'o-cc5-text': #b2b2b2,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'graphene-3': (
|
||||
'o-color-1': #39588e,
|
||||
@@ -962,6 +1009,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
|
||||
'menu': 2,
|
||||
'footer': 2,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'graphene-4': (
|
||||
'o-color-1': #f48747,
|
||||
@@ -971,6 +1019,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-5': #282d30,
|
||||
|
||||
'menu': 5,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'kea-1': (
|
||||
'o-color-1': #e991a3,
|
||||
@@ -1137,6 +1186,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-3': #f5f5f5,
|
||||
'o-color-4': #ffffff,
|
||||
'o-color-5': #111111,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'monglia-2': (
|
||||
'o-color-1': #8f8747,
|
||||
@@ -1144,6 +1195,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-3': #f0ecdb,
|
||||
'o-color-4': #ffffff,
|
||||
'o-color-5': #21263a,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'monglia-3': (
|
||||
'o-color-1': #c0ce75,
|
||||
@@ -1151,6 +1204,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-3': #dedede,
|
||||
'o-color-4': #ffffff,
|
||||
'o-color-5': #3e3e3e,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'monglia-4': (
|
||||
'o-color-1': #1ad68f,
|
||||
@@ -1158,6 +1213,8 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-color-3': #cfcfcf,
|
||||
'o-color-4': #ffffff,
|
||||
'o-color-5': #000000,
|
||||
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'monglia-5': (
|
||||
'o-color-1': #c5ca60,
|
||||
@@ -1506,6 +1563,7 @@ $o-color-palettes: map-merge($o-color-palettes,
|
||||
'o-cc5-link': #70c7cf,
|
||||
|
||||
'menu': 5,
|
||||
'copyright-custom': 'black-15',
|
||||
),
|
||||
'treehouse-1': (
|
||||
'o-color-1': #68b581,
|
||||
@@ -1868,6 +1926,12 @@ $o-base-website-values-palette: (
|
||||
'navbar-font': null,
|
||||
'buttons-font': null,
|
||||
|
||||
// Gradients
|
||||
'menu-gradient': null,
|
||||
'header-boxed-gradient': null,
|
||||
'footer-gradient': null,
|
||||
'copyright-gradient': null,
|
||||
|
||||
'header-template': 'default', // 'default' / 'hamburger' / 'vertical' / 'sidebar'
|
||||
'header-font-size': null, // Default to BS (normal font-size)
|
||||
'header-links-style': 'default', // 'default' / 'fill' / 'outline' / 'pills' / 'block' / 'border-bottom'
|
||||
@@ -1985,3 +2049,11 @@ $o-theme-font-configs: (
|
||||
@include o-position-absolute($top: 0, $left: 0);
|
||||
padding: 0.25rem 1rem;
|
||||
};
|
||||
|
||||
@mixin o-add-gradient($key) {
|
||||
$-gradient: o-website-value($key);
|
||||
@if $-gradient {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
background-image: $-gradient;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ $o-website-values-palettes: append($o-website-values-palettes, $-actual-user-web
|
||||
|
||||
// Enable last website values palette, which is now the user customized one
|
||||
$o-website-values-palette-number: length($o-website-values-palettes);
|
||||
$o-website-values: $-actual-user-website-values-palette !default;
|
||||
$o-website-values: o-map-force-nulls($-actual-user-website-values-palette) !default;
|
||||
@function o-website-value($key) {
|
||||
@return map-get($o-website-values, $key);
|
||||
}
|
||||
@@ -134,6 +134,27 @@ $o-has-customized-colors:
|
||||
// palette.
|
||||
$-palette-default: map-get($o-color-palettes, $o-color-palette-name) or ();
|
||||
$-actual-user-color-palette: map-merge($-palette-default, o-map-omit($o-user-color-palette));
|
||||
// Compatibility with old values in old names
|
||||
@each $name, $custom-name in (
|
||||
// Each of those values were either a number for a color combination, a
|
||||
// string for a color name or a color. Now they should only be a number for
|
||||
// a color combination and the other value types for the color name/value
|
||||
// are handled by another variable. That variable can also contain a string
|
||||
// for a color gradient.
|
||||
'menu': 'menu-custom',
|
||||
'header-boxed': 'header-boxed-custom',
|
||||
'footer': 'footer-custom',
|
||||
'copyright': 'copyright-custom'
|
||||
) {
|
||||
$-base-value: map-get($-actual-user-color-palette, $name);
|
||||
@if $-base-value and $-base-value != 'NULL' and type-of($-base-value) != 'number' {
|
||||
$-base-custom-value: map-get($-actual-user-color-palette, $custom-name);
|
||||
$-actual-user-color-palette: map-merge($-actual-user-color-palette, (
|
||||
$name: 1,
|
||||
$custom-name: $-base-custom-value or $-base-value,
|
||||
));
|
||||
}
|
||||
}
|
||||
$o-color-palettes: map-merge($o-color-palettes, ('user-palette': $-actual-user-color-palette));
|
||||
|
||||
// Gray palette
|
||||
@@ -174,6 +195,7 @@ $-actual-user-theme-color-palette: map-remove($-actual-user-theme-color-palette,
|
||||
'secondary'
|
||||
);
|
||||
$o-theme-color-palettes: map-merge($o-theme-color-palettes, ('user-palette': $-actual-user-theme-color-palette));
|
||||
|
||||
// ---
|
||||
|
||||
// Enable last color and theme color palettes, which are now the user customized
|
||||
|
||||
@@ -136,7 +136,8 @@ $-seen-urls: ();
|
||||
}
|
||||
|
||||
// Vertical alignment when top-menu has visually "no background"
|
||||
@if o-color('menu') == o-color('body') {
|
||||
$-menu-color: o-color('menu-custom') or o-color('menu');
|
||||
@if (not $-menu-color or $-menu-color == o-color('body')) {
|
||||
> header {
|
||||
.navbar, .container {
|
||||
padding-left: 0;
|
||||
@@ -226,9 +227,12 @@ $-seen-urls: ();
|
||||
// Note: this only works because navbar-light is defined before navbar-dark,
|
||||
// we may want to use a safest way when possible.
|
||||
@include o-apply-colors('menu');
|
||||
@if (color-yiq(o-color('menu')) != $yiq-text-dark) {
|
||||
@include o-apply-colors('menu-custom');
|
||||
$-menu-color: o-color('menu-custom') or o-color('menu');
|
||||
@if ($-menu-color and color-yiq($-menu-color) != $yiq-text-dark) {
|
||||
@extend .navbar-dark;
|
||||
}
|
||||
@include o-add-gradient('menu-gradient');
|
||||
}
|
||||
|
||||
$-header-nav-link-height: $nav-link-height;
|
||||
@@ -258,9 +262,14 @@ $-header-nav-link-height: $nav-link-height;
|
||||
|
||||
.o_footer {
|
||||
@include o-apply-colors('footer');
|
||||
@include o-apply-colors('footer-custom');
|
||||
@include o-add-gradient('footer-gradient');
|
||||
|
||||
.o_footer_copyright {
|
||||
@include o-apply-colors('copyright', $background: o-color('footer'));
|
||||
$-footer-color: o-color('footer-custom') or o-color('footer');
|
||||
@include o-apply-colors('copyright', $background: $-footer-color);
|
||||
@include o-apply-colors('copyright-custom', $background: $-footer-color);
|
||||
@include o-add-gradient('copyright-gradient');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +295,7 @@ h6 {
|
||||
}
|
||||
|
||||
// Texts
|
||||
font[style*='background'],
|
||||
font[style*='background']:not(.text-gradient),
|
||||
font[class*='bg-'] {
|
||||
padding: 2px 6px 4px;
|
||||
}
|
||||
@@ -946,7 +955,8 @@ header {
|
||||
}
|
||||
min-width: 250px;
|
||||
margin: 0 !important;
|
||||
background-color: o-color('menu');
|
||||
background-color: o-color('menu-custom') or o-color('menu');
|
||||
@include o-add-gradient('menu-gradient');
|
||||
transition: transform $-transition-duration cubic-bezier(.694, .0482, .335, 1);
|
||||
|
||||
@if $-hamburger-center {
|
||||
@@ -1102,6 +1112,8 @@ header {
|
||||
} @else if o-website-value('header-template') == 'boxed' {
|
||||
#wrapwrap:not(.o_header_overlay) .o_header_boxed_background {
|
||||
@include o-apply-colors('header-boxed');
|
||||
@include o-apply-colors('header-boxed-custom');
|
||||
@include o-add-gradient('header-boxed-gradient');
|
||||
}
|
||||
} @else if o-website-value('header-template') == 'centered_logo' {
|
||||
header .o_header_centered_logo {
|
||||
@@ -1280,8 +1292,8 @@ header {
|
||||
// Footer scrolltop button
|
||||
@if o-website-value('footer-scrolltop') {
|
||||
#o_footer_scrolltop {
|
||||
$-footer-color: o-color('footer');
|
||||
$-copyright-color: o-color('copyright');
|
||||
$-footer-color: o-color('footer-custom') or o-color('footer');
|
||||
$-copyright-color: o-color('copyright-custom') or o-color('copyright');
|
||||
$-copyright-color: mix(rgba($-copyright-color, 1.0), $-footer-color, percentage(alpha($-copyright-color)));
|
||||
|
||||
box-sizing: content-box;
|
||||
@@ -1290,6 +1302,7 @@ header {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
@include o-apply-colors('footer', $with-extras: false, $background: $-footer-color);
|
||||
@include o-apply-colors('footer-custom', $with-extras: false, $background: $-footer-color);
|
||||
text-decoration: none;
|
||||
|
||||
@if $-footer-color == $-copyright-color {
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import tour from 'web_tour.tour';
|
||||
import weUtils from 'web_editor.utils';
|
||||
import wTourUtils from 'website.tour_utils';
|
||||
|
||||
const snippets = [
|
||||
{
|
||||
id: 's_text_image',
|
||||
name: 'Text - Image',
|
||||
},
|
||||
];
|
||||
const gradients = [
|
||||
'linear-gradient(135deg, rgb(203, 94, 238) 0%, rgb(75, 225, 236) 100%)',
|
||||
'linear-gradient(135deg, rgb(255, 222, 202) 0%, rgb(202, 115, 69) 100%)',
|
||||
];
|
||||
|
||||
function checkAndUpdateBackgroundColor({
|
||||
checkCC, checkNoCC,
|
||||
checkBg, checkNoBg,
|
||||
checkGradient, checkNoGradient,
|
||||
changeType, change,
|
||||
finalSelector, finalRun
|
||||
}) {
|
||||
const steps = [
|
||||
wTourUtils.changeBackgroundColor(),
|
||||
];
|
||||
|
||||
function typeToName(xType) {
|
||||
return xType === 'cc' ? 'color combinations' : xType === 'bg' ? 'background colors' : 'gradients';
|
||||
}
|
||||
|
||||
function switchTo(type, _name) {
|
||||
const target = type === 'cc' ? 'color-combinations' : type === 'bg' ? 'custom-colors' : 'gradients';
|
||||
const name = _name || typeToName(type);
|
||||
steps.push({
|
||||
trigger: `.o_we_colorpicker_switch_pane_btn[data-target="${target}"]`,
|
||||
content: `Switch to ${name}`,
|
||||
});
|
||||
}
|
||||
|
||||
function check(checkX, checkNoX, xType, noSwitch = false) {
|
||||
if (!checkX && !checkNoX) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = typeToName(xType);
|
||||
const selectorCheckX = checkX && `.o_we_color_btn[data-color="${checkX}"].selected`;
|
||||
const selectorCheckNoX = checkNoX && `.o_we_color_btn[data-color="${checkNoX}"]:not(.selected)`;
|
||||
const step = {
|
||||
trigger: selectorCheckX || selectorCheckNoX,
|
||||
content: `The correct ${name} is marked as selected`,
|
||||
position: 'bottom',
|
||||
run: () => null,
|
||||
};
|
||||
if (!selectorCheckX && selectorCheckNoX) {
|
||||
step.extra_trigger = selectorCheckNoX;
|
||||
}
|
||||
|
||||
if (!noSwitch) {
|
||||
switchTo(xType, name);
|
||||
}
|
||||
steps.push(step);
|
||||
}
|
||||
|
||||
check(checkCC, checkNoCC, 'cc', true);
|
||||
check(checkBg, checkNoBg, 'bg');
|
||||
check(checkGradient, checkNoGradient, 'gradient');
|
||||
|
||||
if (changeType) {
|
||||
switchTo(changeType);
|
||||
steps.push(wTourUtils.changeOption('ColoredLevelBackground', `.o_we_color_btn[data-color="${change}"]`));
|
||||
steps.push({
|
||||
trigger: finalSelector,
|
||||
content: "The selected colors have been applied (CC AND (BG or GRADIENT))",
|
||||
position: 'bottom',
|
||||
run: finalRun,
|
||||
});
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
tour.register('snippet_background_edition', {
|
||||
test: true,
|
||||
url: '/?enable_editor=1',
|
||||
},
|
||||
[
|
||||
wTourUtils.dragNDrop(snippets[0]),
|
||||
wTourUtils.clickOnSnippet(snippets[0]),
|
||||
|
||||
// Add a color combination
|
||||
...checkAndUpdateBackgroundColor({
|
||||
changeType: 'cc',
|
||||
change: 3,
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc3:not([class*=bg-]):not([style*="background"])`,
|
||||
}),
|
||||
|
||||
// Change the color combination + Check the previous one was marked as selected
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 3,
|
||||
changeType: 'cc',
|
||||
change: 2,
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc2:not(.o_cc3):not([class*=bg-])`,
|
||||
}),
|
||||
|
||||
// Check the color combination was marked as selected + Edit the bg color
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 2,
|
||||
checkNoCC: 3,
|
||||
changeType: 'bg',
|
||||
change: 'black-50',
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc2.bg-black-50`,
|
||||
}),
|
||||
|
||||
// Check the current color palette selection + Change the bg color
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 2,
|
||||
checkBg: 'black-50',
|
||||
changeType: 'bg',
|
||||
change: '800',
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc2.bg-800:not(.bg-black-50)`,
|
||||
}),
|
||||
|
||||
// Check the current color palette selection + Change the color combination
|
||||
// again. It should keep the bg color class.
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 2,
|
||||
checkBg: '800',
|
||||
checkNoBg: 'black-50',
|
||||
changeType: 'cc',
|
||||
change: 4,
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc4:not(.o_cc2).bg-800`,
|
||||
}),
|
||||
|
||||
// Check the current color palette status + Replace the bg color by a gradient
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 4,
|
||||
checkNoCC: 2,
|
||||
checkBg: '800',
|
||||
changeType: 'gradient',
|
||||
change: gradients[0],
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc4:not(.bg-800)[style*="background-image: ${gradients[0]}"]`,
|
||||
}),
|
||||
|
||||
// Check the current color palette status + Replace the gradient
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 4,
|
||||
checkNoBg: '800',
|
||||
checkGradient: gradients[0],
|
||||
changeType: 'gradient',
|
||||
change: gradients[1],
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc4[style*="background-image: ${gradients[1]}"]:not([style*="background-image: ${gradients[0]}"])`,
|
||||
}),
|
||||
|
||||
// Check the current color palette selection + Change the color combination
|
||||
// again. It should keep the gradient.
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 4,
|
||||
checkGradient: gradients[1],
|
||||
checkNoGradient: gradients[0],
|
||||
changeType: 'cc',
|
||||
change: 1,
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc1:not(.o_cc4)[style*="background-image: ${gradients[1]}"]`,
|
||||
}),
|
||||
|
||||
// Final check of the color status in the color palette
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 1,
|
||||
checkNoCC: 4,
|
||||
checkGradient: gradients[1],
|
||||
}),
|
||||
|
||||
// Now, add an image on top of that color combination + gradient
|
||||
wTourUtils.changeOption('ColoredLevelBackground', '[data-name="bg_image_toggle_opt"]'),
|
||||
{
|
||||
trigger: '.o_existing_attachment_cell img',
|
||||
content: "Select an image in the media dialog",
|
||||
},
|
||||
{
|
||||
trigger: `.${snippets[0].id}.o_cc.o_cc1`,
|
||||
run: function () {
|
||||
const parts = weUtils.backgroundImageCssToParts(this.$anchor.css('background-image'));
|
||||
if (!parts.url || !parts.url.startsWith('url(')) {
|
||||
console.error('An image should have been added as background.');
|
||||
}
|
||||
if (parts.gradient !== gradients[1]) {
|
||||
console.error('The gradient should have been kept when adding the background image');
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// Replace the gradient while there is a background-image
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 1,
|
||||
checkGradient: gradients[1],
|
||||
changeType: 'gradient',
|
||||
change: gradients[0],
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc1:not([style*="${gradients[1]}"])`,
|
||||
finalRun: function () {
|
||||
const parts = weUtils.backgroundImageCssToParts(this.$anchor.css('background-image'));
|
||||
if (!parts.url || !parts.url.startsWith('url(')) {
|
||||
console.error('The image should have been kept when changing the gradient');
|
||||
}
|
||||
if (parts.gradient !== gradients[0]) {
|
||||
console.error('The gradient should have been changed');
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Replace the gradient by a bg color
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 1,
|
||||
checkGradient: gradients[0],
|
||||
checkNoGradient: gradients[1],
|
||||
changeType: 'bg',
|
||||
change: 'black-75',
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc1.bg-black-75[style^="background-image: url("]:not([style*="${gradients[0]}"])`
|
||||
}),
|
||||
|
||||
// Re-add a gradient
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 1,
|
||||
checkBg: 'black-75',
|
||||
checkNoGradient: gradients[0],
|
||||
changeType: 'gradient',
|
||||
change: gradients[1],
|
||||
finalSelector: `.${snippets[0].id}.o_cc.o_cc1:not(.bg-black-75)`,
|
||||
finalRun: function () {
|
||||
const parts = weUtils.backgroundImageCssToParts(this.$anchor.css('background-image'));
|
||||
if (!parts.url || !parts.url.startsWith('url(')) {
|
||||
console.error('The image should have been kept when re-adding the gradient');
|
||||
}
|
||||
if (parts.gradient !== gradients[1]) {
|
||||
console.error('The gradient should have been re-added');
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// Final check of color selection and removing the image
|
||||
...checkAndUpdateBackgroundColor({
|
||||
checkCC: 1,
|
||||
checkNoBg: 'black-75',
|
||||
checkGradient: gradients[1],
|
||||
}),
|
||||
wTourUtils.changeOption('ColoredLevelBackground', '[data-name="bg_image_toggle_opt"]'),
|
||||
{
|
||||
trigger: `.${snippets[0].id}.o_cc.o_cc1[style*="background-image: ${gradients[1]}"]`,
|
||||
run: () => null,
|
||||
},
|
||||
|
||||
// Now removing all colors via the 'None' button (note: colorpicker still opened)
|
||||
{
|
||||
trigger: '.o_colorpicker_reset',
|
||||
content: "Click on the None button of the color palette",
|
||||
},
|
||||
{
|
||||
trigger: `.${snippets[0].id}:not(.o_cc):not(.o_cc1):not([style*="background-image"])`,
|
||||
content: "All color classes and properties should have been removed",
|
||||
run: () => null,
|
||||
}
|
||||
]);
|
||||
@@ -55,12 +55,6 @@ tour.register('snippet_empty_parent_autoremove', {
|
||||
trigger: '#wrap .s_banner .o_we_shape',
|
||||
run: () => null,
|
||||
},
|
||||
wTourUtils.changeOption('ColoredLevelBackground', 'Filter'),
|
||||
{
|
||||
content: "Check that background-filter is present",
|
||||
trigger: '#wrap .s_banner .o_we_bg_filter',
|
||||
run: () => null,
|
||||
},
|
||||
{
|
||||
content: "Click on first column",
|
||||
trigger: '#wrap .s_banner .row > :first-child',
|
||||
|
||||
@@ -206,3 +206,13 @@ class TestUi(odoo.tests.HttpCase):
|
||||
def test_10_website_conditional_visibility(self):
|
||||
self.start_tour('/', 'conditional_visibility_1', login='admin')
|
||||
self.start_tour('/', 'conditional_visibility_2', login='admin')
|
||||
|
||||
def test_11_website_snippet_background_edition(self):
|
||||
self.env['ir.attachment'].create({
|
||||
'public': True,
|
||||
'type': 'url',
|
||||
'url': '/web/image/123/test.png',
|
||||
'name': 'test.png',
|
||||
'mimetype': 'image/png',
|
||||
})
|
||||
self.start_tour('/', 'snippet_background_edition', login='admin')
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<t t-set="target" t-value="'.s_blockquote_content'"/>
|
||||
<t t-set="with_colors" t-value="True"/>
|
||||
<t t-set="with_images" t-value="True"/>
|
||||
<t t-set="with_gradients" t-value="True"/>
|
||||
<t t-set="images_dependencies" t-value="'blockquote_cover_opt'"/>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<t t-set="with_colors" t-value="True"/>
|
||||
<t t-set="with_images" t-value="False"/>
|
||||
<t t-set="with_color_combinations" t-value="True"/>
|
||||
<t t-set="with_gradients" t-value="True"/>
|
||||
</t>
|
||||
<div data-js="Box" data-selector=".s_media_list_item" data-target="> .row">
|
||||
<t t-call="website.snippet_options_border_widgets"/>
|
||||
|
||||
@@ -190,8 +190,8 @@
|
||||
<i class="fa fa-fw fa-film"/>
|
||||
</we-button>
|
||||
</xpath>
|
||||
<xpath expr="//we-button[@data-toggle-bg-filter]" position="attributes">
|
||||
<attribute name="data-dependencies" add="bg_video_toggler_opt" separator=","/>
|
||||
<xpath expr="//t[@t-set='color_filter_dependencies']" position="after">
|
||||
<t t-set="color_filter_dependencies" t-valuef="#{color_filter_dependencies}, bg_video_toggler_opt"/>
|
||||
</xpath>
|
||||
<xpath expr="//div[@data-js='BackgroundOptimize']" position="after">
|
||||
<!-- Parallax -->
|
||||
@@ -384,6 +384,7 @@
|
||||
<t t-set="with_colors" t-value="True"/>
|
||||
<t t-set="with_images" t-value="True"/>
|
||||
<t t-set="css_compatible" t-value="True"/>
|
||||
<t t-set="with_gradients" t-value="True"/>
|
||||
</t>
|
||||
<div data-js="Carousel"
|
||||
data-selector="section"
|
||||
@@ -481,6 +482,7 @@
|
||||
<t t-set="with_colors" t-value="True"/>
|
||||
<t t-set="with_images" t-value="True"/>
|
||||
<t t-set="with_color_combinations" t-value="True"/>
|
||||
<t t-set="with_gradients" t-value="True"/>
|
||||
</t>
|
||||
|
||||
<t t-call="web_editor.snippet_options_background_options">
|
||||
@@ -489,6 +491,7 @@
|
||||
<t t-set="with_colors" t-value="True"/>
|
||||
<t t-set="with_images" t-value="False"/>
|
||||
<t t-set="with_color_combinations" t-value="True"/>
|
||||
<t t-set="with_gradients" t-value="True"/>
|
||||
</t>
|
||||
|
||||
<t t-call="web_editor.snippet_options_background_options">
|
||||
@@ -552,7 +555,8 @@
|
||||
data-selector="[data-oe-expression='res_company.partner_id']"
|
||||
data-no-check="true"/>
|
||||
|
||||
<div data-selector="#wrapwrap > header"
|
||||
<div data-js="WebsiteLevelColor"
|
||||
data-selector="#wrapwrap > header"
|
||||
data-no-check="true"
|
||||
groups="website.group_website_designer">
|
||||
<we-select string="Template" data-variable="header-template" data-reload="/">
|
||||
@@ -628,10 +632,18 @@
|
||||
|
||||
<we-row string="Colors">
|
||||
<we-colorpicker data-customize-website-color="" data-color="menu"
|
||||
data-with-combinations="true"/>
|
||||
data-customize-website-layer2-color="" data-layer-color="menu-custom" data-layer-gradient="menu-gradient"
|
||||
data-no-bundle-reload="true"
|
||||
data-null-value="'NULL'"
|
||||
data-with-combinations="customizeWebsiteColor"
|
||||
data-with-gradients="true"/>
|
||||
<we-colorpicker data-dependencies="header_boxed_opt"
|
||||
data-customize-website-color="" data-color="header-boxed"
|
||||
data-with-combinations="true"/>
|
||||
data-customize-website-layer2-color="" data-layer-color="header-boxed-custom" data-layer-gradient="header-boxed-gradient"
|
||||
data-no-bundle-reload="true"
|
||||
data-null-value="'NULL'"
|
||||
data-with-combinations="customizeWebsiteColor"
|
||||
data-with-gradients="true"/>
|
||||
</we-row>
|
||||
</div>
|
||||
|
||||
@@ -834,7 +846,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Footer - Colors & Layouts -->
|
||||
<div data-selector="#wrapwrap > footer"
|
||||
<div data-js="WebsiteLevelColor"
|
||||
data-selector="#wrapwrap > footer"
|
||||
data-no-check="true"
|
||||
groups="website.group_website_designer">
|
||||
|
||||
@@ -878,7 +891,11 @@
|
||||
<!-- Colors -->
|
||||
<we-colorpicker string="Colors"
|
||||
data-customize-website-color="" data-color="footer"
|
||||
data-with-combinations="true"/>
|
||||
data-customize-website-layer2-color="" data-layer-color="footer-custom" data-layer-gradient="footer-gradient"
|
||||
data-no-bundle-reload="true"
|
||||
data-null-value="'NULL'"
|
||||
data-with-combinations="customizeWebsiteColor"
|
||||
data-with-gradients="true"/>
|
||||
<we-select string="Slideout Effect" data-variable="footer-effect" data-reload="/">
|
||||
<we-button string="Regular"
|
||||
data-customize-website-views=""
|
||||
@@ -941,12 +958,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Copyright -->
|
||||
<div data-selector=".o_footer_copyright"
|
||||
<div data-js="WebsiteLevelColor"
|
||||
data-selector=".o_footer_copyright"
|
||||
data-no-check="true"
|
||||
groups="website.group_website_designer">
|
||||
<we-colorpicker string="Colors"
|
||||
data-customize-website-color="" data-color="copyright"
|
||||
data-with-combinations="true"/>
|
||||
data-customize-website-layer2-color="" data-layer-color="copyright-custom" data-layer-gradient="copyright-gradient"
|
||||
data-no-bundle-reload="true"
|
||||
data-null-value="'NULL'"
|
||||
data-with-combinations="customizeWebsiteColor"
|
||||
data-with-gradients="true"/>
|
||||
<we-select string="Language Selector" data-reload="/">
|
||||
<we-button data-name="language_selector_none_opt"
|
||||
data-customize-website-views="">None</we-button>
|
||||
@@ -985,6 +1007,7 @@
|
||||
<we-row string="Background" class="o_we_full_row">
|
||||
<t t-call="web_editor.snippet_options_background_color_widget">
|
||||
<t t-set="with_color_combinations" t-value="True"/>
|
||||
<t t-set="with_gradients" t-value="True"/>
|
||||
</t>
|
||||
<we-button-group class="ml-auto">
|
||||
<we-imagepicker title="Image" data-background="" data-button-style="true"/>
|
||||
|
||||
Reference in New Issue
Block a user