diff options
| author | qsm-odoo <qsm@odoo.com> | 2019-11-19 15:23:44 +0000 |
|---|---|---|
| committer | qsm-odoo <qsm@odoo.com> | 2019-12-01 15:49:57 +0000 |
| commit | fdac73e99d6edce1544c25534f8903c637a5507e (patch) | |
| tree | 6fc957b40227130fc325f0abc9b244020e7f8c43 /addons/web_editor/static/src | |
| parent | 216cf93b4dab381286991dcf650e859931bf1a43 (diff) | |
[IMP] web_editor, *: add new user value colorpicker widget
* web, mass_mailing, website
Introduced a new sub-widget for snippet options: we-colorpicker. This
allows to retrieve a color value (either a normalized css color or a
theme color name) to be used for a snippet option method.
This commit replaces the old 'colorpicker' snippet option whose behavior
is now simply achieved by defining an xml option using a we-colorpicker
widget in combination with the 'selectStyle' standard method.
This commit also adds the possibility to control the border-color of
elements alongside their border-width. Again, just a we-colorpicker
widget in combination with the 'selectStyle' standard method.
Note: this commit reviewed what the colorpalette palette widget
notifies. This is not a cssColor and a 'isClass' flag to say if it is
a css class or not. The colorpalette always returns a color string which
can be the name of a color like 'alpha'. This is up to the caller to
check if the color string is a css color or not with isCSSColor. In the
case it is not, the related code can read the actual color by reading
the related css variable.
Part of https://github.com/odoo/odoo/pull/40282
In preparation of task-2122935 and other tasks
Diffstat (limited to 'addons/web_editor/static/src')
6 files changed, 282 insertions, 202 deletions
diff --git a/addons/web_editor/static/src/js/common/utils.js b/addons/web_editor/static/src/js/common/utils.js index 67267d670ee..c05e7a2064e 100644 --- a/addons/web_editor/static/src/js/common/utils.js +++ b/addons/web_editor/static/src/js/common/utils.js @@ -14,6 +14,7 @@ const ColorpickerDialog = require('web.ColorpickerDialog'); const CSS_SHORTHANDS = { 'border-width': ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'], 'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'], + 'border-color': ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'], }; /** * Key-value mapping to list converters from an unit A to an unit B. diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js index 233a38edb8c..befea94f328 100644 --- a/addons/web_editor/static/src/js/editor/rte.summernote.js +++ b/addons/web_editor/static/src/js/editor/rte.summernote.js @@ -4,6 +4,7 @@ odoo.define('web_editor.rte.summernote', function (require) { var Class = require('web.Class'); const concurrency = require('web.concurrency'); var core = require('web.core'); +const ColorpickerDialog = require('web.ColorpickerDialog'); var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget; var mixins = require('web.mixins'); var fonts = require('wysiwyg.fonts'); @@ -50,13 +51,17 @@ renderer.createPalette = function ($container, options) { const targetNode = r.sc; const targetElement = targetNode.nodeType === Node.ELEMENT_NODE ? targetNode : targetNode.parentNode; colorpicker = new ColorPaletteWidget(parent, { - colorPrefix: eventName === "foreColor" ? 'text-' : 'bg-', excluded: ['transparent_grayscale'], $editable: rte.Class.prototype.editable(), // Our parent is the root widget, we can't retrieve the editable section from it... selectedColor: $(targetElement).css(eventName === "foreColor" ? 'color' : 'backgroundColor'), - targetClasses: [...targetElement.classList], }); - colorpicker.on('color_picked', null, ev => applyColor(ev.data.target, eventName, ev.data.cssColor)); + colorpicker.on('color_picked', null, ev => { + let color = ev.data.color; + if (!ColorpickerDialog.isCSSColor(color)) { + color = (eventName === "foreColor" ? 'text-' : 'bg-') + color; + } + applyColor(ev.data.target, eventName, color); + }); colorpicker.on('color_reset', null, ev => applyColor(ev.data.target, eventName, 'inherit')); return colorpicker.replace(hookEl).then(() => { if (oldColorpicker) { diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index d4fd5c11e1b..6da88c2247a 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -567,6 +567,7 @@ const SelectUserValueWidget = UserValueWidget.extend({ return; } } + this._super(...arguments); }, //-------------------------------------------------------------------------- @@ -827,16 +828,169 @@ const MultiUserValueWidget = UserValueWidget.extend({ }, }); +const ColorpickerUserValueWidget = SelectUserValueWidget.extend({ // FIXME should be reloaded on focus + custom_events: { + 'color_picked': '_onColorPicked', + 'color_hover': '_onColorHovered', + 'color_leave': '_onColorLeft', + 'color_reset': '_onColorReset', + }, + + /** + * @override + */ + init: function () { + this._super(...arguments); + if (!this.title) { + this.title = ' '; + } + }, + /** + * @override + */ + start: async function () { + const _super = this._super.bind(this); + const args = arguments; + + this.el.classList.add('o_we_so_color_palette'); + + // Pre-instanciate the color palette widget + await this._renderColorPalette(); + + // Build the select element with a custom span to hold the color preview + this.colorPreviewEl = document.createElement('span'); + this.options.childNodes = [this.colorPalette.el]; + this.options.valueEl = this.colorPreviewEl; + + return _super(...args); + }, + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + getMethodsParams: function () { + return _.extend(this._super(...arguments), { + colorNames: this.colorPalette.getColorNames(), + }); + }, + /** + * @override + */ + getValue: function (methodName) { + return this._previewColor || this._super(...arguments); + }, + /** + * @override + */ + isContainer: function () { + return false; + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @private + * @returns {Promise} + */ + _renderColorPalette: function () { + const options = {}; + options.selectedColor = this._value; + if (this.options.dataAttributes.excluded) { + options.excluded = this.options.dataAttributes.excluded.replace(/ /g, '').split(','); + } + const oldColorPalette = this.colorPalette; + this.colorPalette = new ColorPaletteWidget(this, options); + if (oldColorPalette) { + return this.colorPalette.insertAfter(oldColorPalette.el).then(() => { + oldColorPalette.destroy(); + }); + } + return this.colorPalette.appendTo(document.createDocumentFragment()); + }, + /** + * Updates the color preview + re-render the whole color palette widget. + * + * @override + */ + _updateUI: async function (color) { + await this._super(...arguments); + + this.colorPreviewEl.classList.remove(...this.colorPalette.getColorNames().map(c => 'bg-' + c)); + this.colorPreviewEl.style.removeProperty('background-color'); + + if (this._value) { + if (ColorpickerDialog.isCSSColor(this._value)) { + this.colorPreviewEl.style.backgroundColor = this._value; + } else { + this.colorPreviewEl.classList.add('bg-' + this._value); + } + } + + await this._renderColorPalette(); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Called when a color button is clicked -> confirms the preview. + * + * @private + * @param {Event} ev + */ + _onColorPicked: function (ev) { + this._previewColor = false; + this._value = ev.data.color; + this._notifyValueChange(false); + }, + /** + * Called when a color button is entered -> previews the background color. + * + * @private + * @param {Event} ev + */ + _onColorHovered: function (ev) { + this._previewColor = ev.data.color; + this._notifyValueChange(true); + }, + /** + * Called when a color button is left -> cancels the preview. + * + * @private + * @param {Event} ev + */ + _onColorLeft: function (ev) { + this._previewColor = false; + this._notifyValueChange('reset'); + }, + /** + * Called when the color reset button is clicked -> removes all color + * classes and color styles. + * + * @private + */ + _onColorReset: function () { + this._value = ''; + this._notifyValueChange(false); + }, +}); + const userValueWidgetsRegistry = { 'we-button': ButtonUserValueWidget, 'we-checkbox': CheckboxUserValueWidget, 'we-select': SelectUserValueWidget, 'we-input': InputUserValueWidget, 'we-multi': MultiUserValueWidget, + 'we-colorpicker': ColorpickerUserValueWidget, }; -//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - /** * Handles a set of options for one snippet. The registry returned by this * module contains the names of the specialized SnippetOptionWidget which can be @@ -1024,7 +1178,54 @@ const SnippetOptionWidget = Widget.extend({ * @param {Object} params */ selectStyle: function (previewMode, widgetValue, params) { + if (params.cssProperty === 'background-color') { + this.$target.trigger('background-color-event', previewMode); + } + const 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); + } + + // 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). + if (params.colorNames && params.colorPrefix) { + const classes = params.colorNames.map(c => params.colorPrefix + c); + this.$target[0].classList.remove(...classes); + + if (params.colorNames.includes(widgetValue)) { + const originalCSSValue = window.getComputedStyle(this.$target[0])[cssProps[0]]; + const className = params.colorPrefix + widgetValue; + this.$target[0].classList.add(className); + if (originalCSSValue !== window.getComputedStyle(this.$target[0])[cssProps[0]]) { + // If applying the class did indeed changed the css + // property we are editing, nothing more has to be done. + // (except adding the extra class) + this.$target.addClass(params.extraClass); + return; + } + // Otherwise, it means that class probably does not exist, + // we remove it and continue. Especially useful for some + // prefixes which only work with some color names but not all. + this.$target[0].classList.remove(className); + } + } + + // 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. + const htmlStyle = window.getComputedStyle(document.documentElement); + const htmlPropValue = htmlStyle.getPropertyValue('--' + widgetValue); + if (htmlPropValue) { + widgetValue = `var(--${widgetValue})`; + } + const values = widgetValue.split(/\s+/g); while (values.length < cssProps.length) { switch (values.length) { @@ -1043,15 +1244,6 @@ const SnippetOptionWidget = Widget.extend({ } } - 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); - } - const styles = window.getComputedStyle(this.$target[0]); let hasUserValue = false; for (let i = cssProps.length - 1; i > 0; i--) { @@ -1160,6 +1352,14 @@ const SnippetOptionWidget = Widget.extend({ return dataValue || params.attributeDefaultValue || ''; } case 'selectStyle': { + if (params.colorPrefix && params.colorNames) { + for (const c of params.colorNames) { + if (this.$target[0].classList.contains(params.colorPrefix + c)) { + return c; + } + } + } + const styles = window.getComputedStyle(this.$target[0]); const cssProps = weUtils.CSS_SHORTHANDS[params.cssProperty] || [params.cssProperty]; const cssValues = cssProps.map(cssProp => { @@ -1198,7 +1398,7 @@ const SnippetOptionWidget = Widget.extend({ * @returns {string} */ _normalizeWidgetValue: function (value) { - value = `${value}`; // Force to string + value = `${value}`.trim(); // Force to a trimmed string value = ColorpickerDialog.normalizeCSSColor(value); // If is a css color, normalize it return value; }, @@ -1683,137 +1883,6 @@ registry['sizing_y'] = registry.sizing.extend({ }); /** - * Handles the edition of snippet's background color classes. - */ -registry.colorpicker = SnippetOptionWidget.extend({ - xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'], - custom_events: { - 'color_picked': '_onColorPicked', - 'color_hover': '_onColorHovered', - 'color_leave': '_onColorLeft', - 'color_reset': '_onColorReset', - }, - - /** - * @override - */ - start: async function () { - const _super = this._super.bind(this); - const args = arguments; - - // Pre-instanciate the color palette widget - const options = { - selectedColor: this.$target.css('background-color'), - targetClasses: [...this.$target[0].classList], - }; - if (this.data.paletteExclude) { - options.excluded = this.data.paletteExclude.replace(/ /g, '').split(','); - } - if (this.data.colorPrefix) { - options.colorPrefix = this.data.colorPrefix; - } - this.colorPalette = new ColorPaletteWidget(this, options); - await this.colorPalette.appendTo(document.createDocumentFragment()); - - // Build the select element with a custom span to hold the color preview - this.colorPreviewEl = document.createElement('span'); - const selectWidget = new SelectUserValueWidget(this, this.data.string, { - classes: ['o_we_so_color_palette'], - childNodes: [this.colorPalette.el], - valueEl: this.colorPreviewEl, - }); - await selectWidget.appendTo(document.createDocumentFragment()); - - // Replace the colorpicker UI with the select element - this.$el.empty().append(selectWidget.el); - return _super(...args); - }, - /** - * @override - */ - onFocus: function () { - this.colorPalette.reloadColorPalette(); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Change the color of the targeted background - * - * @private - * @param {string} cssColor - * @param {boolean} isClass - */ - _changeTargetColor: function (cssColor, isClass) { - this.$target[0].classList.remove(...this.colorPalette.getClasses()); - if (isClass) { - this.$target.addClass(cssColor); - } else { - this.$target.css('background-color', cssColor); - } - }, - /** - * @override - */ - _updateUI: async function () { - await this._super.apply(this, arguments); - this.colorPreviewEl.style.backgroundColor = this.$target.css('background-color'); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when a color button is clicked -> confirm the preview. - * - * @private - * @param {Event} ev - */ - _onColorPicked: function (ev) { - this._changeTargetColor(ev.data.cssColor); - this._updateUI().then(() => { - this.$target.closest('.o_editable').trigger('content_changed'); - this.$target.trigger('background-color-event', false); - }); - }, - /** - * Called when a color button is entered -> preview the background color. - * - * @private - * @param {Event} ev - */ - _onColorHovered: function (ev) { - this._changeTargetColor(ev.data.cssColor, ev.data.isClass); - this.$target.trigger('background-color-event', true); - }, - /** - * Called when a color button is left -> cancel the preview. - * - * @private - * @param {Event} ev - */ - _onColorLeft: function (ev) { - this._changeTargetColor(ev.data.cssColor, ev.data.isClass); - this.$target.trigger('background-color-event', 'reset'); - }, - /** - * Called when the color reset button is clicked -> remove all background - * color classes. - * - * @private - */ - _onColorReset: function () { - this._changeTargetColor(''); - this._updateUI().then(() => { - this.$target.trigger('content_changed'); - }); - }, -}); - -/** * Handles the edition of snippet's background image. */ registry.background = SnippetOptionWidget.extend({ diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js b/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js index 38edef463b6..04a21c79378 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js @@ -27,8 +27,6 @@ const ColorPaletteWidget = Widget.extend({ * * @param {Object} [options] * @param {string} [options.selectedColor] The class or css attribute color selected by default. - * @param {string[]} [options.targetClasses] - * @param {string} [options.colorPrefix='bg-'] Used for the class based colors (theme). * @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. @@ -39,8 +37,6 @@ const ColorPaletteWidget = Widget.extend({ this.summernoteCustomColorsArray = [].concat(...summernoteCustomColors); this.options = _.extend({ selectedColor: false, - targetClasses: [], - colorPrefix: 'bg-', resetButton: true, excluded: [], excludeSectionOf: null, @@ -124,31 +120,26 @@ const ColorPaletteWidget = Widget.extend({ this.$el.append($('<button/>', {'class': 'd-none', 'data-color': 'secondary'})); // Compute class colors - this.classes = []; + this.colorNames = []; + const style = window.getComputedStyle(document.documentElement); this.el.querySelectorAll('button[data-color]').forEach(elem => { - const color = elem.dataset.color; + const colorName = elem.dataset.color; + const color = ColorpickerDialog.normalizeCSSColor(style.getPropertyValue('--' + colorName).trim()); const $color = $(elem); - $color.addClass('bg-' + color); - const className = this.options.colorPrefix + color; - if (this.options.targetClasses.includes(className)) { + $color.addClass('bg-' + colorName); + if (this.options.selectedColor && (this.options.selectedColor === colorName || this.options.selectedColor === color)) { $color.addClass('selected'); } - this.classes.push(className); + this.colorNames.push(colorName); }); return res; }, /** - * Return a list of the color classes used in the color palette + * Return a list of the color names used in the color palette */ - getClasses: function () { - return this.classes; - }, - /** - * Reloads the color palette to get other custom colors on the page - */ - reloadColorPalette: function () { - this._buildCustomColors(); + getColorNames: function () { + return this.colorNames; }, //-------------------------------------------------------------------------- @@ -215,10 +206,9 @@ const ColorPaletteWidget = Widget.extend({ */ _getButtonInfo: function (buttonEl) { const bgColor = buttonEl.style.backgroundColor; - const cssColor = bgColor ? ColorpickerDialog.normalizeCSSColor(bgColor) : (this.options.colorPrefix + buttonEl.dataset.color); + const color = bgColor ? ColorpickerDialog.normalizeCSSColor(bgColor) : buttonEl.dataset.color; return { - cssColor: cssColor, - isClass: !!buttonEl.dataset.color, + color: color, target: buttonEl, }; }, @@ -263,8 +253,7 @@ const ColorPaletteWidget = Widget.extend({ params = this._getButtonInfo(selected); } else { params = { - cssColor: '', - isClass: false, + color: '', }; } params.target = ev.target; @@ -297,8 +286,7 @@ const ColorPaletteWidget = Widget.extend({ colorpicker.on('colorpicker:saved', this, ev => { this._addCustomColorButton(ev.data.cssColor, ['selected']); this.trigger_up('color_picked', { - cssColor: ev.data.cssColor, - isClass: false, + color: ev.data.cssColor, target: target, }); }); diff --git a/addons/web_editor/static/src/scss/web_editor.common.scss b/addons/web_editor/static/src/scss/web_editor.common.scss index baac51c3adf..2de7d389843 100644 --- a/addons/web_editor/static/src/scss/web_editor.common.scss +++ b/addons/web_editor/static/src/scss/web_editor.common.scss @@ -2,6 +2,12 @@ /// This file regroups basic style rules for web_editor enable page edition and backend utils. /// +:root { + @each $color, $value in $grays { + --#{$color}: #{$value}; + } +} + html, body { position: relative; width: 100%; diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index 404bccc7b53..b1f8d1259ae 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -297,26 +297,6 @@ body.editor_enable.editor_has_snippets { @include we-icon-button('\f107'); } - we-row { - display: block; - - > div { - display: flex; - align-items: center; - - > * { - flex: 1 1 auto; - - + * { - margin-left: 0.5em; - } - } - > .fa, > .fas, > .fab, > .far { - flex: 0 0 auto; - } - } - } - we-select { position: relative; display: flex; @@ -374,13 +354,8 @@ body.editor_enable.editor_has_snippets { } we-input { - display: flex; - align-items: center; + @extend we-row.o_we_inline; - > we-title { - min-width: 60%; - margin-right: 5%; - } > input { flex: 1 1 auto; width: 0; @@ -398,11 +373,47 @@ body.editor_enable.editor_has_snippets { } } - we-row we-input { - flex-wrap: wrap; + we-row { + position: relative; + display: block; - > we-title { - min-width: 95%; + > div { + flex: 1 1 auto; + display: flex; + align-items: center; + + > * { + flex: 1 1 auto; + + + * { + margin-left: 0.5em; + } + } + > .fa, > .fas, > .fab, > .far { + flex: 0 0 auto; + } + } + + &.o_we_inline { + display: flex; + align-items: center; + + > we-title { + min-width: 60%; + margin-right: 5%; + } + } + + we-select { + position: static; + } + + we-input { + flex-wrap: wrap; + + > we-title { + min-width: 95%; + } } } |
