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