[IMP] web_editor: support 5 dynamic colors for illustrations
Before this commit only the first default palette color was configurable on illustrations After this commit all 5 default palette colors are configurable on illustrations task-2368585 closes odoo/odoo#60503 Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
This commit is contained in:
@@ -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):
|
||||
<media_id>: {
|
||||
'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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -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($(`<we-colorpicker data-color="true" data-color-name="${colorName}">`)[0]);
|
||||
.prepend($(`<we-colorpicker data-color="true" data-color-name="${colorName}"></we-colorpicker>`)[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);
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -180,7 +180,13 @@
|
||||
data-name="bg_image_opt"
|
||||
data-dependencies="bg_image_opt"/>
|
||||
</we-row>
|
||||
<we-colorpicker string=" ⌙ Dynamic color" data-dynamic-color="true" data-name="dynamic_color_opt"/>
|
||||
<we-row string=" ⌙ Dyn. Colors" title="Dynamic Colors" data-name="dynamic_color_opt"><!--   -->
|
||||
<we-colorpicker data-dynamic-color="true" data-color-name="c1"/>
|
||||
<we-colorpicker data-dynamic-color="true" data-color-name="c2"/>
|
||||
<we-colorpicker data-dynamic-color="true" data-color-name="c3"/>
|
||||
<we-colorpicker data-dynamic-color="true" data-color-name="c4"/>
|
||||
<we-colorpicker data-dynamic-color="true" data-color-name="c5"/>
|
||||
</we-row>
|
||||
</div>
|
||||
|
||||
<div data-js="BackgroundPosition"
|
||||
@@ -372,7 +378,13 @@
|
||||
</div>
|
||||
|
||||
<div data-js="DynamicSvg" data-selector="img">
|
||||
<we-colorpicker string="Dynamic color" data-color="true"/>
|
||||
<we-row string="Dynamic Colors" data-name="colors">
|
||||
<we-colorpicker data-color="true" data-color-name="c1"/>
|
||||
<we-colorpicker data-color="true" data-color-name="c2"/>
|
||||
<we-colorpicker data-color="true" data-color-name="c3"/>
|
||||
<we-colorpicker data-color="true" data-color-name="c4"/>
|
||||
<we-colorpicker data-color="true" data-color-name="c5"/>
|
||||
</we-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user