@uppy/core
Advanced tools
Comparing version 1.1.0 to 1.2.0
428
lib/index.js
@@ -7,2 +7,16 @@ function _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); } | ||
function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } | ||
function _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); } | ||
function 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; } } | ||
function _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); } | ||
function _isNativeFunction(fn) { return Function.toString.call(fn).indexOf("[native code]") !== -1; } | ||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } | ||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } | ||
var Translator = require('@uppy/utils/lib/Translator'); | ||
@@ -12,6 +26,7 @@ | ||
var cuid = require('cuid'); // const throttle = require('lodash.throttle') | ||
var cuid = require('cuid'); | ||
var throttle = require('lodash.throttle'); | ||
var prettyBytes = require('prettier-bytes'); | ||
var prettyBytes = require('@uppy/utils/lib/prettyBytes'); | ||
@@ -28,8 +43,30 @@ var match = require('mime-match'); | ||
var getTimeStamp = require('@uppy/utils/lib/getTimeStamp'); | ||
var supportsUploadProgress = require('./supportsUploadProgress'); | ||
var _require = require('./loggers'), | ||
nullLogger = _require.nullLogger, | ||
debugLogger = _require.debugLogger; | ||
var Plugin = require('./Plugin'); // Exported from here. | ||
var RestrictionError = | ||
/*#__PURE__*/ | ||
function (_Error) { | ||
_inheritsLoose(RestrictionError, _Error); | ||
function RestrictionError() { | ||
var _this; | ||
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
_this = _Error.call.apply(_Error, [this].concat(args)) || this; | ||
_this.isRestriction = true; | ||
return _this; | ||
} | ||
return RestrictionError; | ||
}(_wrapNativeSuper(Error)); | ||
/** | ||
@@ -46,7 +83,8 @@ * Uppy Core module. | ||
/** | ||
* Instantiate Uppy | ||
* @param {object} opts — Uppy options | ||
*/ | ||
* Instantiate Uppy | ||
* | ||
* @param {Object} opts — Uppy options | ||
*/ | ||
function Uppy(opts) { | ||
var _this = this; | ||
var _this2 = this; | ||
@@ -74,7 +112,12 @@ this.defaultLocale = { | ||
noFilesFound: 'You have no files or folders here', | ||
selectXFiles: { | ||
0: 'Select %{smart_count} file', | ||
1: 'Select %{smart_count} files', | ||
2: 'Select %{smart_count} files' | ||
selectX: { | ||
0: 'Select %{smart_count}', | ||
1: 'Select %{smart_count}', | ||
2: 'Select %{smart_count}' | ||
}, | ||
selectAllFilesFromFolderNamed: 'Select all files from folder %{name}', | ||
unselectAllFilesFromFolderNamed: 'Unselect all files from folder %{name}', | ||
selectFileNamed: 'Select file %{name}', | ||
unselectFileNamed: 'Unselect file %{name}', | ||
openFolderNamed: 'Open folder %{name}', | ||
cancel: 'Cancel', | ||
@@ -86,3 +129,9 @@ logOut: 'Log out', | ||
authenticateWithTitle: 'Please authenticate with %{pluginName} to select files', | ||
authenticateWith: 'Connect to %{pluginName}' | ||
authenticateWith: 'Connect to %{pluginName}', | ||
emptyFolderAdded: 'No files were added from empty folder', | ||
folderAdded: { | ||
0: 'Added %{smart_count} file from %{folder}', | ||
1: 'Added %{smart_count} files from %{folder}', | ||
2: 'Added %{smart_count} files from %{folder}' | ||
} | ||
} // set default options | ||
@@ -109,8 +158,23 @@ | ||
}, | ||
store: DefaultStore() // Merge default options with the ones set by user | ||
store: DefaultStore(), | ||
logger: nullLogger // Merge default options with the ones set by user | ||
}; | ||
this.opts = _extends({}, defaultOptions, opts); | ||
this.opts.restrictions = _extends({}, defaultOptions.restrictions, this.opts.restrictions); // i18n | ||
this.opts.restrictions = _extends({}, defaultOptions.restrictions, this.opts.restrictions); // Support debug: true for backwards-compatability, unless logger is set in opts | ||
// opts instead of this.opts to avoid comparing objects — we set logger: nullLogger in defaultOptions | ||
if (opts && opts.logger && opts.debug) { | ||
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'); | ||
} else if (opts && opts.debug) { | ||
this.opts.logger = debugLogger; | ||
} | ||
this.log("Using Core v" + this.constructor.VERSION); | ||
if (this.opts.restrictions.allowedFileTypes && this.opts.restrictions.allowedFileTypes !== null && !Array.isArray(this.opts.restrictions.allowedFileTypes)) { | ||
throw new Error("'restrictions.allowedFileTypes' must be an array"); | ||
} // i18n | ||
this.translator = new Translator([this.defaultLocale, this.opts.locale]); | ||
@@ -131,4 +195,12 @@ this.locale = this.translator.locale; | ||
this.removeFile = this.removeFile.bind(this); | ||
this.pauseResume = this.pauseResume.bind(this); | ||
this._calculateProgress = this._calculateProgress.bind(this); | ||
this.pauseResume = this.pauseResume.bind(this); // ___Why throttle at 500ms? | ||
// - 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). | ||
// [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. | ||
// - We must throttle at around >500ms to avoid performance lags. | ||
// [Practical Check] Firefox, try to upload a big file for a prolonged period of time. Laptop will start to heat up. | ||
this._calculateProgress = throttle(this._calculateProgress.bind(this), 500, { | ||
leading: true, | ||
trailing: true | ||
}); | ||
this.updateOnlineStatus = this.updateOnlineStatus.bind(this); | ||
@@ -170,10 +242,8 @@ this.resetProgress = this.resetProgress.bind(this); | ||
this._storeUnsubscribe = this.store.subscribe(function (prevState, nextState, patch) { | ||
_this.emit('state-update', prevState, nextState, patch); | ||
_this2.emit('state-update', prevState, nextState, patch); | ||
_this.updateAll(nextState); | ||
}); // for debugging and testing | ||
// this.updateNum = 0 | ||
_this2.updateAll(nextState); | ||
}); // Exposing uppy object on window for debugging and testing | ||
if (this.opts.debug && typeof window !== 'undefined') { | ||
window['uppyLog'] = ''; | ||
window[this.opts.id] = this; | ||
@@ -211,3 +281,3 @@ } | ||
* | ||
* @param {object} patch {foo: 'bar'} | ||
* @param {Object} patch {foo: 'bar'} | ||
*/ | ||
@@ -221,3 +291,4 @@ ; | ||
* Returns current state. | ||
* @return {object} | ||
* | ||
* @returns {Object} | ||
*/ | ||
@@ -332,3 +403,3 @@ ; | ||
if (!updatedFiles[fileID]) { | ||
this.log('Was trying to set metadata for a file that’s not with us anymore: ', fileID); | ||
this.log('Was trying to set metadata for a file that has been removed: ', fileID); | ||
return; | ||
@@ -370,6 +441,6 @@ } | ||
/** | ||
* Check if minNumberOfFiles restriction is reached before uploading. | ||
* | ||
* @private | ||
*/ | ||
* Check if minNumberOfFiles restriction is reached before uploading. | ||
* | ||
* @private | ||
*/ | ||
; | ||
@@ -381,3 +452,3 @@ | ||
if (Object.keys(files).length < minNumberOfFiles) { | ||
throw new Error("" + this.i18n('youHaveToAtLeastSelectX', { | ||
throw new RestrictionError("" + this.i18n('youHaveToAtLeastSelectX', { | ||
smart_count: minNumberOfFiles | ||
@@ -388,8 +459,8 @@ })); | ||
/** | ||
* Check if file passes a set of restrictions set in options: maxFileSize, | ||
* maxNumberOfFiles and allowedFileTypes. | ||
* | ||
* @param {object} file object to check | ||
* @private | ||
*/ | ||
* Check if file passes a set of restrictions set in options: maxFileSize, | ||
* maxNumberOfFiles and allowedFileTypes. | ||
* | ||
* @param {Object} file object to check | ||
* @private | ||
*/ | ||
; | ||
@@ -405,3 +476,3 @@ | ||
if (Object.keys(this.getState().files).length + 1 > maxNumberOfFiles) { | ||
throw new Error("" + this.i18n('youCanOnlyUploadX', { | ||
throw new RestrictionError("" + this.i18n('youCanOnlyUploadX', { | ||
smart_count: maxNumberOfFiles | ||
@@ -414,3 +485,2 @@ })); | ||
var isCorrectFileType = allowedFileTypes.some(function (type) { | ||
// if (!file.type) return false | ||
// is this is a mime-type | ||
@@ -432,3 +502,3 @@ if (type.indexOf('/') > -1) { | ||
var allowedFileTypesString = allowedFileTypes.join(', '); | ||
throw new Error(this.i18n('youCanOnlyUploadFileTypes', { | ||
throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { | ||
types: allowedFileTypesString | ||
@@ -442,3 +512,3 @@ })); | ||
if (file.data.size > maxFileSize) { | ||
throw new Error(this.i18n('exceedsSize') + " " + prettyBytes(maxFileSize)); | ||
throw new RestrictionError(this.i18n('exceedsSize') + " " + prettyBytes(maxFileSize)); | ||
} | ||
@@ -448,12 +518,12 @@ } | ||
/** | ||
* 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 | ||
*/ | ||
* 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 | ||
*/ | ||
; | ||
_proto.addFile = function addFile(file) { | ||
var _this2 = this, | ||
var _this3 = this, | ||
_extends3; | ||
@@ -468,5 +538,5 @@ | ||
_this2.log(err.message); | ||
_this3.log(err.message); | ||
_this2.info(err.message, 'error', 5000); | ||
_this3.info(err.message, 'error', 5000); | ||
@@ -480,2 +550,4 @@ throw err; | ||
var fileType = getFileType(file); | ||
file.type = fileType; | ||
var onBeforeFileAddedResult = this.opts.onBeforeFileAdded(file, files); | ||
@@ -489,11 +561,5 @@ | ||
if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult) { | ||
// warning after the change in 0.24 | ||
if (onBeforeFileAddedResult.then) { | ||
throw new TypeError('onBeforeFileAdded() returned a Promise, but this is no longer supported. It must be synchronous.'); | ||
} | ||
file = onBeforeFileAddedResult; | ||
} | ||
var fileType = getFileType(file); | ||
var fileName; | ||
@@ -553,6 +619,8 @@ | ||
this.scheduledAutoProceed = setTimeout(function () { | ||
_this2.scheduledAutoProceed = null; | ||
_this3.scheduledAutoProceed = null; | ||
_this2.upload().catch(function (err) { | ||
console.error(err.stack || err.message || err); | ||
_this3.upload().catch(function (err) { | ||
if (!err.isRestriction) { | ||
_this3.uppy.log(err.stack || err.message || err); | ||
} | ||
}); | ||
@@ -564,3 +632,3 @@ }, 4); | ||
_proto.removeFile = function removeFile(fileID) { | ||
var _this3 = this; | ||
var _this4 = this; | ||
@@ -598,3 +666,3 @@ var _this$getState3 = this.getState(), | ||
removeUploads.forEach(function (uploadID) { | ||
_this3._removeUpload(uploadID); | ||
_this4._removeUpload(uploadID); | ||
}); | ||
@@ -687,3 +755,3 @@ | ||
_proto.cancelAll = function cancelAll() { | ||
var _this4 = this; | ||
var _this5 = this; | ||
@@ -693,3 +761,3 @@ this.emit('cancel-all'); | ||
files.forEach(function (fileID) { | ||
_this4.removeFile(fileID); | ||
_this5.removeFile(fileID); | ||
}); | ||
@@ -740,3 +808,3 @@ this.setState({ | ||
// we get more accurate calculations if we don't round this at all. | ||
? Math.floor(data.bytesUploaded / data.bytesTotal * 100) : 0 | ||
? Math.round(data.bytesUploaded / data.bytesTotal * 100) : 0 | ||
}) | ||
@@ -772,3 +840,3 @@ }); | ||
if (sizedFiles.length === 0) { | ||
var progressMax = inProgress.length; | ||
var progressMax = inProgress.length * 100; | ||
var currentProgress = unsizedFiles.reduce(function (acc, file) { | ||
@@ -796,3 +864,3 @@ return acc + file.progress.percentage; | ||
unsizedFiles.forEach(function (file) { | ||
uploadedSize += averageSize * (file.progress.percentage || 0); | ||
uploadedSize += averageSize * (file.progress.percentage || 0) / 100; | ||
}); | ||
@@ -818,6 +886,6 @@ var totalProgress = totalSize === 0 ? 0 : Math.round(uploadedSize / totalSize * 100); // hot fix, because: | ||
_proto._addListeners = function _addListeners() { | ||
var _this5 = this; | ||
var _this6 = this; | ||
this.on('error', function (error) { | ||
_this5.setState({ | ||
_this6.setState({ | ||
error: error.message | ||
@@ -827,3 +895,3 @@ }); | ||
this.on('upload-error', function (file, error, response) { | ||
_this5.setFileState(file.id, { | ||
_this6.setFileState(file.id, { | ||
error: error.message, | ||
@@ -833,7 +901,7 @@ response: response | ||
_this5.setState({ | ||
_this6.setState({ | ||
error: error.message | ||
}); | ||
var message = _this5.i18n('failedToUpload', { | ||
var message = _this6.i18n('failedToUpload', { | ||
file: file.name | ||
@@ -849,6 +917,6 @@ }); | ||
_this5.info(message, 'error', 5000); | ||
_this6.info(message, 'error', 5000); | ||
}); | ||
this.on('upload', function () { | ||
_this5.setState({ | ||
_this6.setState({ | ||
error: null | ||
@@ -858,4 +926,4 @@ }); | ||
this.on('upload-started', function (file, upload) { | ||
if (!_this5.getFile(file.id)) { | ||
_this5.log("Not setting progress for a file that has been removed: " + file.id); | ||
if (!_this6.getFile(file.id)) { | ||
_this6.log("Not setting progress for a file that has been removed: " + file.id); | ||
@@ -865,3 +933,3 @@ return; | ||
_this5.setFileState(file.id, { | ||
_this6.setFileState(file.id, { | ||
progress: { | ||
@@ -875,13 +943,8 @@ uploadStarted: Date.now(), | ||
}); | ||
}); // 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 | ||
// const _throttledCalculateProgress = throttle(this._calculateProgress, 100, { leading: true, trailing: true }) | ||
}); | ||
this.on('upload-progress', this._calculateProgress); | ||
this.on('upload-success', function (file, uploadResp) { | ||
var currentProgress = _this5.getFile(file.id).progress; | ||
var currentProgress = _this6.getFile(file.id).progress; | ||
_this5.setFileState(file.id, { | ||
_this6.setFileState(file.id, { | ||
progress: _extends({}, currentProgress, { | ||
@@ -897,7 +960,7 @@ uploadComplete: true, | ||
_this5._calculateTotalProgress(); | ||
_this6._calculateTotalProgress(); | ||
}); | ||
this.on('preprocess-progress', function (file, progress) { | ||
if (!_this5.getFile(file.id)) { | ||
_this5.log("Not setting progress for a file that has been removed: " + file.id); | ||
if (!_this6.getFile(file.id)) { | ||
_this6.log("Not setting progress for a file that has been removed: " + file.id); | ||
@@ -907,4 +970,4 @@ return; | ||
_this5.setFileState(file.id, { | ||
progress: _extends({}, _this5.getFile(file.id).progress, { | ||
_this6.setFileState(file.id, { | ||
progress: _extends({}, _this6.getFile(file.id).progress, { | ||
preprocess: progress | ||
@@ -915,4 +978,4 @@ }) | ||
this.on('preprocess-complete', function (file) { | ||
if (!_this5.getFile(file.id)) { | ||
_this5.log("Not setting progress for a file that has been removed: " + file.id); | ||
if (!_this6.getFile(file.id)) { | ||
_this6.log("Not setting progress for a file that has been removed: " + file.id); | ||
@@ -922,3 +985,3 @@ return; | ||
var files = _extends({}, _this5.getState().files); | ||
var files = _extends({}, _this6.getState().files); | ||
@@ -930,3 +993,3 @@ files[file.id] = _extends({}, files[file.id], { | ||
_this5.setState({ | ||
_this6.setState({ | ||
files: files | ||
@@ -936,4 +999,4 @@ }); | ||
this.on('postprocess-progress', function (file, progress) { | ||
if (!_this5.getFile(file.id)) { | ||
_this5.log("Not setting progress for a file that has been removed: " + file.id); | ||
if (!_this6.getFile(file.id)) { | ||
_this6.log("Not setting progress for a file that has been removed: " + file.id); | ||
@@ -943,4 +1006,4 @@ return; | ||
_this5.setFileState(file.id, { | ||
progress: _extends({}, _this5.getState().files[file.id].progress, { | ||
_this6.setFileState(file.id, { | ||
progress: _extends({}, _this6.getState().files[file.id].progress, { | ||
postprocess: progress | ||
@@ -951,4 +1014,4 @@ }) | ||
this.on('postprocess-complete', function (file) { | ||
if (!_this5.getFile(file.id)) { | ||
_this5.log("Not setting progress for a file that has been removed: " + file.id); | ||
if (!_this6.getFile(file.id)) { | ||
_this6.log("Not setting progress for a file that has been removed: " + file.id); | ||
@@ -958,3 +1021,3 @@ return; | ||
var files = _extends({}, _this5.getState().files); | ||
var files = _extends({}, _this6.getState().files); | ||
@@ -968,3 +1031,3 @@ files[file.id] = _extends({}, files[file.id], { | ||
_this5.setState({ | ||
_this6.setState({ | ||
files: files | ||
@@ -975,3 +1038,3 @@ }); | ||
// Files may have changed--ensure progress is still accurate. | ||
_this5._calculateTotalProgress(); | ||
_this6._calculateTotalProgress(); | ||
}); // show informer if offline | ||
@@ -981,9 +1044,9 @@ | ||
window.addEventListener('online', function () { | ||
return _this5.updateOnlineStatus(); | ||
return _this6.updateOnlineStatus(); | ||
}); | ||
window.addEventListener('offline', function () { | ||
return _this5.updateOnlineStatus(); | ||
return _this6.updateOnlineStatus(); | ||
}); | ||
setTimeout(function () { | ||
return _this5.updateOnlineStatus(); | ||
return _this6.updateOnlineStatus(); | ||
}, 3000); | ||
@@ -1017,5 +1080,5 @@ } | ||
* | ||
* @param {object} Plugin object | ||
* @param {object} [opts] object with options to be passed to Plugin | ||
* @return {Object} self for chaining | ||
* @param {Object} Plugin object | ||
* @param {Object} [opts] object with options to be passed to Plugin | ||
* @returns {Object} self for chaining | ||
*/ | ||
@@ -1051,2 +1114,6 @@ ; | ||
if (Plugin.VERSION) { | ||
this.log("Using " + pluginId + " v" + Plugin.VERSION); | ||
} | ||
this.plugins[plugin.type].push(plugin); | ||
@@ -1060,3 +1127,3 @@ plugin.install(); | ||
* @param {string} id plugin id | ||
* @return {object | boolean} | ||
* @returns {Object|boolean} | ||
*/ | ||
@@ -1078,3 +1145,3 @@ ; | ||
* | ||
* @param {function} method that will be run on each plugin | ||
* @param {Function} method that will be run on each plugin | ||
*/ | ||
@@ -1084,6 +1151,6 @@ ; | ||
_proto.iteratePlugins = function iteratePlugins(method) { | ||
var _this6 = this; | ||
var _this7 = this; | ||
Object.keys(this.plugins).forEach(function (pluginType) { | ||
_this6.plugins[pluginType].forEach(method); | ||
_this7.plugins[pluginType].forEach(method); | ||
}); | ||
@@ -1094,3 +1161,3 @@ } | ||
* | ||
* @param {object} instance The plugin instance to remove. | ||
* @param {Object} instance The plugin instance to remove. | ||
*/ | ||
@@ -1125,3 +1192,3 @@ ; | ||
_proto.close = function close() { | ||
var _this7 = this; | ||
var _this8 = this; | ||
@@ -1134,3 +1201,3 @@ this.log("Closing Uppy instance " + this.opts.id + ": removing all files and uninstalling plugins"); | ||
this.iteratePlugins(function (plugin) { | ||
_this7.removePlugin(plugin); | ||
_this8.removePlugin(plugin); | ||
}); | ||
@@ -1189,6 +1256,7 @@ } | ||
/** | ||
* Logs stuff to console, only if `debug` is set to true. Silent in production. | ||
* Passes messages to a function, provided in `opt.logger`. | ||
* If `opt.logger: Uppy.debugLogger` or `opt.debug: true`, logs to the browser console. | ||
* | ||
* @param {String|Object} message to log | ||
* @param {String} [type] optional `error` or `warning` | ||
* @param {string|Object} message to log | ||
* @param {string} [type] optional `error` or `warning` | ||
*/ | ||
@@ -1198,19 +1266,17 @@ ; | ||
_proto.log = function log(message, type) { | ||
if (!this.opts.debug) { | ||
return; | ||
} | ||
var logger = this.opts.logger; | ||
var prefix = "[Uppy] [" + getTimeStamp() + "]"; | ||
switch (type) { | ||
case 'error': | ||
logger.error(message); | ||
break; | ||
if (type === 'error') { | ||
console.error(prefix, message); | ||
return; | ||
} | ||
case 'warning': | ||
logger.warn(message); | ||
break; | ||
if (type === 'warning') { | ||
console.warn(prefix, message); | ||
return; | ||
default: | ||
logger.debug(message); | ||
break; | ||
} | ||
console.log(prefix, message); | ||
} | ||
@@ -1246,3 +1312,3 @@ /** | ||
* @param {Array<string>} fileIDs File IDs to include in this upload. | ||
* @return {string} ID of this upload. | ||
* @returns {string} ID of this upload. | ||
*/ | ||
@@ -1288,3 +1354,3 @@ ; | ||
* @param {string} uploadID The ID of the upload. | ||
* @param {object} data Data properties to add to the result object. | ||
* @param {Object} data Data properties to add to the result object. | ||
*/ | ||
@@ -1334,3 +1400,3 @@ ; | ||
_proto._runUpload = function _runUpload(uploadID) { | ||
var _this8 = this; | ||
var _this9 = this; | ||
@@ -1350,16 +1416,22 @@ var uploadData = this.getState().currentUploads[uploadID]; | ||
var _this8$getState = _this8.getState(), | ||
currentUploads = _this8$getState.currentUploads; | ||
var _this9$getState = _this9.getState(), | ||
currentUploads = _this9$getState.currentUploads; | ||
var currentUpload = _extends({}, currentUploads[uploadID], { | ||
var currentUpload = currentUploads[uploadID]; | ||
if (!currentUpload) { | ||
return; | ||
} | ||
var updatedUpload = _extends({}, currentUpload, { | ||
step: step | ||
}); | ||
_this8.setState({ | ||
currentUploads: _extends({}, currentUploads, (_extends6 = {}, _extends6[uploadID] = currentUpload, _extends6)) | ||
}); // TODO give this the `currentUpload` object as its only parameter maybe? | ||
_this9.setState({ | ||
currentUploads: _extends({}, currentUploads, (_extends6 = {}, _extends6[uploadID] = updatedUpload, _extends6)) | ||
}); // TODO give this the `updatedUpload` object as its only parameter maybe? | ||
// Otherwise when more metadata may be added to the upload this would keep getting more parameters | ||
return fn(currentUpload.fileIDs, uploadID); | ||
return fn(updatedUpload.fileIDs, uploadID); | ||
}).then(function (result) { | ||
@@ -1372,10 +1444,10 @@ return null; | ||
lastStep.catch(function (err) { | ||
_this8.emit('error', err, uploadID); | ||
_this9.emit('error', err, uploadID); | ||
_this8._removeUpload(uploadID); | ||
_this9._removeUpload(uploadID); | ||
}); | ||
return lastStep.then(function () { | ||
// Set result data. | ||
var _this8$getState2 = _this8.getState(), | ||
currentUploads = _this8$getState2.currentUploads; | ||
var _this9$getState2 = _this9.getState(), | ||
currentUploads = _this9$getState2.currentUploads; | ||
@@ -1389,3 +1461,3 @@ var currentUpload = currentUploads[uploadID]; | ||
var files = currentUpload.fileIDs.map(function (fileID) { | ||
return _this8.getFile(fileID); | ||
return _this9.getFile(fileID); | ||
}); | ||
@@ -1399,3 +1471,3 @@ var successful = files.filter(function (file) { | ||
_this8.addResultData(uploadID, { | ||
_this9.addResultData(uploadID, { | ||
successful: successful, | ||
@@ -1410,4 +1482,4 @@ failed: failed, | ||
// to an outdated object without the `.result` property. | ||
var _this8$getState3 = _this8.getState(), | ||
currentUploads = _this8$getState3.currentUploads; | ||
var _this9$getState3 = _this9.getState(), | ||
currentUploads = _this9$getState3.currentUploads; | ||
@@ -1421,5 +1493,5 @@ if (!currentUploads[uploadID]) { | ||
_this8.emit('complete', result); | ||
_this9.emit('complete', result); | ||
_this8._removeUpload(uploadID); | ||
_this9._removeUpload(uploadID); | ||
@@ -1429,3 +1501,3 @@ return result; | ||
if (result == null) { | ||
_this8.log("Not setting result for an upload that has been removed: " + uploadID); | ||
_this9.log("Not setting result for an upload that has been removed: " + uploadID); | ||
} | ||
@@ -1439,3 +1511,3 @@ | ||
* | ||
* @return {Promise} | ||
* @returns {Promise} | ||
*/ | ||
@@ -1445,4 +1517,18 @@ ; | ||
_proto.upload = function upload() { | ||
var _this9 = this; | ||
var _this10 = this; | ||
var onError = function onError(err) { | ||
var message = typeof err === 'object' ? err.message : err; | ||
var details = typeof err === 'object' && err.details ? err.details : ''; | ||
_this10.log(message + " " + details); | ||
_this10.info({ | ||
message: message, | ||
details: details | ||
}, 'error', 5000); | ||
throw typeof err === 'object' ? err : new Error(err); | ||
}; | ||
if (!this.plugins.uploader) { | ||
@@ -1460,7 +1546,2 @@ this.log('No uploader type plugins are used', 'warning'); | ||
if (onBeforeUploadResult && typeof onBeforeUploadResult === 'object') { | ||
// warning after the change in 0.24 | ||
if (onBeforeUploadResult.then) { | ||
throw new TypeError('onBeforeUpload() returned a Promise, but this is no longer supported. It must be synchronous.'); | ||
} | ||
files = onBeforeUploadResult; | ||
@@ -1470,6 +1551,6 @@ } | ||
return Promise.resolve().then(function () { | ||
return _this9._checkMinNumberOfFiles(files); | ||
return _this10._checkMinNumberOfFiles(files); | ||
}).then(function () { | ||
var _this9$getState = _this9.getState(), | ||
currentUploads = _this9$getState.currentUploads; // get a list of files that are currently assigned to uploads | ||
var _this10$getState = _this10.getState(), | ||
currentUploads = _this10$getState.currentUploads; // get a list of files that are currently assigned to uploads | ||
@@ -1482,3 +1563,3 @@ | ||
Object.keys(files).forEach(function (fileID) { | ||
var file = _this9.getFile(fileID); // if the file hasn't started uploading and hasn't already been assigned to an upload.. | ||
var file = _this10.getFile(fileID); // if the file hasn't started uploading and hasn't already been assigned to an upload.. | ||
@@ -1491,17 +1572,11 @@ | ||
var uploadID = _this9._createUpload(waitingFileIDs); | ||
var uploadID = _this10._createUpload(waitingFileIDs); | ||
return _this9._runUpload(uploadID); | ||
return _this10._runUpload(uploadID); | ||
}).catch(function (err) { | ||
var message = typeof err === 'object' ? err.message : err; | ||
var details = typeof err === 'object' ? err.details : null; | ||
if (err.isRestriction) { | ||
_this10.emit('restriction-failed', null, err); | ||
} | ||
_this9.log(message + " " + details); | ||
_this9.info({ | ||
message: message, | ||
details: details | ||
}, 'error', 4000); | ||
return Promise.reject(typeof err === 'object' ? err : new Error(err)); | ||
onError(err); | ||
}); | ||
@@ -1520,3 +1595,3 @@ }; | ||
Uppy.VERSION = "1.1.0"; | ||
Uppy.VERSION = "1.2.0"; | ||
@@ -1529,2 +1604,3 @@ module.exports = function (opts) { | ||
module.exports.Uppy = Uppy; | ||
module.exports.Plugin = Plugin; | ||
module.exports.Plugin = Plugin; | ||
module.exports.debugLogger = debugLogger; |
@@ -74,3 +74,3 @@ function _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); } | ||
this.uppy.setState({ | ||
plugins: _extends({}, plugins, (_extends2 = {}, _extends2[this.id] = _extends({}, plugins[this.id], update), _extends2)) | ||
plugins: _extends({}, plugins, (_extends2 = {}, _extends2[this.id] = _extends({}, plugins[this.id], {}, update), _extends2)) | ||
}); | ||
@@ -87,3 +87,6 @@ }; | ||
} | ||
} | ||
} // Called after every state update, after everything's mounted. Debounced. | ||
; | ||
_proto.afterUpdate = function afterUpdate() {} | ||
/** | ||
@@ -103,3 +106,3 @@ * Called when plugin is mounted, whether in DOM or into another plugin. | ||
* | ||
* @param {String|Object} target | ||
* @param {string|Object} target | ||
* | ||
@@ -124,6 +127,8 @@ */ | ||
_this.el = preact.render(_this.render(state), targetElement, _this.el); | ||
_this.afterUpdate(); | ||
}; | ||
this._updateUI = debounce(this.rerender); | ||
this.uppy.log("Installing " + callerPluginName + " to a DOM element"); // clear everything inside the target container | ||
this.uppy.log("Installing " + callerPluginName + " to a DOM element '" + target + "'"); // clear everything inside the target container | ||
@@ -165,3 +170,3 @@ if (this.opts.replaceTargetContent) { | ||
this.uppy.log("Not installing " + callerPluginName); | ||
throw new Error("Invalid target option given to " + callerPluginName + ". Please make sure that the element \n exists on the page, or that the plugin you are targeting has been installed. Check that the <script> tag initializing Uppy \n comes at the bottom of the page, before the closing </body> tag (see https://github.com/transloadit/uppy/issues/1042)."); | ||
throw new Error("Invalid target option given to " + callerPluginName + ". Please make sure that the element\n exists on the page, or that the plugin you are targeting has been installed. Check that the <script> tag initializing Uppy\n comes at the bottom of the page, before the closing </body> tag (see https://github.com/transloadit/uppy/issues/1042)."); | ||
}; | ||
@@ -168,0 +173,0 @@ |
{ | ||
"name": "@uppy/core", | ||
"description": "Core module for the extensible JavaScript file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Instagram, Dropbox, Google Drive, S3 and more :dog:", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"license": "MIT", | ||
@@ -24,3 +24,3 @@ "main": "lib/index.js", | ||
"@uppy/store-default": "1.1.0", | ||
"@uppy/utils": "1.1.0", | ||
"@uppy/utils": "1.2.0", | ||
"cuid": "^2.1.1", | ||
@@ -30,6 +30,5 @@ "lodash.throttle": "^4.1.1", | ||
"namespace-emitter": "^2.0.1", | ||
"preact": "8.2.9", | ||
"prettier-bytes": "^1.0.4" | ||
"preact": "8.2.9" | ||
}, | ||
"gitHead": "28d235fe2fb57d87a399c20883fd6590aa49f4f4" | ||
"gitHead": "bd2beedcffbaa840de7069860e341f02268ddbb1" | ||
} |
232
src/index.js
const Translator = require('@uppy/utils/lib/Translator') | ||
const ee = require('namespace-emitter') | ||
const cuid = require('cuid') | ||
// const throttle = require('lodash.throttle') | ||
const prettyBytes = require('prettier-bytes') | ||
const throttle = require('lodash.throttle') | ||
const prettyBytes = require('@uppy/utils/lib/prettyBytes') | ||
const match = require('mime-match') | ||
@@ -11,6 +11,13 @@ const DefaultStore = require('@uppy/store-default') | ||
const generateFileID = require('@uppy/utils/lib/generateFileID') | ||
const getTimeStamp = require('@uppy/utils/lib/getTimeStamp') | ||
const supportsUploadProgress = require('./supportsUploadProgress') | ||
const { nullLogger, debugLogger } = require('./loggers') | ||
const Plugin = require('./Plugin') // Exported from here. | ||
class RestrictionError extends Error { | ||
constructor (...args) { | ||
super(...args) | ||
this.isRestriction = true | ||
} | ||
} | ||
/** | ||
@@ -25,5 +32,6 @@ * Uppy Core module. | ||
/** | ||
* Instantiate Uppy | ||
* @param {object} opts — Uppy options | ||
*/ | ||
* Instantiate Uppy | ||
* | ||
* @param {Object} opts — Uppy options | ||
*/ | ||
constructor (opts) { | ||
@@ -51,7 +59,12 @@ this.defaultLocale = { | ||
noFilesFound: 'You have no files or folders here', | ||
selectXFiles: { | ||
0: 'Select %{smart_count} file', | ||
1: 'Select %{smart_count} files', | ||
2: 'Select %{smart_count} files' | ||
selectX: { | ||
0: 'Select %{smart_count}', | ||
1: 'Select %{smart_count}', | ||
2: 'Select %{smart_count}' | ||
}, | ||
selectAllFilesFromFolderNamed: 'Select all files from folder %{name}', | ||
unselectAllFilesFromFolderNamed: 'Unselect all files from folder %{name}', | ||
selectFileNamed: 'Select file %{name}', | ||
unselectFileNamed: 'Unselect file %{name}', | ||
openFolderNamed: 'Open folder %{name}', | ||
cancel: 'Cancel', | ||
@@ -63,3 +76,9 @@ logOut: 'Log out', | ||
authenticateWithTitle: 'Please authenticate with %{pluginName} to select files', | ||
authenticateWith: 'Connect to %{pluginName}' | ||
authenticateWith: 'Connect to %{pluginName}', | ||
emptyFolderAdded: 'No files were added from empty folder', | ||
folderAdded: { | ||
0: 'Added %{smart_count} file from %{folder}', | ||
1: 'Added %{smart_count} files from %{folder}', | ||
2: 'Added %{smart_count} files from %{folder}' | ||
} | ||
} | ||
@@ -83,3 +102,4 @@ } | ||
onBeforeUpload: (files) => files, | ||
store: DefaultStore() | ||
store: DefaultStore(), | ||
logger: nullLogger | ||
} | ||
@@ -91,2 +111,18 @@ | ||
// Support debug: true for backwards-compatability, unless logger is set in opts | ||
// opts instead of this.opts to avoid comparing objects — we set logger: nullLogger in defaultOptions | ||
if (opts && opts.logger && opts.debug) { | ||
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') | ||
} else if (opts && opts.debug) { | ||
this.opts.logger = debugLogger | ||
} | ||
this.log(`Using Core v${this.constructor.VERSION}`) | ||
if (this.opts.restrictions.allowedFileTypes && | ||
this.opts.restrictions.allowedFileTypes !== null && | ||
!Array.isArray(this.opts.restrictions.allowedFileTypes)) { | ||
throw new Error(`'restrictions.allowedFileTypes' must be an array`) | ||
} | ||
// i18n | ||
@@ -111,3 +147,10 @@ this.translator = new Translator([ this.defaultLocale, this.opts.locale ]) | ||
this.pauseResume = this.pauseResume.bind(this) | ||
this._calculateProgress = this._calculateProgress.bind(this) | ||
// ___Why throttle at 500ms? | ||
// - 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). | ||
// [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. | ||
// - We must throttle at around >500ms to avoid performance lags. | ||
// [Practical Check] Firefox, try to upload a big file for a prolonged period of time. Laptop will start to heat up. | ||
this._calculateProgress = throttle(this._calculateProgress.bind(this), 500, { leading: true, trailing: true }) | ||
this.updateOnlineStatus = this.updateOnlineStatus.bind(this) | ||
@@ -158,6 +201,4 @@ this.resetProgress = this.resetProgress.bind(this) | ||
// for debugging and testing | ||
// this.updateNum = 0 | ||
// Exposing uppy object on window for debugging and testing | ||
if (this.opts.debug && typeof window !== 'undefined') { | ||
window['uppyLog'] = '' | ||
window[this.opts.id] = this | ||
@@ -193,3 +234,3 @@ } | ||
* | ||
* @param {object} patch {foo: 'bar'} | ||
* @param {Object} patch {foo: 'bar'} | ||
*/ | ||
@@ -202,3 +243,4 @@ setState (patch) { | ||
* Returns current state. | ||
* @return {object} | ||
* | ||
* @returns {Object} | ||
*/ | ||
@@ -310,3 +352,3 @@ getState () { | ||
if (!updatedFiles[fileID]) { | ||
this.log('Was trying to set metadata for a file that’s not with us anymore: ', fileID) | ||
this.log('Was trying to set metadata for a file that has been removed: ', fileID) | ||
return | ||
@@ -339,10 +381,10 @@ } | ||
/** | ||
* Check if minNumberOfFiles restriction is reached before uploading. | ||
* | ||
* @private | ||
*/ | ||
* Check if minNumberOfFiles restriction is reached before uploading. | ||
* | ||
* @private | ||
*/ | ||
_checkMinNumberOfFiles (files) { | ||
const { minNumberOfFiles } = this.opts.restrictions | ||
if (Object.keys(files).length < minNumberOfFiles) { | ||
throw new Error(`${this.i18n('youHaveToAtLeastSelectX', { smart_count: minNumberOfFiles })}`) | ||
throw new RestrictionError(`${this.i18n('youHaveToAtLeastSelectX', { smart_count: minNumberOfFiles })}`) | ||
} | ||
@@ -352,8 +394,8 @@ } | ||
/** | ||
* Check if file passes a set of restrictions set in options: maxFileSize, | ||
* maxNumberOfFiles and allowedFileTypes. | ||
* | ||
* @param {object} file object to check | ||
* @private | ||
*/ | ||
* Check if file passes a set of restrictions set in options: maxFileSize, | ||
* maxNumberOfFiles and allowedFileTypes. | ||
* | ||
* @param {Object} file object to check | ||
* @private | ||
*/ | ||
_checkRestrictions (file) { | ||
@@ -364,3 +406,3 @@ const { maxFileSize, maxNumberOfFiles, allowedFileTypes } = this.opts.restrictions | ||
if (Object.keys(this.getState().files).length + 1 > maxNumberOfFiles) { | ||
throw new Error(`${this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles })}`) | ||
throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles })}`) | ||
} | ||
@@ -371,4 +413,2 @@ } | ||
const isCorrectFileType = allowedFileTypes.some((type) => { | ||
// if (!file.type) return false | ||
// is this is a mime-type | ||
@@ -389,3 +429,3 @@ if (type.indexOf('/') > -1) { | ||
const allowedFileTypesString = allowedFileTypes.join(', ') | ||
throw new Error(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString })) | ||
throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString })) | ||
} | ||
@@ -397,3 +437,3 @@ } | ||
if (file.data.size > maxFileSize) { | ||
throw new Error(`${this.i18n('exceedsSize')} ${prettyBytes(maxFileSize)}`) | ||
throw new RestrictionError(`${this.i18n('exceedsSize')} ${prettyBytes(maxFileSize)}`) | ||
} | ||
@@ -404,8 +444,8 @@ } | ||
/** | ||
* 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 | ||
*/ | ||
* 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 | ||
*/ | ||
addFile (file) { | ||
@@ -425,2 +465,5 @@ const { files, allowNewUpload } = this.getState() | ||
const fileType = getFileType(file) | ||
file.type = fileType | ||
const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(file, files) | ||
@@ -434,10 +477,5 @@ | ||
if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult) { | ||
// warning after the change in 0.24 | ||
if (onBeforeFileAddedResult.then) { | ||
throw new TypeError('onBeforeFileAdded() returned a Promise, but this is no longer supported. It must be synchronous.') | ||
} | ||
file = onBeforeFileAddedResult | ||
} | ||
const fileType = getFileType(file) | ||
let fileName | ||
@@ -503,3 +541,5 @@ if (file.name) { | ||
this.upload().catch((err) => { | ||
console.error(err.stack || err.message || err) | ||
if (!err.isRestriction) { | ||
this.uppy.log(err.stack || err.message || err) | ||
} | ||
}) | ||
@@ -675,3 +715,3 @@ }, 4) | ||
// we get more accurate calculations if we don't round this at all. | ||
? Math.floor(data.bytesUploaded / data.bytesTotal * 100) | ||
? Math.round(data.bytesUploaded / data.bytesTotal * 100) | ||
: 0 | ||
@@ -703,3 +743,3 @@ }) | ||
if (sizedFiles.length === 0) { | ||
const progressMax = inProgress.length | ||
const progressMax = inProgress.length * 100 | ||
const currentProgress = unsizedFiles.reduce((acc, file) => { | ||
@@ -724,3 +764,3 @@ return acc + file.progress.percentage | ||
unsizedFiles.forEach((file) => { | ||
uploadedSize += averageSize * (file.progress.percentage || 0) | ||
uploadedSize += averageSize * (file.progress.percentage || 0) / 100 | ||
}) | ||
@@ -786,8 +826,2 @@ | ||
// 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 | ||
// const _throttledCalculateProgress = throttle(this._calculateProgress, 100, { leading: true, trailing: true }) | ||
this.on('upload-progress', this._calculateProgress) | ||
@@ -905,5 +939,5 @@ | ||
* | ||
* @param {object} Plugin object | ||
* @param {object} [opts] object with options to be passed to Plugin | ||
* @return {Object} self for chaining | ||
* @param {Object} Plugin object | ||
* @param {Object} [opts] object with options to be passed to Plugin | ||
* @returns {Object} self for chaining | ||
*/ | ||
@@ -938,2 +972,6 @@ use (Plugin, opts) { | ||
if (Plugin.VERSION) { | ||
this.log(`Using ${pluginId} v${Plugin.VERSION}`) | ||
} | ||
this.plugins[plugin.type].push(plugin) | ||
@@ -949,3 +987,3 @@ plugin.install() | ||
* @param {string} id plugin id | ||
* @return {object | boolean} | ||
* @returns {Object|boolean} | ||
*/ | ||
@@ -966,3 +1004,3 @@ getPlugin (id) { | ||
* | ||
* @param {function} method that will be run on each plugin | ||
* @param {Function} method that will be run on each plugin | ||
*/ | ||
@@ -978,3 +1016,3 @@ iteratePlugins (method) { | ||
* | ||
* @param {object} instance The plugin instance to remove. | ||
* @param {Object} instance The plugin instance to remove. | ||
*/ | ||
@@ -1060,25 +1098,15 @@ removePlugin (instance) { | ||
/** | ||
* Logs stuff to console, only if `debug` is set to true. Silent in production. | ||
* Passes messages to a function, provided in `opt.logger`. | ||
* If `opt.logger: Uppy.debugLogger` or `opt.debug: true`, logs to the browser console. | ||
* | ||
* @param {String|Object} message to log | ||
* @param {String} [type] optional `error` or `warning` | ||
* @param {string|Object} message to log | ||
* @param {string} [type] optional `error` or `warning` | ||
*/ | ||
log (message, type) { | ||
if (!this.opts.debug) { | ||
return | ||
const { logger } = this.opts | ||
switch (type) { | ||
case 'error': logger.error(message); break | ||
case 'warning': logger.warn(message); break | ||
default: logger.debug(message); break | ||
} | ||
const prefix = `[Uppy] [${getTimeStamp()}]` | ||
if (type === 'error') { | ||
console.error(prefix, message) | ||
return | ||
} | ||
if (type === 'warning') { | ||
console.warn(prefix, message) | ||
return | ||
} | ||
console.log(prefix, message) | ||
} | ||
@@ -1112,3 +1140,3 @@ | ||
* @param {Array<string>} fileIDs File IDs to include in this upload. | ||
* @return {string} ID of this upload. | ||
* @returns {string} ID of this upload. | ||
*/ | ||
@@ -1154,3 +1182,3 @@ _createUpload (fileIDs) { | ||
* @param {string} uploadID The ID of the upload. | ||
* @param {object} data Data properties to add to the result object. | ||
* @param {Object} data Data properties to add to the result object. | ||
*/ | ||
@@ -1210,3 +1238,8 @@ addResultData (uploadID, data) { | ||
const { currentUploads } = this.getState() | ||
const currentUpload = Object.assign({}, currentUploads[uploadID], { | ||
const currentUpload = currentUploads[uploadID] | ||
if (!currentUpload) { | ||
return | ||
} | ||
const updatedUpload = Object.assign({}, currentUpload, { | ||
step: step | ||
@@ -1216,9 +1249,9 @@ }) | ||
currentUploads: Object.assign({}, currentUploads, { | ||
[uploadID]: currentUpload | ||
[uploadID]: updatedUpload | ||
}) | ||
}) | ||
// TODO give this the `currentUpload` object as its only parameter maybe? | ||
// TODO give this the `updatedUpload` object as its only parameter maybe? | ||
// Otherwise when more metadata may be added to the upload this would keep getting more parameters | ||
return fn(currentUpload.fileIDs, uploadID) | ||
return fn(updatedUpload.fileIDs, uploadID) | ||
}).then((result) => { | ||
@@ -1276,5 +1309,13 @@ return null | ||
* | ||
* @return {Promise} | ||
* @returns {Promise} | ||
*/ | ||
upload () { | ||
const onError = (err) => { | ||
const message = typeof err === 'object' ? err.message : err | ||
const details = (typeof err === 'object' && err.details) ? err.details : '' | ||
this.log(`${message} ${details}`) | ||
this.info({ message: message, details: details }, 'error', 5000) | ||
throw (typeof err === 'object' ? err : new Error(err)) | ||
} | ||
if (!this.plugins.uploader) { | ||
@@ -1292,7 +1333,2 @@ this.log('No uploader type plugins are used', 'warning') | ||
if (onBeforeUploadResult && typeof onBeforeUploadResult === 'object') { | ||
// warning after the change in 0.24 | ||
if (onBeforeUploadResult.then) { | ||
throw new TypeError('onBeforeUpload() returned a Promise, but this is no longer supported. It must be synchronous.') | ||
} | ||
files = onBeforeUploadResult | ||
@@ -1321,7 +1357,6 @@ } | ||
.catch((err) => { | ||
const message = typeof err === 'object' ? err.message : err | ||
const details = typeof err === 'object' ? err.details : null | ||
this.log(`${message} ${details}`) | ||
this.info({ message: message, details: details }, 'error', 4000) | ||
return Promise.reject(typeof err === 'object' ? err : new Error(err)) | ||
if (err.isRestriction) { | ||
this.emit('restriction-failed', null, err) | ||
} | ||
onError(err) | ||
}) | ||
@@ -1338,1 +1373,2 @@ } | ||
module.exports.Plugin = Plugin | ||
module.exports.debugLogger = debugLogger |
const fs = require('fs') | ||
const path = require('path') | ||
const prettyBytes = require('prettier-bytes') | ||
const prettyBytes = require('@uppy/utils/lib/prettyBytes') | ||
const Core = require('./index') | ||
@@ -486,3 +486,3 @@ const Plugin = require('./Plugin') | ||
// const fileId = 'foojpg' + lastModifiedTime.getTime() | ||
const fileId = 'uppy-foojpg-image' | ||
const fileId = 'uppy-foo/jpg-1e-image' | ||
@@ -692,2 +692,25 @@ expect(postprocessor1.mock.calls[0][0].length).toEqual(1) | ||
}) | ||
it('does not dedupe different files', async () => { | ||
const core = new Core() | ||
const data = new Blob([sampleImage], { type: 'image/jpeg' }) | ||
data.lastModified = 1562770350937 | ||
core.addFile({ | ||
source: 'jest', | ||
name: 'foo.jpg', | ||
type: 'image/jpeg', | ||
data | ||
}) | ||
core.addFile({ | ||
source: 'jest', | ||
name: 'foo푸.jpg', | ||
type: 'image/jpeg', | ||
data | ||
}) | ||
expect(core.getFiles()).toHaveLength(2) | ||
expect(core.getFile('uppy-foo/jpg-1e-image/jpeg-17175-1562770350937')).toBeDefined() | ||
expect(core.getFile('uppy-foo//jpg-1l3o-1e-image/jpeg-17175-1562770350937')).toBeDefined() | ||
}) | ||
}) | ||
@@ -741,6 +764,6 @@ | ||
upload1: { | ||
fileIDs: ['uppy-file1jpg-image/jpeg', 'uppy-file2jpg-image/jpeg', 'uppy-file3jpg-image/jpeg'] | ||
fileIDs: ['uppy-file1/jpg-1e-image/jpeg', 'uppy-file2/jpg-1e-image/jpeg', 'uppy-file3/jpg-1e-image/jpeg'] | ||
}, | ||
upload2: { | ||
fileIDs: ['uppy-file4jpg-image/jpeg', 'uppy-file5jpg-image/jpeg', 'uppy-file6jpg-image/jpeg'] | ||
fileIDs: ['uppy-file4/jpg-1e-image/jpeg', 'uppy-file5/jpg-1e-image/jpeg', 'uppy-file6/jpg-1e-image/jpeg'] | ||
} | ||
@@ -993,3 +1016,3 @@ } | ||
expect(core.getFile(fileId).progress).toEqual({ | ||
percentage: 71, | ||
percentage: 72, | ||
bytesUploaded: 12345, | ||
@@ -1005,2 +1028,5 @@ bytesTotal: 17175, | ||
}) | ||
core._calculateProgress.flush() | ||
expect(core.getFile(fileId).progress).toEqual({ | ||
@@ -1060,5 +1086,7 @@ percentage: 100, | ||
bytesTotal: 3456, | ||
percentage: 35 | ||
percentage: 36 | ||
}) | ||
expect(core.getState().totalProgress).toBe(36) | ||
finishUpload() | ||
@@ -1076,4 +1104,45 @@ // wait for success event | ||
await uploadPromise | ||
core.close() | ||
}) | ||
it('should estimate progress for unsized files', () => { | ||
const core = new Core() | ||
core.once('file-added', (file) => { | ||
core.emit('upload-started', file) | ||
core.emit('upload-progress', file, { | ||
bytesTotal: 3456, | ||
bytesUploaded: 1234 | ||
}) | ||
}) | ||
core.addFile({ | ||
source: 'instagram', | ||
name: 'foo.jpg', | ||
type: 'image/jpeg', | ||
data: {} | ||
}) | ||
core.once('file-added', (file) => { | ||
core.emit('upload-started', file) | ||
core.emit('upload-progress', file, { | ||
bytesTotal: null, | ||
bytesUploaded: null | ||
}) | ||
}) | ||
core.addFile({ | ||
source: 'instagram', | ||
name: 'bar.jpg', | ||
type: 'image/jpeg', | ||
data: {} | ||
}) | ||
core._calculateTotalProgress() | ||
// foo.jpg at 35%, bar.jpg at 0% | ||
expect(core.getState().totalProgress).toBe(18) | ||
core.close() | ||
}) | ||
it('should calculate the total progress of all file uploads', () => { | ||
@@ -1110,2 +1179,4 @@ const core = new Core() | ||
core._calculateTotalProgress() | ||
core._calculateProgress.flush() | ||
expect(core.getState().totalProgress).toEqual(66) | ||
@@ -1147,2 +1218,3 @@ }) | ||
core._calculateTotalProgress() | ||
core._calculateProgress.flush() | ||
@@ -1224,2 +1296,15 @@ expect(core.getState().totalProgress).toEqual(66) | ||
it('should throw if allowedFileTypes is not an array', () => { | ||
try { | ||
const core = Core({ | ||
restrictions: { | ||
allowedFileTypes: 'image/gif' | ||
} | ||
}) | ||
core.log('hi') | ||
} catch (err) { | ||
expect(err).toMatchObject(new Error(`'restrictions.allowedFileTypes' must be an array`)) | ||
} | ||
}) | ||
it('should enforce the allowedFileTypes rule with file extensions', () => { | ||
@@ -1511,2 +1596,101 @@ const core = new Core({ | ||
}) | ||
describe('log', () => { | ||
it('should log via provided logger function', () => { | ||
const myTestLogger = { | ||
debug: jest.fn(), | ||
warn: jest.fn(), | ||
error: jest.fn() | ||
} | ||
const core = new Core({ | ||
logger: myTestLogger | ||
}) | ||
core.log('test test') | ||
core.log('test test', 'error') | ||
core.log('test test', 'error') | ||
core.log('test test', 'warning') | ||
// logger.debug should have been called 1 time above, | ||
// but we call log in Core’s constructor to output VERSION, hence +1 here | ||
expect(core.opts.logger.debug.mock.calls.length).toBe(2) | ||
expect(core.opts.logger.error.mock.calls.length).toBe(2) | ||
expect(core.opts.logger.warn.mock.calls.length).toBe(1) | ||
}) | ||
it('should log via provided logger function, even if debug: true', () => { | ||
const myTestLogger = { | ||
debug: jest.fn(), | ||
warn: jest.fn(), | ||
error: jest.fn() | ||
} | ||
const core = new Core({ | ||
logger: myTestLogger, | ||
debug: true | ||
}) | ||
core.log('test test') | ||
core.log('test test', 'error') | ||
core.log('test test', 'error') | ||
core.log('test test', 'warning') | ||
// logger.debug should have been called 1 time above, | ||
// but we call log in Core’s constructor to output VERSION, hence +1 here | ||
expect(core.opts.logger.debug.mock.calls.length).toBe(2) | ||
expect(core.opts.logger.error.mock.calls.length).toBe(2) | ||
// logger.warn should have been called 1 time above, | ||
// but we warn in Core when using both logger and debug: true, hence +1 here | ||
expect(core.opts.logger.warn.mock.calls.length).toBe(2) | ||
}) | ||
it('should log to console when logger: Uppy.debugLogger or debug: true is set', () => { | ||
console.debug = jest.fn() | ||
console.error = jest.fn() | ||
const core = new Core({ | ||
logger: Core.debugLogger | ||
}) | ||
core.log('test test') | ||
core.log('beep boop') | ||
core.log('beep beep', 'error') | ||
// console.debug debug should have been called 2 times above, | ||
// ibut we call log n Core’ constructor to output VERSION, hence +1 here | ||
expect(console.debug.mock.calls.length).toBe(3) | ||
expect(console.error.mock.calls.length).toBe(1) | ||
console.debug.mockClear() | ||
console.error.mockClear() | ||
const core2 = new Core({ | ||
debug: true | ||
}) | ||
core2.log('test test') | ||
core2.log('beep boop') | ||
core2.log('beep beep', 'error') | ||
// console.debug debug should have been called 2 times here, | ||
// but we call log in Core constructor to output VERSION, hence +1 here | ||
expect(console.debug.mock.calls.length).toBe(3) | ||
expect(console.error.mock.calls.length).toBe(1) | ||
}) | ||
it('should not log to console when logger is not set', () => { | ||
console.debug = jest.fn() | ||
console.error = jest.fn() | ||
const core = new Core() | ||
core.log('test test') | ||
core.log('beep boop') | ||
core.log('beep beep', 'error') | ||
expect(console.debug.mock.calls.length).toBe(0) | ||
expect(console.error.mock.calls.length).toBe(0) | ||
}) | ||
}) | ||
}) |
@@ -75,2 +75,7 @@ const preact = require('preact') | ||
// Called after every state update, after everything's mounted. Debounced. | ||
afterUpdate () { | ||
} | ||
/** | ||
@@ -91,3 +96,3 @@ * Called when plugin is mounted, whether in DOM or into another plugin. | ||
* | ||
* @param {String|Object} target | ||
* @param {string|Object} target | ||
* | ||
@@ -110,6 +115,7 @@ */ | ||
this.el = preact.render(this.render(state), targetElement, this.el) | ||
this.afterUpdate() | ||
} | ||
this._updateUI = debounce(this.rerender) | ||
this.uppy.log(`Installing ${callerPluginName} to a DOM element`) | ||
this.uppy.log(`Installing ${callerPluginName} to a DOM element '${target}'`) | ||
@@ -153,4 +159,4 @@ // clear everything inside the target container | ||
this.uppy.log(`Not installing ${callerPluginName}`) | ||
throw new Error(`Invalid target option given to ${callerPluginName}. Please make sure that the element | ||
exists on the page, or that the plugin you are targeting has been installed. Check that the <script> tag initializing Uppy | ||
throw new Error(`Invalid target option given to ${callerPluginName}. Please make sure that the element | ||
exists on the page, or that the plugin you are targeting has been installed. Check that the <script> tag initializing Uppy | ||
comes at the bottom of the page, before the closing </body> tag (see https://github.com/transloadit/uppy/issues/1042).`) | ||
@@ -157,0 +163,0 @@ } |
@@ -29,1 +29,10 @@ import Uppy = require('../'); | ||
} | ||
{ | ||
const uppy = Uppy(); | ||
uppy.addFile({ | ||
name: 'empty.json', | ||
data: new Blob(['null'], { type: 'application/json' }), | ||
meta: { path: 'path/to/file' } | ||
}); | ||
} |
import UppyUtils = require('@uppy/utils'); | ||
declare module Uppy { | ||
// Utility types | ||
type OmitKey<T, Key> = Pick<T, Exclude<keyof T, Key>>; | ||
// These are defined in @uppy/utils instead of core so it can be used there without creating import cycles | ||
export type UppyFile<TMeta extends IndexedObject<any> = {}> = UppyUtils.UppyFile<TMeta>; | ||
export type Store = UppyUtils.Store; | ||
export type InternalMetadata = UppyUtils.InternalMetadata; | ||
@@ -13,13 +17,16 @@ interface IndexedObject<T> { | ||
interface UploadedUppyFile<TMeta extends IndexedObject<any> = {}> extends UppyFile<TMeta> { | ||
interface UploadedUppyFile<TMeta> extends UppyFile<TMeta> { | ||
uploadURL: string; | ||
} | ||
interface FailedUppyFile<TMeta extends IndexedObject<any> = {}> extends UppyFile<TMeta> { | ||
interface FailedUppyFile<TMeta> extends UppyFile<TMeta> { | ||
error: string; | ||
} | ||
interface AddFileOptions extends Partial<UppyFile> { | ||
// Replace the `meta` property type with one that allows omitting internal metadata; addFile() will add that | ||
type UppyFileWithoutMeta<TMeta> = OmitKey<UppyFile<TMeta>, 'meta'>; | ||
interface AddFileOptions<TMeta = IndexedObject<any>> extends Partial<UppyFileWithoutMeta<TMeta>> { | ||
// `.data` is the only required property here. | ||
data: Blob | File; | ||
meta?: Partial<InternalMetadata> & TMeta; | ||
} | ||
@@ -55,2 +62,9 @@ | ||
interface Restrictions { | ||
maxFileSize: number | null; | ||
maxNumberOfFiles: number | null; | ||
minNumberOfFiles: number | null; | ||
allowedFileTypes: string[] | null; | ||
} | ||
interface UppyOptions { | ||
@@ -61,8 +75,3 @@ id: string; | ||
debug: boolean; | ||
restrictions: { | ||
maxFileSize: number | null; | ||
maxNumberOfFiles: number | null; | ||
minNumberOfFiles: number | null; | ||
allowedFileTypes: string[] | null; | ||
}; | ||
restrictions: Partial<Restrictions>; | ||
target: string | Plugin; | ||
@@ -114,7 +123,7 @@ meta: any; | ||
removeUploader(fn: any): void; | ||
setMeta(data: any): void; | ||
setFileMeta(fileID: string, data: object): void; | ||
setMeta<TMeta extends IndexedObject<any> = {}>(data: TMeta): void; | ||
setFileMeta<TMeta extends IndexedObject<any> = {}>(fileID: string, data: TMeta): void; | ||
getFile<TMeta extends IndexedObject<any> = {}>(fileID: string): UppyFile<TMeta>; | ||
getFiles<TMeta extends IndexedObject<any> = {}>(): Array<UppyFile<TMeta>>; | ||
addFile(file: AddFileOptions): void; | ||
addFile<TMeta extends IndexedObject<any> = {}>(file: AddFileOptions<TMeta>): void; | ||
removeFile(fileID: string): void; | ||
@@ -138,5 +147,5 @@ pauseResume(fileID: string): boolean; | ||
run(): Uppy; | ||
restore(uploadID: string): Promise<UploadResult>; | ||
restore<TMeta extends IndexedObject<any> = {}>(uploadID: string): Promise<UploadResult>; | ||
addResultData(uploadID: string, data: object): void; | ||
upload(): Promise<UploadResult>; | ||
upload<TMeta extends IndexedObject<any> = {}>(): Promise<UploadResult>; | ||
} | ||
@@ -143,0 +152,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
178911
7
27
4642
+ Added@uppy/utils@1.2.0(transitive)
- Removedprettier-bytes@^1.0.4
- Removed@uppy/utils@1.1.0(transitive)
- Removedprettier-bytes@1.0.4(transitive)
Updated@uppy/utils@1.2.0