summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
authorSamuel Degueldre <sad@odoo.com>2020-07-31 06:47:34 +0000
committerqsm-odoo <qsm@odoo.com>2020-08-07 15:34:45 +0000
commitb84e0af742c51b88b4c108ebec2d0c7fff4b7483 (patch)
tree5f26347b2ffc0dd30ddbe6193c21218a9e922c91 /addons/web_editor/static/src
parentc3dacfea05c531683bfcf94243597ce2d82aeab8 (diff)
[IMP] web_editor, website: add background shapes snippet option
This commit adds a new snippet option allowing the user to choose one of a series of SVG shapes as their background, or to be overlaid over the background image of a snippet and to customize the colors of those shapes. Part of https://github.com/odoo/odoo/pull/53017 task-2210790 closes odoo/odoo#53017 Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/js/common/utils.js15
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js451
-rw-r--r--addons/web_editor/static/src/scss/web_editor.frontend.scss47
-rw-r--r--addons/web_editor/static/src/scss/web_editor.variables.scss98
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_snippets.scss44
5 files changed, 612 insertions, 43 deletions
diff --git a/addons/web_editor/static/src/js/common/utils.js b/addons/web_editor/static/src/js/common/utils.js
index aaa02c58589..c66bb75a47c 100644
--- a/addons/web_editor/static/src/js/common/utils.js
+++ b/addons/web_editor/static/src/js/common/utils.js
@@ -200,6 +200,20 @@ function _normalizeColor(color) {
}
return _getCSSVariableValue(color);
}
+/**
+ * Parse an element's background-image's url.
+ *
+ * @param {string} string a css value in the form 'url("...")'
+ * @returns {string|false} the src of the image or false if not parsable
+ */
+function _getBgImageURL(el) {
+ const string = $(el).css('background-image');
+ const match = string.match(/^url\((['"])(.*?)\1\)$/);
+ if (!match) {
+ return '';
+ }
+ return match[2];
+}
return {
CSS_SHORTHANDS: CSS_SHORTHANDS,
@@ -213,5 +227,6 @@ return {
computeColorClasses: _computeColorClasses,
getCSSVariableValue: _getCSSVariableValue,
normalizeColor: _normalizeColor,
+ getBgImageURL: _getBgImageURL,
};
});
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 1060140c619..e752d07f66c 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -8,7 +8,10 @@ const time = require('web.time');
var Widget = require('web.Widget');
var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget;
const weUtils = require('web_editor.utils');
-const {normalizeColor} = weUtils;
+const {
+ normalizeColor,
+ getBgImageURL,
+} = weUtils;
var weWidgets = require('wysiwyg.widgets');
const {
loadImage,
@@ -1588,6 +1591,87 @@ const RangeUserValueWidget = UserValueWidget.extend({
},
});
+const SelectPagerUserValueWidget = SelectUserValueWidget.extend({
+ className: (SelectUserValueWidget.prototype.className || '') + ' o_we_select_pager',
+ events: Object.assign({}, SelectUserValueWidget.prototype.events, {
+ 'click .o_we_pager_next, .o_we_pager_prev': '_onPageChange',
+ }),
+
+ /**
+ * @override
+ */
+ async start() {
+ const _super = this._super.bind(this);
+ this.pages = this.options.childNodes.filter(node => node.matches && node.matches('we-select-page'));
+ this.numPages = this.pages.length;
+
+ const prev = document.createElement('i');
+ prev.classList.add('o_we_pager_prev', 'fa', 'fa-chevron-left');
+
+ this.pageNum = document.createElement('span');
+ this.currentPage = 0;
+ this._updatePageNum();
+
+ const next = document.createElement('i');
+ next.classList.add('o_we_pager_next', 'fa', 'fa-chevron-right');
+
+ const pagerControls = document.createElement('div');
+ pagerControls.classList.add('o_we_pager_controls');
+ pagerControls.appendChild(prev);
+ pagerControls.appendChild(this.pageNum);
+ pagerControls.appendChild(next);
+
+ await _super(...arguments);
+ this.menuEl.classList.add('o_we_has_pager');
+ $(this.menuEl).prepend(pagerControls);
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Updates the pager's page number display.
+ *
+ * @private
+ */
+ _updatePageNum() {
+ this.pages.forEach((page, i) => page.classList.toggle('active', i === this.currentPage));
+ this.pageNum.textContent = `${this.currentPage + 1}/${this.numPages}`;
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Goes to the previous/next page with wrap-around.
+ *
+ * @private
+ */
+ _onPageChange(ev) {
+ ev.preventDefault();
+ ev.stopPropagation();
+ const delta = ev.target.matches('.o_we_pager_next') ? 1 : -1;
+ this.currentPage = (this.currentPage + this.numPages + delta) % this.numPages;
+ this._updatePageNum();
+ },
+ /**
+ * @override
+ */
+ _onClick(ev) {
+ const activeButton = this._getActiveSubWidget();
+ if (activeButton) {
+ const currentPage = this.pages.indexOf(activeButton.el.closest('we-select-page'));
+ if (currentPage !== -1) {
+ this.currentPage = currentPage;
+ this._updatePageNum();
+ }
+ }
+ return this._super(...arguments);
+ },
+});
+
const userValueWidgetsRegistry = {
'we-button': ButtonUserValueWidget,
'we-checkbox': CheckboxUserValueWidget,
@@ -1600,6 +1684,7 @@ const userValueWidgetsRegistry = {
'we-imagepicker': ImagepickerUserValueWidget,
'we-videopicker': VideopickerUserValueWidget,
'we-range': RangeUserValueWidget,
+ 'we-select-pager': SelectPagerUserValueWidget,
};
/**
@@ -3087,18 +3172,6 @@ registry.ImageOptimize = ImageHandlerOption.extend({
});
/**
- * Returns the src value from a css value related to a background image
- * (e.g. "url('blabla')" => "blabla" / "none" => "").
- *
- * @param {string} value
- * @returns {string}
- */
-const getSrcFromCssValue = value => {
- var srcValueWrapper = /url\(['"]*|['"]*\)|^none$/g;
- return value && value.replace(srcValueWrapper, '') || '';
-};
-
-/**
* Controls background image width and quality.
*/
registry.BackgroundOptimize = ImageHandlerOption.extend({
@@ -3166,9 +3239,9 @@ registry.BackgroundOptimize = ImageHandlerOption.extend({
Object.entries(this.$target[0].dataset).forEach(([key, value]) => {
this.img.dataset[key] = value;
});
- const src = new URL(getSrcFromCssValue(this.$target.css('background-image')), window.location.origin);
+ const src = new URL(getBgImageURL(this.$target[0]), window.location.origin);
// Make URL relative because that is how image urls are stored in the database.
- this.img.src = src.origin === window.location.origin ? src.pathname : src;
+ this.img.src = src.origin === window.location.origin && src.pathname;
return await this._super(...arguments);
},
@@ -3213,6 +3286,18 @@ registry.BackgroundToggler = SnippetOptionWidget.extend({
this._requestUserValueWidgets('bg_image_opt')[0].el.click();
}
},
+ /**
+ * Toggles background shape on or off.
+ *
+ * @see this.selectClass for parameters
+ */
+ toggleBgShape(previewMode, widgetValue, params) {
+ const [shapeWidget] = this._requestUserValueWidgets('bg_shape_opt');
+ const shapeOption = shapeWidget.getParent();
+ // TODO: open select after shape was selected?
+ // TODO: use setWidgetValue instead of calling shapeOption method directly when possible
+ return shapeOption._toggleShape();
+ },
//--------------------------------------------------------------------------
// Private
@@ -3222,10 +3307,17 @@ registry.BackgroundToggler = SnippetOptionWidget.extend({
* @override
*/
_computeWidgetState(methodName, params) {
- if (methodName === 'toggleBgImage') {
- const [bgImageWidget] = this._requestUserValueWidgets('bg_image_opt');
- const bgImageOpt = bgImageWidget.getParent();
- return !!bgImageOpt._computeWidgetState('background', bgImageWidget.getMethodsParams('background'));
+ switch (methodName) {
+ case 'toggleBgImage': {
+ const [bgImageWidget] = this._requestUserValueWidgets('bg_image_opt');
+ const bgImageOpt = bgImageWidget.getParent();
+ return !!bgImageOpt._computeWidgetState('background', bgImageWidget.getMethodsParams('background'));
+ }
+ case 'toggleBgShape': {
+ const [shapeWidget] = this._requestUserValueWidgets('bg_shape_opt');
+ const shapeOption = shapeWidget.getParent();
+ return !!shapeOption._computeWidgetState('shape', shapeWidget.getMethodsParams('shape'));
+ }
}
return this._super(...arguments);
},
@@ -3239,7 +3331,7 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
* @override
*/
start: function () {
- this.__customImageSrc = this._getSrcFromCssValue();
+ this.__customImageSrc = getBgImageURL(this.$target[0]);
return this._super(...arguments);
},
@@ -3254,7 +3346,7 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
*/
background: async function (previewMode, widgetValue, params) {
if (previewMode === true) {
- this.__customImageSrc = this._getSrcFromCssValue();
+ this.__customImageSrc = getBgImageURL(this.$target[0]);
} else if (previewMode === 'reset') {
widgetValue = this.__customImageSrc;
} else {
@@ -3285,7 +3377,7 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
setTarget: function () {
this._super(...arguments);
// TODO should be automatic for all options as equal to the start method
- this.__customImageSrc = this._getSrcFromCssValue();
+ this.__customImageSrc = getBgImageURL(this.$target[0]);
},
//--------------------------------------------------------------------------
@@ -3293,23 +3385,309 @@ registry.BackgroundImage = SnippetOptionWidget.extend({
//--------------------------------------------------------------------------
/**
- * Returns the background image's src.
+ * @override
+ */
+ _computeWidgetState: function (methodName) {
+ if (methodName === 'background') {
+ return getBgImageURL(this.$target[0]);
+ }
+ return this._super(...arguments);
+ },
+});
+
+/**
+ * Handles background shapes.
+ */
+registry.BackgroundShape = SnippetOptionWidget.extend({
+ /**
+ * @override
+ */
+ updateUI() {
+ if (this.rerender) {
+ this.rerender = false;
+ return this._rerenderXML();
+ }
+ return this._super.apply(this, arguments);
+ },
+
+ //--------------------------------------------------------------------------
+ // Options
+ //--------------------------------------------------------------------------
+
+ /**
+ * Sets the current background shape.
*
- * @private
- * @returns {string}
+ * @see this.selectClass for params
+ */
+ shape(previewMode, widgetValue, params) {
+ this._handlePreviewState(previewMode, () => {
+ return {shape: widgetValue, colors: this._getDefaultColors(), flip: []};
+ });
+ },
+ /**
+ * Sets the current background shape's colors.
+ *
+ * @see this.selectClass for params
+ */
+ color(previewMode, widgetValue, params) {
+ this._handlePreviewState(previewMode, () => {
+ const {colorName} = params;
+ const {colors: previousColors} = this._getShapeData();
+ const newColor = widgetValue || this._getDefaultColors()[colorName];
+ const newColors = Object.assign(previousColors, {[colorName]: newColor});
+ return {colors: newColors};
+ });
+ },
+ /**
+ * Flips the shape on its x axis.
+ *
+ * @see this.selectClass for params
*/
- _getSrcFromCssValue: function () {
- return getSrcFromCssValue(this.$target.css('background-image'));
+ flipX(previewMode, widgetValue, params) {
+ this._flipShape(previewMode, 'x');
},
/**
+ * Flips the shape on its y axis.
+ *
+ * @see this.selectClass for params
+ */
+ flipY(previewMode, widgetValue, params) {
+ this._flipShape(previewMode, 'y');
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
* @override
*/
- _computeWidgetState: function (methodName) {
- if (methodName === 'background') {
- return this._getSrcFromCssValue();
+ _computeWidgetState(methodName, params) {
+ switch (methodName) {
+ case 'shape': {
+ return this._getShapeData().shape;
+ }
+ case 'color': {
+ const {shape, colors: customColors} = this._getShapeData();
+ const colors = Object.assign(this._getDefaultColors(), customColors);
+ const color = shape && colors[params.colorName];
+ return color ? normalizeColor(color) : '';
+ }
+ case 'flipX': {
+ return this.$target.find('> .o_we_shape.o_we_flip_x').length !== 0;
+ }
+ case 'flipY': {
+ return this.$target.find('> .o_we_shape.o_we_flip_y').length !== 0;
+ }
+ }
+ return this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ _renderCustomXML(uiFragment) {
+ Object.keys(this._getDefaultColors()).map(colorName => {
+ uiFragment.querySelector('[data-name="colors"]')
+ .prepend($(`<we-colorpicker data-color="true" data-color-name="${colorName}">`)[0]);
+ });
+
+ uiFragment.querySelectorAll('we-select-pager we-button[data-shape]').forEach(btn => {
+ const btnContent = document.createElement('div');
+ btnContent.classList.add('o_we_shape_btn_content', 'position-relative', 'border-dark');
+ const btnContentInnerDiv = document.createElement('div');
+ btnContentInnerDiv.classList.add('o_we_shape');
+ btnContent.appendChild(btnContentInnerDiv);
+
+ const {shape} = btn.dataset;
+ const shapeEl = btnContent.querySelector('.o_we_shape');
+ shapeEl.classList.add(`o_${shape.replace(/\//g, '_')}`);
+ btn.append(btnContent);
+ });
+ return uiFragment;
+ },
+ /**
+ * @override
+ */
+ async _computeWidgetVisibility(widgetName, params) {
+ if (widgetName === 'shape_none_opt') {
+ return false;
}
return this._super(...arguments);
},
+ /**
+ * Flips the shape on its x/y axis.
+ *
+ * @param {boolean} previewMode
+ * @param {'x'|'y'} axis the axis of the shape that should be flipped.
+ */
+ _flipShape(previewMode, axis) {
+ this._handlePreviewState(previewMode, () => {
+ const flip = new Set(this._getShapeData().flip);
+ if (flip.has(axis)) {
+ flip.delete(axis);
+ } else {
+ flip.add(axis);
+ }
+ return {flip: [...flip]};
+ });
+ },
+ /**
+ * Handles everything related to saving state before preview and restoring
+ * it after a preview or locking in the changes when not in preview.
+ *
+ * @param {boolean} previewMode
+ * @param {function} computeShapeData function to compute the new shape data.
+ */
+ _handlePreviewState(previewMode, computeShapeData) {
+ if (previewMode === 'reset') {
+ if (this.prevShape) {
+ this.$target[0].dataset.oeShapeData = this.prevShape;
+ } else {
+ delete this.$target[0].dataset.oeShapeData;
+ }
+ } else {
+ if (previewMode === true) {
+ this.prevShape = this.$target[0].dataset.oeShapeData;
+ }
+ const newShapeData = computeShapeData();
+ this._markShape(newShapeData);
+ if (previewMode === false) {
+ this.prevShape = this.$target[0].dataset.oeShapeData;
+ if (newShapeData.hasOwnProperty('shape')) {
+ // Need to rerender for correct number of colorpickers
+ this.rerender = true;
+ }
+ }
+ }
+
+ // Updates/removes the shape container as needed and gives it the
+ // correct background shape
+ const json = this.$target[0].dataset.oeShapeData;
+ const {shape, colors, flip = []} = json ? JSON.parse(json) : {};
+ const target = this.$target[0];
+ let shapeContainer = target.querySelector(':scope > .o_we_shape');
+ if (!shape) {
+ if (shapeContainer) {
+ shapeContainer.remove();
+ }
+ return;
+ }
+ if (!shapeContainer) {
+ shapeContainer = document.createElement('div');
+ target.prepend(shapeContainer);
+ if (shapeContainer.nextElementSibling.matches('.s_parallax_bg')) {
+ // Move parallax before shape so it shows on top.
+ target.prepend(shapeContainer.nextElementSibling);
+ }
+ target.style.position = 'relative';
+ }
+ shapeContainer.className = `o_we_shape o_${shape.replace(/\//g, '_')}`;
+ flip.forEach(dir => {
+ shapeContainer.classList.add(`o_we_flip_${dir}`);
+ });
+ // Custom colors, overwrite shape that is set by the class
+ $(shapeContainer).css('background-image', colors ? `url("${this._getShapeSrc()}")` : '');
+ },
+ /**
+ * Overwrites shape properties with the specified data.
+ *
+ * @private
+ * @param {Object} newData an object with the new data
+ */
+ _markShape(newData) {
+ const defaultColors = this._getDefaultColors();
+ const shapeData = Object.assign(this._getShapeData(), newData);
+ const areColorsDefault = Object.entries(shapeData.colors).every(([colorName, colorValue]) => {
+ return colorValue === defaultColors[colorName];
+ });
+ if (areColorsDefault) {
+ delete shapeData.colors;
+ }
+ if (!shapeData.shape) {
+ delete this.$target[0].dataset.oeShapeData;
+ } else {
+ this.$target[0].dataset.oeShapeData = JSON.stringify(shapeData);
+ }
+ },
+ /**
+ * Returns the src of the shape corresponding to the current parameters.
+ *
+ * @private
+ */
+ _getShapeSrc() {
+ const {shape, colors} = this._getShapeData();
+ if (!shape) {
+ return '';
+ }
+ const queryString = Object.entries(colors)
+ .map(([colorName, colorValue]) => {
+ const encodedCol = encodeURIComponent(normalizeColor(colorValue));
+ return `${colorName}=${encodedCol}`;
+ }).join('&');
+ return `/web_editor/shape/${shape}.svg?${queryString}`;
+ },
+ /**
+ * Retrieves current shape data from the target's dataset.
+ *
+ * @private
+ * @param {HTMLElement} [target=this.$target[0]] the target on which to read
+ * the shape data.
+ */
+ _getShapeData(target = this.$target[0]) {
+ const defaultData = {
+ shape: '',
+ colors: this._getDefaultColors(),
+ flip: [],
+ };
+ const json = target.dataset.oeShapeData;
+ return json ? Object.assign(defaultData, JSON.parse(json)) : defaultData;
+ },
+ /**
+ * Returns the default colors for the currently selected shape.
+ *
+ * @private
+ */
+ _getDefaultColors() {
+ const $shapeContainer = this.$target.find('> .o_we_shape')
+ .clone()
+ .addClass('d-none')
+ // Needs to be in document for bg-image class to take effect
+ .appendTo(document.body);
+ const shapeContainer = $shapeContainer[0];
+ $shapeContainer.css('background-image', '');
+ const shapeSrc = shapeContainer && getBgImageURL(shapeContainer);
+ $shapeContainer.remove();
+ if (!shapeSrc) {
+ return {};
+ }
+ const url = new URL(shapeSrc, window.location.origin);
+ return Object.fromEntries(url.searchParams.entries());
+ },
+ /**
+ * Toggles whether there is a shape or not, to be called from bg toggler.
+ *
+ * @private
+ */
+ _toggleShape() {
+ if (this._getShapeData().shape) {
+ return this._handlePreviewState(false, () => ({shape: ''}));
+ } else {
+ const target = this.$target[0];
+ const previousSibling = target.previousElementSibling;
+ const [shapeWidget] = this._requestUserValueWidgets('bg_shape_opt');
+ const possibleShapes = shapeWidget.getMethodsParams('shape').possibleValues;
+ let shapeToSelect;
+ if (previousSibling) {
+ const previousShape = this._getShapeData(previousSibling).shape;
+ shapeToSelect = possibleShapes.find((shape, i) => {
+ return possibleShapes[i - 1] === previousShape;
+ });
+ } else {
+ shapeToSelect = possibleShapes[1];
+ }
+ return this._handlePreviewState(false, () => ({shape: shapeToSelect}));
+ }
+ },
});
/**
@@ -3364,7 +3742,7 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
await new Promise(resolve => {
this.img = document.createElement('img');
this.img.addEventListener('load', () => resolve());
- this.img.src = this._getSrcFromCssValue();
+ this.img.src = getBgImageURL(this.$target[0]);
});
const position = this.$target.css('background-position').split(' ').map(v => parseInt(v));
@@ -3399,7 +3777,7 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
* @override
*/
_computeVisibility: function () {
- return this._super(...arguments) && (this.$target.css('background-image') !== 'none');
+ return this._super(...arguments) && !!getBgImageURL(this.$target[0]);
},
/**
* @override
@@ -3507,15 +3885,6 @@ registry.BackgroundPosition = SnippetOptionWidget.extend({
window.setTimeout(() => $(document).on('click.bgposition', this._onDocumentClicked.bind(this)), 0);
},
/**
- * Returns the background image's src.
- *
- * @private
- * @returns {string}
- */
- _getSrcFromCssValue: function () {
- return getSrcFromCssValue(this.$target.css('background-image'));
- },
- /**
* Returns the difference between the target's size and the background's
* rendered size. Background position values in % are a percentage of this.
*
diff --git a/addons/web_editor/static/src/scss/web_editor.frontend.scss b/addons/web_editor/static/src/scss/web_editor.frontend.scss
index a1d057316f8..172ab7fbe22 100644
--- a/addons/web_editor/static/src/scss/web_editor.frontend.scss
+++ b/addons/web_editor/static/src/scss/web_editor.frontend.scss
@@ -31,3 +31,50 @@
}
}
}
+
+// Background shapes
+[data-oe-shape-data] > * {
+ position: relative;
+}
+
+.o_we_shape {
+ @include o-position-absolute(0,0,0,0);
+ // position: absolute needs to be !important otherwise
+ // some classes (video background) override it with position: relative
+ position: absolute !important;
+ background-repeat: no-repeat;
+
+ @each $module, $shapes in $o-bg-shapes {
+ @each $shape, $style in $shapes {
+ $url-params: '';
+ $colors: map-get($style, 'colors');
+ @each $i in $colors {
+ // %23 is the url-encoded form of '#' which doesn't work as is in urls.
+ $color: str-replace("#{map-get($o-color-palette, "o-color-#{$i}")}", '#', '%23');
+ $url-params: '#{$url-params}&c#{$i}=#{$color}';
+ }
+
+ // eg: o_website_shape_bg_1
+ &.o_#{$module}_#{str-replace($shape, '/', '_')} {
+ // When not customized, this URL, built in SCSS, allows for the
+ // shape to be dynamic and adapted if future color changes.
+ background-image: url("/web_editor/shape/#{$module}/#{$shape}.svg?#{str-slice($url-params, 2)}");
+ background-position: map-get($style, 'position');
+ background-size: map-get($style, 'size');
+ @if map-get($style, 'repeat-y') {
+ background-repeat: repeat-y;
+ }
+ }
+ }
+ }
+}
+
+.o_we_flip_x {
+ transform: scaleX(-1);
+}
+.o_we_flip_y {
+ transform: scaleY(-1);
+}
+.o_we_flip_x.o_we_flip_y {
+ transform: scale(-1);
+}
diff --git a/addons/web_editor/static/src/scss/web_editor.variables.scss b/addons/web_editor/static/src/scss/web_editor.variables.scss
index f71d4d36184..0c483f9d668 100644
--- a/addons/web_editor/static/src/scss/web_editor.variables.scss
+++ b/addons/web_editor/static/src/scss/web_editor.variables.scss
@@ -578,3 +578,101 @@ $o-color-extras-nesting-selector: '&, .o_colored_level &';
}
}
}
+
+// format: (module_name: (shape_filename: ('position': X, 'size': Y, 'colors': (1, [3], ...)), ...))
+$o-bg-shapes: ('web_editor': (
+ 'Airy/01': ('position': bottom, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Airy/02': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Airy/03': ('position': top, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Airy/04': ('position': center, 'size': 100% 100%, 'colors': (1), 'repeat-y': false),
+ 'Airy/05': ('position': center, 'size': 100% 100%, 'colors': (1), 'repeat-y': false),
+ 'Airy/06': ('position': bottom, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Airy/07': ('position': top, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Airy/08': ('position': bottom, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Airy/09': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Airy/10': ('position': bottom, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Airy/11': ('position': top, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Airy/12': ('position': top, 'size': 100% auto, 'colors': (1, 3), 'repeat-y': false),
+ 'Airy/13': ('position': bottom, 'size': 100% auto, 'colors': (1, 4), 'repeat-y': false),
+ 'Airy/14': ('position': bottom, 'size': 100% auto, 'colors': (1, 4), 'repeat-y': false),
+ 'Blobs/01': ('position': top, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Blobs/02': ('position': center, 'size': 100% 100%, 'colors': (1, 2), 'repeat-y': false),
+ 'Blobs/03': ('position': top, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Blobs/04': ('position': center, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Blobs/05': ('position': bottom, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Blobs/06': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Blobs/07': ('position': top, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Blobs/08': ('position': right, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Blobs/09': ('position': bottom, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Blobs/10': ('position': top, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Blobs/11': ('position': center, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Blobs/12': ('position': bottom, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Blocks/01': ('position': bottom, 'size': 100% auto, 'colors': (1, 3, 5), 'repeat-y': false),
+ 'Blocks/02': ('position': top, 'size': 100% auto, 'colors': (1, 3, 5), 'repeat-y': false),
+ 'Blocks/03': ('position': bottom, 'size': 100% auto, 'colors': (1, 4), 'repeat-y': false),
+ 'Blocks/04': ('position': bottom, 'size': 100% auto, 'colors': (1, 2, 3, 5), 'repeat-y': false),
+ 'Bold/01': ('position': top, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Bold/02': ('position': bottom, 'size': 100% auto, 'colors': (1, 2, 3), 'repeat-y': false),
+ 'Bold/03': ('position': bottom, 'size': 100% auto, 'colors': (1, 3, 5), 'repeat-y': false),
+ 'Bold/04': ('position': top, 'size': 100% auto, 'colors': (2, 3), 'repeat-y': false),
+ 'Bold/05': ('position': center, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Bold/06': ('position': center, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Bold/07': ('position': bottom, 'size': 100% auto, 'colors': (1, 2), 'repeat-y': false),
+ 'Bold/08': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Bold/09': ('position': bottom, 'size': 100% auto, 'colors': (2, 3), 'repeat-y': false),
+ 'Bold/10': ('position': top, 'size': 100% auto, 'colors': (1, 3, 4, 5), 'repeat-y': false),
+ 'Bold/11': ('position': bottom, 'size': 100% auto, 'colors': (1, 2, 3), 'repeat-y': false),
+ 'Bold/12': ('position': center, 'size': 100% auto, 'colors': (1, 2, 5), 'repeat-y': false),
+ 'Origins/01': ('position': bottom, 'size': 100% auto, 'colors': (2, 5), 'repeat-y': false),
+ 'Origins/02': ('position': bottom, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Origins/03': ('position': top, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Origins/04': ('position': bottom, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Origins/05': ('position': top, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Origins/06': ('position': center, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Origins/07': ('position': center, 'size': 100% 100%, 'colors': (3), 'repeat-y': false),
+ 'Origins/08': ('position': bottom, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Origins/09': ('position': top, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Origins/10': ('position': bottom, 'size': 100% auto, 'colors': (2, 5), 'repeat-y': false),
+ 'Origins/11': ('position': bottom, 'size': 100% auto, 'colors': (3, 5), 'repeat-y': false),
+ 'Origins/12': ('position': top, 'size': 100% auto, 'colors': (3, 5), 'repeat-y': false),
+ 'Origins/13': ('position': center, 'size': 100% auto, 'colors': (3, 5), 'repeat-y': false),
+ 'Origins/14': ('position': bottom, 'size': 100% auto, 'colors': (4), 'repeat-y': false),
+ 'Origins/15': ('position': top, 'size': 100% auto, 'colors': (4), 'repeat-y': false),
+ 'Rainy/01': ('position': bottom, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Rainy/02': ('position': top, 'size': 100% auto, 'colors': (1, 4, 5), 'repeat-y': false),
+ 'Rainy/03': ('position': top, 'size': 100% auto, 'colors': (2, 4, 5), 'repeat-y': true),
+ 'Rainy/04': ('position': top, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Rainy/05': ('position': top, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Rainy/06': ('position': bottom, 'size': 100% auto, 'colors': (1, 2, 3), 'repeat-y': false),
+ 'Rainy/07': ('position': top, 'size': 100% auto, 'colors': (1, 2, 3), 'repeat-y': false),
+ 'Rainy/08': ('position': top, 'size': 100% auto, 'colors': (1, 4), 'repeat-y': false),
+ 'Rainy/09': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Wavy/01': ('position': bottom, 'size': 100% auto, 'colors': (4), 'repeat-y': false),
+ 'Wavy/02': ('position': top, 'size': 100% auto, 'colors': (4), 'repeat-y': false),
+ 'Wavy/03': ('position': top, 'size': 100% auto, 'colors': (1, 2), 'repeat-y': false),
+ 'Wavy/04': ('position': bottom, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Wavy/05': ('position': top, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Wavy/06': ('position': top, 'size': 100% auto, 'colors': (1, 3, 4, 5), 'repeat-y': false),
+ 'Wavy/07': ('position': top, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Wavy/08': ('position': top, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Wavy/09': ('position': bottom, 'size': 100% auto, 'colors': (1, 5), 'repeat-y': false),
+ 'Wavy/10': ('position': center, 'size': 100% auto, 'colors': (1, 2), 'repeat-y': false),
+ 'Wavy/11': ('position': bottom, 'size': 100% auto, 'colors': (1, 4), 'repeat-y': false),
+ 'Wavy/12': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Wavy/13': ('position': bottom, 'size': 100% auto, 'colors': (4), 'repeat-y': false),
+ 'Wavy/14': ('position': bottom, 'size': 100% auto, 'colors': (1, 3), 'repeat-y': false),
+ 'Wavy/15': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Wavy/16': ('position': bottom, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Wavy/17': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Wavy/18': ('position': bottom, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Wavy/19': ('position': top, 'size': 100% auto, 'colors': (5), 'repeat-y': false),
+ 'Wavy/20': ('position': bottom, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Wavy/21': ('position': top, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Wavy/22': ('position': bottom, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Wavy/23': ('position': top, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+ 'Zigs/01': ('position': bottom, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Zigs/02': ('position': bottom, 'size': 100% auto, 'colors': (2), 'repeat-y': false),
+ 'Zigs/03': ('position': top, 'size': 100% auto, 'colors': (1), 'repeat-y': true),
+ 'Zigs/04': ('position': bottom, 'size': 100% auto, 'colors': (1), 'repeat-y': false),
+ 'Zigs/05': ('position': bottom, 'size': 100% auto, 'colors': (3), 'repeat-y': false),
+));
diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
index 7c7e7814f26..aa925a19cb7 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -506,10 +506,13 @@ body.editor_enable.editor_has_snippets {
we-selection-items {
@include o-position-absolute(100%, 0, auto, 0);
z-index: $zindex-dropdown;
- max-height: 600px;
- overflow-y: auto;
margin-top: $o-we-sidebar-content-field-dropdown-spacing !important;
+ &:not(.o_we_has_pager) {
+ max-height: 600px;
+ overflow-y: auto;
+ }
+
> we-title {
line-height: $o-we-sidebar-content-field-dropdown-item-height;
}
@@ -541,6 +544,25 @@ body.editor_enable.editor_has_snippets {
}
}
}
+ .o_we_pager_controls {
+ display: flex;
+ align-items: center;
+ padding: $o-we-sidebar-content-field-label-spacing;
+ }
+ .o_we_pager_next, .o_we_pager_prev {
+ padding: $o-we-sidebar-content-field-label-spacing;
+ cursor: pointer;
+ }
+ we-select-page {
+ display: none;
+ width: 100%;
+ max-height: 600px;
+ overflow-y: auto;
+
+ &.active {
+ display: block;
+ }
+ }
}
// Button groups
@@ -1437,3 +1459,21 @@ body.editor_enable.editor_has_snippets {
.o_container_preview {
outline: 2px dashed $o-we-handles-accent-color;
}
+
+we-select.o_we_shape_menu {
+ we-button[data-shape] {
+ padding: 0 !important;
+
+ &.active {
+ border: 1px solid $o-we-color-success !important;
+ }
+ div {
+ width: 100%;
+ }
+ .o_we_shape_btn_content {
+ width: 100%;
+ height: 75px;
+ background-color: lightgray; // Must be a color which can make all shapes appear
+ }
+ }
+}