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