From cd403480f90cf7103651f3be818a14b06d3c73ca Mon Sep 17 00:00:00 2001 From: Arthur Detroux Date: Fri, 19 Mar 2021 12:03:48 +0000 Subject: [PATCH] [IMP] web_editor, mass_mailing, *: add shape clipping option on images *: tools The ImageOptimizer option can now set shapes on images to use those shapes as clip path and background. It uses a flexible path so that the shape will always fit the image proportionally. We also added an html file alongside a javascript file to semi-convert the shape from illustrator into a usable shape for this usecase (the clip path must have values between one and zero therefore we must do some computation before the shape is ready to use). Thanks to Samuel for this specific shape-converter tool and other technical points about svgs and clip-paths. Thanks to Mehdi for remaining development post-testing and post-reviews and for the many fixes. Thanks to Brieuc for the actual shape SVG files. Part of https://github.com/odoo/odoo/pull/69179 task-2327045 closes odoo/odoo#69179 Signed-off-by: Quentin Smetz (qsm) Co-authored-by: xO-Tx Co-authored-by: Brieuc-brd Co-authored-by: Samuel Degueldre --- .../static/src/js/mass_mailing_snippets.js | 58 ++ addons/web_editor/controllers/main.py | 3 +- .../image_shapes/basic/bsc_organic_1.svg | 21 + .../image_shapes/basic/bsc_organic_2.svg | 23 + .../image_shapes/basic/bsc_organic_3.svg | 23 + .../image_shapes/basic/bsc_square_1.svg | 21 + .../image_shapes/basic/bsc_square_2.svg | 21 + .../image_shapes/basic/bsc_square_3.svg | 22 + .../image_shapes/basic/bsc_square_4.svg | 21 + .../image_shapes/basic/bsc_square_5.svg | 20 + .../image_shapes/basic/bsc_square_6.svg | 20 + .../basic/bsc_square_rounded_1.svg | 21 + .../basic/bsc_square_rounded_2.svg | 20 + .../image_shapes/convert-to-percentages.html | 17 + .../image_shapes/convert-to-percentages.js | 159 +++++ .../image_shapes/float/flt_planets_1.svg | 106 +++ .../image_shapes/float/flt_planets_2.svg | 106 +++ .../image_shapes/float/flt_primary_1.svg | 49 ++ .../image_shapes/float/flt_primary_2.svg | 63 ++ .../image_shapes/float/flt_primary_3.svg | 40 ++ .../image_shapes/float/flt_square_1.svg | 61 ++ .../image_shapes/float/flt_square_2.svg | 50 ++ .../image_shapes/float/flt_square_3.svg | 57 ++ .../image_shapes/float/flt_square_4.svg | 69 ++ .../image_shapes/line/line_square_1.svg | 32 + .../static/image_shapes/line/line_star.svg | 45 ++ .../static/image_shapes/line/line_sun.svg | 69 ++ .../image_shapes/line/line_triangle.svg | 58 ++ .../image_shapes/line/little_lines_1.svg | 31 + .../image_shapes/line/little_lines_2.svg | 40 ++ .../image_shapes/line/little_lines_3.svg | 38 + .../image_shapes/line/organic_1_line.svg | 17 + .../static/image_shapes/line/oval_2_line.svg | 18 + .../image_shapes/line/oval_3_pattern_line.svg | 51 ++ .../pattern/organic_2_pattern_dot.svg | 662 ++++++++++++++++++ .../pattern/organic_3_pattern_cross.svg | 85 +++ .../pattern/organic_4_pattern_caps.svg | 166 +++++ .../pattern/pattern_labyrinth.svg | 86 +++ .../image_shapes/pattern/pattern_points_1.svg | 38 + .../image_shapes/pattern/pattern_waves_1.svg | 31 + .../image_shapes/pattern/pattern_waves_2.svg | 42 ++ .../image_shapes/pattern/pattern_waves_3.svg | 42 ++ .../image_shapes/pattern/pattern_waves_4.svg | 65 ++ .../image_shapes/solid/blob_1_solid_rd.svg | 18 + .../image_shapes/solid/blob_2_solid_str.svg | 16 + .../image_shapes/solid/blob_3_solid_rd.svg | 18 + .../image_shapes/solid/oval_1_solid_rd.svg | 17 + .../static/image_shapes/solid/sld_blob_4.svg | 37 + .../image_shapes/solid/sld_blob_shadow_1.svg | 37 + .../image_shapes/solid/sld_blob_shadow_2.svg | 59 ++ .../image_shapes/solid/sld_square_1.svg | 20 + .../image_shapes/solid/sld_square_2.svg | 20 + .../solid/sld_square_organic_1.svg | 37 + .../image_shapes/special/spl_circuit_1.svg | 122 ++++ .../image_shapes/special/spl_filter_1.svg | 35 + .../image_shapes/special/spl_flag_1.svg | 21 + .../image_shapes/special/spl_organics_1.svg | 20 + .../image_shapes/special/spl_rain_1.svg | 100 +++ .../image_shapes/special/spl_snow_1.svg | 316 +++++++++ .../image_shapes/special/spl_speed_1.svg | 263 +++++++ .../static/src/js/editor/image_processing.js | 4 +- .../static/src/js/editor/snippets.options.js | 283 +++++++- .../static/src/js/wysiwyg/wysiwyg.js | 2 + .../static/src/scss/web_editor.variables.scss | 2 + .../static/src/scss/wysiwyg_snippets.scss | 35 + addons/web_editor/views/snippets.xml | 99 ++- odoo/tools/mail.py | 1 + 67 files changed, 4247 insertions(+), 22 deletions(-) create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_organic_1.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_organic_2.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_organic_3.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_1.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_2.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_3.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_4.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_5.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_6.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_rounded_1.svg create mode 100644 addons/web_editor/static/image_shapes/basic/bsc_square_rounded_2.svg create mode 100644 addons/web_editor/static/image_shapes/convert-to-percentages.html create mode 100644 addons/web_editor/static/image_shapes/convert-to-percentages.js create mode 100644 addons/web_editor/static/image_shapes/float/flt_planets_1.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_planets_2.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_primary_1.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_primary_2.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_primary_3.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_square_1.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_square_2.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_square_3.svg create mode 100644 addons/web_editor/static/image_shapes/float/flt_square_4.svg create mode 100644 addons/web_editor/static/image_shapes/line/line_square_1.svg create mode 100644 addons/web_editor/static/image_shapes/line/line_star.svg create mode 100644 addons/web_editor/static/image_shapes/line/line_sun.svg create mode 100644 addons/web_editor/static/image_shapes/line/line_triangle.svg create mode 100644 addons/web_editor/static/image_shapes/line/little_lines_1.svg create mode 100644 addons/web_editor/static/image_shapes/line/little_lines_2.svg create mode 100644 addons/web_editor/static/image_shapes/line/little_lines_3.svg create mode 100644 addons/web_editor/static/image_shapes/line/organic_1_line.svg create mode 100644 addons/web_editor/static/image_shapes/line/oval_2_line.svg create mode 100644 addons/web_editor/static/image_shapes/line/oval_3_pattern_line.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/organic_2_pattern_dot.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/organic_3_pattern_cross.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/organic_4_pattern_caps.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/pattern_labyrinth.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/pattern_points_1.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/pattern_waves_1.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/pattern_waves_2.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/pattern_waves_3.svg create mode 100644 addons/web_editor/static/image_shapes/pattern/pattern_waves_4.svg create mode 100644 addons/web_editor/static/image_shapes/solid/blob_1_solid_rd.svg create mode 100644 addons/web_editor/static/image_shapes/solid/blob_2_solid_str.svg create mode 100644 addons/web_editor/static/image_shapes/solid/blob_3_solid_rd.svg create mode 100644 addons/web_editor/static/image_shapes/solid/oval_1_solid_rd.svg create mode 100644 addons/web_editor/static/image_shapes/solid/sld_blob_4.svg create mode 100644 addons/web_editor/static/image_shapes/solid/sld_blob_shadow_1.svg create mode 100644 addons/web_editor/static/image_shapes/solid/sld_blob_shadow_2.svg create mode 100644 addons/web_editor/static/image_shapes/solid/sld_square_1.svg create mode 100644 addons/web_editor/static/image_shapes/solid/sld_square_2.svg create mode 100644 addons/web_editor/static/image_shapes/solid/sld_square_organic_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_circuit_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_filter_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_flag_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_organics_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_rain_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_snow_1.svg create mode 100644 addons/web_editor/static/image_shapes/special/spl_speed_1.svg 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', ])