|
| 1 | +import { BaseOptionComponent, useDomState } from "@html_builder/core/utils"; |
| 2 | +import { KeepLast } from "@web/core/utils/concurrency"; |
| 3 | +import { getImageSrc, getMimetype } from "@html_editor/utils/image"; |
| 4 | +import { clamp } from "@web/core/utils/numbers"; |
| 5 | + |
| 6 | +export class ImageFormatOption extends BaseOptionComponent { |
| 7 | + static template = "html_builder.ImageFormat"; |
| 8 | + static props = { |
| 9 | + level: { type: Number, optional: true }, |
| 10 | + computeMaxDisplayWidth: { type: Function, optional: true }, |
| 11 | + }; |
| 12 | + static defaultProps = { |
| 13 | + level: 0, |
| 14 | + }; |
| 15 | + MAX_SUGGESTED_WIDTH = 1920; |
| 16 | + setup() { |
| 17 | + super.setup(); |
| 18 | + const keepLast = new KeepLast(); |
| 19 | + this.state = useDomState((editingElement) => { |
| 20 | + keepLast |
| 21 | + .add( |
| 22 | + this.env.editor.shared.imageFormatOption.computeAvailableFormats( |
| 23 | + editingElement, |
| 24 | + this.computeMaxDisplayWidth.bind(this) |
| 25 | + ) |
| 26 | + ) |
| 27 | + .then((formats) => { |
| 28 | + const hasSrc = !!getImageSrc(editingElement); |
| 29 | + this.state.formats = hasSrc ? formats : []; |
| 30 | + }); |
| 31 | + return { |
| 32 | + showQuality: ["image/jpeg", "image/webp"].includes(getMimetype(editingElement)), |
| 33 | + formats: [], |
| 34 | + }; |
| 35 | + }); |
| 36 | + } |
| 37 | + computeMaxDisplayWidth(img) { |
| 38 | + if (this.props.computeMaxDisplayWidth) { |
| 39 | + return this.props.computeMaxDisplayWidth(img); |
| 40 | + } |
| 41 | + const window = img.ownerDocument.defaultView; |
| 42 | + if (!window) { |
| 43 | + return; |
| 44 | + } |
| 45 | + const computedStyles = window.getComputedStyle(img); |
| 46 | + const displayWidth = parseFloat(computedStyles.getPropertyValue("width")); |
| 47 | + const gutterWidth = |
| 48 | + parseFloat(computedStyles.getPropertyValue("--o-grid-gutter-width")) || 30; |
| 49 | + |
| 50 | + // For the logos we don't want to suggest a width too small. |
| 51 | + if (img.closest("nav")) { |
| 52 | + return Math.round(Math.min(displayWidth * 3, this.MAX_SUGGESTED_WIDTH)); |
| 53 | + // If the image is in a container(-small), it might get bigger on |
| 54 | + // smaller screens. So we suggest the width of the current image unless |
| 55 | + // it is smaller than the size of the container on the md breapoint |
| 56 | + // (which is where our bootstrap columns fallback to full container |
| 57 | + // width since we only use col-lg-* in Odoo). |
| 58 | + } else if (img.closest(".container, .o_container_small")) { |
| 59 | + const mdContainerMaxWidth = |
| 60 | + parseFloat(computedStyles.getPropertyValue("--o-md-container-max-width")) || 720; |
| 61 | + const mdContainerInnerWidth = mdContainerMaxWidth - gutterWidth; |
| 62 | + return Math.round(clamp(displayWidth, mdContainerInnerWidth, this.MAX_SUGGESTED_WIDTH)); |
| 63 | + // If the image is displayed in a container-fluid, it might also get |
| 64 | + // bigger on smaller screens. The same way, we suggest the width of the |
| 65 | + // current image unless it is smaller than the max size of the container |
| 66 | + // on the md breakpoint (which is the LG breakpoint since the container |
| 67 | + // fluid is full-width). |
| 68 | + } else if (img.closest(".container-fluid")) { |
| 69 | + const lgBp = parseFloat(computedStyles.getPropertyValue("--breakpoint-lg")) || 992; |
| 70 | + const mdContainerFluidMaxInnerWidth = lgBp - gutterWidth; |
| 71 | + return Math.round( |
| 72 | + clamp(displayWidth, mdContainerFluidMaxInnerWidth, this.MAX_SUGGESTED_WIDTH) |
| 73 | + ); |
| 74 | + } |
| 75 | + // If it's not in a container, it's probably not going to change size |
| 76 | + // depending on breakpoints. We still keep a margin safety. |
| 77 | + return Math.round(Math.min(displayWidth * 1.5, this.MAX_SUGGESTED_WIDTH)); |
| 78 | + } |
| 79 | +} |
0 commit comments