@vaadin/upload 24.1.5 → 24.2.0-alpha10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,71 +3,15 @@
3
3
  * Copyright (c) 2016 - 2023 Vaadin Ltd.
4
4
  * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
5
5
  */
6
- import '@polymer/polymer/lib/elements/dom-repeat.js';
7
6
  import '@vaadin/button/src/vaadin-button.js';
8
7
  import './vaadin-upload-icon.js';
9
8
  import './vaadin-upload-icons.js';
10
9
  import './vaadin-upload-file-list.js';
11
10
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
12
- import { announce } from '@vaadin/a11y-base/src/announce.js';
13
- import { isTouch } from '@vaadin/component-base/src/browser-utils.js';
14
11
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
15
12
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
16
- import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
17
13
  import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
18
-
19
- class AddButtonController extends SlotController {
20
- constructor(host) {
21
- super(host, 'add-button', 'vaadin-button');
22
- }
23
-
24
- /**
25
- * Override method inherited from `SlotController`
26
- * to add listeners to default and custom node.
27
- *
28
- * @param {Node} node
29
- * @protected
30
- * @override
31
- */
32
- initNode(node) {
33
- // Needed by Flow counterpart to apply i18n to custom button
34
- if (node._isDefault) {
35
- this.defaultNode = node;
36
- }
37
-
38
- node.addEventListener('touchend', (e) => {
39
- this.host._onAddFilesTouchEnd(e);
40
- });
41
-
42
- node.addEventListener('click', (e) => {
43
- this.host._onAddFilesClick(e);
44
- });
45
-
46
- this.host._addButton = node;
47
- }
48
- }
49
-
50
- class DropLabelController extends SlotController {
51
- constructor(host) {
52
- super(host, 'drop-label', 'span');
53
- }
54
-
55
- /**
56
- * Override method inherited from `SlotController`
57
- * to add listeners to default and custom node.
58
- *
59
- * @param {Node} node
60
- * @protected
61
- * @override
62
- */
63
- initNode(node) {
64
- // Needed by Flow counterpart to apply i18n to custom label
65
- if (node._isDefault) {
66
- this.defaultNode = node;
67
- }
68
- this.host._dropLabel = node;
69
- }
70
- }
14
+ import { UploadMixin } from './vaadin-upload-mixin.js';
71
15
 
72
16
  /**
73
17
  * `<vaadin-upload>` is a Web Component for uploading multiple files with drag and drop support.
@@ -115,8 +59,9 @@ class DropLabelController extends SlotController {
115
59
  * @mixes ControllerMixin
116
60
  * @mixes ThemableMixin
117
61
  * @mixes ElementMixin
62
+ * @mixes UploadMixin
118
63
  */
