diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index e85a678f54e..b84a1537e43 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -5,6 +5,7 @@ import logging import re import time import requests +import werkzeug.urls import werkzeug.wrappers from PIL import Image, ImageFont, ImageDraw from lxml import etree @@ -12,7 +13,7 @@ from base64 import b64decode, b64encode from odoo.http import request from odoo import http, tools, _, SUPERUSER_ID -from odoo.addons.http_routing.models.ir_http import slug +from odoo.addons.http_routing.models.ir_http import slug, unslug from odoo.exceptions import UserError from odoo.modules.module import get_module_path, get_resource_path from odoo.tools.misc import file_open @@ -519,8 +520,11 @@ class Web_Editor(http.Controller): """ svg = None if module == 'illustration': - attachment = request.env['ir.attachment'].sudo().search([('url', '=like', request.httprequest.path), ('public', '=', True)], limit=1) - if not attachment: + attachment = request.env['ir.attachment'].sudo().browse(unslug(filename)[1]) + if (not attachment.exists() + or attachment.type != 'binary' + or not attachment.public + or not attachment.url.startswith(request.httprequest.path)): raise werkzeug.exceptions.NotFound() svg = b64decode(attachment.datas).decode('utf-8') else: @@ -587,6 +591,7 @@ class Web_Editor(http.Controller): : { 'query': 'space separated search terms', 'is_dynamic_svg': True/False, + 'dynamic_colors': maps color names to their color, }, ... } """ @@ -618,7 +623,8 @@ class Web_Editor(http.Controller): 'res_id': 0, }) if media[id]['is_dynamic_svg']: - attachment['url'] = '/web_editor/shape/illustration/%s' % slug(attachment) + colorParams = werkzeug.urls.url_encode(media[id]['dynamic_colors']) + attachment['url'] = '/web_editor/shape/illustration/%s?%s' % (slug(attachment), colorParams) attachments.append(attachment._get_media_info()) return attachments diff --git a/addons/web_editor/models/ir_attachment.py b/addons/web_editor/models/ir_attachment.py index 791f919498e..068e52f7f67 100644 --- a/addons/web_editor/models/ir_attachment.py +++ b/addons/web_editor/models/ir_attachment.py @@ -40,7 +40,8 @@ class IrAttachment(models.Model): if attachment.url: # For attachments-by-url, unique is used as a cachebuster. They # currently do not leverage max-age headers. - attachment.image_src = '%s?unique=%s' % (attachment.url, unique) + separator = '&' if '?' in attachment.url else '?' + attachment.image_src = '%s%sunique=%s' % (attachment.url, separator, unique) else: name = url_quote(attachment.name) attachment.image_src = '/web/image/%s-%s/%s' % (attachment.id, unique, name) diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index deeec7a9c79..b5d33eafbe9 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -2078,7 +2078,12 @@ var SnippetsMenu = Widget.extend({ // Color-customize dynamic SVGs in dropped snippets with current theme colors. [...$snippetToInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => { const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin); - colorCustomizedURL.searchParams.set('c1', getCSSVariableValue('o-color-1')); + colorCustomizedURL.searchParams.forEach((key, value) => { + const match = key.match(/^c([1-5])$/); + if (match) { + colorCustomizedURL.searchParams.set(key, getCSSVariableValue(`o-color-${match[1]}`)) + } + }) dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search; }); 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 fd5560d1374..0fcdd034e4d 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -3782,7 +3782,7 @@ registry.BackgroundImage = SnippetOptionWidget.extend({ return; } const newURL = new URL(currentSrc, window.location.origin); - newURL.searchParams.set('c1', normalizeColor(widgetValue)); + newURL.searchParams.set(params.colorName, normalizeColor(widgetValue)); const src = newURL.pathname + newURL.search; await loadImage(src); await this.editorHelpers.setStyle(this.editor, this.$target.get(), 'background-image', `url('${src}')`); @@ -3825,12 +3825,12 @@ registry.BackgroundImage = SnippetOptionWidget.extend({ /** * @override */ - _computeWidgetState: function (methodName) { + _computeWidgetState: function (methodName, params) { switch (methodName) { case 'background': return getBgImageURL(this.$target[0]); case 'dynamicColor': - return new URL(getBgImageURL(this.$target[0]), window.location.origin).searchParams.get('c1'); + return new URL(getBgImageURL(this.$target[0]), window.location.origin).searchParams.get(params.colorName); } return this._super(...arguments); }, @@ -3838,7 +3838,10 @@ registry.BackgroundImage = SnippetOptionWidget.extend({ * @override */ _computeWidgetVisibility(widgetName, params) { - if (widgetName === 'dynamic_color_opt') { + if ('colorName' in params) { + const src = new URL(getBgImageURL(this.$target[0]), window.location.origin); + return src.searchParams.has(params.colorName); + } else if (widgetName === 'dynamic_color_opt') { const src = new URL(getBgImageURL(this.$target[0]), window.location.origin); return src.origin === window.location.origin && src.pathname.startsWith('/web_editor/shape/'); } @@ -3956,7 +3959,7 @@ registry.BackgroundShape = SnippetOptionWidget.extend({ _renderCustomXML(uiFragment) { Object.keys(this._getDefaultColors()).map(colorName => { uiFragment.querySelector('[data-name="colors"]') - .prepend($(``)[0]); + .prepend($(``)[0]); }); uiFragment.querySelectorAll('we-select-pager we-button[data-shape]').forEach(btn => { @@ -4814,7 +4817,7 @@ registry.DynamicSvg = SnippetOptionWidget.extend({ return; } const newURL = new URL(target.src, window.location.origin); - newURL.searchParams.set('c1', normalizeColor(widgetValue)); + newURL.searchParams.set(params.colorName, normalizeColor(widgetValue)); const src = newURL.pathname + newURL.search; await loadImage(src); await this.editorHelpers.setAttribute(this.editor, target, 'src', src); @@ -4833,7 +4836,16 @@ registry.DynamicSvg = SnippetOptionWidget.extend({ _computeWidgetState(methodName, params) { switch (methodName) { case 'color': - return new URL(this.$target[0].src, window.location.origin).searchParams.get('c1'); + return new URL(this.$target[0].src, window.location.origin).searchParams.get(params.colorName); + } + return this._super(...arguments); + }, + /** + * @override + */ + _computeWidgetVisibility(widgetName, params) { + if ('colorName' in params) { + return new URL(this.$target[0].src, window.location.origin).searchParams.get(params.colorName); } return this._super(...arguments); }, diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js index d8892f9f66a..eae349af2f6 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js @@ -383,6 +383,7 @@ var FileWidget = SearchableMediaWidget.extend({ media.id, { query: media.query || '', is_dynamic_svg: !!media.isDynamicSVG, + dynamic_colors: media.dynamicColors, } ])); let mediaAttachments = []; @@ -395,10 +396,15 @@ var FileWidget = SearchableMediaWidget.extend({ }); } const selected = this.selectedAttachments.concat(mediaAttachments).map(attachment => { - // Color-customize dynamic SVGs with the primary theme color + // Color-customize dynamic SVGs with the theme colors if (attachment.image_src && attachment.image_src.startsWith('/web_editor/shape/')) { const colorCustomizedURL = new URL(attachment.image_src, window.location.origin); - colorCustomizedURL.searchParams.set('c1', getCSSVariableValue('o-color-1')); + colorCustomizedURL.searchParams.forEach((value, key) => { + const match = key.match(/^c([1-5])$/); + if (match) { + colorCustomizedURL.searchParams.set(key, getCSSVariableValue(`o-color-${match[1]}`)) + } + }) attachment.image_src = colorCustomizedURL.pathname + colorCustomizedURL.search; } return attachment; @@ -886,13 +892,21 @@ var ImageWidget = FileWidget.extend({ } const result = await this._super(number, offset); // Color-substitution for dynamic SVG attachment - const primaryColor = getCSSVariableValue('o-color-1'); + const primaryColors = {}; + for (let color = 1; color <= 5; color++) { + primaryColors[color] = getCSSVariableValue('o-color-' + color); + } this.attachments.forEach(attachment => { if (attachment.image_src.startsWith('/')) { const newURL = new URL(attachment.image_src, window.location.origin); - // Set the main color of dynamic SVGs to o-color-1 + // Set the main colors of dynamic SVGs to o-color-1~5 if (attachment.image_src.startsWith('/web_editor/shape/')) { - newURL.searchParams.set('c1', primaryColor); + newURL.searchParams.forEach((value, key) => { + const match = key.match(/^c([1-5])$/); + if (match) { + newURL.searchParams.set(key, primaryColors[match[1]]); + } + }) } else { // Set height so that db images load faster newURL.searchParams.set('height', 2 * this.MIN_ROW_HEIGHT); @@ -1026,17 +1040,25 @@ var ImageWidget = FileWidget.extend({ try { const response = await fetch(mediaUrl); if (response.headers.get('content-type') === 'image/svg+xml') { - const svg = await response.text(); - const colorRegex = new RegExp(DEFAULT_PALETTE['1'], 'gi'); - if (colorRegex.test(svg)) { - const fileName = mediaUrl.split('/').pop(); - const file = new File([svg.replace(colorRegex, getCSSVariableValue('o-color-1'))], fileName, { + let svg = await response.text(); + const fileName = mediaUrl.split('/').pop(); + const dynamicColors = {}; + const combinedColorsRegex = new RegExp(Object.values(DEFAULT_PALETTE).join('|'), 'gi'); + svg = svg.replace(combinedColorsRegex, match => { + const colorId = Object.keys(DEFAULT_PALETTE).find(key => DEFAULT_PALETTE[key] === match.toUpperCase()); + const colorKey = 'c' + colorId + dynamicColors[colorKey] = getCSSVariableValue('o-color-' + colorId); + return dynamicColors[colorKey]; + }); + if (Object.keys(dynamicColors).length) { + const file = new File([svg], fileName, { type: "image/svg+xml", }); img.src = URL.createObjectURL(file); const media = this.libraryMedia.find(media => media.id === parseInt(cell.dataset.mediaId)); if (media) { media.isDynamicSVG = true; + media.dynamicColors = dynamicColors; } // We changed the src: wait for the next load event to do the styling return; diff --git a/addons/web_editor/views/snippets.xml b/addons/web_editor/views/snippets.xml index 0d1fbd9cb3b..b84d6165e1a 100644 --- a/addons/web_editor/views/snippets.xml +++ b/addons/web_editor/views/snippets.xml @@ -180,7 +180,13 @@ data-name="bg_image_opt" data-dependencies="bg_image_opt"/> - + + + + + + +
- + + + + + + +