Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

{"version":3,"file":"embedpreview.min.js","sources":["../../src/embed/embedpreview.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 embed preview and details class.\n *\n * This handles the embed file/url preview before embedding them into tiny editor.\n *\n * @module      tiny_media/embed/embedpreview\n * @copyright   2024 Stevani Andolo <stevani@hotmail.com.au>\n * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Selectors from '../selectors';\nimport {component} from '../common';\nimport {getString} from 'core/str';\nimport {\n    sourceTypeChecked,\n    getFileName,\n    setPropertiesFromData,\n    showElements,\n    stopMediaLoading,\n    hideElements,\n} from '../helpers';\nimport {EmbedHandler} from './embedhandler';\nimport {MediaBase} from '../mediabase';\nimport Notification from 'core/notification';\nimport EmbedModal from '../embedmodal';\nimport {\n    getEmbeddedMediaDetails,\n    insertMediaThumbnailTemplateContext,\n    fetchPreview,\n} from './embedhelpers';\nimport {notifyFilterContentUpdated} from 'core_filters/events';\n\nexport class EmbedPreview extends MediaBase {\n\n    // Selector type for \"EMBED\".\n    selectorType = Selectors.EMBED.type;\n\n    // Fixed aspect ratio used for external media providers.\n    linkMediaAspectRatio = 1.78;\n\n    constructor(data) {\n        super();\n        setPropertiesFromData(this, data); // Creates dynamic properties based on \"data\" param.\n    }\n\n    /**\n     * Init the media details preview.\n     */\n    init = async() => {\n        this.currentModal.setTitle(getString('mediadetails', component));\n        sourceTypeChecked({\n            fetchedTitle: this.fetchedMediaLinkTitle ?? null,\n            source: this.originalUrl,\n            root: this.root,\n            urlSelector: Selectors.EMBED.elements.fromUrl,\n            fileNameSelector: Selectors.EMBED.elements.fileNameLabel,\n        });\n        this.setMediaSourceAndPoster();\n        this.registerMediaDetailsEventListeners(this.currentModal);\n    };\n\n    /**\n     * Sets media source and thumbnail for the video.\n     */\n    setMediaSourceAndPoster = async() => {\n        const box = this.root.querySelector(Selectors.EMBED.elements.previewBox);\n        const previewArea = document.querySelector(Selectors.EMBED.elements.mediaPreviewContainer);\n        previewArea.setAttribute('data-original-url', this.originalUrl);\n\n        // Previewing existing media could be a link one.\n        // Or, new media added using url input and mediaType is neither video or audio.\n        if (this.mediaType === 'link' || (this.newMediaLink && !['video', 'audio'].includes(this.mediaType))) {\n            previewArea.setAttribute('data-media-type', 'link');\n            previewArea.innerHTML = await fetchPreview(this.originalUrl, this.contextId);\n            notifyFilterContentUpdated(previewArea);\n        } else if (this.mediaType === 'video') {\n            const video = document.createElement('video');\n            video.src = this.originalUrl;\n\n            // Media url can be played using html video.\n            video.addEventListener('loadedmetadata', () => {\n                const videoHeight = video.videoHeight;\n                const videoWidth = video.videoWidth;\n                const widthProportion = (videoWidth - videoHeight);\n                const isLandscape = widthProportion > 0;\n\n                // Store dimensions of the raw video.\n                this.mediaDimensions = {\n                    width: videoWidth,\n                    height: videoHeight,\n                };\n\n                // Set the media preview based on the media dimensions.\n                if (isLandscape) {\n                    video.width = box.offsetWidth;\n                } else {\n                    video.height = box.offsetHeight;\n                }\n\n                const height = this.root.querySelector(Selectors.EMBED.elements.height);\n                const width = this.root.querySelector(Selectors.EMBED.elements.width);\n\n                if (height.value === '' && width.value === '') {\n                    height.value = videoHeight;\n                    width.value = videoWidth;\n                }\n\n                // Size checking and adjustment.\n                if (videoHeight === parseInt(height.value) && videoWidth === parseInt(width.value)) {\n                    this.currentWidth = this.mediaDimensions.width;\n                    this.currentHeight = this.mediaDimensions.height;\n                    this.sizeChecked('original');\n                } else {\n                    this.currentWidth = parseInt(width.value);\n                    this.currentHeight = parseInt(height.value);\n                    this.sizeChecked('custom');\n                }\n            });\n\n            video.controls = true;\n            if (this.media.poster) {\n                previewArea.setAttribute('data-media-poster', this.media.poster);\n                if (!video.classList.contains('w-100')) {\n                    video.classList.add('w-100');\n                }\n                video.poster = this.media.poster;\n            }\n            video.load();\n\n            previewArea.setAttribute('data-media-type', 'video');\n            previewArea.innerHTML = video.outerHTML;\n            notifyFilterContentUpdated(previewArea);\n        } else if (this.mediaType === 'audio') {\n            const audio = document.createElement('audio');\n            audio.src = this.originalUrl;\n            audio.controls = true;\n            audio.load();\n\n            previewArea.setAttribute('data-media-type', 'audio');\n            previewArea.innerHTML = audio.outerHTML;\n            notifyFilterContentUpdated(previewArea);\n        } else {\n            // Show warning notification.\n            const urlWarningLabelEle = this.root.querySelector(Selectors.EMBED.elements.urlWarning);\n            urlWarningLabelEle.innerHTML = await getString('medianotavailabledesc', component, this.originalUrl);\n            showElements(Selectors.EMBED.elements.urlWarning, this.root);\n\n            // Stop the spinner.\n            stopMediaLoading(this.root, Selectors.EMBED.type);\n\n            // Reset the upload form.\n            (new EmbedHandler(this)).resetUploadForm();\n            return;\n        }\n\n        // Stop the loader and display back the body template when the media is loaded.\n        stopMediaLoading(this.root, Selectors.EMBED.type);\n        showElements(Selectors.EMBED.elements.mediaDetailsBody, this.root);\n\n        // Set the media name/title.\n        this.root.querySelector(Selectors.EMBED.elements.title).value = this.setMediaTitle();\n    };\n\n    /**\n     * Set media name/title.\n     *\n     * @returns {string}\n     */\n    setMediaTitle = () => {\n        // Getting and setting up media title/name.\n        let fileName = null;\n        if (['video', 'audio'].includes(this.mediaType)) {\n            fileName = getFileName(this.originalUrl); // Get original filename.\n        } else if (this.fetchedMediaLinkTitle) {\n            fileName = this.fetchedMediaLinkTitle;\n        } else {\n            fileName = this.originalUrl;\n        }\n\n        if (this.isUpdating) {\n            if (!this.newMediaLink) {\n                fileName = this.mediaTitle; // Title from the selected media.\n            }\n        }\n\n        return fileName;\n    };\n\n    /**\n     * Deletes the media after confirming with the user and loads the insert media page.\n     */\n    deleteMedia = () => {\n        Notification.deleteCancelPromise(\n            getString('deletemedia', component),\n            getString('deletemediawarning', component),\n        ).then(() => {\n            // Reset media upload form.\n            (new EmbedHandler(this)).resetUploadForm();\n\n            // Delete any selected media mediaData.\n            delete this.mediaData;\n            return;\n        }).catch(error => {\n            window.console.log(error);\n        });\n    };\n\n    /**\n     * Delete embedded media thumbnail.\n     */\n    deleteEmbeddedThumbnail = () => {\n        Notification.deleteCancelPromise(\n            getString('deleteembeddedthumbnail', component),\n            getString('deleteembeddedthumbnailwarning', component),\n        ).then(async() => {\n            if (this.mediaType === 'video') {\n                const video = this.root.querySelector('video');\n                if (video) {\n                    video.removeAttribute('poster');\n                    const preview = this.root.querySelector(Selectors.EMBED.elements.mediaPreviewContainer);\n                    preview.removeAttribute('data-media-poster');\n                }\n            }\n\n            const deleteCustomThumbnail = this.root.querySelector(Selectors.EMBED.actions.deleteCustomThumbnail);\n            deleteCustomThumbnail.remove();\n\n            const uploadCustomThumbnail = this.root.querySelector(Selectors.EMBED.actions.uploadCustomThumbnail);\n            uploadCustomThumbnail.textContent = await getString('uploadthumbnail', component);\n            return;\n        }).catch(error => {\n            window.console.log(error);\n        });\n    };\n\n    /**\n     * Shows the insert thumbnail dialogue.\n     */\n    showUploadThumbnail = async() => {\n        const uploadThumbnailModal = await EmbedModal.create({\n            large: true,\n            templateContext: {elementid: this.editor.getElement().id},\n        });\n        const root = uploadThumbnailModal.getRoot()[0];\n\n        // Get selected media metadata.\n        const mediaData = getEmbeddedMediaDetails(this);\n        mediaData.isUpdating = this.isUpdating;\n\n        const embedHandler = new EmbedHandler(this);\n        embedHandler.loadInsertThumbnailTemplatePromise(\n            insertMediaThumbnailTemplateContext(this), // Get template context for creating media thumbnail.\n            {root, uploadThumbnailModal}, // Required root elements.\n            await embedHandler.getMediaTemplateContext(mediaData) // Get current media data.\n        );\n    };\n\n    /**\n     * Only registers event listeners for new loaded elements in embed preview modal.\n     */\n    registerMediaDetailsEventListeners = async() => {\n        // Handle the original size when selected.\n        const sizeOriginalEle = this.root.querySelector(Selectors.EMBED.elements.sizeOriginal);\n        if (sizeOriginalEle) {\n            sizeOriginalEle.addEventListener('change', () => {\n                this.sizeChecked('original');\n            });\n        }\n\n        // Handle the custom size when selected.\n        const sizeCustomEle = this.root.querySelector(Selectors.EMBED.elements.sizeCustom);\n        if (sizeCustomEle) {\n            sizeCustomEle.addEventListener('change', () => {\n                this.sizeChecked('custom');\n            });\n        }\n\n        const widthEle = this.root.querySelector(Selectors.EMBED.elements.width);\n        const heightEle = this.root.querySelector(Selectors.EMBED.elements.height);\n\n        // Handle the custom with size when inputted.\n        if (widthEle) {\n            widthEle.addEventListener('input', () => {\n                if (this.mediaType === 'link') {\n                    // Let's apply the 16:9 aspect ratio if it's a link media type.\n                    heightEle.value = Math.round(widthEle.value / this.linkMediaAspectRatio);\n                } else {\n                    // Avoid empty value.\n                    widthEle.value = widthEle.value === \"\" ? 0 : Number(widthEle.value);\n                    this.autoAdjustSize();\n                }\n            });\n        }\n\n        // Handle the custom height size when inputted.\n        if (heightEle) {\n            heightEle.addEventListener('input', () => {\n                if (this.mediaType === 'link') {\n                    // Let's apply the 16:9 aspect ratio if it's a link media type.\n                    widthEle.value = Math.round(heightEle.value * this.linkMediaAspectRatio);\n                } else {\n                    // Avoid empty value.\n                    heightEle.value = heightEle.value === \"\" ? 0 : Number(heightEle.value);\n                    this.autoAdjustSize(true);\n                }\n            });\n        }\n\n        // Handle media preview delete.\n        const deleteMedia = this.root.querySelector(Selectors.EMBED.actions.deleteMedia);\n        if (deleteMedia) {\n            deleteMedia.addEventListener('click', (e) => {\n                e.preventDefault();\n                this.deleteMedia();\n            });\n        }\n\n        // Show subtitles and captions settings.\n        const showSubtitleCaption = this.root.querySelector(Selectors.EMBED.actions.showSubtitleCaption);\n        if (showSubtitleCaption) {\n            showSubtitleCaption.addEventListener('click', (e) => {\n                e.preventDefault();\n                hideElements([\n                    Selectors.EMBED.actions.showSubtitleCaption,\n                    Selectors.EMBED.actions.cancelMediaDetails,\n                    Selectors.EMBED.elements.mediaDetailsBody,\n                ], this.root);\n                showElements([\n                    Selectors.EMBED.actions.backToMediaDetails,\n                    Selectors.EMBED.elements.mediaSubtitleCaptionBody,\n                ], this.root);\n            });\n        }\n\n        // Back to media preview.\n        const backToMediaDetails = this.root.querySelector(Selectors.EMBED.actions.backToMediaDetails);\n        if (backToMediaDetails) {\n            backToMediaDetails.addEventListener('click', () => {\n                hideElements([\n                    Selectors.EMBED.actions.backToMediaDetails,\n                    Selectors.EMBED.elements.mediaSubtitleCaptionBody,\n                ], this.root);\n                showElements([\n                    Selectors.EMBED.actions.showSubtitleCaption,\n                    Selectors.EMBED.actions.cancelMediaDetails,\n                    Selectors.EMBED.elements.mediaDetailsBody,\n                ], this.root);\n            });\n        }\n\n        // Handles upload media thumbnail.\n        const uploadCustomThumbnail = this.root.querySelector(Selectors.EMBED.actions.uploadCustomThumbnail);\n        if (uploadCustomThumbnail) {\n            uploadCustomThumbnail.addEventListener('click', () => {\n                this.showUploadThumbnail();\n            });\n        }\n\n        // Handles delete media thumbnail.\n        const deleteCustomThumbnail = this.root.querySelector(Selectors.EMBED.actions.deleteCustomThumbnail);\n        if (deleteCustomThumbnail) {\n            deleteCustomThumbnail.addEventListener('click', () => {\n                this.deleteEmbeddedThumbnail();\n            });\n        }\n\n        // Handles language track selection.\n        const langTracks = this.root.querySelectorAll(Selectors.EMBED.elements.trackLang);\n        if (langTracks) {\n            langTracks.forEach((dropdown) => {\n                const defaultVal = dropdown.getAttribute('data-value');\n                if (defaultVal) {\n                    Array.from(dropdown.options).some(option => {\n                        // Check if srclang in track is a language code like \"en\"\n                        // or language name like \"English\" prior to MDL-85159.\n                        if (option.dataset.languageCode === defaultVal || option.value === defaultVal) {\n                            option.selected = true;\n                            return true;\n                        }\n                        return false;\n                    });\n                }\n            });\n        }\n    };\n}\n"],"names":["EmbedPreview","MediaBase","constructor","data","Selectors","EMBED","type","async","currentModal","setTitle","component","fetchedTitle","this","fetchedMediaLinkTitle","source","originalUrl","root","urlSelector","elements","fromUrl","fileNameSelector","fileNameLabel","setMediaSourceAndPoster","registerMediaDetailsEventListeners","box","querySelector","previewBox","previewArea","document","mediaPreviewContainer","setAttribute","mediaType","newMediaLink","includes","innerHTML","contextId","video","createElement","src","addEventListener","videoHeight","videoWidth","isLandscape","mediaDimensions","width","height","offsetWidth","offsetHeight","value","parseInt","currentWidth","currentHeight","sizeChecked","controls","media","poster","classList","contains","add","load","outerHTML","urlWarning","EmbedHandler","resetUploadForm","audio","mediaDetailsBody","title","setMediaTitle","fileName","isUpdating","mediaTitle","deleteCancelPromise","then","mediaData","catch","error","window","console","log","removeAttribute","actions","deleteCustomThumbnail","remove","uploadCustomThumbnail","textContent","uploadThumbnailModal","EmbedModal","create","large","templateContext","elementid","editor","getElement","id","getRoot","embedHandler","loadInsertThumbnailTemplatePromise","getMediaTemplateContext","sizeOriginalEle","sizeOriginal","sizeCustomEle","sizeCustom","widthEle","heightEle","Math","round","linkMediaAspectRatio","Number","autoAdjustSize","deleteMedia","e","preventDefault","showSubtitleCaption","cancelMediaDetails","backToMediaDetails","mediaSubtitleCaptionBody","showUploadThumbnail","deleteEmbeddedThumbnail","langTracks","querySelectorAll","trackLang","forEach","dropdown","defaultVal","getAttribute","Array","from","options","some","option","dataset","languageCode","selected"],"mappings":"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"}