[FIX] website_sale: automatically convert uploaded extra images to webp

Since [1] images uploaded through the website builder that are neither
GIF nor SVG are converted to the WEBP format by default.
The upload of product extra images does not follow this rule.
Adding the extra images is done through its own specific route instead
of putting the images inside the DOM before the save, it therefore does
not run though the general image tools code.

This commit converts the uploaded product extra images to WEBP before
saving them into the records.
It is achieved by following the same approach as when uploading a WEBP
in the backend [2], before calling the `/shop/product/extra-images` RPC.

The fix is not done by replacing the custom route by an update of the
DOM with the images because a full page rendering from the server is
needed when adding extra images.

Steps to reproduce:
- Edit a product through the website builder
- Add a JPG extra image
- Save the page to leave the website builder
- Check the image content

=> Image was a JPG instead of WEBP.

[1]: https://github.com/odoo/odoo/commit/0449fe85cb0e1d639a4e1aeba26e90906f79254d
[2]: https://github.com/odoo/odoo/commit/40955397658e4bf858e969668079d0c6a4cef43b

task-3460171

closes odoo/odoo#134037

X-original-commit: f970147a2113608d18b47c6f59de6c6338bb2f43
Signed-off-by: Robin Lejeune (role) <role@odoo.com>
This commit is contained in:
Benoit Socias
2023-09-04 08:03:45 +00:00
parent 05978d603b
commit b284293c18
@@ -789,12 +789,24 @@ options.registry.WebsiteSaleProductPage = options.Class.extend({
message: 'Pictures will be added to the main image. Use "Instant" attributes to set pictures on each variants'
});
}
let extraImageEls;
const dialog = new ComponentWrapper(this, AttachmentMediaDialogWrapper, {
multiImages: true,
onlyImages: true,
// Kinda hack-ish but the regular save does not get the information we need
save: async () => {},
save: async (imgEls) => {
extraImageEls = imgEls;
},
extraImageSave: async (attachments) => {
for (const index in attachments) {
const attachment = attachments[index];
if (attachment.mimetype.startsWith("image/")) {
if (["image/gif", "image/svg+xml"].includes(attachment.mimetype)) {
continue;
}
await this._convertAttachmentToWebp(attachment, extraImageEls[index]);
}
}
this._rpc({
route: `/shop/product/extra-images`,
params: {
@@ -811,6 +823,61 @@ options.registry.WebsiteSaleProductPage = options.Class.extend({
dialog.mount(document.body);
},
async _convertAttachmentToWebp(attachment, imageEl) {
// This method is widely adapted from onFileUploaded in ImageField.
// Upon change, make sure to verify whether the same change needs
// to be applied on both sides.
// Generate alternate sizes and format for reports.
const imgEl = document.createElement("img");
imgEl.src = imageEl.src;
await new Promise(resolve => imgEl.addEventListener("load", resolve));
const originalSize = Math.max(imgEl.width, imgEl.height);
const smallerSizes = [1024, 512, 256, 128].filter(size => size < originalSize);
const webpName = attachment.name.replace(/\.(jpe?g|png)$/i, ".webp");
let referenceId = undefined;
for (const size of [originalSize, ...smallerSizes]) {
const ratio = size / originalSize;
const canvas = document.createElement("canvas");
canvas.width = imgEl.width * ratio;
canvas.height = imgEl.height * ratio;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imgEl, 0, 0, imgEl.width, imgEl.height, 0, 0, canvas.width, canvas.height);
const [resizedId] = await this._rpc({
model: "ir.attachment",
method: "create_unique",
args: [[{
name: webpName,
description: size === originalSize ? "" : `resize: ${size}`,
datas: canvas.toDataURL("image/webp", 0.75).split(",")[1],
res_id: referenceId,
res_model: "ir.attachment",
mimetype: "image/webp",
}]],
});
if (size === originalSize) {
attachment.original_id = attachment.id;
attachment.id = resizedId;
attachment.image_src = `/web/image/${resizedId}-autowebp/${attachment.name}`;
attachment.mimetype = "image/webp";
}
referenceId = referenceId || resizedId; // Keep track of original.
await this._rpc({
model: "ir.attachment",
method: "create_unique",
args: [[{
name: webpName.replace(/\.webp$/, ".jpg"),
description: "format: jpeg",
datas: canvas.toDataURL("image/jpeg", 0.75).split(",")[1],
res_id: resizedId,
res_model: "ir.attachment",
mimetype: "image/jpeg",
}]],
});
}
},
/**
* Removes all extra-images from the product.
*/