UNPKG

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