UNPKG

59.3 kBJavaScriptView Raw
1function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
2
3function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
4
5function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
6
7function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); }
8
9function _wrapNativeSuper(Class) { var _cache = typeof Map === "function" ? new Map() : undefined; _wrapNativeSuper = function _wrapNativeSuper(Class) { if (Class === null || !_isNativeFunction(Class)) return Class; if (typeof Class !== "function") { throw new TypeError("Super expression must either be null or a function"); } if (typeof _cache !== "undefined") { if (_cache.has(Class)) return _cache.get(Class); _cache.set(Class, Wrapper); } function Wrapper() { return _construct(Class, arguments, _getPrototypeOf(this).constructor); } Wrapper.prototype = Object.create(Class.prototype, { constructor: { value: Wrapper, enumerable: false, writable: true, configurable: true } }); return _setPrototypeOf(Wrapper, Class); }; return _wrapNativeSuper(Class); }
10
11function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; } else { _construct = function _construct(Parent, args, Class) { var a = [null]; a.push.apply(a, args); var Constructor = Function.bind.apply(Parent, a); var instance = new Constructor(); if (Class) _setPrototypeOf(instance, Class.prototype); return instance; }; } return _construct.apply(null, arguments); }
12
13function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
14
15function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; }
16
17function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
18
19function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
20
21/* global AggregateError */
22var Translator = require('@uppy/utils/lib/Translator');
23
24var ee = require('namespace-emitter');
25
26var cuid = require('cuid');
27
28var throttle = require('lodash.throttle');
29
30var prettierBytes = require('@transloadit/prettier-bytes');
31
32var match = require('mime-match');
33
34var DefaultStore = require('@uppy/store-default');
35
36var getFileType = require('@uppy/utils/lib/getFileType');
37
38var getFileNameAndExtension = require('@uppy/utils/lib/getFileNameAndExtension');
39
40var generateFileID = require('@uppy/utils/lib/generateFileID');
41
42var findIndex = require('@uppy/utils/lib/findIndex');
43
44var supportsUploadProgress = require('./supportsUploadProgress');
45
46var _require = require('./loggers'),
47 justErrorsLogger = _require.justErrorsLogger,
48 debugLogger = _require.debugLogger;
49
50var Plugin = require('./Plugin'); // Exported from here.
51
52
53var RestrictionError = /*#__PURE__*/function (_Error) {
54 _inheritsLoose(RestrictionError, _Error);
55
56 function RestrictionError() {
57 var _this;
58
59 for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
60 args[_key] = arguments[_key];
61 }
62
63 _this = _Error.call.apply(_Error, [this].concat(args)) || this;
64 _this.isRestriction = true;
65 return _this;
66 }
67
68 return RestrictionError;
69}( /*#__PURE__*/_wrapNativeSuper(Error));
70/**
71 * Uppy Core module.
72 * Manages plugins, state updates, acts as an event bus,
73 * adds/removes files and metadata.
74 */
75
76
77var Uppy = /*#__PURE__*/function () {
78 /**
79 * Instantiate Uppy
80 *
81 * @param {object} opts — Uppy options
82 */
83 function Uppy(opts) {
84 var _this2 = this;
85
86 this.defaultLocale = {
87 strings: {
88 addBulkFilesFailed: {
89 0: 'Failed to add %{smart_count} file due to an internal error',
90 1: 'Failed to add %{smart_count} files due to internal errors'
91 },
92 youCanOnlyUploadX: {
93 0: 'You can only upload %{smart_count} file',
94 1: 'You can only upload %{smart_count} files'
95 },
96 youHaveToAtLeastSelectX: {
97 0: 'You have to select at least %{smart_count} file',
98 1: 'You have to select at least %{smart_count} files'
99 },
100 // The default `exceedsSize2` string only combines the `exceedsSize` string (%{backwardsCompat}) with the size.
101 // Locales can override `exceedsSize2` to specify a different word order. This is for backwards compat with
102 // Uppy 1.9.x and below which did a naive concatenation of `exceedsSize2 + size` instead of using a locale-specific
103 // substitution.
104 // TODO: In 2.0 `exceedsSize2` should be removed in and `exceedsSize` updated to use substitution.
105 exceedsSize2: '%{backwardsCompat} %{size}',
106 exceedsSize: 'This file exceeds maximum allowed size of',
107 inferiorSize: 'This file is smaller than the allowed size of %{size}',
108 youCanOnlyUploadFileTypes: 'You can only upload: %{types}',
109 noNewAlreadyUploading: 'Cannot add new files: already uploading',
110 noDuplicates: 'Cannot add the duplicate file \'%{fileName}\', it already exists',
111 companionError: 'Connection with Companion failed',
112 companionUnauthorizeHint: 'To unauthorize to your %{provider} account, please go to %{url}',
113 failedToUpload: 'Failed to upload %{file}',
114 noInternetConnection: 'No Internet connection',
115 connectedToInternet: 'Connected to the Internet',
116 // Strings for remote providers
117 noFilesFound: 'You have no files or folders here',
118 selectX: {
119 0: 'Select %{smart_count}',
120 1: 'Select %{smart_count}'
121 },
122 selectAllFilesFromFolderNamed: 'Select all files from folder %{name}',
123 unselectAllFilesFromFolderNamed: 'Unselect all files from folder %{name}',
124 selectFileNamed: 'Select file %{name}',
125 unselectFileNamed: 'Unselect file %{name}',
126 openFolderNamed: 'Open folder %{name}',
127 cancel: 'Cancel',
128 logOut: 'Log out',
129 filter: 'Filter',
130 resetFilter: 'Reset filter',
131 loading: 'Loading...',
132 authenticateWithTitle: 'Please authenticate with %{pluginName} to select files',
133 authenticateWith: 'Connect to %{pluginName}',
134 searchImages: 'Search for images',
135 enterTextToSearch: 'Enter text to search for images',
136 backToSearch: 'Back to Search',
137 emptyFolderAdded: 'No files were added from empty folder',
138 folderAdded: {
139 0: 'Added %{smart_count} file from %{folder}',
140 1: 'Added %{smart_count} files from %{folder}'
141 }
142 }
143 };
144 var defaultOptions = {
145 id: 'uppy',
146 autoProceed: false,
147 allowMultipleUploads: true,
148 debug: false,
149 restrictions: {
150 maxFileSize: null,
151 minFileSize: null,
152 maxTotalFileSize: null,
153 maxNumberOfFiles: null,
154 minNumberOfFiles: null,
155 allowedFileTypes: null
156 },
157 meta: {},
158 onBeforeFileAdded: function onBeforeFileAdded(currentFile, files) {
159 return currentFile;
160 },
161 onBeforeUpload: function onBeforeUpload(files) {
162 return files;
163 },
164 store: DefaultStore(),
165 logger: justErrorsLogger,
166 infoTimeout: 5000
167 }; // Merge default options with the ones set by user,
168 // making sure to merge restrictions too
169
170 this.opts = _extends({}, defaultOptions, opts, {
171 restrictions: _extends({}, defaultOptions.restrictions, opts && opts.restrictions)
172 }); // Support debug: true for backwards-compatability, unless logger is set in opts
173 // opts instead of this.opts to avoid comparing objects — we set logger: justErrorsLogger in defaultOptions
174
175 if (opts && opts.logger && opts.debug) {
176 this.log('You are using a custom `logger`, but also set `debug: true`, which uses built-in logger to output logs to console. Ignoring `debug: true` and using your custom `logger`.', 'warning');
177 } else if (opts && opts.debug) {
178 this.opts.logger = debugLogger;
179 }
180
181 this.log("Using Core v" + this.constructor.VERSION);
182
183 if (this.opts.restrictions.allowedFileTypes && this.opts.restrictions.allowedFileTypes !== null && !Array.isArray(this.opts.restrictions.allowedFileTypes)) {
184 throw new TypeError('`restrictions.allowedFileTypes` must be an array');
185 }
186
187 this.i18nInit(); // Container for different types of plugins
188
189 this.plugins = {};
190 this.getState = this.getState.bind(this);
191 this.getPlugin = this.getPlugin.bind(this);
192 this.setFileMeta = this.setFileMeta.bind(this);
193 this.setFileState = this.setFileState.bind(this);
194 this.log = this.log.bind(this);
195 this.info = this.info.bind(this);
196 this.hideInfo = this.hideInfo.bind(this);
197 this.addFile = this.addFile.bind(this);
198 this.removeFile = this.removeFile.bind(this);
199 this.pauseResume = this.pauseResume.bind(this);
200 this.validateRestrictions = this.validateRestrictions.bind(this); // ___Why throttle at 500ms?
201 // - We must throttle at >250ms for superfocus in Dashboard to work well (because animation takes 0.25s, and we want to wait for all animations to be over before refocusing).
202 // [Practical Check]: if thottle is at 100ms, then if you are uploading a file, and click 'ADD MORE FILES', - focus won't activate in Firefox.
203 // - We must throttle at around >500ms to avoid performance lags.
204 // [Practical Check] Firefox, try to upload a big file for a prolonged period of time. Laptop will start to heat up.
205
206 this._calculateProgress = throttle(this._calculateProgress.bind(this), 500, {
207 leading: true,
208 trailing: true
209 });
210 this.updateOnlineStatus = this.updateOnlineStatus.bind(this);
211 this.resetProgress = this.resetProgress.bind(this);
212 this.pauseAll = this.pauseAll.bind(this);
213 this.resumeAll = this.resumeAll.bind(this);
214 this.retryAll = this.retryAll.bind(this);
215 this.cancelAll = this.cancelAll.bind(this);
216 this.retryUpload = this.retryUpload.bind(this);
217 this.upload = this.upload.bind(this);
218 this.emitter = ee();
219 this.on = this.on.bind(this);
220 this.off = this.off.bind(this);
221 this.once = this.emitter.once.bind(this.emitter);
222 this.emit = this.emitter.emit.bind(this.emitter);
223 this.preProcessors = [];
224 this.uploaders = [];
225 this.postProcessors = [];
226 this.store = this.opts.store;
227 this.setState({
228 plugins: {},
229 files: {},
230 currentUploads: {},
231 allowNewUpload: true,
232 capabilities: {
233 uploadProgress: supportsUploadProgress(),
234 individualCancellation: true,
235 resumableUploads: false
236 },
237 totalProgress: 0,
238 meta: _extends({}, this.opts.meta),
239 info: {
240 isHidden: true,
241 type: 'info',
242 message: ''
243 }
244 });
245 this._storeUnsubscribe = this.store.subscribe(function (prevState, nextState, patch) {
246 _this2.emit('state-update', prevState, nextState, patch);
247
248 _this2.updateAll(nextState);
249 }); // Exposing uppy object on window for debugging and testing
250
251 if (this.opts.debug && typeof window !== 'undefined') {
252 window[this.opts.id] = this;
253 }
254
255 this._addListeners(); // Re-enable if we’ll need some capabilities on boot, like isMobileDevice
256 // this._setCapabilities()
257
258 } // _setCapabilities = () => {
259 // const capabilities = {
260 // isMobileDevice: isMobileDevice()
261 // }
262 // this.setState({
263 // ...this.getState().capabilities,
264 // capabilities
265 // })
266 // }
267
268
269 var _proto = Uppy.prototype;
270
271 _proto.on = function on(event, callback) {
272 this.emitter.on(event, callback);
273 return this;
274 };
275
276 _proto.off = function off(event, callback) {
277 this.emitter.off(event, callback);
278 return this;
279 }
280 /**
281 * Iterate on all plugins and run `update` on them.
282 * Called each time state changes.
283 *
284 */
285 ;
286
287 _proto.updateAll = function updateAll(state) {
288 this.iteratePlugins(function (plugin) {
289 plugin.update(state);
290 });
291 }
292 /**
293 * Updates state with a patch
294 *
295 * @param {object} patch {foo: 'bar'}
296 */
297 ;
298
299 _proto.setState = function setState(patch) {
300 this.store.setState(patch);
301 }
302 /**
303 * Returns current state.
304 *
305 * @returns {object}
306 */
307 ;
308
309 _proto.getState = function getState() {
310 return this.store.getState();
311 }
312 /**
313 * Back compat for when uppy.state is used instead of uppy.getState().
314 */
315 ;
316
317 /**
318 * Shorthand to set state for a specific file.
319 */
320 _proto.setFileState = function setFileState(fileID, state) {
321 var _extends2;
322
323 if (!this.getState().files[fileID]) {
324 throw new Error("Can\u2019t set state for " + fileID + " (the file could have been removed)");
325 }
326
327 this.setState({
328 files: _extends({}, this.getState().files, (_extends2 = {}, _extends2[fileID] = _extends({}, this.getState().files[fileID], state), _extends2))
329 });
330 };
331
332 _proto.i18nInit = function i18nInit() {
333 this.translator = new Translator([this.defaultLocale, this.opts.locale]);
334 this.locale = this.translator.locale;
335 this.i18n = this.translator.translate.bind(this.translator);
336 this.i18nArray = this.translator.translateArray.bind(this.translator);
337 };
338
339 _proto.setOptions = function setOptions(newOpts) {
340 this.opts = _extends({}, this.opts, newOpts, {
341 restrictions: _extends({}, this.opts.restrictions, newOpts && newOpts.restrictions)
342 });
343
344 if (newOpts.meta) {
345 this.setMeta(newOpts.meta);
346 }
347
348 this.i18nInit();
349
350 if (newOpts.locale) {
351 this.iteratePlugins(function (plugin) {
352 plugin.setOptions();
353 });
354 }
355
356 this.setState(); // so that UI re-renders with new options
357 };
358
359 _proto.resetProgress = function resetProgress() {
360 var defaultProgress = {
361 percentage: 0,
362 bytesUploaded: 0,
363 uploadComplete: false,
364 uploadStarted: null
365 };
366
367 var files = _extends({}, this.getState().files);
368
369 var updatedFiles = {};
370 Object.keys(files).forEach(function (fileID) {
371 var updatedFile = _extends({}, files[fileID]);
372
373 updatedFile.progress = _extends({}, updatedFile.progress, defaultProgress);
374 updatedFiles[fileID] = updatedFile;
375 });
376 this.setState({
377 files: updatedFiles,
378 totalProgress: 0
379 });
380 this.emit('reset-progress');
381 };
382
383 _proto.addPreProcessor = function addPreProcessor(fn) {
384 this.preProcessors.push(fn);
385 };
386
387 _proto.removePreProcessor = function removePreProcessor(fn) {
388 var i = this.preProcessors.indexOf(fn);
389
390 if (i !== -1) {
391 this.preProcessors.splice(i, 1);
392 }
393 };
394
395 _proto.addPostProcessor = function addPostProcessor(fn) {
396 this.postProcessors.push(fn);
397 };
398
399 _proto.removePostProcessor = function removePostProcessor(fn) {
400 var i = this.postProcessors.indexOf(fn);
401
402 if (i !== -1) {
403 this.postProcessors.splice(i, 1);
404 }
405 };
406
407 _proto.addUploader = function addUploader(fn) {
408 this.uploaders.push(fn);
409 };
410
411 _proto.removeUploader = function removeUploader(fn) {
412 var i = this.uploaders.indexOf(fn);
413
414 if (i !== -1) {
415 this.uploaders.splice(i, 1);
416 }
417 };
418
419 _proto.setMeta = function setMeta(data) {
420 var updatedMeta = _extends({}, this.getState().meta, data);
421
422 var updatedFiles = _extends({}, this.getState().files);
423
424 Object.keys(updatedFiles).forEach(function (fileID) {
425 updatedFiles[fileID] = _extends({}, updatedFiles[fileID], {
426 meta: _extends({}, updatedFiles[fileID].meta, data)
427 });
428 });
429 this.log('Adding metadata:');
430 this.log(data);
431 this.setState({
432 meta: updatedMeta,
433 files: updatedFiles
434 });
435 };
436
437 _proto.setFileMeta = function setFileMeta(fileID, data) {
438 var updatedFiles = _extends({}, this.getState().files);
439
440 if (!updatedFiles[fileID]) {
441 this.log('Was trying to set metadata for a file that has been removed: ', fileID);
442 return;
443 }
444
445 var newMeta = _extends({}, updatedFiles[fileID].meta, data);
446
447 updatedFiles[fileID] = _extends({}, updatedFiles[fileID], {
448 meta: newMeta
449 });
450 this.setState({
451 files: updatedFiles
452 });
453 }
454 /**
455 * Get a file object.
456 *
457 * @param {string} fileID The ID of the file object to return.
458 */
459 ;
460
461 _proto.getFile = function getFile(fileID) {
462 return this.getState().files[fileID];
463 }
464 /**
465 * Get all files in an array.
466 */
467 ;
468
469 _proto.getFiles = function getFiles() {
470 var _this$getState = this.getState(),
471 files = _this$getState.files;
472
473 return Object.keys(files).map(function (fileID) {
474 return files[fileID];
475 });
476 }
477 /**
478 * A public wrapper for _checkRestrictions — checks if a file passes a set of restrictions.
479 * For use in UI pluigins (like Providers), to disallow selecting files that won’t pass restrictions.
480 *
481 * @param {object} file object to check
482 * @param {Array} [files] array to check maxNumberOfFiles and maxTotalFileSize
483 * @returns {object} { result: true/false, reason: why file didn’t pass restrictions }
484 */
485 ;
486
487 _proto.validateRestrictions = function validateRestrictions(file, files) {
488 try {
489 this._checkRestrictions(file, files);
490
491 return {
492 result: true
493 };
494 } catch (err) {
495 return {
496 result: false,
497 reason: err.message
498 };
499 }
500 }
501 /**
502 * Check if file passes a set of restrictions set in options: maxFileSize, minFileSize,
503 * maxNumberOfFiles and allowedFileTypes.
504 *
505 * @param {object} file object to check
506 * @param {Array} [files] array to check maxNumberOfFiles and maxTotalFileSize
507 * @private
508 */
509 ;
510
511 _proto._checkRestrictions = function _checkRestrictions(file, files) {
512 if (files === void 0) {
513 files = this.getFiles();
514 }
515
516 var _this$opts$restrictio = this.opts.restrictions,
517 maxFileSize = _this$opts$restrictio.maxFileSize,
518 minFileSize = _this$opts$restrictio.minFileSize,
519 maxTotalFileSize = _this$opts$restrictio.maxTotalFileSize,
520 maxNumberOfFiles = _this$opts$restrictio.maxNumberOfFiles,
521 allowedFileTypes = _this$opts$restrictio.allowedFileTypes;
522
523 if (maxNumberOfFiles) {
524 if (files.length + 1 > maxNumberOfFiles) {
525 throw new RestrictionError("" + this.i18n('youCanOnlyUploadX', {
526 smart_count: maxNumberOfFiles
527 }));
528 }
529 }
530
531 if (allowedFileTypes) {
532 var isCorrectFileType = allowedFileTypes.some(function (type) {
533 // check if this is a mime-type
534 if (type.indexOf('/') > -1) {
535 if (!file.type) return false;
536 return match(file.type.replace(/;.*?$/, ''), type);
537 } // otherwise this is likely an extension
538
539
540 if (type[0] === '.' && file.extension) {
541 return file.extension.toLowerCase() === type.substr(1).toLowerCase();
542 }
543
544 return false;
545 });
546
547 if (!isCorrectFileType) {
548 var allowedFileTypesString = allowedFileTypes.join(', ');
549 throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', {
550 types: allowedFileTypesString
551 }));
552 }
553 } // We can't check maxTotalFileSize if the size is unknown.
554
555
556 if (maxTotalFileSize && file.size != null) {
557 var totalFilesSize = 0;
558 totalFilesSize += file.size;
559 files.forEach(function (file) {
560 totalFilesSize += file.size;
561 });
562
563 if (totalFilesSize > maxTotalFileSize) {
564 throw new RestrictionError(this.i18n('exceedsSize2', {
565 backwardsCompat: this.i18n('exceedsSize'),
566 size: prettierBytes(maxTotalFileSize)
567 }));
568 }
569 } // We can't check maxFileSize if the size is unknown.
570
571
572 if (maxFileSize && file.size != null) {
573 if (file.size > maxFileSize) {
574 throw new RestrictionError(this.i18n('exceedsSize2', {
575 backwardsCompat: this.i18n('exceedsSize'),
576 size: prettierBytes(maxFileSize)
577 }));
578 }
579 } // We can't check minFileSize if the size is unknown.
580
581
582 if (minFileSize && file.size != null) {
583 if (file.size < minFileSize) {
584 throw new RestrictionError(this.i18n('inferiorSize', {
585 size: prettierBytes(minFileSize)
586 }));
587 }
588 }
589 }
590 /**
591 * Check if minNumberOfFiles restriction is reached before uploading.
592 *
593 * @private
594 */
595 ;
596
597 _proto._checkMinNumberOfFiles = function _checkMinNumberOfFiles(files) {
598 var minNumberOfFiles = this.opts.restrictions.minNumberOfFiles;
599
600 if (Object.keys(files).length < minNumberOfFiles) {
601 throw new RestrictionError("" + this.i18n('youHaveToAtLeastSelectX', {
602 smart_count: minNumberOfFiles
603 }));
604 }
605 }
606 /**
607 * Logs an error, sets Informer message, then throws the error.
608 * Emits a 'restriction-failed' event if it’s a restriction error
609 *
610 * @param {object | string} err — Error object or plain string message
611 * @param {object} [options]
612 * @param {boolean} [options.showInformer=true] — Sometimes developer might want to show Informer manually
613 * @param {object} [options.file=null] — File object used to emit the restriction error
614 * @param {boolean} [options.throwErr=true] — Errors shouldn’t be thrown, for example, in `upload-error` event
615 * @private
616 */
617 ;
618
619 _proto._showOrLogErrorAndThrow = function _showOrLogErrorAndThrow(err, _temp) {
620 var _ref = _temp === void 0 ? {} : _temp,
621 _ref$showInformer = _ref.showInformer,
622 showInformer = _ref$showInformer === void 0 ? true : _ref$showInformer,
623 _ref$file = _ref.file,
624 file = _ref$file === void 0 ? null : _ref$file,
625 _ref$throwErr = _ref.throwErr,
626 throwErr = _ref$throwErr === void 0 ? true : _ref$throwErr;
627
628 var message = typeof err === 'object' ? err.message : err;
629 var details = typeof err === 'object' && err.details ? err.details : ''; // Restriction errors should be logged, but not as errors,
630 // as they are expected and shown in the UI.
631
632 var logMessageWithDetails = message;
633
634 if (details) {
635 logMessageWithDetails += " " + details;
636 }
637
638 if (err.isRestriction) {
639 this.log(logMessageWithDetails);
640 this.emit('restriction-failed', file, err);
641 } else {
642 this.log(logMessageWithDetails, 'error');
643 } // Sometimes informer has to be shown manually by the developer,
644 // for example, in `onBeforeFileAdded`.
645
646
647 if (showInformer) {
648 this.info({
649 message: message,
650 details: details
651 }, 'error', this.opts.infoTimeout);
652 }
653
654 if (throwErr) {
655 throw typeof err === 'object' ? err : new Error(err);
656 }
657 };
658
659 _proto._assertNewUploadAllowed = function _assertNewUploadAllowed(file) {
660 var _this$getState2 = this.getState(),
661 allowNewUpload = _this$getState2.allowNewUpload;
662
663 if (allowNewUpload === false) {
664 this._showOrLogErrorAndThrow(new RestrictionError(this.i18n('noNewAlreadyUploading')), {
665 file: file
666 });
667 }
668 }
669 /**
670 * Create a file state object based on user-provided `addFile()` options.
671 *
672 * Note this is extremely side-effectful and should only be done when a file state object will be added to state immediately afterward!
673 *
674 * The `files` value is passed in because it may be updated by the caller without updating the store.
675 */
676 ;
677
678 _proto._checkAndCreateFileStateObject = function _checkAndCreateFileStateObject(files, file) {
679 var fileType = getFileType(file);
680 file.type = fileType;
681 var onBeforeFileAddedResult = this.opts.onBeforeFileAdded(file, files);
682
683 if (onBeforeFileAddedResult === false) {
684 // Don’t show UI info for this error, as it should be done by the developer
685 this._showOrLogErrorAndThrow(new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.'), {
686 showInformer: false,
687 file: file
688 });
689 }
690
691 if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult) {
692 file = onBeforeFileAddedResult;
693 }
694
695 var fileName;
696
697 if (file.name) {
698 fileName = file.name;
699 } else if (fileType.split('/')[0] === 'image') {
700 fileName = fileType.split('/')[0] + "." + fileType.split('/')[1];
701 } else {
702 fileName = 'noname';
703 }
704
705 var fileExtension = getFileNameAndExtension(fileName).extension;
706 var isRemote = file.isRemote || false;
707 var fileID = generateFileID(file);
708
709 if (files[fileID]) {
710 this._showOrLogErrorAndThrow(new RestrictionError(this.i18n('noDuplicates', {
711 fileName: fileName
712 })), {
713 file: file
714 });
715 }
716
717 var meta = file.meta || {};
718 meta.name = fileName;
719 meta.type = fileType; // `null` means the size is unknown.
720
721 var size = isFinite(file.data.size) ? file.data.size : null;
722 var newFile = {
723 source: file.source || '',
724 id: fileID,
725 name: fileName,
726 extension: fileExtension || '',
727 meta: _extends({}, this.getState().meta, meta),
728 type: fileType,
729 data: file.data,
730 progress: {
731 percentage: 0,
732 bytesUploaded: 0,
733 bytesTotal: size,
734 uploadComplete: false,
735 uploadStarted: null
736 },
737 size: size,
738 isRemote: isRemote,
739 remote: file.remote || '',
740 preview: file.preview
741 };
742
743 try {
744 var filesArray = Object.keys(files).map(function (i) {
745 return files[i];
746 });
747
748 this._checkRestrictions(newFile, filesArray);
749 } catch (err) {
750 this._showOrLogErrorAndThrow(err, {
751 file: newFile
752 });
753 }
754
755 return newFile;
756 } // Schedule an upload if `autoProceed` is enabled.
757 ;
758
759 _proto._startIfAutoProceed = function _startIfAutoProceed() {
760 var _this3 = this;
761
762 if (this.opts.autoProceed && !this.scheduledAutoProceed) {
763 this.scheduledAutoProceed = setTimeout(function () {
764 _this3.scheduledAutoProceed = null;
765
766 _this3.upload().catch(function (err) {
767 if (!err.isRestriction) {
768 _this3.log(err.stack || err.message || err);
769 }
770 });
771 }, 4);
772 }
773 }
774 /**
775 * Add a new file to `state.files`. This will run `onBeforeFileAdded`,
776 * try to guess file type in a clever way, check file against restrictions,
777 * and start an upload if `autoProceed === true`.
778 *
779 * @param {object} file object to add
780 * @returns {string} id for the added file
781 */
782 ;
783
784 _proto.addFile = function addFile(file) {
785 var _extends3;
786
787 this._assertNewUploadAllowed(file);
788
789 var _this$getState3 = this.getState(),
790 files = _this$getState3.files;
791
792 var newFile = this._checkAndCreateFileStateObject(files, file);
793
794 this.setState({
795 files: _extends({}, files, (_extends3 = {}, _extends3[newFile.id] = newFile, _extends3))
796 });
797 this.emit('file-added', newFile);
798 this.emit('files-added', [newFile]);
799 this.log("Added file: " + newFile.name + ", " + newFile.id + ", mime type: " + newFile.type);
800
801 this._startIfAutoProceed();
802
803 return newFile.id;
804 }
805 /**
806 * Add multiple files to `state.files`. See the `addFile()` documentation.
807 *
808 * If an error occurs while adding a file, it is logged and the user is notified. This is good for UI plugins, but not for programmatic use. Programmatic users should usually still use `addFile()` on individual files.
809 */
810 ;
811
812 _proto.addFiles = function addFiles(fileDescriptors) {
813 var _this4 = this;
814
815 this._assertNewUploadAllowed(); // create a copy of the files object only once
816
817
818 var files = _extends({}, this.getState().files);
819
820 var newFiles = [];
821 var errors = [];
822
823 for (var i = 0; i < fileDescriptors.length; i++) {
824 try {
825 var newFile = this._checkAndCreateFileStateObject(files, fileDescriptors[i]);
826
827 newFiles.push(newFile);
828 files[newFile.id] = newFile;
829 } catch (err) {
830 if (!err.isRestriction) {
831 errors.push(err);
832 }
833 }
834 }
835
836 this.setState({
837 files: files
838 });
839 newFiles.forEach(function (newFile) {
840 _this4.emit('file-added', newFile);
841 });
842 this.emit('files-added', newFiles);
843
844 if (newFiles.length > 5) {
845 this.log("Added batch of " + newFiles.length + " files");
846 } else {
847 Object.keys(newFiles).forEach(function (fileID) {
848 _this4.log("Added file: " + newFiles[fileID].name + "\n id: " + newFiles[fileID].id + "\n type: " + newFiles[fileID].type);
849 });
850 }
851
852 if (newFiles.length > 0) {
853 this._startIfAutoProceed();
854 }
855
856 if (errors.length > 0) {
857 var message = 'Multiple errors occurred while adding files:\n';
858 errors.forEach(function (subError) {
859 message += "\n * " + subError.message;
860 });
861 this.info({
862 message: this.i18n('addBulkFilesFailed', {
863 smart_count: errors.length
864 }),
865 details: message
866 }, 'error', this.opts.infoTimeout);
867
868 if (typeof AggregateError === 'function') {
869 throw new AggregateError(errors, message);
870 } else {
871 var err = new Error(message);
872 err.errors = errors;
873 throw err;
874 }
875 }
876 };
877
878 _proto.removeFiles = function removeFiles(fileIDs, reason) {
879 var _this5 = this;
880
881 var _this$getState4 = this.getState(),
882 files = _this$getState4.files,
883 currentUploads = _this$getState4.currentUploads;
884
885 var updatedFiles = _extends({}, files);
886
887 var updatedUploads = _extends({}, currentUploads);
888
889 var removedFiles = Object.create(null);
890 fileIDs.forEach(function (fileID) {
891 if (files[fileID]) {
892 removedFiles[fileID] = files[fileID];
893 delete updatedFiles[fileID];
894 }
895 }); // Remove files from the `fileIDs` list in each upload.
896
897 function fileIsNotRemoved(uploadFileID) {
898 return removedFiles[uploadFileID] === undefined;
899 }
900
901 var uploadsToRemove = [];
902 Object.keys(updatedUploads).forEach(function (uploadID) {
903 var newFileIDs = currentUploads[uploadID].fileIDs.filter(fileIsNotRemoved); // Remove the upload if no files are associated with it anymore.
904
905 if (newFileIDs.length === 0) {
906 uploadsToRemove.push(uploadID);
907 return;
908 }
909
910 updatedUploads[uploadID] = _extends({}, currentUploads[uploadID], {
911 fileIDs: newFileIDs
912 });
913 });
914 uploadsToRemove.forEach(function (uploadID) {
915 delete updatedUploads[uploadID];
916 });
917 var stateUpdate = {
918 currentUploads: updatedUploads,
919 files: updatedFiles
920 }; // If all files were removed - allow new uploads!
921
922 if (Object.keys(updatedFiles).length === 0) {
923 stateUpdate.allowNewUpload = true;
924 stateUpdate.error = null;
925 }
926
927 this.setState(stateUpdate);
928
929 this._calculateTotalProgress();
930
931 var removedFileIDs = Object.keys(removedFiles);
932 removedFileIDs.forEach(function (fileID) {
933 _this5.emit('file-removed', removedFiles[fileID], reason);
934 });
935
936 if (removedFileIDs.length > 5) {
937 this.log("Removed " + removedFileIDs.length + " files");
938 } else {
939 this.log("Removed files: " + removedFileIDs.join(', '));
940 }
941 };
942
943 _proto.removeFile = function removeFile(fileID, reason) {
944 if (reason === void 0) {
945 reason = null;
946 }
947
948 this.removeFiles([fileID], reason);
949 };
950
951 _proto.pauseResume = function pauseResume(fileID) {
952 if (!this.getState().capabilities.resumableUploads || this.getFile(fileID).uploadComplete) {
953 return;
954 }
955
956 var wasPaused = this.getFile(fileID).isPaused || false;
957 var isPaused = !wasPaused;
958 this.setFileState(fileID, {
959 isPaused: isPaused
960 });
961 this.emit('upload-pause', fileID, isPaused);
962 return isPaused;
963 };
964
965 _proto.pauseAll = function pauseAll() {
966 var updatedFiles = _extends({}, this.getState().files);
967
968 var inProgressUpdatedFiles = Object.keys(updatedFiles).filter(function (file) {
969 return !updatedFiles[file].progress.uploadComplete && updatedFiles[file].progress.uploadStarted;
970 });
971 inProgressUpdatedFiles.forEach(function (file) {
972 var updatedFile = _extends({}, updatedFiles[file], {
973 isPaused: true
974 });
975
976 updatedFiles[file] = updatedFile;
977 });
978 this.setState({
979 files: updatedFiles
980 });
981 this.emit('pause-all');
982 };
983
984 _proto.resumeAll = function resumeAll() {
985 var updatedFiles = _extends({}, this.getState().files);
986
987 var inProgressUpdatedFiles = Object.keys(updatedFiles).filter(function (file) {
988 return !updatedFiles[file].progress.uploadComplete && updatedFiles[file].progress.uploadStarted;
989 });
990 inProgressUpdatedFiles.forEach(function (file) {
991 var updatedFile = _extends({}, updatedFiles[file], {
992 isPaused: false,
993 error: null
994 });
995
996 updatedFiles[file] = updatedFile;
997 });
998 this.setState({
999 files: updatedFiles
1000 });
1001 this.emit('resume-all');
1002 };
1003
1004 _proto.retryAll = function retryAll() {
1005 var updatedFiles = _extends({}, this.getState().files);
1006
1007 var filesToRetry = Object.keys(updatedFiles).filter(function (file) {
1008 return updatedFiles[file].error;
1009 });
1010 filesToRetry.forEach(function (file) {
1011 var updatedFile = _extends({}, updatedFiles[file], {
1012 isPaused: false,
1013 error: null
1014 });
1015
1016 updatedFiles[file] = updatedFile;
1017 });
1018 this.setState({
1019 files: updatedFiles,
1020 error: null
1021 });
1022 this.emit('retry-all', filesToRetry);
1023
1024 if (filesToRetry.length === 0) {
1025 return Promise.resolve({
1026 successful: [],
1027 failed: []
1028 });
1029 }
1030
1031 var uploadID = this._createUpload(filesToRetry, {
1032 forceAllowNewUpload: true // create new upload even if allowNewUpload: false
1033
1034 });
1035
1036 return this._runUpload(uploadID);
1037 };
1038
1039 _proto.cancelAll = function cancelAll() {
1040 this.emit('cancel-all');
1041
1042 var _this$getState5 = this.getState(),
1043 files = _this$getState5.files;
1044
1045 var fileIDs = Object.keys(files);
1046
1047 if (fileIDs.length) {
1048 this.removeFiles(fileIDs, 'cancel-all');
1049 }
1050
1051 this.setState({
1052 totalProgress: 0,
1053 error: null
1054 });
1055 };
1056
1057 _proto.retryUpload = function retryUpload(fileID) {
1058 this.setFileState(fileID, {
1059 error: null,
1060 isPaused: false
1061 });
1062 this.emit('upload-retry', fileID);
1063
1064 var uploadID = this._createUpload([fileID], {
1065 forceAllowNewUpload: true // create new upload even if allowNewUpload: false
1066
1067 });
1068
1069 return this._runUpload(uploadID);
1070 };
1071
1072 _proto.reset = function reset() {
1073 this.cancelAll();
1074 };
1075
1076 _proto.logout = function logout() {
1077 this.iteratePlugins(function (plugin) {
1078 if (plugin.provider && plugin.provider.logout) {
1079 plugin.provider.logout();
1080 }
1081 });
1082 };
1083
1084 _proto._calculateProgress = function _calculateProgress(file, data) {
1085 if (!this.getFile(file.id)) {
1086 this.log("Not setting progress for a file that has been removed: " + file.id);
1087 return;
1088 } // bytesTotal may be null or zero; in that case we can't divide by it
1089
1090
1091 var canHavePercentage = isFinite(data.bytesTotal) && data.bytesTotal > 0;
1092 this.setFileState(file.id, {
1093 progress: _extends({}, this.getFile(file.id).progress, {
1094 bytesUploaded: data.bytesUploaded,
1095 bytesTotal: data.bytesTotal,
1096 percentage: canHavePercentage // TODO(goto-bus-stop) flooring this should probably be the choice of the UI?
1097 // we get more accurate calculations if we don't round this at all.
1098 ? Math.round(data.bytesUploaded / data.bytesTotal * 100) : 0
1099 })
1100 });
1101
1102 this._calculateTotalProgress();
1103 };
1104
1105 _proto._calculateTotalProgress = function _calculateTotalProgress() {
1106 // calculate total progress, using the number of files currently uploading,
1107 // multiplied by 100 and the summ of individual progress of each file
1108 var files = this.getFiles();
1109 var inProgress = files.filter(function (file) {
1110 return file.progress.uploadStarted || file.progress.preprocess || file.progress.postprocess;
1111 });
1112
1113 if (inProgress.length === 0) {
1114 this.emit('progress', 0);
1115 this.setState({
1116 totalProgress: 0
1117 });
1118 return;
1119 }
1120
1121 var sizedFiles = inProgress.filter(function (file) {
1122 return file.progress.bytesTotal != null;
1123 });
1124 var unsizedFiles = inProgress.filter(function (file) {
1125 return file.progress.bytesTotal == null;
1126 });
1127
1128 if (sizedFiles.length === 0) {
1129 var progressMax = inProgress.length * 100;
1130 var currentProgress = unsizedFiles.reduce(function (acc, file) {
1131 return acc + file.progress.percentage;
1132 }, 0);
1133
1134 var _totalProgress = Math.round(currentProgress / progressMax * 100);
1135
1136 this.setState({
1137 totalProgress: _totalProgress
1138 });
1139 return;
1140 }
1141
1142 var totalSize = sizedFiles.reduce(function (acc, file) {
1143 return acc + file.progress.bytesTotal;
1144 }, 0);
1145 var averageSize = totalSize / sizedFiles.length;
1146 totalSize += averageSize * unsizedFiles.length;
1147 var uploadedSize = 0;
1148 sizedFiles.forEach(function (file) {
1149 uploadedSize += file.progress.bytesUploaded;
1150 });
1151 unsizedFiles.forEach(function (file) {
1152 uploadedSize += averageSize * (file.progress.percentage || 0) / 100;
1153 });
1154 var totalProgress = totalSize === 0 ? 0 : Math.round(uploadedSize / totalSize * 100); // hot fix, because:
1155 // uploadedSize ended up larger than totalSize, resulting in 1325% total
1156
1157 if (totalProgress > 100) {
1158 totalProgress = 100;
1159 }
1160
1161 this.setState({
1162 totalProgress: totalProgress
1163 });
1164 this.emit('progress', totalProgress);
1165 }
1166 /**
1167 * Registers listeners for all global actions, like:
1168 * `error`, `file-removed`, `upload-progress`
1169 */
1170 ;
1171
1172 _proto._addListeners = function _addListeners() {
1173 var _this6 = this;
1174
1175 this.on('error', function (error) {
1176 var errorMsg = 'Unknown error';
1177
1178 if (error.message) {
1179 errorMsg = error.message;
1180 }
1181
1182 if (error.details) {
1183 errorMsg += " " + error.details;
1184 }
1185
1186 _this6.setState({
1187 error: errorMsg
1188 });
1189 });
1190 this.on('upload-error', function (file, error, response) {
1191 var errorMsg = 'Unknown error';
1192
1193 if (error.message) {
1194 errorMsg = error.message;
1195 }
1196
1197 if (error.details) {
1198 errorMsg += " " + error.details;
1199 }
1200
1201 _this6.setFileState(file.id, {
1202 error: errorMsg,
1203 response: response
1204 });
1205
1206 _this6.setState({
1207 error: error.message
1208 });
1209
1210 if (typeof error === 'object' && error.message) {
1211 var newError = new Error(error.message);
1212 newError.details = error.message;
1213
1214 if (error.details) {
1215 newError.details += " " + error.details;
1216 }
1217
1218 newError.message = _this6.i18n('failedToUpload', {
1219 file: file.name
1220 });
1221
1222 _this6._showOrLogErrorAndThrow(newError, {
1223 throwErr: false
1224 });
1225 } else {
1226 _this6._showOrLogErrorAndThrow(error, {
1227 throwErr: false
1228 });
1229 }
1230 });
1231 this.on('upload', function () {
1232 _this6.setState({
1233 error: null
1234 });
1235 });
1236 this.on('upload-started', function (file, upload) {
1237 if (!_this6.getFile(file.id)) {
1238 _this6.log("Not setting progress for a file that has been removed: " + file.id);
1239
1240 return;
1241 }
1242
1243 _this6.setFileState(file.id, {
1244 progress: {
1245 uploadStarted: Date.now(),
1246 uploadComplete: false,
1247 percentage: 0,
1248 bytesUploaded: 0,
1249 bytesTotal: file.size
1250 }
1251 });
1252 });
1253 this.on('upload-progress', this._calculateProgress);
1254 this.on('upload-success', function (file, uploadResp) {
1255 if (!_this6.getFile(file.id)) {
1256 _this6.log("Not setting progress for a file that has been removed: " + file.id);
1257
1258 return;
1259 }
1260
1261 var currentProgress = _this6.getFile(file.id).progress;
1262
1263 _this6.setFileState(file.id, {
1264 progress: _extends({}, currentProgress, {
1265 postprocess: _this6.postProcessors.length > 0 ? {
1266 mode: 'indeterminate'
1267 } : null,
1268 uploadComplete: true,
1269 percentage: 100,
1270 bytesUploaded: currentProgress.bytesTotal
1271 }),
1272 response: uploadResp,
1273 uploadURL: uploadResp.uploadURL,
1274 isPaused: false
1275 });
1276
1277 _this6._calculateTotalProgress();
1278 });
1279 this.on('preprocess-progress', function (file, progress) {
1280 if (!_this6.getFile(file.id)) {
1281 _this6.log("Not setting progress for a file that has been removed: " + file.id);
1282
1283 return;
1284 }
1285
1286 _this6.setFileState(file.id, {
1287 progress: _extends({}, _this6.getFile(file.id).progress, {
1288 preprocess: progress
1289 })
1290 });
1291 });
1292 this.on('preprocess-complete', function (file) {
1293 if (!_this6.getFile(file.id)) {
1294 _this6.log("Not setting progress for a file that has been removed: " + file.id);
1295
1296 return;
1297 }
1298
1299 var files = _extends({}, _this6.getState().files);
1300
1301 files[file.id] = _extends({}, files[file.id], {
1302 progress: _extends({}, files[file.id].progress)
1303 });
1304 delete files[file.id].progress.preprocess;
1305
1306 _this6.setState({
1307 files: files
1308 });
1309 });
1310 this.on('postprocess-progress', function (file, progress) {
1311 if (!_this6.getFile(file.id)) {
1312 _this6.log("Not setting progress for a file that has been removed: " + file.id);
1313
1314 return;
1315 }
1316
1317 _this6.setFileState(file.id, {
1318 progress: _extends({}, _this6.getState().files[file.id].progress, {
1319 postprocess: progress
1320 })
1321 });
1322 });
1323 this.on('postprocess-complete', function (file) {
1324 if (!_this6.getFile(file.id)) {
1325 _this6.log("Not setting progress for a file that has been removed: " + file.id);
1326
1327 return;
1328 }
1329
1330 var files = _extends({}, _this6.getState().files);
1331
1332 files[file.id] = _extends({}, files[file.id], {
1333 progress: _extends({}, files[file.id].progress)
1334 });
1335 delete files[file.id].progress.postprocess; // TODO should we set some kind of `fullyComplete` property on the file object
1336 // so it's easier to see that the file is upload…fully complete…rather than
1337 // what we have to do now (`uploadComplete && !postprocess`)
1338
1339 _this6.setState({
1340 files: files
1341 });
1342 });
1343 this.on('restored', function () {
1344 // Files may have changed--ensure progress is still accurate.
1345 _this6._calculateTotalProgress();
1346 }); // show informer if offline
1347
1348 if (typeof window !== 'undefined' && window.addEventListener) {
1349 window.addEventListener('online', function () {
1350 return _this6.updateOnlineStatus();
1351 });
1352 window.addEventListener('offline', function () {
1353 return _this6.updateOnlineStatus();
1354 });
1355 setTimeout(function () {
1356 return _this6.updateOnlineStatus();
1357 }, 3000);
1358 }
1359 };
1360
1361 _proto.updateOnlineStatus = function updateOnlineStatus() {
1362 var online = typeof window.navigator.onLine !== 'undefined' ? window.navigator.onLine : true;
1363
1364 if (!online) {
1365 this.emit('is-offline');
1366 this.info(this.i18n('noInternetConnection'), 'error', 0);
1367 this.wasOffline = true;
1368 } else {
1369 this.emit('is-online');
1370
1371 if (this.wasOffline) {
1372 this.emit('back-online');
1373 this.info(this.i18n('connectedToInternet'), 'success', 3000);
1374 this.wasOffline = false;
1375 }
1376 }
1377 };
1378
1379 _proto.getID = function getID() {
1380 return this.opts.id;
1381 }
1382 /**
1383 * Registers a plugin with Core.
1384 *
1385 * @param {object} Plugin object
1386 * @param {object} [opts] object with options to be passed to Plugin
1387 * @returns {object} self for chaining
1388 */
1389 ;
1390
1391 _proto.use = function use(Plugin, opts) {
1392 if (typeof Plugin !== 'function') {
1393 var msg = "Expected a plugin class, but got " + (Plugin === null ? 'null' : typeof Plugin) + "." + ' Please verify that the plugin was imported and spelled correctly.';
1394 throw new TypeError(msg);
1395 } // Instantiate
1396
1397
1398 var plugin = new Plugin(this, opts);
1399 var pluginId = plugin.id;
1400 this.plugins[plugin.type] = this.plugins[plugin.type] || [];
1401
1402 if (!pluginId) {
1403 throw new Error('Your plugin must have an id');
1404 }
1405
1406 if (!plugin.type) {
1407 throw new Error('Your plugin must have a type');
1408 }
1409
1410 var existsPluginAlready = this.getPlugin(pluginId);
1411
1412 if (existsPluginAlready) {
1413 var _msg = "Already found a plugin named '" + existsPluginAlready.id + "'. " + ("Tried to use: '" + pluginId + "'.\n") + 'Uppy plugins must have unique `id` options. See https://uppy.io/docs/plugins/#id.';
1414
1415 throw new Error(_msg);
1416 }
1417
1418 if (Plugin.VERSION) {
1419 this.log("Using " + pluginId + " v" + Plugin.VERSION);
1420 }
1421
1422 this.plugins[plugin.type].push(plugin);
1423 plugin.install();
1424 return this;
1425 }
1426 /**
1427 * Find one Plugin by name.
1428 *
1429 * @param {string} id plugin id
1430 * @returns {object|boolean}
1431 */
1432 ;
1433
1434 _proto.getPlugin = function getPlugin(id) {
1435 var foundPlugin = null;
1436 this.iteratePlugins(function (plugin) {
1437 if (plugin.id === id) {
1438 foundPlugin = plugin;
1439 return false;
1440 }
1441 });
1442 return foundPlugin;
1443 }
1444 /**
1445 * Iterate through all `use`d plugins.
1446 *
1447 * @param {Function} method that will be run on each plugin
1448 */
1449 ;
1450
1451 _proto.iteratePlugins = function iteratePlugins(method) {
1452 var _this7 = this;
1453
1454 Object.keys(this.plugins).forEach(function (pluginType) {
1455 _this7.plugins[pluginType].forEach(method);
1456 });
1457 }
1458 /**
1459 * Uninstall and remove a plugin.
1460 *
1461 * @param {object} instance The plugin instance to remove.
1462 */
1463 ;
1464
1465 _proto.removePlugin = function removePlugin(instance) {
1466 var _extends4;
1467
1468 this.log("Removing plugin " + instance.id);
1469 this.emit('plugin-remove', instance);
1470
1471 if (instance.uninstall) {
1472 instance.uninstall();
1473 }
1474
1475 var list = this.plugins[instance.type].slice(); // list.indexOf failed here, because Vue3 converted the plugin instance
1476 // to a Proxy object, which failed the strict comparison test:
1477 // obj !== objProxy
1478
1479 var index = findIndex(list, function (item) {
1480 return item.id === instance.id;
1481 });
1482
1483 if (index !== -1) {
1484 list.splice(index, 1);
1485 this.plugins[instance.type] = list;
1486 }
1487
1488 var state = this.getState();
1489 var updatedState = {
1490 plugins: _extends({}, state.plugins, (_extends4 = {}, _extends4[instance.id] = undefined, _extends4))
1491 };
1492 this.setState(updatedState);
1493 }
1494 /**
1495 * Uninstall all plugins and close down this Uppy instance.
1496 */
1497 ;
1498
1499 _proto.close = function close() {
1500 var _this8 = this;
1501
1502 this.log("Closing Uppy instance " + this.opts.id + ": removing all files and uninstalling plugins");
1503 this.reset();
1504
1505 this._storeUnsubscribe();
1506
1507 this.iteratePlugins(function (plugin) {
1508 _this8.removePlugin(plugin);
1509 });
1510 }
1511 /**
1512 * Set info message in `state.info`, so that UI plugins like `Informer`
1513 * can display the message.
1514 *
1515 * @param {string | object} message Message to be displayed by the informer
1516 * @param {string} [type]
1517 * @param {number} [duration]
1518 */
1519 ;
1520
1521 _proto.info = function info(message, type, duration) {
1522 if (type === void 0) {
1523 type = 'info';
1524 }
1525
1526 if (duration === void 0) {
1527 duration = 3000;
1528 }
1529
1530 var isComplexMessage = typeof message === 'object';
1531 this.setState({
1532 info: {
1533 isHidden: false,
1534 type: type,
1535 message: isComplexMessage ? message.message : message,
1536 details: isComplexMessage ? message.details : null
1537 }
1538 });
1539 this.emit('info-visible');
1540 clearTimeout(this.infoTimeoutID);
1541
1542 if (duration === 0) {
1543 this.infoTimeoutID = undefined;
1544 return;
1545 } // hide the informer after `duration` milliseconds
1546
1547
1548 this.infoTimeoutID = setTimeout(this.hideInfo, duration);
1549 };
1550
1551 _proto.hideInfo = function hideInfo() {
1552 var newInfo = _extends({}, this.getState().info, {
1553 isHidden: true
1554 });
1555
1556 this.setState({
1557 info: newInfo
1558 });
1559 this.emit('info-hidden');
1560 }
1561 /**
1562 * Passes messages to a function, provided in `opts.logger`.
1563 * If `opts.logger: Uppy.debugLogger` or `opts.debug: true`, logs to the browser console.
1564 *
1565 * @param {string|object} message to log
1566 * @param {string} [type] optional `error` or `warning`
1567 */
1568 ;
1569
1570 _proto.log = function log(message, type) {
1571 var logger = this.opts.logger;
1572
1573 switch (type) {
1574 case 'error':
1575 logger.error(message);
1576 break;
1577
1578 case 'warning':
1579 logger.warn(message);
1580 break;
1581
1582 default:
1583 logger.debug(message);
1584 break;
1585 }
1586 }
1587 /**
1588 * Obsolete, event listeners are now added in the constructor.
1589 */
1590 ;
1591
1592 _proto.run = function run() {
1593 this.log('Calling run() is no longer necessary.', 'warning');
1594 return this;
1595 }
1596 /**
1597 * Restore an upload by its ID.
1598 */
1599 ;
1600
1601 _proto.restore = function restore(uploadID) {
1602 this.log("Core: attempting to restore upload \"" + uploadID + "\"");
1603
1604 if (!this.getState().currentUploads[uploadID]) {
1605 this._removeUpload(uploadID);
1606
1607 return Promise.reject(new Error('Nonexistent upload'));
1608 }
1609
1610 return this._runUpload(uploadID);
1611 }
1612 /**
1613 * Create an upload for a bunch of files.
1614 *
1615 * @param {Array<string>} fileIDs File IDs to include in this upload.
1616 * @returns {string} ID of this upload.
1617 */
1618 ;
1619
1620 _proto._createUpload = function _createUpload(fileIDs, opts) {
1621 var _extends5;
1622
1623 if (opts === void 0) {
1624 opts = {};
1625 }
1626
1627 var _opts = opts,
1628 _opts$forceAllowNewUp = _opts.forceAllowNewUpload,
1629 forceAllowNewUpload = _opts$forceAllowNewUp === void 0 ? false : _opts$forceAllowNewUp;
1630
1631 var _this$getState6 = this.getState(),
1632 allowNewUpload = _this$getState6.allowNewUpload,
1633 currentUploads = _this$getState6.currentUploads;
1634
1635 if (!allowNewUpload && !forceAllowNewUpload) {
1636 throw new Error('Cannot create a new upload: already uploading.');
1637 }
1638
1639 var uploadID = cuid();
1640 this.emit('upload', {
1641 id: uploadID,
1642 fileIDs: fileIDs
1643 });
1644 this.setState({
1645 allowNewUpload: this.opts.allowMultipleUploads !== false,
1646 currentUploads: _extends({}, currentUploads, (_extends5 = {}, _extends5[uploadID] = {
1647 fileIDs: fileIDs,
1648 step: 0,
1649 result: {}
1650 }, _extends5))
1651 });
1652 return uploadID;
1653 };
1654
1655 _proto._getUpload = function _getUpload(uploadID) {
1656 var _this$getState7 = this.getState(),
1657 currentUploads = _this$getState7.currentUploads;
1658
1659 return currentUploads[uploadID];
1660 }
1661 /**
1662 * Add data to an upload's result object.
1663 *
1664 * @param {string} uploadID The ID of the upload.
1665 * @param {object} data Data properties to add to the result object.
1666 */
1667 ;
1668
1669 _proto.addResultData = function addResultData(uploadID, data) {
1670 var _extends6;
1671
1672 if (!this._getUpload(uploadID)) {
1673 this.log("Not setting result for an upload that has been removed: " + uploadID);
1674 return;
1675 }
1676
1677 var currentUploads = this.getState().currentUploads;
1678
1679 var currentUpload = _extends({}, currentUploads[uploadID], {
1680 result: _extends({}, currentUploads[uploadID].result, data)
1681 });
1682
1683 this.setState({
1684 currentUploads: _extends({}, currentUploads, (_extends6 = {}, _extends6[uploadID] = currentUpload, _extends6))
1685 });
1686 }
1687 /**
1688 * Remove an upload, eg. if it has been canceled or completed.
1689 *
1690 * @param {string} uploadID The ID of the upload.
1691 */
1692 ;
1693
1694 _proto._removeUpload = function _removeUpload(uploadID) {
1695 var currentUploads = _extends({}, this.getState().currentUploads);
1696
1697 delete currentUploads[uploadID];
1698 this.setState({
1699 currentUploads: currentUploads
1700 });
1701 }
1702 /**
1703 * Run an upload. This picks up where it left off in case the upload is being restored.
1704 *
1705 * @private
1706 */
1707 ;
1708
1709 _proto._runUpload = function _runUpload(uploadID) {
1710 var _this9 = this;
1711
1712 var uploadData = this.getState().currentUploads[uploadID];
1713 var restoreStep = uploadData.step;
1714 var steps = [].concat(this.preProcessors, this.uploaders, this.postProcessors);
1715 var lastStep = Promise.resolve();
1716 steps.forEach(function (fn, step) {
1717 // Skip this step if we are restoring and have already completed this step before.
1718 if (step < restoreStep) {
1719 return;
1720 }
1721
1722 lastStep = lastStep.then(function () {
1723 var _extends7;
1724
1725 var _this9$getState = _this9.getState(),
1726 currentUploads = _this9$getState.currentUploads;
1727
1728 var currentUpload = currentUploads[uploadID];
1729
1730 if (!currentUpload) {
1731 return;
1732 }
1733
1734 var updatedUpload = _extends({}, currentUpload, {
1735 step: step
1736 });
1737
1738 _this9.setState({
1739 currentUploads: _extends({}, currentUploads, (_extends7 = {}, _extends7[uploadID] = updatedUpload, _extends7))
1740 }); // TODO give this the `updatedUpload` object as its only parameter maybe?
1741 // Otherwise when more metadata may be added to the upload this would keep getting more parameters
1742
1743
1744 return fn(updatedUpload.fileIDs, uploadID);
1745 }).then(function (result) {
1746 return null;
1747 });
1748 }); // Not returning the `catch`ed promise, because we still want to return a rejected
1749 // promise from this method if the upload failed.
1750
1751 lastStep.catch(function (err) {
1752 _this9.emit('error', err, uploadID);
1753
1754 _this9._removeUpload(uploadID);
1755 });
1756 return lastStep.then(function () {
1757 // Set result data.
1758 var _this9$getState2 = _this9.getState(),
1759 currentUploads = _this9$getState2.currentUploads;
1760
1761 var currentUpload = currentUploads[uploadID];
1762
1763 if (!currentUpload) {
1764 return;
1765 } // Mark postprocessing step as complete if necessary; this addresses a case where we might get
1766 // stuck in the postprocessing UI while the upload is fully complete.
1767 // If the postprocessing steps do not do any work, they may not emit postprocessing events at
1768 // all, and never mark the postprocessing as complete. This is fine on its own but we
1769 // introduced code in the @uppy/core upload-success handler to prepare postprocessing progress
1770 // state if any postprocessors are registered. That is to avoid a "flash of completed state"
1771 // before the postprocessing plugins can emit events.
1772 //
1773 // So, just in case an upload with postprocessing plugins *has* completed *without* emitting
1774 // postprocessing completion, we do it instead.
1775
1776
1777 currentUpload.fileIDs.forEach(function (fileID) {
1778 var file = _this9.getFile(fileID);
1779
1780 if (file && file.progress.postprocess) {
1781 _this9.emit('postprocess-complete', file);
1782 }
1783 });
1784 var files = currentUpload.fileIDs.map(function (fileID) {
1785 return _this9.getFile(fileID);
1786 });
1787 var successful = files.filter(function (file) {
1788 return !file.error;
1789 });
1790 var failed = files.filter(function (file) {
1791 return file.error;
1792 });
1793
1794 _this9.addResultData(uploadID, {
1795 successful: successful,
1796 failed: failed,
1797 uploadID: uploadID
1798 });
1799 }).then(function () {
1800 // Emit completion events.
1801 // This is in a separate function so that the `currentUploads` variable
1802 // always refers to the latest state. In the handler right above it refers
1803 // to an outdated object without the `.result` property.
1804 var _this9$getState3 = _this9.getState(),
1805 currentUploads = _this9$getState3.currentUploads;
1806
1807 if (!currentUploads[uploadID]) {
1808 return;
1809 }
1810
1811 var currentUpload = currentUploads[uploadID];
1812 var result = currentUpload.result;
1813
1814 _this9.emit('complete', result);
1815
1816 _this9._removeUpload(uploadID);
1817
1818 return result;
1819 }).then(function (result) {
1820 if (result == null) {
1821 _this9.log("Not setting result for an upload that has been removed: " + uploadID);
1822 }
1823
1824 return result;
1825 });
1826 }
1827 /**
1828 * Start an upload for all the files that are not currently being uploaded.
1829 *
1830 * @returns {Promise}
1831 */
1832 ;
1833
1834 _proto.upload = function upload() {
1835 var _this10 = this;
1836
1837 if (!this.plugins.uploader) {
1838 this.log('No uploader type plugins are used', 'warning');
1839 }
1840
1841 var files = this.getState().files;
1842 var onBeforeUploadResult = this.opts.onBeforeUpload(files);
1843
1844 if (onBeforeUploadResult === false) {
1845 return Promise.reject(new Error('Not starting the upload because onBeforeUpload returned false'));
1846 }
1847
1848 if (onBeforeUploadResult && typeof onBeforeUploadResult === 'object') {
1849 files = onBeforeUploadResult; // Updating files in state, because uploader plugins receive file IDs,
1850 // and then fetch the actual file object from state
1851
1852 this.setState({
1853 files: files
1854 });
1855 }
1856
1857 return Promise.resolve().then(function () {
1858 return _this10._checkMinNumberOfFiles(files);
1859 }).catch(function (err) {
1860 _this10._showOrLogErrorAndThrow(err);
1861 }).then(function () {
1862 var _this10$getState = _this10.getState(),
1863 currentUploads = _this10$getState.currentUploads; // get a list of files that are currently assigned to uploads
1864
1865
1866 var currentlyUploadingFiles = Object.keys(currentUploads).reduce(function (prev, curr) {
1867 return prev.concat(currentUploads[curr].fileIDs);
1868 }, []);
1869 var waitingFileIDs = [];
1870 Object.keys(files).forEach(function (fileID) {
1871 var file = _this10.getFile(fileID); // if the file hasn't started uploading and hasn't already been assigned to an upload..
1872
1873
1874 if (!file.progress.uploadStarted && currentlyUploadingFiles.indexOf(fileID) === -1) {
1875 waitingFileIDs.push(file.id);
1876 }
1877 });
1878
1879 var uploadID = _this10._createUpload(waitingFileIDs);
1880
1881 return _this10._runUpload(uploadID);
1882 }).catch(function (err) {
1883 _this10._showOrLogErrorAndThrow(err, {
1884 showInformer: false
1885 });
1886 });
1887 };
1888
1889 _createClass(Uppy, [{
1890 key: "state",
1891 get: function get() {
1892 return this.getState();
1893 }
1894 }]);
1895
1896 return Uppy;
1897}();
1898
1899Uppy.VERSION = "1.18.1";
1900
1901module.exports = function (opts) {
1902 return new Uppy(opts);
1903}; // Expose class constructor.
1904
1905
1906module.exports.Uppy = Uppy;
1907module.exports.Plugin = Plugin;
1908module.exports.debugLogger = debugLogger;
\No newline at end of file