[TASK] Migrate Modal to TypeScript
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Resources / Private / TypeScript / DragUploader.ts
1 /*
2  * This file is part of the TYPO3 CMS project.
3  *
4  * It is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License, either version 2
6  * of the License, or any later version.
7  *
8  * For the full copyright and license information, please read the
9  * LICENSE.txt file that was distributed with this source code.
10  *
11  * The TYPO3 project - inspiring people to share!
12  */
13
14 /**
15  * Module: TYPO3/CMS/Backend/DragUploader
16  */
17 import {SeverityEnum} from './Enum/Severity';
18 import * as $ from 'jquery';
19 import moment = require('moment');
20 import NProgress = require('nprogress');
21 import Modal = require('./Modal');
22 import Notification = require('./Notification');
23
24 /**
25  * Possible actions for conflicts w/ existing files
26  */
27 enum Action {
28   OVERRIDE = 'replace',
29   RENAME = 'rename',
30   SKIP = 'cancel',
31   USE_EXISTING = 'useExisting'
32 }
33
34 /**
35  * Properties of a file as returned from the AJAX action; essential, this is a serialized instance of
36  * \TYPO3\CMS\Core\Resource\File plus some extra properties (see FileController::flattenResultDataValue())
37  */
38 interface UploadedFile {
39   name: string;
40   id: number;
41   uid: number;
42   icon: string;
43   extension: string;
44   permissions: {read: boolean; write: boolean};
45   size: number;
46   // formatted as ddmmyy
47   date: string;
48
49   mtime: Date;
50   thumbUrl: string;
51   type: string;
52 }
53
54 class DragUploaderPlugin {
55   public irreObjectUid: number;
56   public $fileList: JQuery;
57   public fileListColumnCount: number;
58   public filesExtensionsAllowed: string;
59   public fileDenyPattern: RegExp | null;
60   public maxFileSize: number;
61   public $trigger: JQuery;
62   public target: string;
63
64   /**
65    * Array of files which are asked for being overridden
66    */
67   private askForOverride: Array<{original: UploadedFile, uploaded: File, action: Action}> = [];
68
69   private percentagePerFile: number = 1;
70
71   private $body: JQuery;
72   private $element: JQuery;
73   private $dropzone: JQuery;
74   private $dropzoneMask: JQuery;
75   private fileInput: HTMLInputElement;
76   private browserCapabilities: { fileReader: boolean; DnD: boolean; Progress: boolean };
77   private dropZoneInsertBefore: boolean;
78   private queueLength: number;
79
80   constructor(element: HTMLElement) {
81     this.$body = $('body');
82     this.$element = $(element);
83     const hasTrigger = this.$element.data('dropzoneTrigger') !== undefined;
84     this.$trigger = $(this.$element.data('dropzoneTrigger'));
85     this.$dropzone = $('<div />').addClass('dropzone').hide();
86     this.irreObjectUid = this.$element.data('fileIrreObject');
87
88     const dropZoneEscapedTarget = this.$element.data('dropzoneTarget');
89     if (this.irreObjectUid && this.$element.nextAll(dropZoneEscapedTarget).length !== 0) {
90       this.dropZoneInsertBefore = true;
91       this.$dropzone.insertBefore(dropZoneEscapedTarget);
92     } else {
93       this.dropZoneInsertBefore = false;
94       this.$dropzone.insertAfter(dropZoneEscapedTarget);
95     }
96     this.$dropzoneMask = $('<div />').addClass('dropzone-mask').appendTo(this.$dropzone);
97     this.fileInput = <HTMLInputElement>document.createElement('input');
98     this.fileInput.setAttribute('type', 'file');
99     this.fileInput.setAttribute('multiple', 'multiple');
100     this.fileInput.setAttribute('name', 'files[]');
101     this.fileInput.classList.add('upload-file-picker');
102     this.$body.append(this.fileInput);
103
104     this.$fileList = $(this.$element.data('progress-container'));
105     this.fileListColumnCount = $('thead tr:first th', this.$fileList).length;
106     this.filesExtensionsAllowed = this.$element.data('file-allowed');
107     this.fileDenyPattern = this.$element.data('file-deny-pattern') ? new RegExp(this.$element.data('file-deny-pattern'), 'i') : null;
108     this.maxFileSize = parseInt(this.$element.data('max-file-size'), 10);
109     this.target = this.$element.data('target-folder');
110
111     this.browserCapabilities = {
112       fileReader: typeof FileReader !== 'undefined',
113       DnD: 'draggable' in document.createElement('span'),
114       Progress: 'upload' in new XMLHttpRequest
115     };
116
117
118     if (!this.browserCapabilities.DnD) {
119       console.warn('Browser has no Drag and drop capabilities; cannot initialize DragUploader');
120       return;
121     }
122
123     this.$body.on('dragover', this.dragFileIntoDocument);
124     this.$body.on('dragend', this.dragAborted);
125     this.$body.on('drop', this.ignoreDrop);
126
127     this.$dropzone.on('dragenter', this.fileInDropzone);
128     this.$dropzoneMask.on('dragenter', this.fileInDropzone);
129     this.$dropzoneMask.on('dragleave', this.fileOutOfDropzone);
130     this.$dropzoneMask.on('drop', (ev: JQueryEventObject) => this.handleDrop(<JQueryTypedEvent<DragEvent>>ev));
131
132     this.$dropzone.prepend(
133       '<div class="dropzone-hint">' +
134       '<div class="dropzone-hint-media">' +
135       '<div class="dropzone-hint-icon"></div>' +
136       '</div>' +
137       '<div class="dropzone-hint-body">' +
138       '<h3 class="dropzone-hint-title">' +
139       TYPO3.lang['file_upload.dropzonehint.title'] +
140       '</h3>' +
141       '<p class="dropzone-hint-message">' +
142       TYPO3.lang['file_upload.dropzonehint.message'] +
143       '</p>' +
144       '</div>' +
145       '</div>'
146     ).click(() => {
147       this.fileInput.click();
148     });
149     $('<span />').addClass('dropzone-close').click(this.hideDropzone).appendTo(this.$dropzone);
150
151     // no filelist then create own progress table
152     if (this.$fileList.length === 0) {
153       this.$fileList = $('<table />')
154         .attr('id', 'typo3-filelist')
155         .addClass('table table-striped table-hover upload-queue')
156         .html('<tbody></tbody>').hide();
157
158       if (this.dropZoneInsertBefore) {
159         this.$fileList.insertAfter(this.$dropzone);
160       } else {
161         this.$fileList.insertBefore(this.$dropzone);
162       }
163       this.fileListColumnCount = 7;
164     }
165
166     this.fileInput.addEventListener('change', () => {
167       this.processFiles(Array.apply(null, this.fileInput.files));
168     });
169
170     this.bindUploadButton(hasTrigger === true ? this.$trigger : this.$element);
171   }
172
173   public showDropzone(): void {
174     this.$dropzone.show();
175   }
176
177   /**
178    *
179    * @param {Event} event
180    */
181   public hideDropzone(event: Event): void {
182     event.stopPropagation();
183     event.preventDefault();
184     this.$dropzone.hide();
185   }
186
187   /**
188    * @param {Event} event
189    * @returns {boolean}
190    */
191   public dragFileIntoDocument = (event: Event): boolean => {
192     event.stopPropagation();
193     event.preventDefault();
194     $(event.currentTarget).addClass('drop-in-progress');
195     this.showDropzone();
196     return false;
197   }
198
199   /**
200    *
201    * @param {Event} event
202    * @returns {Boolean}
203    */
204   public dragAborted = (event: Event): boolean => {
205     event.stopPropagation();
206     event.preventDefault();
207     $(event.currentTarget).removeClass('drop-in-progress');
208     return false;
209   }
210
211   public ignoreDrop = (event: Event): boolean => {
212     // stops the browser from redirecting.
213     event.stopPropagation();
214     event.preventDefault();
215     this.dragAborted(event);
216     return false;
217   }
218
219   public handleDrop = (event: JQueryTypedEvent<DragEvent>): void => {
220     this.ignoreDrop(event);
221     this.processFiles(event.originalEvent.dataTransfer.files);
222     this.$dropzone.removeClass('drop-status-ok');
223   }
224
225   /**
226    * @param {FileList} files
227    */
228   public processFiles(files: FileList): void {
229     this.queueLength = files.length;
230
231     if (!this.$fileList.is(':visible')) {
232       this.$fileList.show();
233     }
234
235     NProgress.start();
236     this.percentagePerFile = 1 / files.length;
237
238     // Check for each file if is already exist before adding it to the queue
239     const ajaxCalls: JQueryXHR[] = [];
240     $.each(files, (i: string, file) => {
241       ajaxCalls[parseInt(i, 10)] = $.ajax({
242         url: TYPO3.settings.ajaxUrls.file_exists,
243         data: {
244           fileName: file.name,
245           fileTarget: this.target
246         },
247         cache: false,
248         success: (response: any) => {
249           const fileExists = typeof response.uid !== 'undefined';
250           if (fileExists) {
251             this.askForOverride.push({
252               original: response,
253               uploaded: file,
254               action: this.irreObjectUid ? Action.USE_EXISTING : Action.SKIP
255             });
256             NProgress.inc(this.percentagePerFile);
257           } else {
258             // Unused var _ is necessary as "no-unused-expression" is active
259             const _ = new FileQueueItem(this, file, Action.SKIP);
260           }
261         }
262       });
263     });
264
265     $.when.apply($, ajaxCalls).done(() => {
266       this.drawOverrideModal();
267       NProgress.done();
268     });
269
270     this.fileInput.value = '';
271   }
272
273   public fileInDropzone = (): void => {
274     this.$dropzone.addClass('drop-status-ok');
275   }
276
277   public fileOutOfDropzone = (): void => {
278     this.$dropzone.removeClass('drop-status-ok');
279   }
280
281   /**
282    * Bind file picker to default upload button
283    *
284    * @param {Object} button
285    */
286   public bindUploadButton(button: JQuery): void {
287     button.click((event: Event) => {
288       event.preventDefault();
289       this.fileInput.click();
290       this.showDropzone();
291     });
292   }
293
294   /**
295    * Decrements the queue and renders a flash message if queue is empty
296    */
297   public decrementQueueLength(): void {
298     if (this.queueLength > 0) {
299       this.queueLength--;
300       if (this.queueLength === 0) {
301         $.ajax({
302           url: TYPO3.settings.ajaxUrls.flashmessages_render,
303           cache: false,
304           success: (data) => {
305             $.each(data, (index: number, flashMessage: {title: string, message: string, severity: number}) => {
306               Notification.showMessage(flashMessage.title, flashMessage.message, flashMessage.severity);
307             });
308           }
309         });
310       }
311     }
312   }
313
314   /**
315    * Renders the modal for existing files
316    */
317   public drawOverrideModal(): void {
318     const amountOfItems = Object.keys(this.askForOverride).length;
319     if (amountOfItems === 0) {
320       return;
321     }
322     const $modalContent = $('<div/>').append(
323       $('<p/>').text(TYPO3.lang['file_upload.existingfiles.description']),
324       $('<table/>', {class: 'table'}).append(
325         $('<thead/>').append(
326           $('<tr />').append(
327             $('<th/>'),
328             $('<th/>').text(TYPO3.lang['file_upload.header.originalFile']),
329             $('<th/>').text(TYPO3.lang['file_upload.header.uploadedFile']),
330             $('<th/>').text(TYPO3.lang['file_upload.header.action'])
331           )
332         )
333       )
334     );
335
336     for (let i = 0; i < amountOfItems; ++i) {
337       const $record = $('<tr />').append(
338         $('<td />').append(
339           (this.askForOverride[i].original.thumbUrl !== ''
340               ? $('<img />', {src: this.askForOverride[i].original.thumbUrl, height: 40})
341               : $(this.askForOverride[i].original.icon)
342           )
343         ),
344         $('<td />').html(
345           this.askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(this.askForOverride[i].uploaded.size)) + ')' +
346           '<br>' + moment(this.askForOverride[i].uploaded.lastModifiedDate, 'x').format('YYYY-MM-DD HH:mm')
347         ),
348         $('<td />').html(
349           this.askForOverride[i].uploaded.name + ' (' + (DragUploader.fileSizeAsString(this.askForOverride[i].original.size)) + ')' +
350           '<br>' + moment(this.askForOverride[i].original.mtime, 'X').format('YYYY-MM-DD HH:mm')
351         ),
352         $('<td />').append(
353           $('<select />', {class: 'form-control t3js-actions', 'data-override': i}).append(
354             (this.irreObjectUid ? $('<option/>').val(Action.USE_EXISTING).text(TYPO3.lang['file_upload.actions.use_existing']) : ''),
355             $('<option />').val(Action.SKIP).text(TYPO3.lang['file_upload.actions.skip']),
356             $('<option />').val(Action.RENAME).text(TYPO3.lang['file_upload.actions.rename']),
357             $('<option />').val(Action.OVERRIDE).text(TYPO3.lang['file_upload.actions.override'])
358           )
359         )
360       );
361       $modalContent.find('table').append('<tbody />').append($record);
362     }
363
364     const $modal = Modal.confirm(
365       TYPO3.lang['file_upload.existingfiles.title'], $modalContent, SeverityEnum.warning,
366       [
367         {
368           text: $(this).data('button-close-text') || TYPO3.lang['file_upload.button.cancel'] || 'Cancel',
369           active: true,
370           btnClass: 'btn-default',
371           name: 'cancel'
372         },
373         {
374           text: $(this).data('button-ok-text') || TYPO3.lang['file_upload.button.continue'] || 'Continue with selected actions',
375           btnClass: 'btn-warning',
376           name: 'continue'
377         }
378       ],
379       ['modal-inner-scroll']
380     );
381     $modal.find('.modal-dialog').addClass('modal-lg');
382
383     $modal.find('.modal-footer').prepend(
384       $('<span/>').addClass('form-inline').append(
385         $('<label/>').text(TYPO3.lang['file_upload.actions.all.label']),
386         $('<select/>', {class: 'form-control t3js-actions-all'}).append(
387           $('<option/>').val('').text(TYPO3.lang['file_upload.actions.all.empty']),
388           (this.irreObjectUid ? $('<option/>').val(Action.USE_EXISTING).text(TYPO3.lang['file_upload.actions.all.use_existing']) : ''),
389           $('<option/>').val(Action.SKIP).text(TYPO3.lang['file_upload.actions.all.skip']),
390           $('<option/>').val(Action.RENAME).text(TYPO3.lang['file_upload.actions.all.rename']),
391           $('<option/>').val(Action.OVERRIDE).text(TYPO3.lang['file_upload.actions.all.override'])
392         )
393       )
394     );
395
396     const uploader = this;
397     $modal.on('change', '.t3js-actions-all', function(this: HTMLInputElement): void {
398       const $this = $(this),
399         value = $this.val();
400
401       if (value !== '') {
402         // mass action was selected, apply action to every file
403         $modal.find('.t3js-actions').each((i, select) => {
404           const $select = $(select),
405             index = parseInt($select.data('override'), 10);
406           $select.val(value).prop('disabled', 'disabled');
407           uploader.askForOverride[index].action = <Action>$select.val();
408         });
409       } else {
410         $modal.find('.t3js-actions').removeProp('disabled');
411       }
412     }).on('change', '.t3js-actions', function(this: HTMLInputElement): void {
413       const $this = $(this),
414         index = parseInt($this.data('override'), 10);
415       uploader.askForOverride[index].action = <Action>$this.val();
416     }).on('button.clicked', function(this: HTMLInputElement, e: Event): void {
417       if ((<HTMLInputElement>(e.target)).name === 'cancel') {
418         uploader.askForOverride = [];
419         Modal.dismiss();
420       } else if ((<HTMLInputElement>(e.target)).name === 'continue') {
421         $.each(uploader.askForOverride, (key, fileInfo) => {
422           if (fileInfo.action === Action.USE_EXISTING) {
423             DragUploader.addFileToIrre(
424               uploader.irreObjectUid,
425               fileInfo.original
426             );
427           } else if (fileInfo.action !== Action.SKIP) {
428             // Unused var _ is necessary as "no-unused-expression" is active
429             const _ = new FileQueueItem(uploader, fileInfo.uploaded, fileInfo.action);
430           }
431         });
432         uploader.askForOverride = [];
433         Modal.dismiss();
434       }
435     }).on('hidden.bs.modal', () => {
436       this.askForOverride = [];
437     });
438   }
439 }
440
441 class FileQueueItem {
442   private $row: JQuery;
443   private $iconCol: JQuery;
444   private $fileName: JQuery;
445   private $progress: JQuery;
446   private $progressContainer: JQuery;
447   private $progressBar: JQuery;
448   private $progressPercentage: JQuery;
449   private $progressMessage: JQuery;
450   private dragUploader: DragUploaderPlugin;
451   private file: File;
452   private override: Action;
453   private upload: XMLHttpRequest;
454
455   constructor(dragUploader: DragUploaderPlugin, file: File, override: Action) {
456     this.dragUploader = dragUploader;
457     this.file = file;
458     this.override = override;
459
460     this.$row = $('<tr />').addClass('upload-queue-item uploading');
461     this.$iconCol = $('<td />').addClass('col-icon').appendTo(this.$row);
462     this.$fileName = $('<td />').text(file.name).appendTo(this.$row);
463     this.$progress = $('<td />').attr('colspan', this.dragUploader.fileListColumnCount - 2).appendTo(this.$row);
464     this.$progressContainer = $('<div />').addClass('upload-queue-progress').appendTo(this.$progress);
465     this.$progressBar = $('<div />').addClass('upload-queue-progress-bar').appendTo(this.$progressContainer);
466     this.$progressPercentage = $('<span />').addClass('upload-queue-progress-percentage').appendTo(this.$progressContainer);
467     this.$progressMessage = $('<span />').addClass('upload-queue-progress-message').appendTo(this.$progressContainer);
468
469
470     // position queue item in filelist
471     if ($('tbody tr.upload-queue-item', this.dragUploader.$fileList).length === 0) {
472       this.$row.prependTo($('tbody', this.dragUploader.$fileList));
473       this.$row.addClass('last');
474     } else {
475       this.$row.insertBefore($('tbody tr.upload-queue-item:first', this.dragUploader.$fileList));
476     }
477
478     // set dummy file icon
479     this.$iconCol.html('<span class="t3-icon t3-icon-mimetypes t3-icon-other-other">&nbsp;</span>');
480
481     // check file size
482     if (this.dragUploader.maxFileSize > 0 && this.file.size > this.dragUploader.maxFileSize) {
483       this.updateMessage(TYPO3.lang['file_upload.maxFileSizeExceeded']
484         .replace(/\{0\}/g, this.file.name)
485         .replace(/\{1\}/g, DragUploader.fileSizeAsString(this.dragUploader.maxFileSize)));
486       this.$row.addClass('error');
487
488       // check filename/extension against deny pattern
489     } else if (this.dragUploader.fileDenyPattern && this.file.name.match(this.dragUploader.fileDenyPattern)) {
490       this.updateMessage(TYPO3.lang['file_upload.fileNotAllowed'].replace(/\{0\}/g, this.file.name));
491       this.$row.addClass('error');
492
493     } else if (!this.checkAllowedExtensions()) {
494       this.updateMessage(TYPO3.lang['file_upload.fileExtensionExpected']
495         .replace(/\{0\}/g, this.dragUploader.filesExtensionsAllowed)
496       );
497       this.$row.addClass('error');
498     } else {
499       this.updateMessage('- ' + DragUploader.fileSizeAsString(this.file.size));
500
501       const formData = new FormData();
502       formData.append('data[upload][1][target]', this.dragUploader.target);
503       formData.append('data[upload][1][data]', '1');
504       formData.append('overwriteExistingFiles', this.override);
505       formData.append('redirect', '');
506       formData.append('upload_1', this.file);
507
508       const s = $.extend(true, {}, $.ajaxSettings, {
509         url: TYPO3.settings.ajaxUrls.file_process,
510         contentType: false,
511         processData: false,
512         data: formData,
513         cache: false,
514         type: 'POST',
515         success: (data: {upload?: UploadedFile[]}) => this.uploadSuccess(data),
516         error: (response: XMLHttpRequest) => this.uploadError(response)
517       });
518
519       s.xhr = () => {
520         const xhr = $.ajaxSettings.xhr();
521         xhr.upload.addEventListener('progress', (e: ProgressEvent) => this.updateProgress(e));
522         return xhr;
523       };
524
525       // start upload
526       this.upload = $.ajax(s);
527     }
528   }
529
530   /**
531    * @param {string} message
532    */
533   public updateMessage(message: string): void {
534     this.$progressMessage.text(message);
535   }
536
537   /**
538    * Remove the progress bar
539    */
540   public removeProgress(): void {
541     if (this.$progress) {
542       this.$progress.remove();
543     }
544   }
545
546   public uploadStart(): void {
547     this.$progressPercentage.text('(0%)');
548     this.$progressBar.width('1%');
549     this.dragUploader.$trigger.trigger('uploadStart', [this]);
550   }
551
552   /**
553    * @param {XMLHttpRequest} response
554    */
555   public uploadError(response: XMLHttpRequest): void {
556     this.updateMessage(TYPO3.lang['file_upload.uploadFailed'].replace(/\{0\}/g, this.file.name));
557     const error = $(response.responseText);
558     if (error.is('t3err')) {
559       this.$progressPercentage.text(error.text());
560     } else {
561       this.$progressPercentage.text('(' + response.statusText + ')');
562     }
563     this.$row.addClass('error');
564     this.dragUploader.decrementQueueLength();
565     this.dragUploader.$trigger.trigger('uploadError', [this, response]);
566   }
567
568   /**
569    * @param {ProgressEvent} event
570    */
571   public updateProgress(event: ProgressEvent): void {
572     const percentage = Math.round((event.loaded / event.total) * 100) + '%';
573     this.$progressBar.outerWidth(percentage);
574     this.$progressPercentage.text(percentage);
575     this.dragUploader.$trigger.trigger('updateProgress', [this, percentage, event]);
576   }
577
578   /**
579    * @param {{upload?: UploadedFile[]}} data
580    */
581   public uploadSuccess(data: {upload?: UploadedFile[]}): void {
582     if (data.upload) {
583       this.dragUploader.decrementQueueLength();
584       this.$row.removeClass('uploading');
585       this.$fileName.text(data.upload[0].name);
586       this.$progressPercentage.text('');
587       this.$progressMessage.text('100%');
588       this.$progressBar.outerWidth('100%');
589
590       // replace file icon
591       if (data.upload[0].icon) {
592         this.$iconCol
593           .html(
594             '<a href="#" class="t3js-contextmenutrigger" data-uid="'
595             + data.upload[0].id + '" data-table="sys_file">'
596             + data.upload[0].icon + '&nbsp;</span></a>'
597           );
598       }
599
600       if (this.dragUploader.irreObjectUid) {
601         DragUploader.addFileToIrre(
602           this.dragUploader.irreObjectUid,
603           data.upload[0]
604         );
605         setTimeout(
606           () => {
607             this.$row.remove();
608             if ($('tr', this.dragUploader.$fileList).length === 0) {
609               this.dragUploader.$fileList.hide();
610               this.dragUploader.$trigger.trigger('uploadSuccess', [this, data]);
611             }
612           },
613           3000);
614       } else {
615         setTimeout(
616           () => {
617             this.showFileInfo(data.upload[0]);
618             this.dragUploader.$trigger.trigger('uploadSuccess', [this, data]);
619           },
620           3000);
621       }
622     }
623   }
624
625   /**
626    * @param {UploadedFile} fileInfo
627    */
628   public showFileInfo(fileInfo: UploadedFile): void {
629     this.removeProgress();
630     // add spacing cells when clibboard and/or extended view is enabled
631     for (let i = 7; i < this.dragUploader.fileListColumnCount; i++) {
632       $('<td />').text('').appendTo(this.$row);
633     }
634     $('<td />').text(fileInfo.extension.toUpperCase()).appendTo(this.$row);
635     $('<td />').text(fileInfo.date).appendTo(this.$row);
636     $('<td />').text(DragUploader.fileSizeAsString(fileInfo.size)).appendTo(this.$row);
637     let permissions = '';
638     if (fileInfo.permissions.read) {
639       permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.read'] + '</strong>';
640     }
641     if (fileInfo.permissions.write) {
642       permissions += '<strong class="text-danger">' + TYPO3.lang['permissions.write'] + '</strong>';
643     }
644     $('<td />').html(permissions).appendTo(this.$row);
645     $('<td />').text('-').appendTo(this.$row);
646   }
647
648   public checkAllowedExtensions(): boolean {
649     if (!this.dragUploader.filesExtensionsAllowed) {
650       return true;
651     }
652     const extension = this.file.name.split('.').pop();
653     const allowed = this.dragUploader.filesExtensionsAllowed.split(',');
654
655     return $.inArray(extension.toLowerCase(), allowed) !== -1;
656   }
657 }
658
659 class DragUploader {
660   private static options: DragUploaderOptions;
661   fileListColumnCount: number;
662   filesExtensionsAllowed: string;
663   fileDenyPattern: string;
664
665   public static fileSizeAsString(size: number): string {
666     const sizeKB: number = size / 1024;
667     let str = '';
668
669     if (sizeKB > 1024) {
670       str = (sizeKB / 1024).toFixed(1) + ' MB';
671     } else {
672       str = sizeKB.toFixed(1) + ' KB';
673     }
674     return str;
675   }
676
677   /**
678    * @param {number} irre_object
679    * @param {UploadedFile} file
680    */
681   public static addFileToIrre(irre_object: number, file: UploadedFile): void {
682     window.inline.delayedImportElement(
683       irre_object,
684       'sys_file',
685       file.uid,
686       'file'
687     );
688   }
689
690   public static init(): void {
691     const me = this;
692     const opts = me.options;
693
694     // register the jQuery plugin "DragUploaderPlugin"
695     $.fn.extend({
696       dragUploader: function (options?: DragUploaderOptions | string): JQuery {
697         return this.each((index: number, elem: HTMLElement): void => {
698           const $this = $(elem);
699           let data = $this.data('DragUploaderPlugin');
700           if (!data) {
701             $this.data('DragUploaderPlugin', (data = new DragUploaderPlugin(elem)));
702           }
703           if (typeof options === 'string') {
704             data[options]();
705           }
706         });
707       }
708     });
709
710     $(() => {
711       $('.t3js-drag-uploader').dragUploader(opts);
712     });
713   }
714
715 }
716
717 /**
718  * Function to apply the example plugin to the selected elements of a jQuery result.
719  */
720 interface DragUploaderFunction {
721   /**
722    * Apply the example plugin to the elements selected in the jQuery result.
723    *
724    * @param options Options to use for this application of the example plugin.
725    * @returns jQuery result.
726    */
727   (options: DragUploaderOptions): JQuery;
728 }
729
730 export const initialize = function(): void {
731   DragUploader.init();
732
733   // load required modules to hook in the post initialize function
734   if (
735     'undefined' !== typeof TYPO3.settings
736     && 'undefined' !== typeof TYPO3.settings.RequireJS
737     && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules
738     && 'undefined' !== typeof TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader']
739   ) {
740     $.each(
741       TYPO3.settings.RequireJS.PostInitializationModules['TYPO3/CMS/Backend/DragUploader'], (pos, moduleName) => {
742         require([moduleName]);
743       }
744     );
745   }
746 };
747
748 initialize();