Proyectos de Subversion Moodle

Rev

Rev 1 | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |

{"version":3,"file":"imagedetails.min.js","sources":["../src/imagedetails.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny media plugin image details class for Moodle.\n *\n * @module      tiny_media/imagedetails\n * @copyright   2024 Meirza <meirza.arson@moodle.com>\n * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Config from 'core/config';\nimport ModalEvents from 'core/modal_events';\nimport Notification from 'core/notification';\nimport Pending from 'core/pending';\nimport Selectors from './selectors';\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport {ImageInsert} from 'tiny_media/imageinsert';\nimport {MediaBase} from './mediabase';\nimport {\n    body,\n    footer,\n    hideElements,\n    showElements,\n    isPercentageValue,\n} from './helpers';\n\nexport class ImageDetails extends MediaBase {\n    DEFAULTS = {\n        WIDTH: 160,\n        HEIGHT: 160,\n    };\n\n    selectorType = Selectors.IMAGE.type;\n\n    mediaDimensions = null;\n\n    constructor(\n        root,\n        editor,\n        currentModal,\n        canShowFilePicker,\n        canShowDropZone,\n        currentUrl,\n        image,\n    ) {\n        super();\n        this.root = root;\n        this.editor = editor;\n        this.currentModal = currentModal;\n        this.canShowFilePicker = canShowFilePicker;\n        this.canShowDropZone = canShowDropZone;\n        this.currentUrl = currentUrl;\n        this.image = image;\n        this.toggleMaxlengthFeedbackSuffix = false;\n    }\n\n    init = function() {\n        this.currentModal.setTitle(getString('imagedetails', 'tiny_media'));\n        this.imageTypeChecked();\n        this.presentationChanged();\n        this.storeImageDimensions(this.image);\n        this.setImageDimensions();\n        this.registerEventListeners();\n    };\n\n    /**\n     * Loads and displays a preview image based on the provided URL, and handles image loading events.\n     */\n    loadInsertImage = async function() {\n        const templateContext = {\n            elementid: this.editor.id,\n            showfilepicker: this.canShowFilePicker,\n            showdropzone: this.canShowDropZone,\n            bodyTemplate: Selectors.IMAGE.template.body.insertImageBody,\n            footerTemplate: Selectors.IMAGE.template.footer.insertImageFooter,\n            selector: Selectors.IMAGE.type,\n        };\n\n        Promise.all([body(templateContext, this.root), footer(templateContext, this.root)])\n            .then(() => {\n                const imageinsert = new ImageInsert(\n                    this.root,\n                    this.editor,\n                    this.currentModal,\n                    this.canShowFilePicker,\n                    this.canShowDropZone,\n                );\n                imageinsert.init();\n                return;\n            })\n            .catch(error => {\n                window.console.log(error);\n            });\n    };\n\n    storeImageDimensions(image) {\n        // Store dimensions of the raw image, falling back to defaults for images without dimensions (e.g. SVG).\n        this.mediaDimensions = {\n            width: image.width || this.DEFAULTS.WIDTH,\n            height: image.height || this.DEFAULTS.HEIGHT,\n        };\n\n        const getCurrentWidth = (element) => {\n            if (element.value === '') {\n                element.value = this.mediaDimensions.width;\n            }\n            return element.value;\n        };\n\n        const getCurrentHeight = (element) => {\n            if (element.value === '') {\n                element.value = this.mediaDimensions.height;\n            }\n            return element.value;\n        };\n\n        const widthInput = this.root.querySelector(Selectors.IMAGE.elements.width);\n        const currentWidth = getCurrentWidth(widthInput);\n\n        const heightInput = this.root.querySelector(Selectors.IMAGE.elements.height);\n        const currentHeight = getCurrentHeight(heightInput);\n\n        const preview = this.root.querySelector(Selectors.IMAGE.elements.preview);\n        preview.setAttribute('src', image.src);\n        preview.style.display = '';\n\n        // Ensure the checkbox always in unchecked status when an image loads at first.\n        const constrain = this.root.querySelector(Selectors.IMAGE.elements.constrain);\n        if (isPercentageValue(currentWidth) && isPercentageValue(currentHeight)) {\n            constrain.checked = currentWidth === currentHeight;\n        } else if (image.width === 0 || image.height === 0) {\n            // If we don't have both dimensions of the image, we can't auto-size it, so disable control.\n            constrain.disabled = 'disabled';\n        } else {\n            // This is the same as comparing to 3 decimal places.\n            const widthRatio = Math.round(100 * parseInt(currentWidth, 10) / image.width);\n            const heightRatio = Math.round(100 * parseInt(currentHeight, 10) / image.height);\n            constrain.checked = widthRatio === heightRatio;\n        }\n\n        /**\n         * Sets the selected size option based on current width and height values.\n         *\n         * @param {number} currentWidth - The current width value.\n         * @param {number} currentHeight - The current height value.\n         */\n        const setSelectedSize = (currentWidth, currentHeight) => {\n            if (this.mediaDimensions.width === currentWidth &&\n                this.mediaDimensions.height === currentHeight\n            ) {\n                this.currentWidth = this.mediaDimensions.width;\n                this.currentHeight = this.mediaDimensions.height;\n                this.sizeChecked('original');\n            } else {\n                this.currentWidth = currentWidth;\n                this.currentHeight = currentHeight;\n                this.sizeChecked('custom');\n            }\n        };\n\n        setSelectedSize(Number(currentWidth), Number(currentHeight));\n    }\n\n    /**\n     * Sets the dimensions of the image preview element based on user input and constraints.\n     */\n    setImageDimensions = () => {\n        const imagePreviewBox = this.root.querySelector(Selectors.IMAGE.elements.previewBox);\n        const image = this.root.querySelector(Selectors.IMAGE.elements.preview);\n        const widthField = this.root.querySelector(Selectors.IMAGE.elements.width);\n        const heightField = this.root.querySelector(Selectors.IMAGE.elements.height);\n\n        const updateImageDimensions = () => {\n            // Get the latest dimensions of the preview box for responsiveness.\n            const boxWidth = imagePreviewBox.clientWidth;\n            const boxHeight = imagePreviewBox.clientHeight;\n            // Get the new width and height for the image.\n            const dimensions = this.fitSquareIntoBox(widthField.value, heightField.value, boxWidth, boxHeight);\n            image.style.width = `${dimensions.width}px`;\n            image.style.height = `${dimensions.height}px`;\n        };\n        // If the client size is zero, then get the new dimensions once the modal is shown.\n        if (imagePreviewBox.clientWidth === 0) {\n            // Call the shown event.\n            this.currentModal.getRoot().on(ModalEvents.shown, () => {\n                updateImageDimensions();\n            });\n        } else {\n            updateImageDimensions();\n        }\n    };\n\n    /**\n     * Handles changes in the image presentation checkbox and enables/disables the image alt text input accordingly.\n     */\n    async presentationChanged() {\n        const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation);\n        const alt = this.root.querySelector(Selectors.IMAGE.elements.alt);\n        alt.disabled = presentation.checked;\n\n        // Counting the image description characters.\n        await this.handleKeyupCharacterCount();\n    }\n\n    /**\n     * This function checks whether an image URL is local (within the same website's domain) or external (from an external source).\n     * Depending on the result, it dynamically updates the visibility and content of HTML elements in a user interface.\n     * If the image is local then we only show it's filename.\n     * If the image is external then it will show full URL and it can be updated.\n     */\n    imageTypeChecked() {\n        const regex = new RegExp(`${Config.wwwroot}`);\n\n        // True if the URL is from external, otherwise false.\n        const isExternalUrl = regex.test(this.currentUrl) === false;\n\n        // Hide the URL input.\n        hideElements(Selectors.IMAGE.elements.url, this.root);\n\n        if (!isExternalUrl) {\n            // Split the URL by '/' to get an array of segments.\n            const segments = this.currentUrl.split('/');\n            // Get the last segment, which should be the filename.\n            const filename = segments.pop().split('?')[0];\n            // Show the file name.\n            this.setFilenameLabel(decodeURI(filename));\n        } else {\n\n            this.setFilenameLabel(decodeURI(this.currentUrl));\n        }\n    }\n\n    /**\n     * Set the string for the URL label element.\n     *\n     * @param {string} label - The label text to set.\n     */\n    setFilenameLabel(label) {\n        const urlLabelEle = this.root.querySelector(Selectors.IMAGE.elements.fileNameLabel);\n        if (urlLabelEle) {\n            urlLabelEle.innerHTML = label;\n            urlLabelEle.setAttribute(\"title\", label);\n        }\n    }\n\n    toggleAriaInvalid(selectors, predicate) {\n        selectors.forEach((selector) => {\n            const elements = this.root.querySelectorAll(selector);\n            elements.forEach((element) => element.setAttribute('aria-invalid', predicate));\n        });\n    }\n\n    hasErrorUrlField() {\n        const urlError = this.currentUrl === '';\n        if (urlError) {\n            showElements(Selectors.IMAGE.elements.urlWarning, this.root);\n        } else {\n            hideElements(Selectors.IMAGE.elements.urlWarning, this.root);\n        }\n        this.toggleAriaInvalid([Selectors.IMAGE.elements.url], urlError);\n\n        return urlError;\n    }\n\n    hasErrorAltField() {\n        const alt = this.root.querySelector(Selectors.IMAGE.elements.alt).value;\n        const presentation = this.root.querySelector(Selectors.IMAGE.elements.presentation).checked;\n        const imageAltError = alt === '' && !presentation;\n        if (imageAltError) {\n            showElements(Selectors.IMAGE.elements.altWarning, this.root);\n        } else {\n            hideElements(Selectors.IMAGE.elements.altWarning, this.root);\n        }\n        this.toggleAriaInvalid([Selectors.IMAGE.elements.alt, Selectors.IMAGE.elements.presentation], imageAltError);\n\n        return imageAltError;\n    }\n\n    updateWarning() {\n        const urlError = this.hasErrorUrlField();\n        const imageAltError = this.hasErrorAltField();\n\n        return urlError || imageAltError;\n    }\n\n    getImageContext() {\n        // Check if there are any accessibility issues.\n        if (this.updateWarning()) {\n            return null;\n        }\n\n        const classList = [];\n        const constrain = this.root.querySelector(Selectors.IMAGE.elements.constrain).checked;\n        const sizeOriginal = this.root.querySelector(Selectors.IMAGE.elements.sizeOriginal).checked;\n        if (constrain || sizeOriginal) {\n            // If the Auto size checkbox is checked or the Original size is checked, then apply the responsive class.\n            classList.push(Selectors.IMAGE.styles.responsive);\n        } else {\n            // Otherwise, remove it.\n            classList.pop(Selectors.IMAGE.styles.responsive);\n        }\n\n        return {\n            url: this.currentUrl,\n            alt: this.root.querySelector(Selectors.IMAGE.elements.alt).value,\n            width: this.root.querySelector(Selectors.IMAGE.elements.width).value,\n            height: this.root.querySelector(Selectors.IMAGE.elements.height).value,\n            presentation: this.root.querySelector(Selectors.IMAGE.elements.presentation).checked,\n            customStyle: this.root.querySelector(Selectors.IMAGE.elements.customStyle).value,\n            classlist: classList.join(' '),\n        };\n    }\n\n    setImage() {\n        const pendingPromise = new Pending('tiny_media:setImage');\n        const url = this.currentUrl;\n        if (url === '') {\n            return;\n        }\n\n        // Check if there are any accessibility issues.\n        if (this.updateWarning()) {\n            pendingPromise.resolve();\n            return;\n        }\n\n        // Check for invalid width or height.\n        const width = this.root.querySelector(Selectors.IMAGE.elements.width).value;\n        if (!isPercentageValue(width) && isNaN(parseInt(width, 10))) {\n            this.root.querySelector(Selectors.IMAGE.elements.width).focus();\n            pendingPromise.resolve();\n            return;\n        }\n\n        const height = this.root.querySelector(Selectors.IMAGE.elements.height).value;\n        if (!isPercentageValue(height) && isNaN(parseInt(height, 10))) {\n            this.root.querySelector(Selectors.IMAGE.elements.height).focus();\n            pendingPromise.resolve();\n            return;\n        }\n\n        Templates.render('tiny_media/image', this.getImageContext())\n        .then((html) => {\n            this.editor.insertContent(html);\n            this.currentModal.destroy();\n            pendingPromise.resolve();\n\n            return html;\n        })\n        .catch(error => {\n            window.console.log(error);\n        });\n    }\n\n    /**\n     * Deletes the image after confirming with the user and loads the insert image page.\n     */\n    deleteImage() {\n        Notification.deleteCancelPromise(\n            getString('deleteimage', 'tiny_media'),\n            getString('deleteimagewarning', 'tiny_media'),\n        ).then(() => {\n            hideElements(Selectors.IMAGE.elements.altWarning, this.root);\n            // Removing the image in the preview will bring the user to the insert page.\n            this.loadInsertImage();\n            return;\n        }).catch(error => {\n            window.console.log(error);\n        });\n    }\n\n    registerEventListeners() {\n        const submitAction = this.root.querySelector(Selectors.IMAGE.actions.submit);\n        submitAction.addEventListener('click', (e) => {\n            e.preventDefault();\n            this.setImage();\n        });\n\n        const deleteImageEle = this.root.querySelector(Selectors.IMAGE.actions.deleteImage);\n        deleteImageEle.addEventListener('click', () => {\n            this.deleteImage();\n        });\n        deleteImageEle.addEventListener(\"keydown\", (e) => {\n            if (e.key === \"Enter\") {\n                this.deleteImage();\n            }\n        });\n\n        this.root.addEventListener('change', async(e) => {\n            const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);\n            if (presentationEle) {\n                await this.presentationChanged();\n            }\n\n            const constrainEle = e.target.closest(Selectors.IMAGE.elements.constrain);\n            if (constrainEle) {\n                this.autoAdjustSize();\n            }\n\n            const sizeOriginalEle = e.target.closest(Selectors.IMAGE.elements.sizeOriginal);\n            if (sizeOriginalEle) {\n                this.sizeChecked('original');\n            }\n\n            const sizeCustomEle = e.target.closest(Selectors.IMAGE.elements.sizeCustom);\n            if (sizeCustomEle) {\n                this.sizeChecked('custom');\n            }\n        });\n\n        this.root.addEventListener('blur', async(e) => {\n            if (e.target.nodeType === Node.ELEMENT_NODE) {\n\n                const presentationEle = e.target.closest(Selectors.IMAGE.elements.presentation);\n                if (presentationEle) {\n                    await this.presentationChanged();\n                }\n            }\n        }, true);\n\n        // Character count.\n        this.root.addEventListener('keyup', async(e) => {\n            const altEle = e.target.closest(Selectors.IMAGE.elements.alt);\n            if (altEle) {\n                await this.handleKeyupCharacterCount();\n            }\n        });\n\n        this.root.addEventListener('input', (e) => {\n            const widthEle = e.target.closest(Selectors.IMAGE.elements.width);\n            if (widthEle) {\n                // Avoid empty value.\n                widthEle.value = widthEle.value === \"\" ? 0 : Number(widthEle.value);\n                this.autoAdjustSize();\n            }\n\n            const heightEle = e.target.closest(Selectors.IMAGE.elements.height);\n            if (heightEle) {\n                // Avoid empty value.\n                heightEle.value = heightEle.value === \"\" ? 0 : Number(heightEle.value);\n                this.autoAdjustSize(true);\n            }\n        });\n    }\n\n    async handleKeyupCharacterCount() {\n        const altField = this.root.querySelector(Selectors.IMAGE.elements.alt);\n        const alt = altField.value;\n        const current = this.root.querySelector('#currentcount');\n        current.innerHTML = alt.length;\n        const maxLength = altField.getAttribute('maxlength');\n        const maxLengthFeedback = document.getElementById('maxlength_feedback');\n        if (alt.length >= maxLength) {\n            maxLengthFeedback.textContent = await getString('maxlengthreached', 'core', maxLength);\n\n            // Clever (or hacky?;p) way to ensure that the feedback message is announced to screen readers.\n            const suffix = this.toggleMaxlengthFeedbackSuffix ? '' : '.';\n            maxLengthFeedback.textContent += suffix;\n            this.toggleMaxlengthFeedbackSuffix = !this.toggleMaxlengthFeedbackSuffix;\n\n            // Clear the feedback message after 4 seconds. This is similar to the default timeout of toast messages\n            // before disappearing from view. It is important to clear the message to prevent screen reader users from navigating\n            // into this region and avoiding confusion.\n            setTimeout(() => {\n                maxLengthFeedback.textContent = '';\n            }, 4000);\n        }\n    }\n\n    /**\n     * Calculates the dimensions to fit a square into a specified box while maintaining aspect ratio.\n     *\n     * @param {number} squareWidth - The width of the square.\n     * @param {number} squareHeight - The height of the square.\n     * @param {number} boxWidth - The width of the box.\n     * @param {number} boxHeight - The height of the box.\n     * @returns {Object} An object with the new width and height of the square to fit in the box.\n     */\n    fitSquareIntoBox = (squareWidth, squareHeight, boxWidth, boxHeight) => {\n        if (squareWidth < boxWidth && squareHeight < boxHeight) {\n          // If the square is smaller than the box, keep its dimensions.\n          return {\n            width: squareWidth,\n            height: squareHeight,\n          };\n        }\n        // Calculate the scaling factor based on the minimum scaling required to fit in the box.\n        const widthScaleFactor = boxWidth / squareWidth;\n        const heightScaleFactor = boxHeight / squareHeight;\n        const minScaleFactor = Math.min(widthScaleFactor, heightScaleFactor);\n        // Scale the square's dimensions based on the aspect ratio and the minimum scaling factor.\n        const newWidth = squareWidth * minScaleFactor;\n        const newHeight = squareHeight * minScaleFactor;\n        return {\n          width: newWidth,\n          height: newHeight,\n        };\n    };\n}\n"],"names":["ImageDetails","MediaBase","constructor","root","editor","currentModal","canShowFilePicker","canShowDropZone","currentUrl","image","WIDTH","HEIGHT","Selectors","IMAGE","type","setTitle","imageTypeChecked","presentationChanged","storeImageDimensions","this","setImageDimensions","registerEventListeners","async","templateContext","elementid","id","showfilepicker","showdropzone","bodyTemplate","template","body","insertImageBody","footerTemplate","footer","insertImageFooter","selector","Promise","all","then","ImageInsert","init","catch","error","window","console","log","imagePreviewBox","querySelector","elements","previewBox","preview","widthField","width","heightField","height","updateImageDimensions","boxWidth","clientWidth","boxHeight","clientHeight","dimensions","fitSquareIntoBox","value","style","getRoot","on","ModalEvents","shown","squareWidth","squareHeight","widthScaleFactor","heightScaleFactor","minScaleFactor","Math","min","toggleMaxlengthFeedbackSuffix","mediaDimensions","DEFAULTS","currentWidth","element","getCurrentWidth","currentHeight","getCurrentHeight","setAttribute","src","display","constrain","checked","disabled","widthRatio","round","parseInt","heightRatio","sizeChecked","setSelectedSize","Number","presentation","alt","handleKeyupCharacterCount","isExternalUrl","RegExp","Config","wwwroot","test","url","setFilenameLabel","decodeURI","filename","split","pop","label","urlLabelEle","fileNameLabel","innerHTML","toggleAriaInvalid","selectors","predicate","forEach","querySelectorAll","hasErrorUrlField","urlError","urlWarning","hasErrorAltField","imageAltError","altWarning","updateWarning","getImageContext","classList","sizeOriginal","push","styles","responsive","customStyle","classlist","join","setImage","pendingPromise","Pending","resolve","isNaN","focus","render","html","insertContent","destroy","deleteImage","deleteCancelPromise","loadInsertImage","actions","submit","addEventListener","e","preventDefault","deleteImageEle","key","target","closest","autoAdjustSize","sizeCustom","nodeType","Node","ELEMENT_NODE","widthEle","heightEle","altField","length","maxLength","getAttribute","maxLengthFeedback","document","getElementById","textContent","suffix","setTimeout"],"mappings":"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"}