text: 'use strict';
  
  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
  
  var _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; };
  
  var _createClass = function () { function 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); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  var Utils = require('../core/Utils');
  var Translator = require('../core/Translator');
  var ee = require('namespace-emitter');
  var cuid = require('cuid');
  var throttle = require('lodash.throttle');
  var prettyBytes = require('prettier-bytes');
  var match = require('mime-match');
  var DefaultStore = require('../store/DefaultStore'
  // const deepFreeze = require('deep-freeze-strict')
  
  /**
   * Uppy Core module.
   * Manages plugins, state updates, acts as an event bus,
   * adds/removes files and metadata.
   *
   * @param {object} opts — Uppy options
   */
  );
  var Uppy = function () {
    function Uppy(opts) {
      var _this = this;
  
      _classCallCheck(this, Uppy);
  
      var defaultLocale = {
        strings: {
          youCanOnlyUploadX: {
            0: 'You can only upload %{smart_count} file',
            1: 'You can only upload %{smart_count} files'
          },
          youHaveToAtLeastSelectX: {
            0: 'You have to select at least %{smart_count} file',
            1: 'You have to select at least %{smart_count} files'
          },
          exceedsSize: 'This file exceeds maximum allowed size of',
          youCanOnlyUploadFileTypes: 'You can only upload:',
          uppyServerError: 'Connection with Uppy Server failed'
        }
  
        // set default options
      };var defaultOptions = {
        id: 'uppy',
        autoProceed: true,
        debug: false,
        restrictions: {
          maxFileSize: false,
          maxNumberOfFiles: false,
          minNumberOfFiles: false,
          allowedFileTypes: false
        },
        meta: {},
        onBeforeFileAdded: function onBeforeFileAdded(currentFile, files) {
          return Promise.resolve();
        },
        onBeforeUpload: function onBeforeUpload(files, done) {
          return Promise.resolve();
        },
        locale: defaultLocale,
        store: new DefaultStore(),
        thumbnailGeneration: true
  
        // Merge default options with the ones set by user
      };this.opts = _extends({}, defaultOptions, opts);
  
      this.locale = _extends({}, defaultLocale, this.opts.locale);
      this.locale.strings = _extends({}, defaultLocale.strings, this.opts.locale.strings
  
      // i18n
      );this.translator = new Translator({ locale: this.locale });
      this.i18n = this.translator.translate.bind(this.translator
  
      // Container for different types of plugins
      );this.plugins = {};
  
      this.translator = new Translator({ locale: this.opts.locale });
      this.i18n = this.translator.translate.bind(this.translator);
      this.getState = this.getState.bind(this);
      this.getPlugin = this.getPlugin.bind(this);
      this.setFileMeta = this.setFileMeta.bind(this);
      this.setFileState = this.setFileState.bind(this);
      this.log = this.log.bind(this);
      this.info = this.info.bind(this);
      this.hideInfo = this.hideInfo.bind(this);
      this.addFile = this.addFile.bind(this);
      this.removeFile = this.removeFile.bind(this);
      this.pauseResume = this.pauseResume.bind(this);
      this._calculateProgress = this._calculateProgress.bind(this);
      this.updateOnlineStatus = this.updateOnlineStatus.bind(this);
      this.resetProgress = this.resetProgress.bind(this);
  
      this.pauseAll = this.pauseAll.bind(this);
      this.resumeAll = this.resumeAll.bind(this);
      this.retryAll = this.retryAll.bind(this);
      this.cancelAll = this.cancelAll.bind(this);
      this.retryUpload = this.retryUpload.bind(this);
      this.upload = this.upload.bind(this);
  
      this.emitter = ee();
      this.on = this.emitter.on.bind(this.emitter);
      this.off = this.emitter.off.bind(this.emitter);
      this.once = this.emitter.once.bind(this.emitter);
      this.emit = this.emitter.emit.bind(this.emitter);
  
      this.preProcessors = [];
      this.uploaders = [];
      this.postProcessors = [];
  
      this.store = this.opts.store;
      this.setState({
        plugins: {},
        files: {},
        currentUploads: {},
        capabilities: {
          resumableUploads: false
        },
        totalProgress: 0,
        meta: _extends({}, this.opts.meta),
        info: {
          isHidden: true,
          type: 'info',
          message: ''
        }
      });
  
      this._storeUnsubscribe = this.store.subscribe(function (prevState, nextState, patch) {
        _this.emit('state-update', prevState, nextState, patch);
        _this.updateAll(nextState);
      }
  
      // for debugging and testing
      // this.updateNum = 0
      );if (this.opts.debug) {
        global.uppyLog = '';
        global[this.opts.id] = this;
      }
    }
  
    /**
     * Iterate on all plugins and run `update` on them.
     * Called each time state changes.
     *
     */
  
  
    Uppy.prototype.updateAll = function updateAll(state) {
      this.iteratePlugins(function (plugin) {
        plugin.update(state);
      });
    };
  
    /**
     * Updates state
     *
     * @param {patch} object
     */
  
  
    Uppy.prototype.setState = function setState(patch) {
      this.store.setState(patch);
    };
  
    /**
     * Returns current state.
     */
  
  
    Uppy.prototype.getState = function getState() {
      return this.store.getState();
    };
  
    /**
    * Back compat for when this.state is used instead of this.getState().
    */
  
  
    /**
    * Shorthand to set state for a specific file.
    */
    Uppy.prototype.setFileState = function setFileState(fileID, state) {
      var _extends2;
  
      this.setState({
        files: _extends({}, this.getState().files, (_extends2 = {}, _extends2[fileID] = _extends({}, this.getState().files[fileID], state), _extends2))
      });
    };
  
    Uppy.prototype.resetProgress = function resetProgress() {
      var defaultProgress = {
        percentage: 0,
        bytesUploaded: 0,
        uploadComplete: false,
        uploadStarted: false
      };
      var files = _extends({}, this.getState().files);
      var updatedFiles = {};
      Object.keys(files).forEach(function (fileID) {
        var updatedFile = _extends({}, files[fileID]);
        updatedFile.progress = _extends({}, updatedFile.progress, defaultProgress);
        updatedFiles[fileID] = updatedFile;
      });
  
      this.setState({
        files: updatedFiles,
        totalProgress: 0
      }
  
      // TODO Document on the website
      );this.emit('reset-progress');
    };
  
    Uppy.prototype.addPreProcessor = function addPreProcessor(fn) {
      this.preProcessors.push(fn);
    };
  
    Uppy.prototype.removePreProcessor = function removePreProcessor(fn) {
      var i = this.preProcessors.indexOf(fn);
      if (i !== -1) {
        this.preProcessors.splice(i, 1);
      }
    };
  
    Uppy.prototype.addPostProcessor = function addPostProcessor(fn) {
      this.postProcessors.push(fn);
    };
  
    Uppy.prototype.removePostProcessor = function removePostProcessor(fn) {
      var i = this.postProcessors.indexOf(fn);
      if (i !== -1) {
        this.postProcessors.splice(i, 1);
      }
    };
  
    Uppy.prototype.addUploader = function addUploader(fn) {
      this.uploaders.push(fn);
    };
  
    Uppy.prototype.removeUploader = function removeUploader(fn) {
      var i = this.uploaders.indexOf(fn);
      if (i !== -1) {
        this.uploaders.splice(i, 1);
      }
    };
  
    Uppy.prototype.setMeta = function setMeta(data) {
      var updatedMeta = _extends({}, this.getState().meta, data);
      var updatedFiles = _extends({}, this.getState().files);
  
      Object.keys(updatedFiles).forEach(function (fileID) {
        updatedFiles[fileID] = _extends({}, updatedFiles[fileID], {
          meta: _extends({}, updatedFiles[fileID].meta, data)
        });
      });
  
      this.log('Adding metadata:');
      this.log(data);
  
      this.setState({
        meta: updatedMeta,
        files: updatedFiles
      });
    };
  
    Uppy.prototype.setFileMeta = function setFileMeta(fileID, data) {
      var updatedFiles = _extends({}, this.getState().files);
      if (!updatedFiles[fileID]) {
        this.log('Was trying to set metadata for a file that’s not with us anymore: ', fileID);
        return;
      }
      var newMeta = _extends({}, updatedFiles[fileID].meta, data);
      updatedFiles[fileID] = _extends({}, updatedFiles[fileID], {
        meta: newMeta
      });
      this.setState({ files: updatedFiles });
    };
  
    /**
     * Get a file object.
     *
     * @param {string} fileID The ID of the file object to return.
     */
  
  
    Uppy.prototype.getFile = function getFile(fileID) {
      return this.getState().files[fileID];
    };
  
    /**
    * Check if minNumberOfFiles restriction is reached before uploading.
    *
    * @return {boolean}
    * @private
    */
  
  
    Uppy.prototype._checkMinNumberOfFiles = function _checkMinNumberOfFiles() {
      var minNumberOfFiles = this.opts.restrictions.minNumberOfFiles;
  
      if (Object.keys(this.getState().files).length < minNumberOfFiles) {
        this.info('' + this.i18n('youHaveToAtLeastSelectX', { smart_count: minNumberOfFiles }), 'error', 5000);
        return false;
      }
      return true;
    };
  
    /**
    * Check if file passes a set of restrictions set in options: maxFileSize,
    * maxNumberOfFiles and allowedFileTypes.
    *
    * @param {object} file object to check
    * @return {boolean}
    * @private
    */
  
  
    Uppy.prototype._checkRestrictions = function _checkRestrictions(file) {
      var _opts$restrictions = this.opts.restrictions,
          maxFileSize = _opts$restrictions.maxFileSize,
          maxNumberOfFiles = _opts$restrictions.maxNumberOfFiles,
          allowedFileTypes = _opts$restrictions.allowedFileTypes;
  
  
      if (maxNumberOfFiles) {
        if (Object.keys(this.getState().files).length + 1 > maxNumberOfFiles) {
          this.info('' + this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles }), 'error', 5000);
          return false;
        }
      }
  
      if (allowedFileTypes) {
        var isCorrectFileType = allowedFileTypes.filter(function (type) {
          if (!file.type) return false;
          return match(file.type, type);
        }).length > 0;
  
        if (!isCorrectFileType) {
          var allowedFileTypesString = allowedFileTypes.join(', ');
          this.info(this.i18n('youCanOnlyUploadFileTypes') + ' ' + allowedFileTypesString, 'error', 5000);
          return false;
        }
      }
  
      if (maxFileSize) {
        if (file.data.size > maxFileSize) {
          this.info(this.i18n('exceedsSize') + ' ' + prettyBytes(maxFileSize), 'error', 5000);
          return false;
        }
      }
  
      return true;
    };
  
    /**
    * Add a new file to `state.files`. This will run `onBeforeFileAdded`,
    * try to guess file type in a clever way, check file against restrictions,
    * and start an upload if `autoProceed === true`.
    *
    * @param {object} file object to add
    */
  
  
    Uppy.prototype.addFile = function addFile(file) {
      var _this2 = this;
  
      // Wrap this in a Promise `.then()` handler so errors will reject the Promise
      // instead of throwing.
      var beforeFileAdded = Promise.resolve().then(function () {
        return _this2.opts.onBeforeFileAdded(file, _this2.getState().files);
      });
  
      return beforeFileAdded.catch(function (err) {
        var message = (typeof err === 'undefined' ? 'undefined' : _typeof(err)) === 'object' ? err.message : err;
        _this2.info(message, 'error', 5000);
        return Promise.reject(new Error('onBeforeFileAdded: ' + message));
      }).then(function () {
        return Utils.getFileType(file).then(function (fileType) {
          var updatedFiles = _extends({}, _this2.getState().files);
          var fileName = void 0;
          if (file.name) {
            fileName = file.name;
          } else if (fileType.split('/')[0] === 'image') {
            fileName = fileType.split('/')[0] + '.' + fileType.split('/')[1];
          } else {
            fileName = 'noname';
          }
          var fileExtension = Utils.getFileNameAndExtension(fileName).extension;
          var isRemote = file.isRemote || false;
  
          var fileID = Utils.generateFileID(file);
  
          var newFile = {
            source: file.source || '',
            id: fileID,
            name: fileName,
            extension: fileExtension || '',
            meta: _extends({}, _this2.getState().meta, {
              name: fileName,
              type: fileType
            }),
            type: fileType,
            data: file.data,
            progress: {
              percentage: 0,
              bytesUploaded: 0,
              bytesTotal: file.data.size || 0,
              uploadComplete: false,
              uploadStarted: false
            },
            size: file.data.size || 'N/A',
            isRemote: isRemote,
            remote: file.remote || '',
            preview: file.preview
          };
  
          var isFileAllowed = _this2._checkRestrictions(newFile);
          if (!isFileAllowed) return Promise.reject(new Error('File not allowed'));
  
          updatedFiles[fileID] = newFile;
          _this2.setState({ files: updatedFiles });
  
          _this2.emit('file-added', newFile);
          _this2.log('Added file: ' + fileName + ', ' + fileID + ', mime type: ' + fileType);
  
          if (_this2.opts.autoProceed && !_this2.scheduledAutoProceed) {
            _this2.scheduledAutoProceed = setTimeout(function () {
              _this2.scheduledAutoProceed = null;
              _this2.upload().catch(function (err) {
                console.error(err.stack || err.message || err);
              });
            }, 4);
          }
        });
      });
    };
  
    Uppy.prototype.removeFile = function removeFile(fileID) {
      var _this3 = this;
  
      var _state = this.state,
          files = _state.files,
          currentUploads = _state.currentUploads;
  
      var updatedFiles = _extends({}, files);
      var removedFile = updatedFiles[fileID];
      delete updatedFiles[fileID];
  
      // Remove this file from its `currentUpload`.
      var updatedUploads = _extends({}, currentUploads);
      var removeUploads = [];
      Object.keys(updatedUploads).forEach(function (uploadID) {
        var newFileIDs = currentUploads[uploadID].fileIDs.filter(function (uploadFileID) {
          return uploadFileID !== fileID;
        }
        // Remove the upload if no files are associated with it anymore.
        );if (newFileIDs.length === 0) {
          removeUploads.push(uploadID);
          return;
        }
  
        updatedUploads[uploadID] = _extends({}, currentUploads[uploadID], {
          fileIDs: newFileIDs
        });
      });
  
      this.setState({
        currentUploads: updatedUploads,
        files: updatedFiles
      });
  
      removeUploads.forEach(function (uploadID) {
        _this3._removeUpload(uploadID);
      });
  
      this._calculateTotalProgress();
      this.emit('file-removed', fileID
  
      // Clean up object URLs.
      );if (removedFile.preview && Utils.isObjectURL(removedFile.preview)) {
        URL.revokeObjectURL(removedFile.preview);
      }
  
      this.log('Removed file: ' + fileID);
    };
  
    /**
     * Generate a preview image for the given file, if possible.
     */
  
  
    Uppy.prototype.generatePreview = function generatePreview(file) {
      var _this4 = this;
  
      if (Utils.isPreviewSupported(file.type) && !file.isRemote) {
        var previewPromise = void 0;
        if (this.opts.thumbnailGeneration === true) {
          previewPromise = Utils.createThumbnail(file, 280);
        } else {
          previewPromise = Promise.resolve(URL.createObjectURL(file.data));
        }
        previewPromise.then(function (preview) {
          _this4.setPreviewURL(file.id, preview);
        }).catch(function (err) {
          console.warn(err.stack || err.message);
        });
      }
    };
  
    /**
     * Set the preview URL for a file.
     */
  
  
    Uppy.prototype.setPreviewURL = function setPreviewURL(fileID, preview) {
      this.setFileState(fileID, { preview: preview });
    };
  
    Uppy.prototype.pauseResume = function pauseResume(fileID) {
      var updatedFiles = _extends({}, this.getState().files);
  
      if (updatedFiles[fileID].uploadComplete) return;
  
      var wasPaused = updatedFiles[fileID].isPaused || false;
      var isPaused = !wasPaused;
  
      var updatedFile = _extends({}, updatedFiles[fileID], {
        isPaused: isPaused
      });
  
      updatedFiles[fileID] = updatedFile;
      this.setState({ files: updatedFiles });
  
      this.emit('upload-pause', fileID, isPaused);
  
      return isPaused;
    };
  
    Uppy.prototype.pauseAll = function pauseAll() {
      var updatedFiles = _extends({}, this.getState().files);
      var inProgressUpdatedFiles = Object.keys(updatedFiles).filter(function (file) {
        return !updatedFiles[file].progress.uploadComplete && updatedFiles[file].progress.uploadStarted;
      });
  
      inProgressUpdatedFiles.forEach(function (file) {
        var updatedFile = _extends({}, updatedFiles[file], {
          isPaused: true
        });
        updatedFiles[file] = updatedFile;
      });
      this.setState({ files: updatedFiles });
  
      this.emit('pause-all');
    };
  
    Uppy.prototype.resumeAll = function resumeAll() {
      var updatedFiles = _extends({}, this.getState().files);
      var inProgressUpdatedFiles = Object.keys(updatedFiles).filter(function (file) {
        return !updatedFiles[file].progress.uploadComplete && updatedFiles[file].progress.uploadStarted;
      });
  
      inProgressUpdatedFiles.forEach(function (file) {
        var updatedFile = _extends({}, updatedFiles[file], {
          isPaused: false,
          error: null
        });
        updatedFiles[file] = updatedFile;
      });
      this.setState({ files: updatedFiles });
  
      this.emit('resume-all');
    };
  
    Uppy.prototype.retryAll = function retryAll() {
      var updatedFiles = _extends({}, this.getState().files);
      var filesToRetry = Object.keys(updatedFiles).filter(function (file) {
        return updatedFiles[file].error;
      });
  
      filesToRetry.forEach(function (file) {
        var updatedFile = _extends({}, updatedFiles[file], {
          isPaused: false,
          error: null
        });
        updatedFiles[file] = updatedFile;
      });
      this.setState({
        files: updatedFiles,
        error: null
      });
  
      this.emit('retry-all', filesToRetry);
  
      var uploadID = this._createUpload(filesToRetry);
      return this._runUpload(uploadID);
    };
  
    Uppy.prototype.cancelAll = function cancelAll() {
      this.emit('cancel-all');
      this.setState({ files: {}, totalProgress: 0 });
    };
  
    Uppy.prototype.retryUpload = function retryUpload(fileID) {
      var updatedFiles = _extends({}, this.getState().files);
      var updatedFile = _extends({}, updatedFiles[fileID], { error: null, isPaused: false });
      updatedFiles[fileID] = updatedFile;
      this.setState({
        files: updatedFiles
      });
  
      this.emit('upload-retry', fileID);
  
      var uploadID = this._createUpload([fileID]);
      return this._runUpload(uploadID);
    };
  
    Uppy.prototype.reset = function reset() {
      this.cancelAll();
    };
  
    Uppy.prototype._calculateProgress = function _calculateProgress(data) {
      var fileID = data.id;
  
      // skip progress event for a file that’s been removed
      if (!this.getState().files[fileID]) {
        this.log('Trying to set progress for a file that’s been removed: ', fileID);
        return;
      }
  
      this.setFileState(fileID, {
        progress: _extends({}, this.getState().files[fileID].progress, {
          bytesUploaded: data.bytesUploaded,
          bytesTotal: data.bytesTotal,
          percentage: Math.floor((data.bytesUploaded / data.bytesTotal * 100).toFixed(2))
        })
      });
  
      this._calculateTotalProgress();
    };
  
    Uppy.prototype._calculateTotalProgress = function _calculateTotalProgress() {
      // calculate total progress, using the number of files currently uploading,
      // multiplied by 100 and the summ of individual progress of each file
      var files = _extends({}, this.getState().files);
  
      var inProgress = Object.keys(files).filter(function (file) {
        return files[file].progress.uploadStarted;
      });
      var progressMax = inProgress.length * 100;
      var progressAll = 0;
      inProgress.forEach(function (file) {
        progressAll = progressAll + files[file].progress.percentage;
      });
  
      var totalProgress = progressMax === 0 ? 0 : Math.floor((progressAll * 100 / progressMax).toFixed(2));
  
      this.setState({
        totalProgress: totalProgress
      });
    };
  
    /**
     * Registers listeners for all global actions, like:
     * `error`, `file-added`, `file-removed`, `upload-progress`
     *
     */
  
  
    Uppy.prototype.actions = function actions() {
      var _this5 = this;
  
      // const log = this.log
      // this.on('*', function (payload) {
      //   log(`[Core] Event: ${this.event}`)
      //   log(payload)
      // })
  
      // stress-test re-rendering
      // setInterval(() => {
      //   this.setState({bla: 'bla'})
      // }, 20)
  
      this.on('error', function (error) {
        _this5.setState({ error: error.message });
      });
  
      this.on('upload-error', function (fileID, error) {
        _this5.setFileState(fileID, { error: error.message });
        _this5.setState({ error: error.message });
  
        var fileName = _this5.getState().files[fileID].name;
        var message = 'Failed to upload ' + fileName;
        if ((typeof error === 'undefined' ? 'undefined' : _typeof(error)) === 'object' && error.message) {
          message = { message: message, details: error.message };
        }
        _this5.info(message, 'error', 5000);
      });
  
      this.on('upload', function () {
        _this5.setState({ error: null });
      }
  
      // this.on('file-add', (data) => {
      //   this.addFile(data)
      // })
  
      );this.on('file-added', function (file) {
        _this5.generatePreview(file);
      });
  
      this.on('file-remove', function (fileID) {
        _this5.removeFile(fileID);
      });
  
      this.on('upload-started', function (fileID, upload) {
        _this5.setFileState(fileID, {
          progress: _extends({}, _this5.getState().files[fileID].progress, {
            uploadStarted: Date.now(),
            uploadComplete: false,
            percentage: 0,
            bytesUploaded: 0
          })
        });
      }
  
      // upload progress events can occur frequently, especially when you have a good
      // connection to the remote server. Therefore, we are throtteling them to
      // prevent accessive function calls.
      // see also: https://github.com/tus/tus-js-client/commit/9940f27b2361fd7e10ba58b09b60d82422183bbb
      );var _throttledCalculateProgress = throttle(this._calculateProgress, 100, { leading: true, trailing: false });
  
      this.on('upload-progress', _throttledCalculateProgress);
  
      this.on('upload-success', function (fileID, uploadResp, uploadURL) {
        _this5.setFileState(fileID, {
          progress: _extends({}, _this5.getState().files[fileID].progress, {
            uploadComplete: true,
            percentage: 100
          }),
          uploadURL: uploadURL,
          isPaused: false
        });
  
        _this5._calculateTotalProgress();
      });
  
      this.on('preprocess-progress', function (fileID, progress) {
        _this5.setFileState(fileID, {
          progress: _extends({}, _this5.getState().files[fileID].progress, {
            preprocess: progress
          })
        });
      });
  
      this.on('preprocess-complete', function (fileID) {
        var files = _extends({}, _this5.getState().files);
        files[fileID] = _extends({}, files[fileID], {
          progress: _extends({}, files[fileID].progress)
        });
        delete files[fileID].progress.preprocess;
  
        _this5.setState({ files: files });
      });
  
      this.on('postprocess-progress', function (fileID, progress) {
        _this5.setFileState(fileID, {
          progress: _extends({}, _this5.getState().files[fileID].progress, {
            postprocess: progress
          })
        });
      });
  
      this.on('postprocess-complete', function (fileID) {
        var files = _extends({}, _this5.getState().files);
        files[fileID] = _extends({}, files[fileID], {
          progress: _extends({}, files[fileID].progress)
        });
        delete files[fileID].progress.postprocess;
        // TODO should we set some kind of `fullyComplete` property on the file object
        // so it's easier to see that the file is upload…fully complete…rather than
        // what we have to do now (`uploadComplete && !postprocess`)
  
        _this5.setState({ files: files });
      });
  
      this.on('restored', function () {
        // Files may have changed--ensure progress is still accurate.
        _this5._calculateTotalProgress();
      }
  
      // show informer if offline
      );if (typeof window !== 'undefined') {
        window.addEventListener('online', function () {
          return _this5.updateOnlineStatus();
        });
        window.addEventListener('offline', function () {
          return _this5.updateOnlineStatus();
        });
        setTimeout(function () {
          return _this5.updateOnlineStatus();
        }, 3000);
      }
    };
  
    Uppy.prototype.updateOnlineStatus = function updateOnlineStatus() {
      var online = typeof window.navigator.onLine !== 'undefined' ? window.navigator.onLine : true;
      if (!online) {
        this.emit('is-offline');
        this.info('No internet connection', 'error', 0);
        this.wasOffline = true;
      } else {
        this.emit('is-online');
        if (this.wasOffline) {
          this.emit('back-online');
          this.info('Connected!', 'success', 3000);
          this.wasOffline = false;
        }
      }
    };
  
    Uppy.prototype.getID = function getID() {
      return this.opts.id;
    };
  
    /**
     * Registers a plugin with Core.
     *
     * @param {Class} Plugin object
     * @param {Object} options object that will be passed to Plugin later
     * @return {Object} self for chaining
     */
  
  
    Uppy.prototype.use = function use(Plugin, opts) {
      if (typeof Plugin !== 'function') {
        var msg = 'Expected a plugin class, but got ' + (Plugin === null ? 'null' : typeof Plugin === 'undefined' ? 'undefined' : _typeof(Plugin)) + '.' + ' Please verify that the plugin was imported and spelled correctly.';
        throw new TypeError(msg);
      }
  
      // Instantiate
      var plugin = new Plugin(this, opts);
      var pluginId = plugin.id;
      this.plugins[plugin.type] = this.plugins[plugin.type] || [];
  
      if (!pluginId) {
        throw new Error('Your plugin must have an id');
      }
  
      if (!plugin.type) {
        throw new Error('Your plugin must have a type');
      }
  
      var existsPluginAlready = this.getPlugin(pluginId);
      if (existsPluginAlready) {
        var _msg = 'Already found a plugin named \'' + existsPluginAlready.id + '\'.\n        Tried to use: \'' + pluginId + '\'.\n        Uppy is currently limited to running one of every plugin.\n        Share your use case with us over at\n        https://github.com/transloadit/uppy/issues/\n        if you want us to reconsider.';
        throw new Error(_msg);
      }
  
      this.plugins[plugin.type].push(plugin);
      plugin.install();
  
      return this;
    };
  
    /**
     * Find one Plugin by name.
     *
     * @param string name description
     */
  
  
    Uppy.prototype.getPlugin = function getPlugin(name) {
      var foundPlugin = false;
      this.iteratePlugins(function (plugin) {
        var pluginName = plugin.id;
        if (pluginName === name) {
          foundPlugin = plugin;
          return false;
        }
      });
      return foundPlugin;
    };
  
    /**
     * Iterate through all `use`d plugins.
     *
     * @param function method description
     */
  
  
    Uppy.prototype.iteratePlugins = function iteratePlugins(method) {
      var _this6 = this;
  
      Object.keys(this.plugins).forEach(function (pluginType) {
        _this6.plugins[pluginType].forEach(method);
      });
    };
  
    /**
     * Uninstall and remove a plugin.
     *
     * @param {Plugin} instance The plugin instance to remove.
     */
  
  
    Uppy.prototype.removePlugin = function removePlugin(instance) {
      var list = this.plugins[instance.type];
  
      if (instance.uninstall) {
        instance.uninstall();
      }
  
      var index = list.indexOf(instance);
      if (index !== -1) {
        list.splice(index, 1);
      }
    };
  
    /**
     * Uninstall all plugins and close down this Uppy instance.
     */
  
  
    Uppy.prototype.close = function close() {
      this.reset();
  
      this._storeUnsubscribe();
  
      this.iteratePlugins(function (plugin) {
        plugin.uninstall();
      });
    };
  
    /**
    * Set info message in `state.info`, so that UI plugins like `Informer`
    * can display the message.
    *
    * @param {string} msg Message to be displayed by the informer
    */
  
    Uppy.prototype.info = function info(message) {
      var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'info';
      var duration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 3000;
  
      var isComplexMessage = (typeof message === 'undefined' ? 'undefined' : _typeof(message)) === 'object';
  
      this.setState({
        info: {
          isHidden: false,
          type: type,
          message: isComplexMessage ? message.message : message,
          details: isComplexMessage ? message.details : null
        }
      });
  
      this.emit('info-visible');
  
      window.clearTimeout(this.infoTimeoutID);
      if (duration === 0) {
        this.infoTimeoutID = undefined;
        return;
      }
  
      // hide the informer after `duration` milliseconds
      this.infoTimeoutID = setTimeout(this.hideInfo, duration);
    };
  
    Uppy.prototype.hideInfo = function hideInfo() {
      var newInfo = _extends({}, this.getState().info, {
        isHidden: true
      });
      this.setState({
        info: newInfo
      });
      this.emit('info-hidden');
    };
  
    /**
     * Logs stuff to console, only if `debug` is set to true. Silent in production.
     *
     * @param {String|Object} msg to log
     * @param {String} type optional `error` or `warning`
     */
  
  
    Uppy.prototype.log = function log(msg, type) {
      if (!this.opts.debug) {
        return;
      }
  
      var message = '[Uppy] [' + Utils.getTimeStamp() + '] ' + msg;
  
      global.uppyLog = global.uppyLog + '\n' + 'DEBUG LOG: ' + msg;
  
      if (type === 'error') {
        console.error(message);
        return;
      }
  
      if (type === 'warning') {
        console.warn(message);
        return;
      }
  
      if (msg === '' + msg) {
        console.log(message);
      } else {
        message = '[Uppy] [' + Utils.getTimeStamp() + ']';
        console.log(message);
        console.dir(msg);
      }
    };
  
    /**
     * Initializes actions.
     *
     */
  
  
    Uppy.prototype.run = function run() {
      this.log('Core is run, initializing actions...');
      this.actions();
  
      return this;
    };
  
    /**
     * Restore an upload by its ID.
     */
  
  
    Uppy.prototype.restore = function restore(uploadID) {
      this.log('Core: attempting to restore upload "' + uploadID + '"');
  
      if (!this.getState().currentUploads[uploadID]) {
        this._removeUpload(uploadID);
        return Promise.reject(new Error('Nonexistent upload'));
      }
  
      return this._runUpload(uploadID);
    };
  
    /**
     * Create an upload for a bunch of files.
     *
     * @param {Array<string>} fileIDs File IDs to include in this upload.
     * @return {string} ID of this upload.
     */
  
  
    Uppy.prototype._createUpload = function _createUpload(fileIDs) {
      var _extends3;
  
      var uploadID = cuid();
  
      this.emit('upload', {
        id: uploadID,
        fileIDs: fileIDs
      });
  
      this.setState({
        currentUploads: _extends({}, this.getState().currentUploads, (_extends3 = {}, _extends3[uploadID] = {
          fileIDs: fileIDs,
          step: 0
        }, _extends3))
      });
  
      return uploadID;
    };
  
    /**
     * Remove an upload, eg. if it has been canceled or completed.
     *
     * @param {string} uploadID The ID of the upload.
     */
  
  
    Uppy.prototype._removeUpload = function _removeUpload(uploadID) {
      var currentUploads = _extends({}, this.getState().currentUploads);
      delete currentUploads[uploadID];
  
      this.setState({
        currentUploads: currentUploads
      });
    };
  
    /**
     * Run an upload. This picks up where it left off in case the upload is being restored.
     *
     * @private
     */
  
  
    Uppy.prototype._runUpload = function _runUpload(uploadID) {
      var _this7 = this;
  
      var uploadData = this.getState().currentUploads[uploadID];
      var fileIDs = uploadData.fileIDs;
      var restoreStep = uploadData.step;
  
      var steps = [].concat(this.preProcessors, this.uploaders, this.postProcessors);
      var lastStep = Promise.resolve();
      steps.forEach(function (fn, step) {
        // Skip this step if we are restoring and have already completed this step before.
        if (step < restoreStep) {
          return;
        }
  
        lastStep = lastStep.then(function () {
          var _extends4;
  
          var _getState = _this7.getState(),
              currentUploads = _getState.currentUploads;
  
          var currentUpload = _extends({}, currentUploads[uploadID], {
            step: step
          });
          _this7.setState({
            currentUploads: _extends({}, currentUploads, (_extends4 = {}, _extends4[uploadID] = currentUpload, _extends4))
          }
          // TODO give this the `currentUpload` object as its only parameter maybe?
          // Otherwise when more metadata may be added to the upload this would keep getting more parameters
          );return fn(fileIDs, uploadID);
        });
      }
  
      // Not returning the `catch`ed promise, because we still want to return a rejected
      // promise from this method if the upload failed.
      );lastStep.catch(function (err) {
        _this7.emit('error', err);
  
        _this7._removeUpload(uploadID);
      });
  
      return lastStep.then(function () {
        var files = fileIDs.map(function (fileID) {
          return _this7.getFile(fileID);
        });
        var successful = files.filter(function (file) {
          return file && !file.error;
        });
        var failed = files.filter(function (file) {
          return file && file.error;
        });
        _this7.emit('complete', { successful: successful, failed: failed }
  
        // Compatibility with pre-0.21
        );_this7.emit('success', fileIDs);
  
        _this7._removeUpload(uploadID);
  
        return { successful: successful, failed: failed };
      });
    };
  
    /**
    * Start an upload for all the files that are not currently being uploaded.
    *
    * @return {Promise}
    */
  
  
    Uppy.prototype.upload = function upload() {
      var _this8 = this;
  
      if (!this.plugins.uploader) {
        this.log('No uploader type plugins are used', 'warning');
      }
  
      var isMinNumberOfFilesReached = this._checkMinNumberOfFiles();
      if (!isMinNumberOfFilesReached) {
        return Promise.reject(new Error('Minimum number of files has not been reached'));
      }
  
      var beforeUpload = Promise.resolve().then(function () {
        return _this8.opts.onBeforeUpload(_this8.getState().files);
      });
  
      return beforeUpload.catch(function (err) {
        var message = (typeof err === 'undefined' ? 'undefined' : _typeof(err)) === 'object' ? err.message : err;
        _this8.info(message, 'error', 5000);
        return Promise.reject(new Error('onBeforeUpload: ' + message));
      }).then(function () {
        var waitingFileIDs = [];
        Object.keys(_this8.getState().files).forEach(function (fileID) {
          var file = _this8.getFile(fileID);
  
          if (!file.progress.uploadStarted) {
            waitingFileIDs.push(file.id);
          }
        });
  
        var uploadID = _this8._createUpload(waitingFileIDs);
        return _this8._runUpload(uploadID);
      });
    };
  
    _createClass(Uppy, [{
      key: 'state',
      get: function get() {
        return this.getState();
      }
    }]);
  
    return Uppy;
  }();
  
  module.exports = function (opts) {
    return new Uppy(opts);
  };
  // Expose class constructor.
  module.exports.Uppy = Uppy;
  //# sourceMappingURL=Core.js.map