[FIX] web_editor: handle WEBP dropped image attachments

Since [1] dropped images are saved as attachments.
When such an image is a WEBP, its pre-computed resizes and conversions
to jpeg are not generated.

This commit adds the behavior of saving an `o_modified_image_to_save`
when a `o_b64_image_to_save` is a WEBP.

[1]: https://github.com/odoo/odoo/commit/3bbce756c69a206d07abd31059f0392c26b36096

task-2774352

Part-of: odoo/odoo#85494
This commit is contained in:
Benoit Socias
2023-07-15 05:10:46 +02:00
parent 0ba3617f9d
commit f8a9669c8d
@@ -957,70 +957,14 @@ const Wysiwyg = Widget.extend({
savePendingImages($editable = this.$editable) {
const defs = Array.from($editable).map(async (editableEl) => {
const {oeModel: resModel, oeId: resId} = editableEl.dataset;
// When saving a webp, o_b64_image_to_save is turned into
// o_modified_image_to_save by _saveB64Image to request the saving
// of the pre-converted webp resizes and all the equivalent jpgs.
const b64Proms = [...editableEl.querySelectorAll('.o_b64_image_to_save')].map(async el => {
const attachment = await this._rpc({
route: '/web_editor/attachment/add_data',
params: {
name: el.dataset.fileName || '',
data: el.getAttribute('src').split(',')[1],
is_image: true,
},
});
el.setAttribute('src', attachment.image_src);
el.classList.remove('o_b64_image_to_save');
await this._saveB64Image(el, resModel, resId);
});
const modifiedProms = [...editableEl.querySelectorAll('.o_modified_image_to_save')].map(async el => {
const isBackground = !el.matches('img');
el.classList.remove('o_modified_image_to_save');
// Modifying an image always creates a copy of the original, even if
// it was modified previously, as the other modified image may be used
// elsewhere if the snippet was duplicated or was saved as a custom one.
let altData = undefined;
if (el.dataset.mimetype === 'image/webp') {
// Generate alternate sizes and format for reports.
altData = {};
const image = document.createElement('img');
image.src = isBackground ? el.dataset.bgSrc : el.getAttribute('src');
await new Promise(resolve => image.addEventListener('load', resolve));
const originalSize = Math.max(image.width, image.height);
const smallerSizes = [1024, 512, 256, 128].filter(size => size < originalSize);
for (const size of [originalSize, ...smallerSizes]) {
const ratio = size / originalSize;
const canvas = document.createElement('canvas');
canvas.width = image.width * ratio;
canvas.height = image.height * ratio;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
altData[size] = {
'image/jpeg': canvas.toDataURL('image/jpeg', 0.75).split(',')[1],
};
if (size !== originalSize) {
altData[size]['image/webp'] = canvas.toDataURL('image/webp', 0.75).split(',')[1];
}
}
}
const newAttachmentSrc = await this._rpc({
route: `/web_editor/modify_image/${encodeURIComponent(el.dataset.originalId)}`,
params: {
res_model: resModel,
res_id: parseInt(resId),
data: (isBackground ? el.dataset.bgSrc : el.getAttribute('src')).split(',')[1],
alt_data: altData,
mimetype: el.dataset.mimetype,
name: (el.dataset.fileName ? el.dataset.fileName : null),
},
});
if (isBackground) {
const parts = weUtils.backgroundImageCssToParts($(el).css('background-image'));
parts.url = `url('${newAttachmentSrc}')`;
const combined = weUtils.backgroundImagePartsToCss(parts);
$(el).css('background-image', combined);
delete el.dataset.bgSrc;
} else {
el.setAttribute('src', newAttachmentSrc);
}
await this._saveModifiedImage(el, resModel, resId);
});
return Promise.all([...b64Proms, ...modifiedProms]);
});
@@ -2781,6 +2725,96 @@ const Wysiwyg = Widget.extend({
_bindOnBlur() {
this.$editable.on('blur', this._onBlur);
},
/**
* Saves a base64 encoded image as an attachment.
* Relies on _saveModifiedImage being called after it for webp.
*
* @private
* @param {Element} el
* @param {string} resModel
* @param {number} resId
*/
async _saveB64Image(el, resModel, resId) {
const attachment = await this._rpc({
route: '/web_editor/attachment/add_data',
params: {
name: el.dataset.fileName || '',
data: el.getAttribute('src').split(',')[1],
is_image: true,
},
});
if (attachment.mimetype === 'image/webp') {
el.classList.add('o_modified_image_to_save');
el.dataset.originalId = attachment.id;
el.dataset.mimetype = attachment.mimetype;
el.dataset.fileName = attachment.name;
this._saveModifiedImage(el, resModel, resId);
} else {
el.setAttribute('src', attachment.image_src);
}
el.classList.remove('o_b64_image_to_save');
},
/**
* Saves a modified image as an attachment.
*
* @private
* @param {Element} el
* @param {string} resModel
* @param {number} resId
*/
async _saveModifiedImage(el, resModel, resId) {
const isBackground = !el.matches('img');
el.classList.remove('o_modified_image_to_save');
// Modifying an image always creates a copy of the original, even if
// it was modified previously, as the other modified image may be used
// elsewhere if the snippet was duplicated or was saved as a custom one.
let altData = undefined;
if (el.dataset.mimetype === 'image/webp') {
// Generate alternate sizes and format for reports.
altData = {};
const image = document.createElement('img');
image.src = isBackground ? el.dataset.bgSrc : el.getAttribute('src');
await new Promise(resolve => image.addEventListener('load', resolve));
const originalSize = Math.max(image.width, image.height);
const smallerSizes = [1024, 512, 256, 128].filter(size => size < originalSize);
for (const size of [originalSize, ...smallerSizes]) {
const ratio = size / originalSize;
const canvas = document.createElement('canvas');
canvas.width = image.width * ratio;
canvas.height = image.height * ratio;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(255, 255, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height);
altData[size] = {
'image/jpeg': canvas.toDataURL('image/jpeg', 0.75).split(',')[1],
};
if (size !== originalSize) {
altData[size]['image/webp'] = canvas.toDataURL('image/webp', 0.75).split(',')[1];
}
}
}
const newAttachmentSrc = await this._rpc({
route: `/web_editor/modify_image/${encodeURIComponent(el.dataset.originalId)}`,
params: {
res_model: resModel,
res_id: parseInt(resId),
data: (isBackground ? el.dataset.bgSrc : el.getAttribute('src')).split(',')[1],
alt_data: altData,
mimetype: el.getAttribute('src').split(":")[1].split(";")[0],
name: (el.dataset.fileName ? el.dataset.fileName : null),
},
});
if (isBackground) {
const parts = weUtils.backgroundImageCssToParts($(el).css('background-image'));
parts.url = `url('${newAttachmentSrc}')`;
const combined = weUtils.backgroundImagePartsToCss(parts);
$(el).css('background-image', combined);
delete el.dataset.bgSrc;
} else {
el.setAttribute('src', newAttachmentSrc);
}
},
});
Wysiwyg.activeCollaborationChannelNames = new Set();