MDL-78096 tiny_media: Implement drag-drop to upload an image

This commit is contained in:
meirzamoodle 2024-02-06 23:27:18 +07:00
parent a420f895d6
commit 4d3c8e895e
9 changed files with 82 additions and 18 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
define("tiny_media/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_image_urlentrysubmit",imageBrowser:".openimagebrowser",addUrl:".tiny_image_addurl"},elements:{form:"form.tiny_image_form",alignSettings:".tiny_image_button",alt:".tiny_image_altentry",altWarning:".tiny_image_altwarning",height:".tiny_image_heightentry",width:".tiny_image_widthentry",url:".tiny_image_urlentry",urlWarning:".tiny_image_urlwarning",size:".tiny_image_size",presentation:".tiny_image_presentation",constrain:".tiny_image_constrain",customStyle:".tiny_image_customstyle",preview:".tiny_image_preview",previewBox:".tiny_image_preview_box",loaderIcon:".tiny_image_loader",insertImage:".tiny_image_insert_image",modalFooter:".modal-footer"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_media_submit",mediaBrowser:".openmediabrowser"},elements:{form:"form.tiny_media_form",source:".tiny_media_source",track:".tiny_media_track",mediaSource:".tiny_media_media_source",linkSource:".tiny_media_link_source",linkSize:".tiny_media_link_size",posterSource:".tiny_media_poster_source",posterSize:".tiny_media_poster_size",displayOptions:".tiny_media_display_options",name:".tiny_media_name_entry",title:".tiny_media_title_entry",url:".tiny_media_url_entry",width:".tiny_media_width_entry",height:".tiny_media_height_entry",trackSource:".tiny_media_track_source",trackKind:".tiny_media_track_kind_entry",trackLabel:".tiny_media_track_label_entry",trackLang:".tiny_media_track_lang_entry",trackDefault:".tiny_media_track_default",mediaControl:".tiny_media_controls",mediaAutoplay:".tiny_media_autoplay",mediaMute:".tiny_media_mute",mediaLoop:".tiny_media_loop",advancedSettings:".tiny_media_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}}},_exports.default}));
define("tiny_media/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_image_urlentrysubmit",imageBrowser:".openimagebrowser",addUrl:".tiny_image_addurl"},elements:{form:"form.tiny_image_form",alignSettings:".tiny_image_button",alt:".tiny_image_altentry",altWarning:".tiny_image_altwarning",height:".tiny_image_heightentry",width:".tiny_image_widthentry",url:".tiny_image_urlentry",urlWarning:".tiny_image_urlwarning",size:".tiny_image_size",presentation:".tiny_image_presentation",constrain:".tiny_image_constrain",customStyle:".tiny_image_customstyle",preview:".tiny_image_preview",previewBox:".tiny_image_preview_box",loaderIcon:".tiny_image_loader",loaderIconContainer:".tiny_image_loader_container",insertImage:".tiny_image_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_image_dropzone_container",fileInput:"#tiny_image_fileinput"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_media_submit",mediaBrowser:".openmediabrowser"},elements:{form:"form.tiny_media_form",source:".tiny_media_source",track:".tiny_media_track",mediaSource:".tiny_media_media_source",linkSource:".tiny_media_link_source",linkSize:".tiny_media_link_size",posterSource:".tiny_media_poster_source",posterSize:".tiny_media_poster_size",displayOptions:".tiny_media_display_options",name:".tiny_media_name_entry",title:".tiny_media_title_entry",url:".tiny_media_url_entry",width:".tiny_media_width_entry",height:".tiny_media_height_entry",trackSource:".tiny_media_track_source",trackKind:".tiny_media_track_kind_entry",trackLabel:".tiny_media_track_label_entry",trackLang:".tiny_media_track_lang_entry",trackDefault:".tiny_media_track_default",mediaControl:".tiny_media_controls",mediaAutoplay:".tiny_media_autoplay",mediaMute:".tiny_media_mute",mediaLoop:".tiny_media_loop",advancedSettings:".tiny_media_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}}},_exports.default}));
//# sourceMappingURL=selectors.min.js.map

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,8 @@
*/
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";
@ -38,6 +40,7 @@ prefetchStrings('tiny_media', [
'imageurlrequired',
'uploading',
'loading',
'addfilesdrop',
]);
export class ImageInsert {
@ -65,12 +68,25 @@ export class ImageInsert {
'imageurlrequired',
'uploading',
'loading',
'addfilesdrop',
];
const langStringvalues = await getStrings([...langStringKeys].map((key) => ({key, component})));
// Convert array to object.
this.langStrings = Object.fromEntries(langStringKeys.map((key, index) => [key, langStringvalues[index]]));
this.currentModal.setTitle(this.langStrings.insertimage);
if (this.canShowDropZone) {
const dropZoneEle = document.querySelector(Selectors.IMAGE.elements.dropzoneContainer);
const dropZone = new Dropzone(
dropZoneEle,
'image/*',
files => {
this.handleUploadedFile(files);
}
);
dropZone.setLabel(this.langStrings.addfilesdrop);
dropZone.init();
}
await this.registerEventListeners();
};
@ -164,6 +180,43 @@ export class ImageInsert {
}
}
/**
* 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).
* @returns {void}
*/
updateLoaderIcon = (root, langStrings, progress = null) => {
const loaderIcon = root.querySelector(Selectors.IMAGE.elements.loaderIconContainer + ' div');
loaderIcon.innerHTML = progress !== null ? `${langStrings.uploading} ${Math.round(progress)}%` : langStrings.loading;
};
/**
* 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 {
this.startImageLoading();
const fileURL = await uploadFile(this.editor, 'image', files[0], files[0].name, (progress) => {
this.updateLoaderIcon(this.root, this.langStrings, progress);
});
// Set the loader icon content to "loading" after the file upload completes.
this.updateLoaderIcon(this.root, this.langStrings);
this.filePickerCallback({url: fileURL});
} catch (error) {
// Handle the error.
const urlWarningLabelEle = this.root.querySelector(Selectors.IMAGE.elements.urlWarning);
urlWarningLabelEle.innerHTML = error.error !== undefined ? error.error : error;
showElements(Selectors.IMAGE.elements.urlWarning, this.root);
this.stopImageLoading();
}
};
registerEventListeners() {
this.root.addEventListener('click', async(e) => {
const addUrlEle = e.target.closest(Selectors.IMAGE.actions.addUrl);
@ -185,5 +238,12 @@ export class ImageInsert {
this.toggleUrlButton();
}
});
const fileInput = this.root.querySelector(Selectors.IMAGE.elements.fileInput);
if (fileInput) {
fileInput.addEventListener('change', () => {
this.handleUploadedFile(fileInput.files);
});
}
}
}

View File

@ -44,8 +44,11 @@ export default {
preview: '.tiny_image_preview',
previewBox: '.tiny_image_preview_box',
loaderIcon: '.tiny_image_loader',
loaderIconContainer: '.tiny_image_loader_container',
insertImage: '.tiny_image_insert_image',
modalFooter: '.modal-footer',
dropzoneContainer: '.tiny_image_dropzone_container',
fileInput: '#tiny_image_fileinput',
},
styles: {
responsive: 'img-fluid',

View File

@ -25,6 +25,7 @@
$string['addcaptionstrack'] = 'Add caption track';
$string['addchapterstrack'] = 'Add chapter track';
$string['adddescriptionstrack'] = 'Add description track';
$string['addfilesdrop'] = 'Drag and drop an image to upload, or click to select';
$string['addmetadatatrack'] = 'Add metadata track';
$string['addsource_help'] = 'You are recommended to provide an alternative media source, as desktop and mobile browsers support different file formats.';
$string['addsource'] = 'Add alternative source';
@ -69,6 +70,7 @@ $string['label'] = 'Label';
$string['languagesavailable'] = 'Languages available';
$string['languagesinstalled'] = 'Languages installed';
$string['link'] = 'Link';
$string['loading'] = 'Preparing the image';
$string['loop'] = 'Loop';
$string['managefiles'] = 'Manage files';
$string['mediabuttontitle'] = 'Multimedia';
@ -84,6 +86,7 @@ $string['presentation'] = 'This image is decorative only';
$string['presentationoraltrequired'] = 'An image must have a description, unless it is marked as decorative only.';
$string['privacy:metadata'] = 'The media plugin for TinyMCE does not store any personal data.';
$string['remove'] = 'Remove';
$string['repositorynotpermitted'] = 'Paste an image link in the field below.';
$string['saveimage'] = 'Save image';
$string['size'] = 'Width x height (in pixels)';
$string['srclang'] = 'Language';
@ -96,6 +99,7 @@ $string['unusedfilesdesc'] = 'The following embedded files are not used in the t
$string['unusedfilesheader'] = 'Unused files';
$string['unusedfilesremovalnotice'] = 'Any unused files will be automatically deleted when saving changes.';
$string['updatemedia'] = 'Update media';
$string['uploading'] = 'Uploading';
$string['video'] = 'Video';
$string['videoheight'] = 'Video height';
$string['videosourcelabel'] = 'Video source URL';

View File

@ -34,8 +34,12 @@ iframe.mm_iframe {
color: red;
}
.tiny_image_form .tiny_image_dropzone {
padding: 1.5rem 1rem;
.tiny_image_form .tiny_image_dropzone_container {
height: 200px;
}
.tiny_image_form .tiny_image_dropzone_container .dropzone-label {
font-size: 1.25rem;
}
.tiny_image_form .tiny_image_loader_container {

View File

@ -27,25 +27,18 @@
}}
<div class="tiny_image_insert_image">
<div class="tiny_image_dropzone mb-1 d-flex align-items-center flex-column justify-content-center border rounded">
<div class="tiny_image_dropzone d-flex flex-column justify-content-center">
{{#showdropzone}}
<div class="tiny_image_dropzone_iconlabel text-center" role="button">
<!-- Dropzone icon -->
<i class="tiny_image_dropzone_icon fa-6x pb-2 text-secondary fa fa-cloud-upload"></i>
<!-- Dropzone string -->
<div class="tiny_image_dropzone_label lead text-center">
<p class="mb-0">{{#str}} draganddrop, tiny_media {{/str}}</p>
</div>
</div>
<div class="tiny_image_dropzone_container"></div>
<!-- File input -->
<input type="file" id="tiny_image_fileinput" accept="image/*" class="d-none">
{{/showdropzone}}
{{^showfilepicker}}
<div class="tiny_image_dropzone_iconlabel text-center">
<div class="text-center">
<!-- Dropzone icon -->
<i class="tiny_image_dropzone_icon fa-6x pb-2 text-secondary fa fa-cloud"></i>
<i class="fa-6x pb-2 text-secondary fa fa-cloud"></i>
<!-- Dropzone string -->
<div class="tiny_image_dropzone_label lead text-center">
<div class="lead text-center">
<p class="mb-0">{{#str}} repositorynotpermitted, tiny_media {{/str}}</p>
</div>
</div>