import { debounce_timeout } from './constants.js'; /** * Drag and drop handler * * Can be used on any element, enabling drag&drop styling and callback on drop. */ export class DragAndDropHandler { /** @private @type {JQuery.Selector} */ selector; /** @private @type {(files: File[], event:JQuery.DropEvent) => void} */ onDropCallback; /** @private @type {NodeJS.Timeout} Remark: Not actually NodeJS timeout, but it's close */ dragLeaveTimeout; /** @private @type {boolean} */ noAnimation; /** * Create a DragAndDropHandler * @param {JQuery.Selector} selector - The CSS selector for the elements to enable drag and drop * @param {(files: File[], event:JQuery.DropEvent) => void} onDropCallback - The callback function to handle the drop event */ constructor(selector, onDropCallback, { noAnimation = false } = {}) { this.selector = selector; this.onDropCallback = onDropCallback; this.dragLeaveTimeout = null; this.noAnimation = noAnimation; this.init(); } /** * Destroy the drag and drop functionality */ destroy() { if (this.selector === 'body') { $(document.body).off('dragover', this.handleDragOver.bind(this)); $(document.body).off('dragleave', this.handleDragLeave.bind(this)); $(document.body).off('drop', this.handleDrop.bind(this)); } else { $(document.body).off('dragover', this.selector, this.handleDragOver.bind(this)); $(document.body).off('dragleave', this.selector, this.handleDragLeave.bind(this)); $(document.body).off('drop', this.selector, this.handleDrop.bind(this)); } $(this.selector).remove('drop_target no_animation'); } /** * Initialize the drag and drop functionality * Automatically called on construction * @private */ init() { if (this.selector === 'body') { $(document.body).on('dragover', this.handleDragOver.bind(this)); $(document.body).on('dragleave', this.handleDragLeave.bind(this)); $(document.body).on('drop', this.handleDrop.bind(this)); } else { $(document.body).on('dragover', this.selector, this.handleDragOver.bind(this)); $(document.body).on('dragleave', this.selector, this.handleDragLeave.bind(this)); $(document.body).on('drop', this.selector, this.handleDrop.bind(this)); } $(this.selector).addClass('drop_target'); if (this.noAnimation) $(this.selector).addClass('no_animation'); } /** * @param {JQuery.DragOverEvent} event - The dragover event * @private */ handleDragOver(event) { event.preventDefault(); event.stopPropagation(); clearTimeout(this.dragLeaveTimeout); $(this.selector).addClass('drop_target dragover'); if (this.noAnimation) $(this.selector).addClass('no_animation'); } /** * @param {JQuery.DragLeaveEvent} event - The dragleave event * @private */ handleDragLeave(event) { event.preventDefault(); event.stopPropagation(); // Debounce the removal of the class, so it doesn't "flicker" on dragging over clearTimeout(this.dragLeaveTimeout); this.dragLeaveTimeout = setTimeout(() => { $(this.selector).removeClass('dragover'); }, debounce_timeout.quick); } /** * @param {JQuery.DropEvent} event - The drop event * @private */ handleDrop(event) { event.preventDefault(); event.stopPropagation(); clearTimeout(this.dragLeaveTimeout); $(this.selector).removeClass('dragover'); const files = Array.from(event.originalEvent.dataTransfer.files); this.onDropCallback(files, event); } }