diff --git a/addons/website/static/src/builder/plugins/image/image_size_tag.js b/addons/website/static/src/builder/plugins/image/image_size_tag.js new file mode 100644 index 0000000000000..42407bc40748b --- /dev/null +++ b/addons/website/static/src/builder/plugins/image/image_size_tag.js @@ -0,0 +1,20 @@ +import { Component, useState } from "@odoo/owl"; +import { useDomState } from "@html_builder/core/utils"; +import { loadImageDataURL, getImageSizeFromCache } from "@html_editor/utils/image_processing"; +import { KeepLast } from "@web/core/utils/concurrency"; + +export class ImageSizeTag extends Component { + static template = "website.ImageSizeTag"; + setup() { + this.keepLast = new KeepLast(); + this.state = useState({ size: 0 }); + useDomState((imageEl) => this.updateImageSize(imageEl)); + this.updateImageSize(this.env.getEditingElement()); + } + + async updateImageSize(imageEl) { + const src = imageEl.src; + await this.keepLast.add(loadImageDataURL(src)); + this.state.size = Math.round((getImageSizeFromCache(src) / 1024) * 10) / 10; + } +} diff --git a/addons/website/static/src/builder/plugins/image/image_size_tag.xml b/addons/website/static/src/builder/plugins/image/image_size_tag.xml new file mode 100644 index 0000000000000..6c975a5d1795b --- /dev/null +++ b/addons/website/static/src/builder/plugins/image/image_size_tag.xml @@ -0,0 +1,6 @@ + + + + kB + + diff --git a/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js b/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js index 9a04043531593..0368bd1ba6d93 100644 --- a/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js +++ b/addons/website/static/src/builder/plugins/image/image_tool_option_plugin.js @@ -1,4 +1,8 @@ -import { cropperDataFieldsWithAspectRatio, isGif, loadImage } from "@html_editor/utils/image_processing"; +import { + cropperDataFieldsWithAspectRatio, + isGif, + loadImage, +} from "@html_editor/utils/image_processing"; import { registry } from "@web/core/registry"; import { Plugin } from "@html_editor/plugin"; import { ImageToolOption } from "./image_tool_option"; @@ -11,6 +15,7 @@ import { } from "@html_builder/utils/option_sequence"; import { ReplaceMediaOption, searchSupportedParentLinkEl } from "./replace_media_option"; import { computeMaxDisplayWidth } from "./image_format_option"; +import { ImageSizeTag } from "./image_size_tag"; export const REPLACE_MEDIA_SELECTOR = "img, .media_iframe_video, span.fa, i.fa"; export const REPLACE_MEDIA_EXCLUDE = @@ -28,6 +33,10 @@ class ImageToolOptionPlugin extends Plugin { ]; static shared = ["canHaveHoverEffect"]; resources = { + builder_header_middle_buttons: { + Component: ImageSizeTag, + selector: "img", + }, builder_options: [ withSequence(REPLACE_MEDIA, { OptionComponent: ReplaceMediaOption, @@ -49,32 +58,45 @@ class ImageToolOptionPlugin extends Plugin { on_media_dialog_saved_handlers: async (elements, { node }) => { for (const image of elements) { if (image && image.tagName === "IMG") { - const updateImageAttributes = await this.dependencies.imagePostProcess.processImage({ - img: image, - newDataset: { - formatMimetype: "image/webp", - }, - // TODO Using a callback is currently needed to avoid - // the extra RPC that would occur if loadImageInfo was - // called before processImage as well. This flow can be - // simplified if image infos are somehow cached. - onImageInfoLoaded: async (dataset) => { - if (!dataset.originalSrc || !dataset.originalId) { - return true; - } - const original = await loadImage(dataset.originalSrc); - const maxWidth = dataset.width ? image.naturalWidth : original.naturalWidth; - const optimizedWidth = Math.min(maxWidth, computeMaxDisplayWidth(node || this.editable)); - if (!["image/gif", "image/svg+xml"].includes(dataset.mimetypeBeforeConversion)) { - // Convert to recommended format and width. - dataset.resizeWidth = optimizedWidth; - } else if (dataset.shape && dataset.mimetypeBeforeConversion !== "image/gif") { - dataset.resizeWidth = optimizedWidth; - } else { - return true; - } - }, - }); + const updateImageAttributes = + await this.dependencies.imagePostProcess.processImage({ + img: image, + newDataset: { + formatMimetype: "image/webp", + }, + // TODO Using a callback is currently needed to avoid + // the extra RPC that would occur if loadImageInfo was + // called before processImage as well. This flow can be + // simplified if image infos are somehow cached. + onImageInfoLoaded: async (dataset) => { + if (!dataset.originalSrc || !dataset.originalId) { + return true; + } + const original = await loadImage(dataset.originalSrc); + const maxWidth = dataset.width + ? image.naturalWidth + : original.naturalWidth; + const optimizedWidth = Math.min( + maxWidth, + computeMaxDisplayWidth(node || this.editable) + ); + if ( + !["image/gif", "image/svg+xml"].includes( + dataset.mimetypeBeforeConversion + ) + ) { + // Convert to recommended format and width. + dataset.resizeWidth = optimizedWidth; + } else if ( + dataset.shape && + dataset.mimetypeBeforeConversion !== "image/gif" + ) { + dataset.resizeWidth = optimizedWidth; + } else { + return true; + } + }, + }); updateImageAttributes(); } } @@ -91,7 +113,10 @@ class ImageToolOptionPlugin extends Plugin { onClose: resolve, onSave: async (newDataset) => { resolve( - this.dependencies.imagePostProcess.processImage({ img, newDataset }) + this.dependencies.imagePostProcess.processImage({ + img, + newDataset, + }) ); }, });