| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * JavaScript to handle dropzone.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module     core/dropzone
 | 
        
           |  |  | 20 |  * @copyright  2024 Huong Nguyen <huongnv13@gmail.com>
 | 
        
           |  |  | 21 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 22 |  * @since      4.4
 | 
        
           |  |  | 23 |  */
 | 
        
           |  |  | 24 |   | 
        
           |  |  | 25 | import {getString} from 'core/str';
 | 
        
           |  |  | 26 | import Log from 'core/log';
 | 
        
           |  |  | 27 | import {prefetchString} from 'core/prefetch';
 | 
        
           |  |  | 28 | import Templates from 'core/templates';
 | 
        
           |  |  | 29 |   | 
        
           |  |  | 30 | /**
 | 
        
           |  |  | 31 |  * A dropzone.
 | 
        
           |  |  | 32 |  *
 | 
        
           |  |  | 33 |  * @class core/dropzone
 | 
        
           |  |  | 34 |  */
 | 
        
           |  |  | 35 | const DropZone = class {
 | 
        
           |  |  | 36 |   | 
        
           |  |  | 37 |     /**
 | 
        
           |  |  | 38 |      * The element to render the dropzone.
 | 
        
           |  |  | 39 |      * @type {Element}
 | 
        
           |  |  | 40 |      */
 | 
        
           |  |  | 41 |     dropZoneElement;
 | 
        
           |  |  | 42 |   | 
        
           |  |  | 43 |     /**
 | 
        
           |  |  | 44 |      * The file types that are allowed to be uploaded.
 | 
        
           |  |  | 45 |      * @type {String}
 | 
        
           |  |  | 46 |      */
 | 
        
           |  |  | 47 |     fileTypes;
 | 
        
           |  |  | 48 |   | 
        
           |  |  | 49 |     /**
 | 
        
           |  |  | 50 |      * The function to call when a file is dropped.
 | 
        
           |  |  | 51 |      * @type {CallableFunction}
 | 
        
           |  |  | 52 |      */
 | 
        
           |  |  | 53 |     callback;
 | 
        
           |  |  | 54 |   | 
        
           |  |  | 55 |     /**
 | 
        
           |  |  | 56 |      * The label to display in the dropzone.
 | 
        
           |  |  | 57 |      * @type {string}
 | 
        
           |  |  | 58 |      */
 | 
        
           |  |  | 59 |     dropZoneLabel = '';
 | 
        
           |  |  | 60 |   | 
        
           |  |  | 61 |     /**
 | 
        
           |  |  | 62 |      * Constructor.
 | 
        
           |  |  | 63 |      *
 | 
        
           |  |  | 64 |      * @param {Element} dropZoneElement The element to render the dropzone.
 | 
        
           |  |  | 65 |      * @param {String} fileTypes The file types that are allowed to be uploaded. Example: image/*
 | 
        
           |  |  | 66 |      * @param {CallableFunction} callback The function to call when a file is dropped.
 | 
        
           |  |  | 67 |      */
 | 
        
           |  |  | 68 |     constructor(dropZoneElement, fileTypes, callback) {
 | 
        
           |  |  | 69 |         prefetchString('core', 'addfilesdrop');
 | 
        
           |  |  | 70 |         this.dropZoneElement = dropZoneElement;
 | 
        
           |  |  | 71 |         this.fileTypes = fileTypes;
 | 
        
           |  |  | 72 |         this.callback = callback;
 | 
        
           |  |  | 73 |     }
 | 
        
           |  |  | 74 |   | 
        
           |  |  | 75 |     /**
 | 
        
           |  |  | 76 |      * Initialise the dropzone.
 | 
        
           |  |  | 77 |      *
 | 
        
           |  |  | 78 |      * @returns {DropZone}
 | 
        
           |  |  | 79 |      */
 | 
        
           |  |  | 80 |     init() {
 | 
        
           |  |  | 81 |         this.dropZoneElement.addEventListener('dragover', (e) => {
 | 
        
           |  |  | 82 |             const dropZone = this.getDropZoneFromEvent(e);
 | 
        
           |  |  | 83 |             if (!dropZone) {
 | 
        
           |  |  | 84 |                 return;
 | 
        
           |  |  | 85 |             }
 | 
        
           |  |  | 86 |             e.preventDefault();
 | 
        
           |  |  | 87 |             dropZone.classList.add('dragover');
 | 
        
           |  |  | 88 |         });
 | 
        
           |  |  | 89 |         this.dropZoneElement.addEventListener('dragleave', (e) => {
 | 
        
           |  |  | 90 |             const dropZone = this.getDropZoneFromEvent(e);
 | 
        
           |  |  | 91 |             if (!dropZone) {
 | 
        
           |  |  | 92 |                 return;
 | 
        
           |  |  | 93 |             }
 | 
        
           |  |  | 94 |             e.preventDefault();
 | 
        
           |  |  | 95 |             dropZone.classList.remove('dragover');
 | 
        
           |  |  | 96 |         });
 | 
        
           |  |  | 97 |         this.dropZoneElement.addEventListener('drop', (e) => {
 | 
        
           |  |  | 98 |             const dropZone = this.getDropZoneFromEvent(e);
 | 
        
           |  |  | 99 |             if (!dropZone) {
 | 
        
           |  |  | 100 |                 return;
 | 
        
           |  |  | 101 |             }
 | 
        
           |  |  | 102 |             e.preventDefault();
 | 
        
           |  |  | 103 |             dropZone.classList.remove('dragover');
 | 
        
           |  |  | 104 |             this.callback(e.dataTransfer.files);
 | 
        
           |  |  | 105 |         });
 | 
        
           |  |  | 106 |         this.dropZoneElement.addEventListener('click', (e) => {
 | 
        
           |  |  | 107 |             const dropZoneContainer = this.getDropZoneContainerFromEvent(e);
 | 
        
           |  |  | 108 |             if (!dropZoneContainer) {
 | 
        
           |  |  | 109 |                 return;
 | 
        
           |  |  | 110 |             }
 | 
        
           |  |  | 111 |             this.getFileElementFromEvent(e).click();
 | 
        
           |  |  | 112 |         });
 | 
        
           |  |  | 113 |         this.dropZoneElement.addEventListener('click', (e) => {
 | 
        
           |  |  | 114 |             const dropZoneLabel = e.target.closest('.dropzone-sr-only-focusable');
 | 
        
           |  |  | 115 |             if (!dropZoneLabel) {
 | 
        
           |  |  | 116 |                 return;
 | 
        
           |  |  | 117 |             }
 | 
        
           |  |  | 118 |             this.getFileElementFromEvent(e).click();
 | 
        
           |  |  | 119 |         });
 | 
        
           |  |  | 120 |         this.dropZoneElement.addEventListener('change', (e) => {
 | 
        
           |  |  | 121 |             const fileInput = this.getFileElementFromEvent(e);
 | 
        
           |  |  | 122 |             if (fileInput) {
 | 
        
           |  |  | 123 |                 e.preventDefault();
 | 
        
           |  |  | 124 |                 this.callback(fileInput.files);
 | 
        
           |  |  | 125 |             }
 | 
        
           |  |  | 126 |         });
 | 
        
           |  |  | 127 |         this.renderDropZone(this.dropZoneElement, this.fileTypes);
 | 
        
           |  |  | 128 |         Log.info('Dropzone has been initialized!');
 | 
        
           |  |  | 129 |         return this;
 | 
        
           |  |  | 130 |     }
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 |     /**
 | 
        
           |  |  | 133 |      * Get the dropzone.
 | 
        
           |  |  | 134 |      *
 | 
        
           |  |  | 135 |      * @param {Event} e The event.
 | 
        
           |  |  | 136 |      * @returns {HTMLElement|bool}
 | 
        
           |  |  | 137 |      */
 | 
        
           |  |  | 138 |     getDropZoneFromEvent(e) {
 | 
        
           |  |  | 139 |         return e.target.closest('.dropzone');
 | 
        
           |  |  | 140 |     }
 | 
        
           |  |  | 141 |   | 
        
           |  |  | 142 |     /**
 | 
        
           |  |  | 143 |      * Get the dropzone container.
 | 
        
           |  |  | 144 |      *
 | 
        
           |  |  | 145 |      * @param {Event} e The event.
 | 
        
           |  |  | 146 |      * @returns {HTMLElement|bool}
 | 
        
           |  |  | 147 |      */
 | 
        
           |  |  | 148 |     getDropZoneContainerFromEvent(e) {
 | 
        
           |  |  | 149 |         return e.target.closest('.dropzone-container');
 | 
        
           |  |  | 150 |     }
 | 
        
           |  |  | 151 |   | 
        
           |  |  | 152 |     /**
 | 
        
           |  |  | 153 |      * Get the file element.
 | 
        
           |  |  | 154 |      *
 | 
        
           |  |  | 155 |      * @param {Event} e The event.
 | 
        
           |  |  | 156 |      * @returns {HTMLElement|bool}
 | 
        
           |  |  | 157 |      */
 | 
        
           |  |  | 158 |     getFileElementFromEvent(e) {
 | 
        
           |  |  | 159 |         return e.target.closest('.dropzone-container').querySelector('.drop-zone-fileinput');
 | 
        
           |  |  | 160 |     }
 | 
        
           |  |  | 161 |   | 
        
           |  |  | 162 |     /**
 | 
        
           |  |  | 163 |      * Set the label to display in the dropzone.
 | 
        
           |  |  | 164 |      *
 | 
        
           |  |  | 165 |      * @param {String} label The label to display in the dropzone.
 | 
        
           |  |  | 166 |      */
 | 
        
           |  |  | 167 |     setLabel(label) {
 | 
        
           |  |  | 168 |         this.dropZoneLabel = label;
 | 
        
           |  |  | 169 |     }
 | 
        
           |  |  | 170 |   | 
        
           |  |  | 171 |     /**
 | 
        
           |  |  | 172 |      * Get the label to display in the dropzone.
 | 
        
           |  |  | 173 |      *
 | 
        
           |  |  | 174 |      * @return {String} The label to display in the dropzone.
 | 
        
           |  |  | 175 |      */
 | 
        
           |  |  | 176 |     getLabel() {
 | 
        
           |  |  | 177 |         return this.dropZoneLabel;
 | 
        
           |  |  | 178 |     }
 | 
        
           |  |  | 179 |   | 
        
           |  |  | 180 |     /**
 | 
        
           |  |  | 181 |      * Render the dropzone.
 | 
        
           |  |  | 182 |      *
 | 
        
           |  |  | 183 |      * @param {Element} dropZoneElement The element to render the dropzone.
 | 
        
           |  |  | 184 |      * @param {String} fileTypes The file types that are allowed to be uploaded.
 | 
        
           |  |  | 185 |      * @returns {Promise}
 | 
        
           |  |  | 186 |      */
 | 
        
           |  |  | 187 |     async renderDropZone(dropZoneElement, fileTypes) {
 | 
        
           |  |  | 188 |         if (!this.getLabel()) {
 | 
        
           |  |  | 189 |             // Use the default one.
 | 
        
           |  |  | 190 |             this.setLabel(await getString('addfilesdrop', 'core'));
 | 
        
           |  |  | 191 |         }
 | 
        
           |  |  | 192 |         const dropZoneLabel = this.getLabel();
 | 
        
           |  |  | 193 |         dropZoneElement.innerHTML = await Templates.render('core/dropzone', {
 | 
        
           |  |  | 194 |             label: dropZoneLabel,
 | 
        
           |  |  | 195 |             filetypes: fileTypes,
 | 
        
           |  |  | 196 |         });
 | 
        
           |  |  | 197 |     }
 | 
        
           |  |  | 198 | };
 | 
        
           |  |  | 199 |   | 
        
           |  |  | 200 | export default DropZone;
 |