// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Tiny media plugin embed thumbnail upload class.
*
* This handles the embed thumbnail upload using drag-drop.
*
* @module tiny_media/embed/embedthumbnailinsert
* @copyright 2024 Stevani Andolo <stevani@hotmail.com.au>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Selectors from '../selectors';
import Dropzone from 'core/dropzone';
import uploadFile from 'editor_tiny/uploader';
import {prefetchStrings} from 'core/prefetch';
import {getStrings} from 'core/str';
import {component} from "../common";
import {
showElements,
startMediaLoading,
stopMediaLoading,
setPropertiesFromData,
body,
footer,
} from '../helpers';
import {EmbedThumbnailPreview} from './embedthumbnailpreview';
import {EmbedHandler} from './embedhandler';
import {displayFilepicker} from 'editor_tiny/utils';
prefetchStrings(component, [
'insertmediathumbnail',
'uploading',
'loadingembedthumbnail',
'addmediathumbnaildrop',
]);
export class EmbedThumbnailInsert {
constructor(data) {
setPropertiesFromData(this, data); // Creates dynamic properties based on "data" param.
}
/**
* Init the dropzone and lang strings.
*
* @param {object} mediaData Object of selected media data
*/
init = async(mediaData) => {
this.mediaData = mediaData; // Current selected media data passed from embedPreview.
const langStringKeys = [
'insertmediathumbnail',
'uploading',
'loadingembedthumbnail',
'addmediathumbnaildrop',
];
const langStringvalues = await getStrings([...langStringKeys].map((key) => ({key, component})));
this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringvalues[index]]));
this.currentModal.uploadThumbnailModal.setTitle(this.langStrings.insertmediathumbnail);
// Let's init the dropzone if canShowDropZone is true.
if (this.canShowDropZone) {
const dropZoneEle = document.querySelector(Selectors.EMBED.elements.dropzoneContainer);
const dropZone = new Dropzone(
dropZoneEle,
this.acceptedImageTypes,
files => {
this.handleUploadedFile(files);
}
);
dropZone.setLabel(this.langStrings.addmediathumbnaildrop);
dropZone.init();
}
this.registerInsertMediaThumbnailEvents(this.thumbnailModalRoot);
};
/**
* Load and display a preview thumbnail based on the provided URL, and handles thumbnail loading events.
*
* @param {string} url - The URL of the thumbnail to load and display.
*/
loadPreviewThumbnail = (url) => {
this.media.poster = url;
let templateContext = {
bodyTemplate: Selectors.EMBED.template.body.mediaThumbnailBody,
footerTemplate: Selectors.EMBED.template.footer.mediaThumbnailFooter,
selector: Selectors.EMBED.type,
};
Promise.all([body(templateContext, this.thumbnailModalRoot), footer(templateContext, this.thumbnailModalRoot)])
.then(() => {
const mediaThumbnail = new EmbedThumbnailPreview(this);
mediaThumbnail.init(this.mediaData);
return;
})
.catch(error => {
window.console.log(error);
});
};
/**
* Handles media preview on file picker callback.
*
* @param {object} params Object of uploaded file
*/
filePickerCallback = (params) => {
if (params.url) {
this.loadPreviewThumbnail(params.url);
}
};
/**
* Updates the content of the loader icon.
*
* @param {HTMLElement} root - The root element containing the loader icon.
* @param {object} langStrings - An object containing language strings.
* @param {number|null} progress - The progress percentage (optional).
*/
updateLoaderIcon = (root, langStrings, progress = null) => {
const loaderIconState = root.querySelector(Selectors.EMBED.elements.loaderIconContainer + ' div');
loaderIconState.innerHTML = (progress !== null) ?
`${langStrings.uploading} ${Math.round(progress)}%` :
langStrings.loadingembedthumbnail;
};
/**
* Handles changes in the media URL input field and loads a preview of the media if the URL has changed.
*/
urlChanged() {
const url = this.thumbnailModalRoot.querySelector(Selectors.EMBED.elements.fromUrl).value;
if (url && url !== this.currentUrl) {
this.loadPreviewThumbnail(url);
}
}
/**
* Handles the uploaded file, initiates the upload process, and updates the UI during the upload.
*
* @param {FileList} files - The list of files to upload (usually from a file input field).
* @returns {Promise<void>} A promise that resolves when the file is uploaded and processed.
*/
handleUploadedFile = async(files) => {
try {
startMediaLoading(this.thumbnailModalRoot, Selectors.EMBED.type);
const fileURL = await uploadFile(this.editor, 'image', files[0], files[0].name, (progress) => {
this.updateLoaderIcon(this.thumbnailModalRoot, this.langStrings, progress);
});
// Set the loader icon content to "loading" after the file upload completes.
this.updateLoaderIcon(this.thumbnailModalRoot, this.langStrings);
this.filePickerCallback({url: fileURL});
} catch (error) {
// Handle the error.
const urlWarningLabelEle = this.thumbnailModalRoot.querySelector(Selectors.EMBED.elements.urlWarning);
urlWarningLabelEle.innerHTML = error.error !== undefined ? error.error : error;
showElements(Selectors.EMBED.elements.urlWarning, this.thumbnailModalRoot);
stopMediaLoading(this.thumbnailModalRoot, Selectors.EMBED.type);
}
};
/**
* Registers events for insert thumbnail modal.
*
* @param {HTMLElement} root - The root element containing the loader icon.
*/
registerInsertMediaThumbnailEvents = (root) => {
const urlEle = root.querySelector(Selectors.EMBED.elements.fromUrl);
if (urlEle) {
urlEle.addEventListener('input', () => {
(new EmbedHandler(this)).toggleUrlButton(urlEle, this.thumbnailModalRoot);
});
}
// Handles add media url.
const addUrlEle = root.querySelector(Selectors.EMBED.actions.addUrl);
if (addUrlEle) {
addUrlEle.addEventListener('click', () => {
startMediaLoading(this.thumbnailModalRoot, Selectors.EMBED.type);
this.urlChanged();
});
}
// Handle repository browsing.
const imageBrowser = root.querySelector(Selectors.IMAGE.actions.imageBrowser);
if (imageBrowser) {
imageBrowser.addEventListener('click', async(e) => {
e.preventDefault();
const params = await displayFilepicker(this.editor, 'image');
this.filePickerCallback(params);
});
}
};
}