Blame Raw
· · · 107 lines (3.9 KB)
0 contributors
1import { debounce_timeout } from './constants.js';
2
3/**
4 * Drag and drop handler
5 *
6 * Can be used on any element, enabling drag&drop styling and callback on drop.
7 */
8export class DragAndDropHandler {
9 /** @private @type {JQuery.Selector} */ selector;
10 /** @private @type {(files: File[], event:JQuery.DropEvent<HTMLElement, undefined, any, any>) => void} */ onDropCallback;
11 /** @private @type {NodeJS.Timeout} Remark: Not actually NodeJS timeout, but it's close */ dragLeaveTimeout;
12
13 /** @private @type {boolean} */ noAnimation;
14
15 /**
16 * Create a DragAndDropHandler
17 * @param {JQuery.Selector} selector - The CSS selector for the elements to enable drag and drop
18 * @param {(files: File[], event:JQuery.DropEvent<HTMLElement, undefined, any, any>) => void} onDropCallback - The callback function to handle the drop event
19 */
20 constructor(selector, onDropCallback, { noAnimation = false } = {}) {
21 this.selector = selector;
22 this.onDropCallback = onDropCallback;
23 this.dragLeaveTimeout = null;
24
25 this.noAnimation = noAnimation;
26
27 this.init();
28 }
29
30 /**
31 * Destroy the drag and drop functionality
32 */
33 destroy() {
34 if (this.selector === 'body') {
35 $(document.body).off('dragover', this.handleDragOver.bind(this));
36 $(document.body).off('dragleave', this.handleDragLeave.bind(this));
37 $(document.body).off('drop', this.handleDrop.bind(this));
38 } else {
39 $(document.body).off('dragover', this.selector, this.handleDragOver.bind(this));
40 $(document.body).off('dragleave', this.selector, this.handleDragLeave.bind(this));
41 $(document.body).off('drop', this.selector, this.handleDrop.bind(this));
42 }
43
44 $(this.selector).remove('drop_target no_animation');
45 }
46
47 /**
48 * Initialize the drag and drop functionality
49 * Automatically called on construction
50 * @private
51 */
52 init() {
53 if (this.selector === 'body') {
54 $(document.body).on('dragover', this.handleDragOver.bind(this));
55 $(document.body).on('dragleave', this.handleDragLeave.bind(this));
56 $(document.body).on('drop', this.handleDrop.bind(this));
57 } else {
58 $(document.body).on('dragover', this.selector, this.handleDragOver.bind(this));
59 $(document.body).on('dragleave', this.selector, this.handleDragLeave.bind(this));
60 $(document.body).on('drop', this.selector, this.handleDrop.bind(this));
61 }
62
63 $(this.selector).addClass('drop_target');
64 if (this.noAnimation) $(this.selector).addClass('no_animation');
65 }
66
67 /**
68 * @param {JQuery.DragOverEvent<HTMLElement, undefined, any, any>} event - The dragover event
69 * @private
70 */
71 handleDragOver(event) {
72 event.preventDefault();
73 event.stopPropagation();
74 clearTimeout(this.dragLeaveTimeout);
75 $(this.selector).addClass('drop_target dragover');
76 if (this.noAnimation) $(this.selector).addClass('no_animation');
77 }
78
79 /**
80 * @param {JQuery.DragLeaveEvent<HTMLElement, undefined, any, any>} event - The dragleave event
81 * @private
82 */
83 handleDragLeave(event) {
84 event.preventDefault();
85 event.stopPropagation();
86
87 // Debounce the removal of the class, so it doesn't "flicker" on dragging over
88 clearTimeout(this.dragLeaveTimeout);
89 this.dragLeaveTimeout = setTimeout(() => {
90 $(this.selector).removeClass('dragover');
91 }, debounce_timeout.quick);
92 }
93
94 /**
95 * @param {JQuery.DropEvent<HTMLElement, undefined, any, any>} event - The drop event
96 * @private
97 */
98 handleDrop(event) {
99 event.preventDefault();
100 event.stopPropagation();
101 clearTimeout(this.dragLeaveTimeout);
102 $(this.selector).removeClass('dragover');
103
104 const files = Array.from(event.originalEvent.dataTransfer.files);
105 this.onDropCallback(files, event);
106 }
107}