diff --git a/addons/mass_mailing/static/src/js/mass_mailing_snippets.js b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js index 72832b02357..9d7a5992944 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_snippets.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_snippets.js @@ -2,6 +2,8 @@ odoo.define('mass_mailing.snippets.options', function (require) { "use strict"; var options = require('web_editor.snippets.options'); +const {ColorpickerWidget} = require('web.Colorpicker'); +const {_t} = require('web.core'); // Snippet option for resizing image and column width inline like excel options.registry.mass_mailing_sizing_x = options.Class.extend({ @@ -139,4 +141,60 @@ options.registry.BackgroundImage = options.registry.BackgroundImage.extend({ } }); +options.registry.ImageOptimize.include({ + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * @override + */ + async updateUIVisibility() { + await this._super(...arguments); + + // The image shape option should work correctly with this update of the + // ImageOptimize option but unfortunately, SVG support in mail clients + // prevents the final rendering of the image. For now, we disable the + // feature. + const imgShapeContainerEl = this.el.querySelector('.o_we_image_shape'); + if (imgShapeContainerEl) { + imgShapeContainerEl.classList.toggle('d-none', !odoo.debug); + } + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _getCSSColorValue(color) { + const doc = this.options.document; + if (doc && doc.querySelector('.o_mass_mailing_iframe') && !ColorpickerWidget.isCSSColor(color)) { + const tempEl = doc.body.appendChild(doc.createElement('div')); + tempEl.className = `bg-${color}`; + const colorValue = window.getComputedStyle(tempEl).getPropertyValue("background-color").trim(); + tempEl.parentNode.removeChild(tempEl); + return ColorpickerWidget.normalizeCSSColor(colorValue).replace(/"/g, "'"); + } + return this._super(...arguments); + }, + /** + * @override + */ + async _renderCustomWidgets(uiFragment) { + await this._super(...arguments); + + const imgShapeTitleEl = uiFragment.querySelector('.o_we_image_shape we-title'); + if (imgShapeTitleEl) { + const warningEl = document.createElement('i'); + warningEl.classList.add('fa', 'fa-exclamation-triangle', 'ml-1'); + warningEl.title = _t("Be aware that this option may not work on many mail clients"); + imgShapeTitleEl.appendChild(warningEl); + } + }, +}); + }); diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 5f2e714f5a8..1726a5f128c 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -485,7 +485,7 @@ class Web_Editor(http.Controller): return View.render_public_asset(xmlid, {k: values[k] for k in values if k in trusted_value_keys}) @http.route('/web_editor/modify_image/', type="json", auth="user", website=True) - def modify_image(self, attachment, res_model=None, res_id=None, name=None, data=None, original_id=None): + def modify_image(self, attachment, res_model=None, res_id=None, name=None, data=None, original_id=None, mimetype=None): """ Creates a modified copy of an attachment and returns its image_src to be inserted into the DOM. @@ -495,6 +495,7 @@ class Web_Editor(http.Controller): 'datas': data, 'type': 'binary', 'res_model': res_model or 'ir.ui.view', + 'mimetype': mimetype or attachment.mimetype, } if fields['res_model'] == 'ir.ui.view': fields['res_id'] = 0 diff --git a/addons/web_editor/static/image_shapes/basic/bsc_organic_1.svg b/addons/web_editor/static/image_shapes/basic/bsc_organic_1.svg new file mode 100644 index 00000000000..2d6e77b4492 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_organic_1.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_organic_2.svg b/addons/web_editor/static/image_shapes/basic/bsc_organic_2.svg new file mode 100644 index 00000000000..472c2d2a45f --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_organic_2.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_organic_3.svg b/addons/web_editor/static/image_shapes/basic/bsc_organic_3.svg new file mode 100644 index 00000000000..c39ed0765c4 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_organic_3.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_1.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_1.svg new file mode 100644 index 00000000000..ccfe8948892 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_1.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_2.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_2.svg new file mode 100644 index 00000000000..bb3265a7ba0 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_2.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_3.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_3.svg new file mode 100644 index 00000000000..3c5d1d75f9c --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_3.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_4.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_4.svg new file mode 100644 index 00000000000..17c876308eb --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_4.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_5.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_5.svg new file mode 100644 index 00000000000..9d7337e416c --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_5.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_6.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_6.svg new file mode 100644 index 00000000000..1629f7447b8 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_6.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_rounded_1.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_rounded_1.svg new file mode 100644 index 00000000000..15b178df8c0 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_rounded_1.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/basic/bsc_square_rounded_2.svg b/addons/web_editor/static/image_shapes/basic/bsc_square_rounded_2.svg new file mode 100644 index 00000000000..6b38d2daed3 --- /dev/null +++ b/addons/web_editor/static/image_shapes/basic/bsc_square_rounded_2.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/convert-to-percentages.html b/addons/web_editor/static/image_shapes/convert-to-percentages.html new file mode 100644 index 00000000000..cf93a00616b --- /dev/null +++ b/addons/web_editor/static/image_shapes/convert-to-percentages.html @@ -0,0 +1,17 @@ + + + + + SVGs to Clip Path converter + + +

This tool is made to help designers import shapes that have a clip path component.

+

The shape must have at least one path set with an id="filterPath" and a maximum of 5 background colors

+ + +
+

Your download link will appear here

+
+ + + diff --git a/addons/web_editor/static/image_shapes/convert-to-percentages.js b/addons/web_editor/static/image_shapes/convert-to-percentages.js new file mode 100644 index 00000000000..2fb4ca601c7 --- /dev/null +++ b/addons/web_editor/static/image_shapes/convert-to-percentages.js @@ -0,0 +1,159 @@ +// The goal of this script is to have a shape ready for use with the +// "Shape on Image" feature of Odoo. +// Therefor we need to rearrange the file a little. +// Marks which axis each parameter of a command belongs to, as well as whether +// It's a positional measurement (x/y), a distance (dx/dy) or none (angles, flags) +const commandAxes = { + 'M': ['x', 'y'], + 'm': ['dx', 'dy'], + 'L': ['x', 'y'], + 'l': ['dx', 'dy'], + 'H': ['x'], + 'h': ['dx'], + 'V': ['y'], + 'v': ['dy'], + 'Z': [], + 'z': [], + 'C': ['x', 'y', 'x', 'y', 'x', 'y'], + 'c': ['dx', 'dy', 'dx', 'dy', 'dx', 'dy'], + 'S': ['x', 'y', 'x', 'y'], + 's': ['dx', 'dy', 'dx', 'dy'], + 'Q': ['x', 'y', 'x', 'y'], + 'q': ['dx', 'dy', 'dx', 'dy'], + 'T': ['x', 'y'], + 't': ['dx', 'dy'], + 'A': ['dx', 'dy', 'none', 'none', 'none', 'x', 'y'], + 'a': ['dx', 'dy', 'none', 'none', 'none', 'dx', 'dy'], +}; + +const toUserSpace = (x, y, width, height, precision = 4) => ({ + x: val => +((parseFloat(val) - x) / width).toFixed(precision), + dx: val => +(parseFloat(val) / width).toFixed(precision), + y: val => +((parseFloat(val) - y) / height).toFixed(precision), + dy: val => +(parseFloat(val) / height).toFixed(precision), + none: val => val, +}); + +const filePicker = document.getElementById('svgPicker'); +const submitButton = document.getElementById('submitButton'); +submitButton.addEventListener('click', async (ev) => { + if (!filePicker.files.length > 0) { + alert('Please select files using the file picker first'); + return; + } + Array.from(filePicker.files).forEach(async file => { + const fileReader = new FileReader(); + const readerPromise = new Promise((resolve, reject) => { + fileReader.addEventListener('load', () => resolve(fileReader.result)); + fileReader.addEventListener('error', () => reject(fileReader.error)); + }); + fileReader.readAsText(file, 'utf-8'); + const svgString = await readerPromise; + const parser = new DOMParser(); + const svg = parser.parseFromString(svgString, 'image/svg+xml'); + const path = svg.getElementById('filterPath'); + const svgDocumentElement = svg.documentElement; + // Some SVGs come without xlink + svgDocumentElement.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink'); + // We add the SVG to the body so we can take measurements of its + // original size + document.body.appendChild(svg.documentElement); + const { x, y, width, height } = svgDocumentElement.getBBox(); + const scalers = toUserSpace(x, y, width, height); + + // Converts the clipPath in values between 0 and 1 so we can use + // object bounding box as clip path units. It will make the clip path + // always adapt to the size of the picture. + const commands = path.getAttribute('d').match(/[a-z][^a-z]*/ig).map(c => { + return c.split(/[, ]|(?=-)|(?<=[a-z])(?=[0-9])/i).filter(part => !!part.length); + }); + const relSpaceCommands = commands.map(([command, ...nums]) => { + const axes = commandAxes[command]; + const relSpaceNums = nums.map((n, i) => { + const scaler = scalers[axes[i % axes.length]]; + return scaler(n); + }); + return `${command}${relSpaceNums.join(',')}`.replace(/,-/g, '-'); + }); + path.setAttribute('d', relSpaceCommands.join('')); + path.removeAttribute('fill'); + svgDocumentElement.removeAttribute('viewBox'); + + let defsEl = svgDocumentElement.querySelector('defs'); + if (!defsEl) { + defsEl = svg.createElementNS('http://www.w3.org/2000/svg', 'defs'); + svgDocumentElement.appendChild(defsEl); + } + + let clipPathEl = svgDocumentElement.querySelector('clipPath'); + if (!clipPathEl) { + clipPathEl = svg.createElementNS('http://www.w3.org/2000/svg', 'clipPath'); + clipPathEl.setAttribute('id', 'clip-path'); + defsEl.appendChild(clipPathEl); + } + + clipPathEl.setAttribute('clipPathUnits', 'objectBoundingBox'); + const backgroundEls = svgDocumentElement.getElementsByClassName('background'); + // We set the BG elements into their own svg so that when the total + // space gets stretched out, so does the backgrounds elements + Array.from(backgroundEls).forEach(el => { + const bgBbox = el.getBBox(); + const svgBackground = document.createElement('svg'); + const strokeWidth = el.getAttribute('stroke-width'); + // If the background has a strokeWidth, the viewBox need to take it + // into account + if (strokeWidth) { + const adj = parseFloat(strokeWidth) / 2; + svgBackground.setAttributeNS('http://www.w3.org/2000/svg', 'viewBox', + `${bgBbox.x - adj} ${bgBbox.y - adj} ${bgBbox.width + (adj * 2)} ${bgBbox.height + (adj * 2)}`); + } else { + svgBackground.setAttributeNS('http://www.w3.org/2000/svg', 'viewBox', `${bgBbox.x} ${bgBbox.y} ${bgBbox.width} ${bgBbox.height}`); + } + svgBackground.setAttributeNS('http://www.w3.org/2000/svg', 'preserveAspectRatio', 'none'); + svgBackground.appendChild(el); + svgDocumentElement.appendChild(svgBackground); + }); + + defsEl.appendChild(path); + // Setting the clip path for use and for preview + const useClipPathEl = document.createElementNS('http://www.w3.org/2000/svg', 'use'); + useClipPathEl.setAttributeNS('http://www.w3.org/1999/xlink', 'href', '#filterPath'); + useClipPathEl.setAttribute('fill', 'none'); + clipPathEl.appendChild(useClipPathEl); + + const svgPreviewEl = svg.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svgPreviewEl.setAttributeNS('http://www.w3.org/2000/svg', 'viewBox', '0 0 1 1'); + svgPreviewEl.setAttribute('width', '800'); + svgPreviewEl.setAttribute('height', '600'); + svgPreviewEl.setAttribute('id', 'preview'); + svgPreviewEl.setAttributeNS('http://www.w3.org/2000/svg', 'preserveAspectRatio', 'none'); + const previewUseEl = useClipPathEl.cloneNode(true); + previewUseEl.setAttribute('fill', 'darkgrey'); + svgPreviewEl.appendChild(previewUseEl); + svgDocumentElement.appendChild(svgPreviewEl); + + const imageEl = document.createElement('image'); + imageEl.setAttribute('xlink:href', ''); + imageEl.setAttribute('clip-path', 'url(#clip-path)'); + svgDocumentElement.appendChild(imageEl); + // Give a default size to the SVGs for an easier preview on disk + svgDocumentElement.setAttribute('width', '800'); + svgDocumentElement.setAttribute('height', '600'); + + const outFile = new File([svgDocumentElement.outerHTML], filePicker.files[0].name, { type: 'image/svg+xml' }); + const outFileReader = new FileReader(); + const outReaderPromise = new Promise((resolve, reject) => { + outFileReader.addEventListener('load', () => resolve(outFileReader.result)); + outFileReader.addEventListener('error', () => reject(outFileReader.error)); + }); + outFileReader.readAsDataURL(outFile); + const dataURL = await outReaderPromise; + + const downloadLinkEl = document.createElement('a'); + downloadLinkEl.href = dataURL; + downloadLinkEl.innerText = 'Download'; + downloadLinkEl.setAttribute('download', file.name); + downloadLinkEl.classList.add('dl_link'); + document.getElementById('downloadArea').appendChild(downloadLinkEl); + }); +}); diff --git a/addons/web_editor/static/image_shapes/float/flt_planets_1.svg b/addons/web_editor/static/image_shapes/float/flt_planets_1.svg new file mode 100644 index 00000000000..ab8eae8a9ec --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_planets_1.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_planets_2.svg b/addons/web_editor/static/image_shapes/float/flt_planets_2.svg new file mode 100644 index 00000000000..afd938782d1 --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_planets_2.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_primary_1.svg b/addons/web_editor/static/image_shapes/float/flt_primary_1.svg new file mode 100644 index 00000000000..7556a4fb3ec --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_primary_1.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_primary_2.svg b/addons/web_editor/static/image_shapes/float/flt_primary_2.svg new file mode 100644 index 00000000000..f0cc8cdff93 --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_primary_2.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_primary_3.svg b/addons/web_editor/static/image_shapes/float/flt_primary_3.svg new file mode 100644 index 00000000000..2321c7319b8 --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_primary_3.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_square_1.svg b/addons/web_editor/static/image_shapes/float/flt_square_1.svg new file mode 100644 index 00000000000..105162da79f --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_square_1.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_square_2.svg b/addons/web_editor/static/image_shapes/float/flt_square_2.svg new file mode 100644 index 00000000000..aa679f2067b --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_square_2.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_square_3.svg b/addons/web_editor/static/image_shapes/float/flt_square_3.svg new file mode 100644 index 00000000000..358c75ab672 --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_square_3.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/float/flt_square_4.svg b/addons/web_editor/static/image_shapes/float/flt_square_4.svg new file mode 100644 index 00000000000..2a5758f9ead --- /dev/null +++ b/addons/web_editor/static/image_shapes/float/flt_square_4.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/line_square_1.svg b/addons/web_editor/static/image_shapes/line/line_square_1.svg new file mode 100644 index 00000000000..57644889428 --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/line_square_1.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/line_star.svg b/addons/web_editor/static/image_shapes/line/line_star.svg new file mode 100644 index 00000000000..601779591d6 --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/line_star.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/line_sun.svg b/addons/web_editor/static/image_shapes/line/line_sun.svg new file mode 100644 index 00000000000..fbe18764a85 --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/line_sun.svg @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/line_triangle.svg b/addons/web_editor/static/image_shapes/line/line_triangle.svg new file mode 100644 index 00000000000..e0c5283475d --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/line_triangle.svg @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/little_lines_1.svg b/addons/web_editor/static/image_shapes/line/little_lines_1.svg new file mode 100644 index 00000000000..80f30dfeb77 --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/little_lines_1.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/little_lines_2.svg b/addons/web_editor/static/image_shapes/line/little_lines_2.svg new file mode 100644 index 00000000000..96354a04bb6 --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/little_lines_2.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/little_lines_3.svg b/addons/web_editor/static/image_shapes/line/little_lines_3.svg new file mode 100644 index 00000000000..51eedb256b9 --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/little_lines_3.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/organic_1_line.svg b/addons/web_editor/static/image_shapes/line/organic_1_line.svg new file mode 100644 index 00000000000..925a746f20d --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/organic_1_line.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/oval_2_line.svg b/addons/web_editor/static/image_shapes/line/oval_2_line.svg new file mode 100644 index 00000000000..a40a728b9ff --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/oval_2_line.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/line/oval_3_pattern_line.svg b/addons/web_editor/static/image_shapes/line/oval_3_pattern_line.svg new file mode 100644 index 00000000000..fa10f0521ae --- /dev/null +++ b/addons/web_editor/static/image_shapes/line/oval_3_pattern_line.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/organic_2_pattern_dot.svg b/addons/web_editor/static/image_shapes/pattern/organic_2_pattern_dot.svg new file mode 100644 index 00000000000..b47013db8d7 --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/organic_2_pattern_dot.svg @@ -0,0 +1,662 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/organic_3_pattern_cross.svg b/addons/web_editor/static/image_shapes/pattern/organic_3_pattern_cross.svg new file mode 100644 index 00000000000..03d70f2f408 --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/organic_3_pattern_cross.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/organic_4_pattern_caps.svg b/addons/web_editor/static/image_shapes/pattern/organic_4_pattern_caps.svg new file mode 100644 index 00000000000..2fe3d9e129e --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/organic_4_pattern_caps.svg @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/pattern_labyrinth.svg b/addons/web_editor/static/image_shapes/pattern/pattern_labyrinth.svg new file mode 100644 index 00000000000..8c4b18f1012 --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/pattern_labyrinth.svg @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/pattern_points_1.svg b/addons/web_editor/static/image_shapes/pattern/pattern_points_1.svg new file mode 100644 index 00000000000..8d2fe37444a --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/pattern_points_1.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/pattern_waves_1.svg b/addons/web_editor/static/image_shapes/pattern/pattern_waves_1.svg new file mode 100644 index 00000000000..babae1e93b3 --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/pattern_waves_1.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/pattern_waves_2.svg b/addons/web_editor/static/image_shapes/pattern/pattern_waves_2.svg new file mode 100644 index 00000000000..2c1b6c5ca73 --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/pattern_waves_2.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/pattern_waves_3.svg b/addons/web_editor/static/image_shapes/pattern/pattern_waves_3.svg new file mode 100644 index 00000000000..a5fca9f1b8a --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/pattern_waves_3.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/pattern/pattern_waves_4.svg b/addons/web_editor/static/image_shapes/pattern/pattern_waves_4.svg new file mode 100644 index 00000000000..c4752773973 --- /dev/null +++ b/addons/web_editor/static/image_shapes/pattern/pattern_waves_4.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/blob_1_solid_rd.svg b/addons/web_editor/static/image_shapes/solid/blob_1_solid_rd.svg new file mode 100644 index 00000000000..49d2706e366 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/blob_1_solid_rd.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/blob_2_solid_str.svg b/addons/web_editor/static/image_shapes/solid/blob_2_solid_str.svg new file mode 100644 index 00000000000..8d827b39601 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/blob_2_solid_str.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/blob_3_solid_rd.svg b/addons/web_editor/static/image_shapes/solid/blob_3_solid_rd.svg new file mode 100644 index 00000000000..742008c61a6 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/blob_3_solid_rd.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/oval_1_solid_rd.svg b/addons/web_editor/static/image_shapes/solid/oval_1_solid_rd.svg new file mode 100644 index 00000000000..d7c4fdab5e8 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/oval_1_solid_rd.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/sld_blob_4.svg b/addons/web_editor/static/image_shapes/solid/sld_blob_4.svg new file mode 100644 index 00000000000..c7b3fb9717a --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/sld_blob_4.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/sld_blob_shadow_1.svg b/addons/web_editor/static/image_shapes/solid/sld_blob_shadow_1.svg new file mode 100644 index 00000000000..162c24d0c78 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/sld_blob_shadow_1.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/sld_blob_shadow_2.svg b/addons/web_editor/static/image_shapes/solid/sld_blob_shadow_2.svg new file mode 100644 index 00000000000..7bcd52ced84 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/sld_blob_shadow_2.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/sld_square_1.svg b/addons/web_editor/static/image_shapes/solid/sld_square_1.svg new file mode 100644 index 00000000000..2caa6b3a00b --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/sld_square_1.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/sld_square_2.svg b/addons/web_editor/static/image_shapes/solid/sld_square_2.svg new file mode 100644 index 00000000000..6fdb10a3f87 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/sld_square_2.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/solid/sld_square_organic_1.svg b/addons/web_editor/static/image_shapes/solid/sld_square_organic_1.svg new file mode 100644 index 00000000000..4b92cd83955 --- /dev/null +++ b/addons/web_editor/static/image_shapes/solid/sld_square_organic_1.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_circuit_1.svg b/addons/web_editor/static/image_shapes/special/spl_circuit_1.svg new file mode 100644 index 00000000000..6d496261e44 --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_circuit_1.svg @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_filter_1.svg b/addons/web_editor/static/image_shapes/special/spl_filter_1.svg new file mode 100644 index 00000000000..cc2a278e624 --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_filter_1.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_flag_1.svg b/addons/web_editor/static/image_shapes/special/spl_flag_1.svg new file mode 100644 index 00000000000..a68177553ad --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_flag_1.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_organics_1.svg b/addons/web_editor/static/image_shapes/special/spl_organics_1.svg new file mode 100644 index 00000000000..d75bfbca0fc --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_organics_1.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_rain_1.svg b/addons/web_editor/static/image_shapes/special/spl_rain_1.svg new file mode 100644 index 00000000000..69037ebdb9b --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_rain_1.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_snow_1.svg b/addons/web_editor/static/image_shapes/special/spl_snow_1.svg new file mode 100644 index 00000000000..f492449992b --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_snow_1.svg @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/image_shapes/special/spl_speed_1.svg b/addons/web_editor/static/image_shapes/special/spl_speed_1.svg new file mode 100644 index 00000000000..f7ca285903d --- /dev/null +++ b/addons/web_editor/static/image_shapes/special/spl_speed_1.svg @@ -0,0 +1,263 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/addons/web_editor/static/src/js/editor/image_processing.js b/addons/web_editor/static/src/js/editor/image_processing.js index e3497634678..6ee86c50be6 100644 --- a/addons/web_editor/static/src/js/editor/image_processing.js +++ b/addons/web_editor/static/src/js/editor/image_processing.js @@ -197,12 +197,12 @@ const glFilters = { * @param {HTMLImageElement} img the image to which modifications are applied * @returns {string} dataURL of the image with the applied modifications */ -async function applyModifications(img) { +async function applyModifications(img, dataOptions = {}) { const data = Object.assign({ glFilter: '', filter: '#0000', quality: '75', - }, img.dataset); + }, img.dataset, dataOptions); let { width, height, 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 1b9f5e1e756..d4ff1ea90d9 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -12,6 +12,7 @@ const weUtils = require('web_editor.utils'); const { normalizeColor, getBgImageURL, + DEFAULT_PALETTE, } = weUtils; var weWidgets = require('wysiwyg.widgets'); const { @@ -4453,7 +4454,7 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ */ async _renderCustomXML(uiFragment) { const img = this._getImg(); - if (!this.originalSrc || !['image/png', 'image/jpeg'].includes(img.dataset.mimetype)) { + if (!this.originalSrc || !['image/png', 'image/jpeg'].includes(this._getImageMimetype(img))) { [...uiFragment.childNodes].forEach(node => node.remove()); return; } @@ -4462,7 +4463,7 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ $select.append(`${label}`); }); - if (img.dataset.mimetype !== 'image/jpeg') { + if (this._getImageMimetype(img) !== 'image/jpeg') { uiFragment.querySelector('we-range[data-set-quality]').remove(); } }, @@ -4504,11 +4505,11 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ if (!update && !(img && img.complete)) { return; } - if (!['image/jpeg', 'image/png'].includes(img.dataset.mimetype)) { + if (!['image/jpeg', 'image/png'].includes(this._getImageMimetype(img))) { this.originalId = null; return; } - const dataURL = await applyModifications(img); + const dataURL = await applyModifications(img, {mimetype: this._getImageMimetype(img)}); this._filesize = dataURL.split(',')[1].length / 4 * 3 / 1024; if (update) { @@ -4571,12 +4572,44 @@ const ImageHandlerOption = SnippetOptionWidget.extend({ * @param {HTMLImageElement} img */ _applyImage(img) {}, + /** + * @private + * @param {HTMLImageElement} img + * @returns {String} The right mimetype used to apply options on image. + */ + _getImageMimetype(img) { + return img.dataset.mimetype; + }, }); +/** + * @param {Element} containerEl + * @returns {Element} + */ +const _addAnimatedShapeLabel = function addAnimatedShapeLabel(containerEl) { + const labelEl = document.createElement('span'); + labelEl.classList.add('o_we_shape_animated_label'); + const labelStr = _t("Animated"); + labelEl.textContent = labelStr[0]; + const spanEl = document.createElement('span'); + spanEl.textContent = labelStr.substr(1); + labelEl.appendChild(spanEl); + containerEl.classList.add('position-relative'); + containerEl.appendChild(labelEl); + return labelEl; +}; + /** * Controls image width and quality. */ registry.ImageOptimize = ImageHandlerOption.extend({ + /** + * @constructor + */ + init() { + this.shapeCache = {}; + return this._super(...arguments); + }, /** * @override */ @@ -4593,10 +4626,160 @@ registry.ImageOptimize = ImageHandlerOption.extend({ return this._super(...arguments); }, + //-------------------------------------------------------------------------- + // Options + //-------------------------------------------------------------------------- + + /** + * @see this.selectClass for parameters + */ + async setImgShape(previewMode, widgetValue, params) { + const img = this._getImg(); + const saveData = previewMode === false; + if (widgetValue) { + await this._loadShape(widgetValue); + if (previewMode === 'reset' && img.dataset.shapeColors) { + // When we reset the shape we need to reapply the colors the + // user had selected. + await this._applyShapeAndColors(false, img.dataset.shapeColors.split(';')); + } else { + // If the preview mode === false we want to save the colors + // as the user chose their shape + await this._applyShapeAndColors(saveData); + if (saveData && img.dataset.mimetype !== 'image/svg+xml') { + img.dataset.originalMimetype = img.dataset.mimetype; + img.dataset.mimetype = 'image/svg+xml'; + } + } + } else { + // Re-applying the modifications and deleting the shapes + img.src = await applyModifications(img, {mimetype: this._getImageMimetype(img)}); + delete img.dataset.shape; + delete img.dataset.shapeColors; + delete img.dataset.fileName; + if (saveData) { + img.dataset.mimetype = img.dataset.originalMimetype; + delete img.dataset.originalMimetype; + } + } + img.classList.add('o_modified_image_to_save'); + }, + /** + * Handles color assignment on the shape. Widget is a color picker. + * If no value, we reset to the current color palette. + * + * @see this.selectClass for parameters + */ + async setImgShapeColor(previewMode, widgetValue, params) { + const img = this._getImg(); + const newColorId = parseInt(params.colorId); + const oldColors = img.dataset.shapeColors.split(';'); + const newColors = oldColors.slice(0); + newColors[newColorId] = this._getCSSColorValue(widgetValue === '' ? `o-color-${(newColorId + 1)}` : widgetValue); + await this._applyShapeAndColors(true, newColors); + img.classList.add('o_modified_image_to_save'); + }, + //-------------------------------------------------------------------------- // Private //-------------------------------------------------------------------------- + /** + * @override + */ + async _applyOptions() { + const img = await this._super(...arguments); + if (img && img.dataset.shape) { + // Reapplying the shape + await this._loadShape(img.dataset.shape); + await this._applyShapeAndColors(true, (img.dataset.shapeColors && img.dataset.shapeColors.split(';'))); + } + return img; + }, + /** + * Loads the shape into cache if not already and sets it in the dataset of + * the img + * + * @param {string} shapeName identifier of the shape + */ + async _loadShape(shapeName) { + const [module, directory, fileName] = shapeName.split('/'); + let shape = this.shapeCache[fileName]; + if (!shape) { + const shapeURL = `/${module}/static/image_shapes/${directory}/${fileName}.svg`; + shape = await (await fetch(shapeURL)).text(); + this.shapeCache[fileName] = shape; + } + this._getImg().dataset.shape = shapeName; + }, + + /** + * Applies the shape in img.dataset.shape and replaces the previous hex + * color values with new ones or current theme + * ones then calls _writeShape() + * + * @param {boolean} save true if the colors need to be saved in the + * data-attribute + * @param {string[]} [newColors] Array of HEX color code, default + * theme colors are applied if not supplied + */ + async _applyShapeAndColors(save, newColors) { + const img = this._getImg(); + let shape = this.shapeCache[img.dataset.shape.split('/')[2]]; + + // Map the default palette colors to an array if the shape includes them + // If they do not map a NULL, this way we know if a default color is in + // the shape + const oldColors = Object.values(DEFAULT_PALETTE).map(color => shape.includes(color) ? color : null); + if (!newColors) { + // If we do not have newColors, we still replace the default + // shape's colors by the current palette's + newColors = oldColors.map((color, i) => color !== null ? this._getCSSColorValue(`o-color-${(i + 1)}`) : null); + } + newColors.forEach((color, i) => shape = shape.replace(new RegExp(oldColors[i], 'g'), color)); + await this._writeShape(shape); + if (save) { + img.dataset.shapeColors = newColors.join(';'); + } + }, + /** + * Sets the image in the supplied SVG and replace the src with a dataURL + * + * @param {string} svgText svg file as text + * @returns {Promise} resolved once the svg is properly loaded + * in the document + */ + async _writeShape(svgText) { + const img = this._getImg(); + + const svg = new DOMParser().parseFromString(svgText, 'image/svg+xml').documentElement; + // We will store the image in base64 inside the SVG. + // applyModifications will return a dataURL with the current filters + // and size options. + const imgDataURL = await applyModifications(img, {mimetype: this._getImageMimetype(img)}); + svg.removeChild(svg.querySelector('#preview')); + svg.querySelector('image').setAttribute('xlink:href', imgDataURL); + // Force natural width & height (note: loading the original image is + // needed for Safari where natural width & height of SVG does not return + // the correct values). + const originalImage = await loadImage(img.src); + svg.setAttribute('width', originalImage.naturalWidth); + svg.setAttribute('height', originalImage.naturalHeight); + // Transform the current SVG in a base64 file to be saved by the server + const blob = new Blob([svg.outerHTML], { + type: 'image/svg+xml', + }); + + const reader = new FileReader(); + const readPromise = new Promise(resolve => { + reader.addEventListener('load', () => resolve(reader.result)); + }); + reader.readAsDataURL(blob); + const dataURL = await readPromise; + const imgFilename = (img.dataset.originalSrc.split('/').pop()).split('.')[0]; + img.dataset.fileName = `${imgFilename}.svg`; + return loadImage(dataURL, img); + }, /** * @override */ @@ -4630,6 +4813,77 @@ registry.ImageOptimize = ImageHandlerOption.extend({ const titleTextEl = leftPanelEl.querySelector('we-title > span'); this.$weight.appendTo(titleTextEl); }, + /** + * @override + */ + async _computeWidgetVisibility(widgetName, params) { + if (widgetName.startsWith('img-shape-color')) { + const img = this._getImg(); + const shapeName = img.dataset.shape; + if (!shapeName) { + return false; + } + const colors = img.dataset.shapeColors.split(';'); + return colors[parseInt(params.colorId)]; + } + return this._super(); + }, + /** + * @override + */ + _computeWidgetState(methodName, params) { + switch (methodName) { + case 'setImgShape': + return this._getImg().dataset.shape || ''; + case 'setImgShapeColor': { + const img = this._getImg(); + return (img.dataset.shapeColors && img.dataset.shapeColors.split(';')[parseInt(params.colorId)]) || ''; + } + } + return this._super(...arguments); + }, + /** + * Appends the SVG as an image. + * Due to the nature of image_shapes' SVGs, it is easier to render them as + * img compared to appending their content to the DOM + * (which is what the current data-img does) + * + * @override + */ + async _renderCustomXML(uiFragment) { + await this._super(...arguments); + uiFragment.querySelectorAll('we-select-page we-button[data-set-img-shape]').forEach(btn => { + const image = document.createElement('img'); + const [moduleName, directory, shapeName] = btn.dataset.setImgShape.split('/'); + image.src = `/${moduleName}/static/image_shapes/${directory}/${shapeName}.svg`; + $(btn).prepend(image); + + if (btn.dataset.animated) { + _addAnimatedShapeLabel(btn); + } + }); + }, + /** + * @override + */ + _getImageMimetype(img) { + if (img.dataset.shape) { + return img.dataset.originalMimetype; + } + return this._super(...arguments); + }, + /** + * Gets the CSS value of a color variable name so it can be used on shapes. + * + * @param {string} color + * @returns {string} + */ + _getCSSColorValue(color) { + if (ColorpickerWidget.isCSSColor(color)) { + return color; + } + return weUtils.getCSSVariableValue(color); + }, //-------------------------------------------------------------------------- // Handlers @@ -4643,7 +4897,12 @@ registry.ImageOptimize = ImageHandlerOption.extend({ */ async _onImageChanged(ev) { this.trigger_up('snippet_edition_request', {exec: async () => { + const img = this._getImg(); await this._autoOptimizeImage(); + if (img.dataset.shape) { + img.dataset.originalMimetype = img.dataset.mimetype; + img.dataset.mimetype = 'image/svg+xml'; + } this.trigger_up('cover_update'); }}); }, @@ -4654,6 +4913,11 @@ registry.ImageOptimize = ImageHandlerOption.extend({ * @param {Event} ev */ async _onImageCropped(ev) { + const img = this._getImg(); + if (img.dataset.shape) { + await this._loadShape(img.dataset.shape); + await this._applyShapeAndColors(true, (img.dataset.shapeColors && img.dataset.shapeColors.split(';'))); + } await this._rerenderXML(); }, }); @@ -5140,14 +5404,7 @@ registry.BackgroundShape = SnippetOptionWidget.extend({ btnContent.appendChild(btnContentInnerDiv); if (btn.dataset.animated) { - const animatedLabelEl = document.createElement('span'); - animatedLabelEl.classList.add('o_we_shape_animated_label'); - const labelStr = _t("Animated"); - animatedLabelEl.textContent = labelStr[0]; - const spanEl = document.createElement('span'); - spanEl.textContent = labelStr.substr(1); - animatedLabelEl.appendChild(spanEl); - btnContent.appendChild(animatedLabelEl); + _addAnimatedShapeLabel(btnContent); } const {shape} = btn.dataset; @@ -6120,6 +6377,8 @@ return { buildRowElement: _buildRowElement, buildCollapseElement: _buildCollapseElement, + addAnimatedShapeLabel: _addAnimatedShapeLabel, + // Other names for convenience Class: SnippetOptionWidget, registry: registry, diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 209f3a63fff..c95828e3be6 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -377,6 +377,8 @@ const Wysiwyg = Widget.extend({ res_model: resModel, res_id: parseInt(resId), data: (isBackground ? el.dataset.bgSrc : el.getAttribute('src')).split(',')[1], + mimetype: el.dataset.mimetype, + name: (el.dataset.fileName ? el.dataset.fileName : null), }, }); if (isBackground) { 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 4d10a9eb8b5..7876f8fda57 100644 --- a/addons/web_editor/static/src/scss/web_editor.variables.scss +++ b/addons/web_editor/static/src/scss/web_editor.variables.scss @@ -153,6 +153,8 @@ $o-we-sidebar-content-field-dropdown-item-color: $o-we-dropdown-item-color !defa $o-we-sidebar-content-field-dropdown-item-hover-color: $o-we-dropdown-item-hover-color !default; $o-we-sidebar-content-field-dropdown-item-active-bg: $o-we-dropdown-item-active-bg !default; $o-we-sidebar-content-field-dropdown-item-active-color: $o-we-dropdown-item-active-color !default; +$o-we-sidebar-content-field-dropdown-grid-item-height: 60px !default; +$o-we-sidebar-content-field-dropdown-grid-item-width: 80px !default; $o-we-sidebar-content-field-colorpicker-size: 20px !default; $o-we-sidebar-content-field-colorpicker-size-large: 26px !default; diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index a244047cefe..8802458aba3 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -1016,6 +1016,41 @@ body.editor_enable.editor_has_snippets { } } } + we-select.o_we_user_value_widget.o_we_select_grid { + we-select-page { + + we-button img { + height: $o-we-sidebar-content-field-dropdown-grid-item-height; + width: $o-we-sidebar-content-field-dropdown-grid-item-width; + } + &.active { + display: flex; + flex-wrap: wrap; + we-button { + width: percentage(1 / 3); + padding: 0; + justify-content: center; + img { + padding: $o-we-sidebar-content-field-spacing; + } + } + we-button.active { + border: 2px solid $o-we-color-success !important; + } + } + } + &.o_we_fake_transparent_background { + we-button { + background-image: url(/web/static/img/transparent.png); + background-size: 10px auto; + } + } + } + we-button.o_we_image_shape_remove { + div { + display: flex; + } + } // Button groups we-button-group.o_we_user_value_widget { diff --git a/addons/web_editor/views/snippets.xml b/addons/web_editor/views/snippets.xml index 30fafa97b0e..d967d7a3f86 100644 --- a/addons/web_editor/views/snippets.xml +++ b/addons/web_editor/views/snippets.xml @@ -256,7 +256,7 @@ - + @@ -411,7 +411,97 @@ title="'Alt tag' specifies an alternate text for an image, if the image cannot be displayed (slow connection, missing image, screen reader ...)."/> + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
@@ -435,7 +525,7 @@ Right - + @@ -445,11 +535,6 @@
-
- -
-
diff --git a/odoo/tools/mail.py b/odoo/tools/mail.py index cf14de6208a..792bb1e876b 100644 --- a/odoo/tools/mail.py +++ b/odoo/tools/mail.py @@ -41,6 +41,7 @@ safe_attrs = clean.defs.safe_attrs | frozenset( 'data-oe-model', 'data-oe-id', 'data-oe-field', 'data-oe-type', 'data-oe-expression', 'data-oe-translation-id', 'data-oe-nodeid', 'data-publish', 'data-id', 'data-res_id', 'data-interval', 'data-member_id', 'data-scroll-background-ratio', 'data-view-id', 'data-class', 'data-mimetype', 'data-original-src', 'data-original-id', 'data-gl-filter', 'data-quality', 'data-resize-width', + 'data-shape', 'data-shape-colors', 'data-file-name', 'data-original-mimetype', ])