119
- class Upload extends ElementMixin(ThemableMixin(ControllerMixin(PolymerElement))) {
64
+ class Upload extends UploadMixin(ElementMixin(ThemableMixin(ControllerMixin(PolymerElement)))) {
120
65
  static get template() {
121
66
  return html`
122
67
  <style>
@@ -160,869 +105,6 @@ class Upload extends ElementMixin(ThemableMixin(ControllerMixin(PolymerElement))
160
105
  return 'vaadin-upload';
161
106
  }
162
107
 
163
- static get properties() {
164
- return {
165
- /**
166
- * Define whether the element supports dropping files on it for uploading.
167
- * By default it's enabled in desktop and disabled in touch devices
168
- * because mobile devices do not support drag events in general. Setting
169
- * it false means that drop is enabled even in touch-devices, and true
170
- * disables drop in all devices.
171
- *
172
- * @type {boolean}
173
- * @default true in touch-devices, false otherwise.
174
- */
175
- nodrop: {
176
- type: Boolean,
177
- reflectToAttribute: true,
178
- value: isTouch,
179
- },
180
-
181
- /**
182
- * The server URL. The default value is an empty string, which means that
183
- * _window.location_ will be used.
184
- * @type {string}
185
- */
186
- target: {
187
- type: String,
188
- value: '',
189
- },
190
-
191
- /**
192
- * HTTP Method used to send the files. Only POST and PUT are allowed.
193
- * @type {!UploadMethod}
194
- */
195
- method: {
196
- type: String,
197
- value: 'POST',
198
- },
199
-
200
- /**
201
- * Key-Value map to send to the server. If you set this property as an
202
- * attribute, use a valid JSON string, for example:
203
- * ```
204
- * <vaadin-upload headers='{"X-Foo": "Bar"}'></vaadin-upload>
205
- * ```
206
- * @type {object | string}
207
- */
208
- headers: {
209
- type: Object,
210
- value: {},
211
- },
212
-
213
- /**
214
- * Max time in milliseconds for the entire upload process, if exceeded the
215
- * request will be aborted. Zero means that there is no timeout.
216
- * @type {number}
217
- */
218
- timeout: {
219
- type: Number,
220
- value: 0,
221
- },
222
-
223
- /** @private */
224
- _dragover: {
225
- type: Boolean,
226
- value: false,
227
- observer: '_dragoverChanged',
228
- },
229
-
230
- /**
231
- * The array of files being processed, or already uploaded.
232
- *
233
- * Each element is a [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File)
234
- * object with a number of extra properties to track the upload process:
235
- * - `uploadTarget`: The target URL used to upload this file.
236
- * - `elapsed`: Elapsed time since the upload started.
237
- * - `elapsedStr`: Human-readable elapsed time.
238
- * - `remaining`: Number of seconds remaining for the upload to finish.
239
- * - `remainingStr`: Human-readable remaining time for the upload to finish.
240
- * - `progress`: Percentage of the file already uploaded.
241
- * - `speed`: Upload speed in kB/s.
242
- * - `size`: File size in bytes.
243
- * - `totalStr`: Human-readable total size of the file.
244
- * - `loaded`: Bytes transferred so far.
245
- * - `loadedStr`: Human-readable uploaded size at the moment.
246
- * - `status`: Status of the upload process.
247
- * - `error`: Error message in case the upload failed.
248
- * - `abort`: True if the file was canceled by the user.
249
- * - `complete`: True when the file was transferred to the server.
250
- * - `uploading`: True while transferring data to the server.
251
- * @type {!Array<!UploadFile>}
252
- */
253
- files: {
254
- type: Array,
255
- notify: true,
256
- value: () => [],
257
- },
258
-
259
- /**
260
- * Limit of files to upload, by default it is unlimited. If the value is
261
- * set to one, native file browser will prevent selecting multiple files.
262
- * @attr {number} max-files
263
- * @type {number}
264
- */
265
- maxFiles: {
266
- type: Number,
267
- value: Infinity,
268
- },
269
-
270
- /**
271
- * Specifies if the maximum number of files have been uploaded
272
- * @attr {boolean} max-files-reached
273
- * @type {boolean}
274
- */
275
- maxFilesReached: {
276
- type: Boolean,
277
- value: false,
278
- notify: true,
279
- readOnly: true,
280
- reflectToAttribute: true,
281
- computed: '_maxFilesAdded(maxFiles, files.length)',
282
- },
283
-
284
- /**
285
- * Specifies the types of files that the server accepts.
286
- * Syntax: a comma-separated list of MIME type patterns (wildcards are
287
- * allowed) or file extensions.
288
- * Notice that MIME types are widely supported, while file extensions
289
- * are only implemented in certain browsers, so avoid using it.
290
- * Example: accept="video/*,image/tiff" or accept=".pdf,audio/mp3"
291
- * @type {string}
292
- */
293
- accept: {
294
- type: String,
295
- value: '',
296
- },
297
-
298
- /**
299
- * Specifies the maximum file size in bytes allowed to upload.
300
- * Notice that it is a client-side constraint, which will be checked before
301
- * sending the request. Obviously you need to do the same validation in
302
- * the server-side and be sure that they are aligned.
303
- * @attr {number} max-file-size
304
- * @type {number}
305
- */
306
- maxFileSize: {
307
- type: Number,
308
- value: Infinity,
309
- },
310
-
311
- /**
312
- * Specifies if the dragover is validated with maxFiles and
313
- * accept properties.
314
- * @private
315
- */
316
- _dragoverValid: {
317
- type: Boolean,
318
- value: false,
319
- observer: '_dragoverValidChanged',
320
- },
321
-
322
- /**
323
- * Specifies the 'name' property at Content-Disposition
324
- * @attr {string} form-data-name
325
- * @type {string}
326
- */
327
- formDataName: {
328
- type: String,
329
- value: 'file',
330
- },
331
-
332
- /**
333
- * Prevents upload(s) from immediately uploading upon adding file(s).
334
- * When set, you must manually trigger uploads using the `uploadFiles` method
335
- * @attr {boolean} no-auto
336
- * @type {boolean}
337
- */
338
- noAuto: {
339
- type: Boolean,
340
- value: false,
341
- },
342
-
343
- /**
344
- * Set the withCredentials flag on the request.
345
- * @attr {boolean} with-credentials
346
- * @type {boolean}
347
- */
348
- withCredentials: {
349
- type: Boolean,
350
- value: false,
351
- },
352
-
353
- /**
354
- * Pass-through to input's capture attribute. Allows user to trigger device inputs
355
- * such as camera or microphone immediately.
356
- */
357
- capture: String,
358
-
359
- /**
360
- * The object used to localize this component.
361
- * For changing the default localization, change the entire
362
- * _i18n_ object or just the property you want to modify.
363
- *
364
- * The object has the following JSON structure and default values:
365
- *
366
- * ```
367
- * {
368
- * dropFiles: {
369
- * one: 'Drop file here',
370
- * many: 'Drop files here'
371
- * },
372
- * addFiles: {
373
- * one: 'Select File...',
374
- * many: 'Upload Files...'
375
- * },
376
- * error: {
377
- * tooManyFiles: 'Too Many Files.',
378
- * fileIsTooBig: 'File is Too Big.',
379
- * incorrectFileType: 'Incorrect File Type.'
380
- * },
381
- * uploading: {
382
- * status: {
383
- * connecting: 'Connecting...',
384
- * stalled: 'Stalled',
385
- * processing: 'Processing File...',
386
- * held: 'Queued'
387
- * },
388
- * remainingTime: {
389
- * prefix: 'remaining time: ',
390
- * unknown: 'unknown remaining time'
391
- * },
392
- * error: {
393
- * serverUnavailable: 'Server Unavailable',
394
- * unexpectedServerError: 'Unexpected Server Error',
395
- * forbidden: 'Forbidden'
396
- * }
397
- * },
398
- * file: {
399
- * retry: 'Retry',
400
- * start: 'Start',
401
- * remove: 'Remove'
402
- * },
403
- * units: {
404
- * size: ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
405
- * sizeBase: 1000
406
- * },
407
- * formatSize: function(bytes) {
408
- * // returns the size followed by the best suitable unit
409
- * },
410
- * formatTime: function(seconds, [secs, mins, hours]) {
411
- * // returns a 'HH:MM:SS' string
412
- * }
413
- * }
414
- * ```
415
- *
416
- * @type {!UploadI18n}
417
- * @default {English}
418
- */
419
- i18n: {
420
- type: Object,
421
- value() {
422
- return {
423
- dropFiles: {
424
- one: 'Drop file here',
425
- many: 'Drop files here',
426
- },
427
- addFiles: {
428
- one: 'Upload File...',
429
- many: 'Upload Files...',
430
- },
431
- error: {
432
- tooManyFiles: 'Too Many Files.',
433
- fileIsTooBig: 'File is Too Big.',
434
- incorrectFileType: 'Incorrect File Type.',
435
- },
436
- uploading: {
437
- status: {
438
- connecting: 'Connecting...',
439
- stalled: 'Stalled',
440
- processing: 'Processing File...',
441
- held: 'Queued',
442
- },
443
- remainingTime: {
444
- prefix: 'remaining time: ',
445
- unknown: 'unknown remaining time',
446
- },
447
- error: {
448
- serverUnavailable: 'Upload failed, please try again later',
449
- unexpectedServerError: 'Upload failed due to server error',
450
- forbidden: 'Upload forbidden',
451
- },
452
- },
453
- file: {
454
- retry: 'Retry',
455
- start: 'Start',
456
- remove: 'Remove',
457
- },
458
- units: {
459
- size: ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
460
- },
461
- };
462
- },
463
- },
464
-
465
- /** @private */
466
- _addButton: {
467
- type: Object,
468
- },
469
-
470
- /** @private */
471
- _dropLabel: {
472
- type: Object,
473
- },
474
-
475
- /** @private */
476
- _fileList: {
477
- type: Object,
478
- },
479
-
480
- /** @private */
481
- _files: {
482
- type: Array,
483
- },
484
- };
485
- }
486
-
487
- static get observers() {
488
- return [
489
- '__updateAddButton(_addButton, maxFiles, i18n, maxFilesReached)',
490
- '__updateDropLabel(_dropLabel, maxFiles, i18n)',
491
- '__updateFileList(_fileList, files, i18n)',
492
- ];
493
- }
494
-
495
- /** @private */
496
- get __acceptRegexp() {
497
- if (!this.accept) {
498
- return null;
499
- }
500
- const processedTokens = this.accept.split(',').map((token) => {
501
- let processedToken = token.trim();
502
- // Escape regex operators common to mime types
503
- processedToken = processedToken.replace(/[+.]/gu, '\\$&');
504
- // Make extension patterns match the end of the file name
505
- if (processedToken.startsWith('\\.')) {
506
- processedToken = `.*${processedToken}$`;
507
- }
508
- // Handle star (*) wildcards
509
- return processedToken.replace(/\/\*/gu, '/.*');
510
- });
511
- // Create accept regex
512
- return new RegExp(`^(${processedTokens.join('|')})$`, 'iu');
513
- }
514
-
515
- /** @protected */
516
- ready() {
517
- super.ready();
518
- this.addEventListener('dragover', this._onDragover.bind(this));
519
- this.addEventListener('dragleave', this._onDragleave.bind(this));
520
- this.addEventListener('drop', this._onDrop.bind(this));
521
- this.addEventListener('file-retry', this._onFileRetry.bind(this));
522
- this.addEventListener('file-abort', this._onFileAbort.bind(this));
523
- this.addEventListener('file-start', this._onFileStart.bind(this));
524
- this.addEventListener('file-reject', this._onFileReject.bind(this));
525
- this.addEventListener('upload-start', this._onUploadStart.bind(this));
526
- this.addEventListener('upload-success', this._onUploadSuccess.bind(this));
527
- this.addEventListener('upload-error', this._onUploadError.bind(this));
528
-
529
- this._addButtonController = new AddButtonController(this);
530
- this.addController(this._addButtonController);
531
-
532
- this._dropLabelController = new DropLabelController(this);
533
- this.addController(this._dropLabelController);
534
-
535
- this.addController(
536
- new SlotController(this, 'file-list', 'vaadin-upload-file-list', {
537
- initializer: (list) => {
538
- this._fileList = list;
539
- },
540
- }),
541
- );
542
-
543
- this.addController(new SlotController(this, 'drop-label-icon', 'vaadin-upload-icon'));
544
- }
545
-
546
- /** @private */
547
- _formatSize(bytes) {
548
- if (typeof this.i18n.formatSize === 'function') {
549
- return this.i18n.formatSize(bytes);
550
- }
551
-
552
- // https://wiki.ubuntu.com/UnitsPolicy
553
- const base = this.i18n.units.sizeBase || 1000;
554
- const unit = ~~(Math.log(bytes) / Math.log(base));
555
- const dec = Math.max(0, Math.min(3, unit - 1));
556
- const size = parseFloat((bytes / base ** unit).toFixed(dec));
557
- return `${size} ${this.i18n.units.size[unit]}`;
558
- }
559
-
560
- /** @private */
561
- _splitTimeByUnits(time) {
562
- const unitSizes = [60, 60, 24, Infinity];
563
- const timeValues = [0];
564
-
565
- for (let i = 0; i < unitSizes.length && time > 0; i++) {
566
- timeValues[i] = time % unitSizes[i];
567
- time = Math.floor(time / unitSizes[i]);
568
- }
569
-
570
- return timeValues;
571
- }
572
-
573
- /** @private */
574
- _formatTime(seconds, split) {
575
- if (typeof this.i18n.formatTime === 'function') {
576
- return this.i18n.formatTime(seconds, split);
577
- }
578
-
579
- // Fill HH:MM:SS with leading zeros
580
- while (split.length < 3) {
581
- split.push(0);
582
- }
583
-
584
- return split
585
- .reverse()
586
- .map((number) => {
587
- return (number < 10 ? '0' : '') + number;
588
- })
589
- .join(':');
590
- }
591
-
592
- /** @private */
593
- _formatFileProgress(file) {
594
- const remainingTime =
595
- file.loaded > 0
596
- ? this.i18n.uploading.remainingTime.prefix + file.remainingStr
597
- : this.i18n.uploading.remainingTime.unknown;
598
-
599
- return `${file.totalStr}: ${file.progress}% (${remainingTime})`;
600
- }
601
-
602
- /** @private */
603
- _maxFilesAdded(maxFiles, numFiles) {
604
- return maxFiles >= 0 && numFiles >= maxFiles;
605
- }
606
-
607
- /** @private */
608
- __updateAddButton(addButton, maxFiles, i18n, maxFilesReached) {
609
- if (addButton) {
610
- addButton.disabled = maxFilesReached;
611
-
612
- // Only update text content for the default button element
613
- if (addButton === this._addButtonController.defaultNode) {
614
- addButton.textContent = this._i18nPlural(maxFiles, i18n.addFiles);
615
- }
616
- }
617
- }
618
-
619
- /** @private */
620
- __updateDropLabel(dropLabel, maxFiles, i18n) {
621
- // Only update text content for the default label element
622
- if (dropLabel && dropLabel === this._dropLabelController.defaultNode) {
623
- dropLabel.textContent = this._i18nPlural(maxFiles, i18n.dropFiles);
624
- }
625
- }
626
-
627
- /** @private */
628
- __updateFileList(list, files, i18n) {
629
- if (list) {
630
- list.items = [...files];
631
- list.i18n = i18n;
632
- }
633
- }
634
-
635
- /** @private */
636
- _onDragover(event) {
637
- event.preventDefault();
638
- if (!this.nodrop && !this._dragover) {
639
- this._dragoverValid = !this.maxFilesReached;
640
- this._dragover = true;
641
- }
642
- event.dataTransfer.dropEffect = !this._dragoverValid || this.nodrop ? 'none' : 'copy';
643
- }
644
-
645
- /** @private */
646
- _onDragleave(event) {
647
- event.preventDefault();
648
- if (this._dragover && !this.nodrop) {
649
- this._dragover = this._dragoverValid = false;
650
- }
651
- }
652
-
653
- /** @private */
654
- _onDrop(event) {
655
- if (!this.nodrop) {
656
- event.preventDefault();
657
- this._dragover = this._dragoverValid = false;
658
- this._addFiles(event.dataTransfer.files);
659
- }
660
- }
661
-
662
- /** @private */
663
- _createXhr() {
664
- return new XMLHttpRequest();
665
- }
666
-
667
- /** @private */
668
- _configureXhr(xhr) {
669
- if (typeof this.headers === 'string') {
670
- try {
671
- this.headers = JSON.parse(this.headers);
672
- } catch (e) {
673
- this.headers = undefined;
674
- }
675
- }
676
- Object.entries(this.headers).forEach(([key, value]) => {
677
- xhr.setRequestHeader(key, value);
678
- });
679
- if (this.timeout) {
680
- xhr.timeout = this.timeout;
681
- }
682
- xhr.withCredentials = this.withCredentials;
683
- }
684
-
685
- /** @private */
686
- _setStatus(file, total, loaded, elapsed) {
687
- file.elapsed = elapsed;
688
- file.elapsedStr = this._formatTime(file.elapsed, this._splitTimeByUnits(file.elapsed));
689
- file.remaining = Math.ceil(elapsed * (total / loaded - 1));
690
- file.remainingStr = this._formatTime(file.remaining, this._splitTimeByUnits(file.remaining));
691
- file.speed = ~~(total / elapsed / 1024);
692
- file.totalStr = this._formatSize(total);
693
- file.loadedStr = this._formatSize(loaded);
694
- file.status = this._formatFileProgress(file);
695
- }
696
-
697
- /**
698
- * Triggers the upload of any files that are not completed
699
- *
700
- * @param {!UploadFile | !Array<!UploadFile>=} files - Files being uploaded. Defaults to all outstanding files
701
- */
702
- uploadFiles(files = this.files) {
703
- if (files && !Array.isArray(files)) {
704
- files = [files];
705
- }
706
- files = files.filter((file) => !file.complete);
707
- Array.prototype.forEach.call(files, this._uploadFile.bind(this));
708
- }
709
-
710
- /** @private */
711
- _uploadFile(file) {
712
- if (file.uploading) {
713
- return;
714
- }
715
-
716
- const ini = Date.now();
717
- const xhr = (file.xhr = this._createXhr());
718
-
719
- let stalledId, last;
720
- // Onprogress is called always after onreadystatechange
721
- xhr.upload.onprogress = (e) => {
722
- clearTimeout(stalledId);
723
-
724
- last = Date.now();
725
- const elapsed = (last - ini) / 1000;
726
- const loaded = e.loaded,
727
- total = e.total,
728
- progress = ~~((loaded / total) * 100);
729
- file.loaded = loaded;
730
- file.progress = progress;
731
- file.indeterminate = loaded <= 0 || loaded >= total;
732
-
733
- if (file.error) {
734
- file.indeterminate = file.status = undefined;
735
- } else if (!file.abort) {
736
- if (progress < 100) {
737
- this._setStatus(file, total, loaded, elapsed);
738
- stalledId = setTimeout(() => {
739
- file.status = this.i18n.uploading.status.stalled;
740
- this._renderFileList();
741
- }, 2000);
742
- } else {
743
- file.loadedStr = file.totalStr;
744
- file.status = this.i18n.uploading.status.processing;
745
- }
746
- }
747
-
748
- this._renderFileList();
749
- this.dispatchEvent(new CustomEvent('upload-progress', { detail: { file, xhr } }));
750
- };
751
-
752
- // More reliable than xhr.onload
753
- xhr.onreadystatechange = () => {
754
- if (xhr.readyState === 4) {
755
- clearTimeout(stalledId);
756
- file.indeterminate = file.uploading = false;
757
- if (file.abort) {
758
- return;
759
- }
760
- file.status = '';
761
- // Custom listener can modify the default behavior either
762
- // preventing default, changing the xhr, or setting the file error
763
- const evt = this.dispatchEvent(
764
- new CustomEvent('upload-response', {
765
- detail: { file, xhr },
766
- cancelable: true,
767
- }),
768
- );
769
-
770
- if (!evt) {
771
- return;
772
- }
773
- if (xhr.status === 0) {
774
- file.error = this.i18n.uploading.error.serverUnavailable;
775
- } else if (xhr.status >= 500) {
776
- file.error = this.i18n.uploading.error.unexpectedServerError;
777
- } else if (xhr.status >= 400) {
778
- file.error = this.i18n.uploading.error.forbidden;
779
- }
780
-
781
- file.complete = !file.error;
782
- this.dispatchEvent(
783
- new CustomEvent(`upload-${file.error ? 'error' : 'success'}`, {
784
- detail: { file, xhr },
785
- }),
786
- );
787
- this._renderFileList();
788
- }
789
- };
790
-
791
- const formData = new FormData();
792
-
793
- file.uploadTarget = file.uploadTarget || this.target || '';
794
- file.formDataName = this.formDataName;
795
-
796
- const evt = this.dispatchEvent(
797
- new CustomEvent('upload-before', {
798
- detail: { file, xhr },
799
- cancelable: true,
800
- }),
801
- );
802
- if (!evt) {
803
- return;
804
- }
805
-
806
- formData.append(file.formDataName, file, file.name);
807
-
808
- xhr.open(this.method, file.uploadTarget, true);
809
- this._configureXhr(xhr);
810
-
811
- file.status = this.i18n.uploading.status.connecting;
812
- file.uploading = file.indeterminate = true;
813
- file.complete = file.abort = file.error = file.held = false;
814
-
815
- xhr.upload.onloadstart = () => {
816
- this.dispatchEvent(
817
- new CustomEvent('upload-start', {
818
- detail: { file, xhr },
819
- }),
820
- );
821
- this._renderFileList();
822
- };
823
-
824
- // Custom listener could modify the xhr just before sending it
825
- // preventing default
826
- const uploadEvt = this.dispatchEvent(
827
- new CustomEvent('upload-request', {
828
- detail: { file, xhr, formData },
829
- cancelable: true,
830
- }),
831
- );
832
- if (uploadEvt) {
833
- xhr.send(formData);
834
- }
835
- }
836
-
837
- /** @private */
838
- _retryFileUpload(file) {
839
- const evt = this.dispatchEvent(
840
- new CustomEvent('upload-retry', {
841
- detail: { file, xhr: file.xhr },
842
- cancelable: true,
843
- }),
844
- );
845
- if (evt) {
846
- this._uploadFile(file);
847
- }
848
- }
849
-
850
- /** @private */
851
- _abortFileUpload(file) {
852
- const evt = this.dispatchEvent(
853
- new CustomEvent('upload-abort', {
854
- detail: { file, xhr: file.xhr },
855
- cancelable: true,
856
- }),
857
- );
858
- if (evt) {
859
- file.abort = true;
860
- if (file.xhr) {
861
- file.xhr.abort();
862
- }
863
- this._removeFile(file);
864
- }
865
- }
866
-
867
- /** @private */
868
- _renderFileList() {
869
- if (this._fileList) {
870
- this._fileList.requestContentUpdate();
871
- }
872
- }
873
-
874
- /** @private */
875
- _addFiles(files) {
876
- Array.prototype.forEach.call(files, this._addFile.bind(this));
877
- }
878
-
879
- /**
880
- * Add the file for uploading. Called internally for each file after picking files from dialog or dropping files.
881
- *
882
- * @param {!UploadFile} file File being added
883
- * @protected
884
- */
885
- _addFile(file) {
886
- if (this.maxFilesReached) {
887
- this.dispatchEvent(
888
- new CustomEvent('file-reject', {
889
- detail: { file, error: this.i18n.error.tooManyFiles },
890
- }),
891
- );
892
- return;
893
- }
894
- if (this.maxFileSize >= 0 && file.size > this.maxFileSize) {
895
- this.dispatchEvent(
896
- new CustomEvent('file-reject', {
897
- detail: { file, error: this.i18n.error.fileIsTooBig },
898
- }),
899
- );
900
- return;
901
- }
902
- const re = this.__acceptRegexp;
903
- if (re && !(re.test(file.type) || re.test(file.name))) {
904
- this.dispatchEvent(
905
- new CustomEvent('file-reject', {
906
- detail: { file, error: this.i18n.error.incorrectFileType },
907
- }),
908
- );
909
- return;
910
- }
911
- file.loaded = 0;
912
- file.held = true;
913
- file.status = this.i18n.uploading.status.held;
914
- this.files = [file, ...this.files];
915
-
916
- if (!this.noAuto) {
917
- this._uploadFile(file);
918
- }
919
- }
920
-
921
- /**
922
- * Remove file from upload list. Called internally if file upload was canceled.
923
- * @param {!UploadFile} file File to remove
924
- * @protected
925
- */
926
- _removeFile(file) {
927
- if (this.files.indexOf(file) > -1) {
928
- this.files = this.files.filter((i) => i !== file);
929
-
930
- this.dispatchEvent(
931
- new CustomEvent('file-remove', {
932
- detail: { file },
933
- bubbles: true,
934
- composed: true,
935
- }),
936
- );
937
- }
938
- }
939
-
940
- /** @private */
941
- _onAddFilesTouchEnd(e) {
942
- // Cancel the event to avoid the following click event
943
- e.preventDefault();
944
- this._onAddFilesClick(e);
945
- }
946
-
947
- /** @private */
948
- _onAddFilesClick(e) {
949
- if (this.maxFilesReached) {
950
- return;
951
- }
952
-
953
- e.stopPropagation();
954
- this.$.fileInput.value = '';
955
- this.$.fileInput.click();
956
- }
957
-
958
- /** @private */
959
- _onFileInputChange(event) {
960
- this._addFiles(event.target.files);
961
- }
962
-
963
- /** @private */
964
- _onFileStart(event) {
965
- this._uploadFile(event.detail.file);
966
- }
967
-
968
- /** @private */
969
- _onFileRetry(event) {
970
- this._retryFileUpload(event.detail.file);
971
- }
972
-
973
- /** @private */
974
- _onFileAbort(event) {
975
- this._abortFileUpload(event.detail.file);
976
- }
977
-
978
- /** @private */
979
- _onFileReject(event) {
980
- announce(`${event.detail.file.name}: ${event.detail.file.error}`, { mode: 'alert' });
981
- }
982
-
983
- /** @private */
984
- _onUploadStart(event) {
985
- announce(`${event.detail.file.name}: 0%`, { mode: 'alert' });
986
- }
987
-
988
- /** @private */
989
- _onUploadSuccess(event) {
990
- announce(`${event.detail.file.name}: 100%`, { mode: 'alert' });
991
- }
992
-
993
- /** @private */
994
- _onUploadError(event) {
995
- announce(`${event.detail.file.name}: ${event.detail.file.error}`, { mode: 'alert' });
996
- }
997
-
998
- /** @private */
999
- _dragoverChanged(dragover) {
1000
- if (dragover) {
1001
- this.setAttribute('dragover', dragover);
1002
- } else {
1003
- this.removeAttribute('dragover');
1004
- }
1005
- }
1006
-
1007
- /** @private */
1008
- _dragoverValidChanged(dragoverValid) {
1009
- if (dragoverValid) {
1010
- this.setAttribute('dragover-valid', dragoverValid);
1011
- } else {
1012
- this.removeAttribute('dragover-valid');
1013
- }
1014
- }
1015
-
1016
- /** @private */
1017
- _i18nPlural(value, plural) {
1018
- return value === 1 ? plural.one : plural.many;
1019
- }
1020
-
1021
- /** @private */
1022
- _isMultiple(maxFiles) {
1023
- return maxFiles !== 1;
1024
- }
1025
-
1026
108
  /**
1027
109
  * Fired when a file cannot be added to the queue due to a constrain:
1028
110
  * file-size, file-type or maxFiles