UNPKG

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