Uppy.js 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799
  1. "use strict";
  2. var _nonSecure = require("nanoid/non-secure");
  3. var _loggers = require("./loggers.js");
  4. var _Restricter = require("./Restricter.js");
  5. let _Symbol$for, _Symbol$for2;
  6. function _classPrivateFieldLooseBase(receiver, privateKey) { if (!Object.prototype.hasOwnProperty.call(receiver, privateKey)) { throw new TypeError("attempted to use private field on non-instance"); } return receiver; }
  7. var id = 0;
  8. function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
  9. /* eslint-disable max-classes-per-file */
  10. /* global AggregateError */
  11. const Translator = require("@uppy/utils/lib/Translator");
  12. const ee = require("namespace-emitter");
  13. const throttle = require("lodash.throttle");
  14. const DefaultStore = require("@uppy/store-default");
  15. const getFileType = require("@uppy/utils/lib/getFileType");
  16. const getFileNameAndExtension = require("@uppy/utils/lib/getFileNameAndExtension");
  17. const generateFileID = require("@uppy/utils/lib/generateFileID");
  18. const supportsUploadProgress = require("./supportsUploadProgress.js");
  19. const getFileName = require("./getFileName.js");
  20. const packageJson = {
  21. "version": "2.3.4"
  22. };
  23. const locale = require("./locale.js");
  24. /**
  25. * Uppy Core module.
  26. * Manages plugins, state updates, acts as an event bus,
  27. * adds/removes files and metadata.
  28. */
  29. var _plugins = /*#__PURE__*/_classPrivateFieldLooseKey("plugins");
  30. var _restricter = /*#__PURE__*/_classPrivateFieldLooseKey("restricter");
  31. var _storeUnsubscribe = /*#__PURE__*/_classPrivateFieldLooseKey("storeUnsubscribe");
  32. var _emitter = /*#__PURE__*/_classPrivateFieldLooseKey("emitter");
  33. var _preProcessors = /*#__PURE__*/_classPrivateFieldLooseKey("preProcessors");
  34. var _uploaders = /*#__PURE__*/_classPrivateFieldLooseKey("uploaders");
  35. var _postProcessors = /*#__PURE__*/_classPrivateFieldLooseKey("postProcessors");
  36. var _informAndEmit = /*#__PURE__*/_classPrivateFieldLooseKey("informAndEmit");
  37. var _checkRequiredMetaFieldsOnFile = /*#__PURE__*/_classPrivateFieldLooseKey("checkRequiredMetaFieldsOnFile");
  38. var _checkRequiredMetaFields = /*#__PURE__*/_classPrivateFieldLooseKey("checkRequiredMetaFields");
  39. var _assertNewUploadAllowed = /*#__PURE__*/_classPrivateFieldLooseKey("assertNewUploadAllowed");
  40. var _checkAndCreateFileStateObject = /*#__PURE__*/_classPrivateFieldLooseKey("checkAndCreateFileStateObject");
  41. var _startIfAutoProceed = /*#__PURE__*/_classPrivateFieldLooseKey("startIfAutoProceed");
  42. var _addListeners = /*#__PURE__*/_classPrivateFieldLooseKey("addListeners");
  43. var _updateOnlineStatus = /*#__PURE__*/_classPrivateFieldLooseKey("updateOnlineStatus");
  44. var _createUpload = /*#__PURE__*/_classPrivateFieldLooseKey("createUpload");
  45. var _getUpload = /*#__PURE__*/_classPrivateFieldLooseKey("getUpload");
  46. var _removeUpload = /*#__PURE__*/_classPrivateFieldLooseKey("removeUpload");
  47. var _runUpload = /*#__PURE__*/_classPrivateFieldLooseKey("runUpload");
  48. _Symbol$for = Symbol.for('uppy test: getPlugins');
  49. _Symbol$for2 = Symbol.for('uppy test: createUpload');
  50. class Uppy {
  51. /** @type {Record<string, BasePlugin[]>} */
  52. /**
  53. * Instantiate Uppy
  54. *
  55. * @param {object} opts — Uppy options
  56. */
  57. constructor(_opts) {
  58. Object.defineProperty(this, _runUpload, {
  59. value: _runUpload2
  60. });
  61. Object.defineProperty(this, _removeUpload, {
  62. value: _removeUpload2
  63. });
  64. Object.defineProperty(this, _getUpload, {
  65. value: _getUpload2
  66. });
  67. Object.defineProperty(this, _createUpload, {
  68. value: _createUpload2
  69. });
  70. Object.defineProperty(this, _addListeners, {
  71. value: _addListeners2
  72. });
  73. Object.defineProperty(this, _startIfAutoProceed, {
  74. value: _startIfAutoProceed2
  75. });
  76. Object.defineProperty(this, _checkAndCreateFileStateObject, {
  77. value: _checkAndCreateFileStateObject2
  78. });
  79. Object.defineProperty(this, _assertNewUploadAllowed, {
  80. value: _assertNewUploadAllowed2
  81. });
  82. Object.defineProperty(this, _checkRequiredMetaFields, {
  83. value: _checkRequiredMetaFields2
  84. });
  85. Object.defineProperty(this, _checkRequiredMetaFieldsOnFile, {
  86. value: _checkRequiredMetaFieldsOnFile2
  87. });
  88. Object.defineProperty(this, _informAndEmit, {
  89. value: _informAndEmit2
  90. });
  91. Object.defineProperty(this, _plugins, {
  92. writable: true,
  93. value: Object.create(null)
  94. });
  95. Object.defineProperty(this, _restricter, {
  96. writable: true,
  97. value: void 0
  98. });
  99. Object.defineProperty(this, _storeUnsubscribe, {
  100. writable: true,
  101. value: void 0
  102. });
  103. Object.defineProperty(this, _emitter, {
  104. writable: true,
  105. value: ee()
  106. });
  107. Object.defineProperty(this, _preProcessors, {
  108. writable: true,
  109. value: new Set()
  110. });
  111. Object.defineProperty(this, _uploaders, {
  112. writable: true,
  113. value: new Set()
  114. });
  115. Object.defineProperty(this, _postProcessors, {
  116. writable: true,
  117. value: new Set()
  118. });
  119. Object.defineProperty(this, _updateOnlineStatus, {
  120. writable: true,
  121. value: this.updateOnlineStatus.bind(this)
  122. });
  123. this.defaultLocale = locale;
  124. const defaultOptions = {
  125. id: 'uppy',
  126. autoProceed: false,
  127. /**
  128. * @deprecated The method should not be used
  129. */
  130. allowMultipleUploads: true,
  131. allowMultipleUploadBatches: true,
  132. debug: false,
  133. restrictions: _Restricter.defaultOptions,
  134. meta: {},
  135. onBeforeFileAdded: currentFile => currentFile,
  136. onBeforeUpload: files => files,
  137. store: DefaultStore(),
  138. logger: _loggers.justErrorsLogger,
  139. infoTimeout: 5000
  140. }; // Merge default options with the ones set by user,
  141. // making sure to merge restrictions too
  142. this.opts = { ...defaultOptions,
  143. ..._opts,
  144. restrictions: { ...defaultOptions.restrictions,
  145. ...(_opts && _opts.restrictions)
  146. }
  147. }; // Support debug: true for backwards-compatability, unless logger is set in opts
  148. // opts instead of this.opts to avoid comparing objects — we set logger: justErrorsLogger in defaultOptions
  149. if (_opts && _opts.logger && _opts.debug) {
  150. 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');
  151. } else if (_opts && _opts.debug) {
  152. this.opts.logger = _loggers.debugLogger;
  153. }
  154. this.log(`Using Core v${this.constructor.VERSION}`);
  155. this.i18nInit(); // ___Why throttle at 500ms?
  156. // - We must throttle at >250ms for superfocus in Dashboard to work well
  157. // (because animation takes 0.25s, and we want to wait for all animations to be over before refocusing).
  158. // [Practical Check]: if thottle is at 100ms, then if you are uploading a file,
  159. // and click 'ADD MORE FILES', - focus won't activate in Firefox.
  160. // - We must throttle at around >500ms to avoid performance lags.
  161. // [Practical Check] Firefox, try to upload a big file for a prolonged period of time. Laptop will start to heat up.
  162. this.calculateProgress = throttle(this.calculateProgress.bind(this), 500, {
  163. leading: true,
  164. trailing: true
  165. });
  166. this.store = this.opts.store;
  167. this.setState({
  168. plugins: {},
  169. files: {},
  170. currentUploads: {},
  171. allowNewUpload: true,
  172. capabilities: {
  173. uploadProgress: supportsUploadProgress(),
  174. individualCancellation: true,
  175. resumableUploads: false
  176. },
  177. totalProgress: 0,
  178. meta: { ...this.opts.meta
  179. },
  180. info: [],
  181. recoveredState: null
  182. });
  183. _classPrivateFieldLooseBase(this, _restricter)[_restricter] = new _Restricter.Restricter(() => this.opts, this.i18n);
  184. _classPrivateFieldLooseBase(this, _storeUnsubscribe)[_storeUnsubscribe] = this.store.subscribe((prevState, nextState, patch) => {
  185. this.emit('state-update', prevState, nextState, patch);
  186. this.updateAll(nextState);
  187. }); // Exposing uppy object on window for debugging and testing
  188. if (this.opts.debug && typeof window !== 'undefined') {
  189. window[this.opts.id] = this;
  190. }
  191. _classPrivateFieldLooseBase(this, _addListeners)[_addListeners]();
  192. }
  193. emit(event) {
  194. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  195. args[_key - 1] = arguments[_key];
  196. }
  197. _classPrivateFieldLooseBase(this, _emitter)[_emitter].emit(event, ...args);
  198. }
  199. on(event, callback) {
  200. _classPrivateFieldLooseBase(this, _emitter)[_emitter].on(event, callback);
  201. return this;
  202. }
  203. once(event, callback) {
  204. _classPrivateFieldLooseBase(this, _emitter)[_emitter].once(event, callback);
  205. return this;
  206. }
  207. off(event, callback) {
  208. _classPrivateFieldLooseBase(this, _emitter)[_emitter].off(event, callback);
  209. return this;
  210. }
  211. /**
  212. * Iterate on all plugins and run `update` on them.
  213. * Called each time state changes.
  214. *
  215. */
  216. updateAll(state) {
  217. this.iteratePlugins(plugin => {
  218. plugin.update(state);
  219. });
  220. }
  221. /**
  222. * Updates state with a patch
  223. *
  224. * @param {object} patch {foo: 'bar'}
  225. */
  226. setState(patch) {
  227. this.store.setState(patch);
  228. }
  229. /**
  230. * Returns current state.
  231. *
  232. * @returns {object}
  233. */
  234. getState() {
  235. return this.store.getState();
  236. }
  237. /**
  238. * Back compat for when uppy.state is used instead of uppy.getState().
  239. *
  240. * @deprecated
  241. */
  242. get state() {
  243. // Here, state is a non-enumerable property.
  244. return this.getState();
  245. }
  246. /**
  247. * Shorthand to set state for a specific file.
  248. */
  249. setFileState(fileID, state) {
  250. if (!this.getState().files[fileID]) {
  251. throw new Error(`Can’t set state for ${fileID} (the file could have been removed)`);
  252. }
  253. this.setState({
  254. files: { ...this.getState().files,
  255. [fileID]: { ...this.getState().files[fileID],
  256. ...state
  257. }
  258. }
  259. });
  260. }
  261. i18nInit() {
  262. const translator = new Translator([this.defaultLocale, this.opts.locale]);
  263. this.i18n = translator.translate.bind(translator);
  264. this.i18nArray = translator.translateArray.bind(translator);
  265. this.locale = translator.locale;
  266. }
  267. setOptions(newOpts) {
  268. this.opts = { ...this.opts,
  269. ...newOpts,
  270. restrictions: { ...this.opts.restrictions,
  271. ...(newOpts && newOpts.restrictions)
  272. }
  273. };
  274. if (newOpts.meta) {
  275. this.setMeta(newOpts.meta);
  276. }
  277. this.i18nInit();
  278. if (newOpts.locale) {
  279. this.iteratePlugins(plugin => {
  280. plugin.setOptions();
  281. });
  282. } // Note: this is not the preact `setState`, it's an internal function that has the same name.
  283. this.setState(); // so that UI re-renders with new options
  284. }
  285. resetProgress() {
  286. const defaultProgress = {
  287. percentage: 0,
  288. bytesUploaded: 0,
  289. uploadComplete: false,
  290. uploadStarted: null
  291. };
  292. const files = { ...this.getState().files
  293. };
  294. const updatedFiles = {};
  295. Object.keys(files).forEach(fileID => {
  296. const updatedFile = { ...files[fileID]
  297. };
  298. updatedFile.progress = { ...updatedFile.progress,
  299. ...defaultProgress
  300. };
  301. updatedFiles[fileID] = updatedFile;
  302. });
  303. this.setState({
  304. files: updatedFiles,
  305. totalProgress: 0
  306. });
  307. this.emit('reset-progress');
  308. }
  309. addPreProcessor(fn) {
  310. _classPrivateFieldLooseBase(this, _preProcessors)[_preProcessors].add(fn);
  311. }
  312. removePreProcessor(fn) {
  313. return _classPrivateFieldLooseBase(this, _preProcessors)[_preProcessors].delete(fn);
  314. }
  315. addPostProcessor(fn) {
  316. _classPrivateFieldLooseBase(this, _postProcessors)[_postProcessors].add(fn);
  317. }
  318. removePostProcessor(fn) {
  319. return _classPrivateFieldLooseBase(this, _postProcessors)[_postProcessors].delete(fn);
  320. }
  321. addUploader(fn) {
  322. _classPrivateFieldLooseBase(this, _uploaders)[_uploaders].add(fn);
  323. }
  324. removeUploader(fn) {
  325. return _classPrivateFieldLooseBase(this, _uploaders)[_uploaders].delete(fn);
  326. }
  327. setMeta(data) {
  328. const updatedMeta = { ...this.getState().meta,
  329. ...data
  330. };
  331. const updatedFiles = { ...this.getState().files
  332. };
  333. Object.keys(updatedFiles).forEach(fileID => {
  334. updatedFiles[fileID] = { ...updatedFiles[fileID],
  335. meta: { ...updatedFiles[fileID].meta,
  336. ...data
  337. }
  338. };
  339. });
  340. this.log('Adding metadata:');
  341. this.log(data);
  342. this.setState({
  343. meta: updatedMeta,
  344. files: updatedFiles
  345. });
  346. }
  347. setFileMeta(fileID, data) {
  348. const updatedFiles = { ...this.getState().files
  349. };
  350. if (!updatedFiles[fileID]) {
  351. this.log('Was trying to set metadata for a file that has been removed: ', fileID);
  352. return;
  353. }
  354. const newMeta = { ...updatedFiles[fileID].meta,
  355. ...data
  356. };
  357. updatedFiles[fileID] = { ...updatedFiles[fileID],
  358. meta: newMeta
  359. };
  360. this.setState({
  361. files: updatedFiles
  362. });
  363. }
  364. /**
  365. * Get a file object.
  366. *
  367. * @param {string} fileID The ID of the file object to return.
  368. */
  369. getFile(fileID) {
  370. return this.getState().files[fileID];
  371. }
  372. /**
  373. * Get all files in an array.
  374. */
  375. getFiles() {
  376. const {
  377. files
  378. } = this.getState();
  379. return Object.values(files);
  380. }
  381. getObjectOfFilesPerState() {
  382. const {
  383. files: filesObject,
  384. totalProgress,
  385. error
  386. } = this.getState();
  387. const files = Object.values(filesObject);
  388. const inProgressFiles = files.filter(_ref => {
  389. let {
  390. progress
  391. } = _ref;
  392. return !progress.uploadComplete && progress.uploadStarted;
  393. });
  394. const newFiles = files.filter(file => !file.progress.uploadStarted);
  395. const startedFiles = files.filter(file => file.progress.uploadStarted || file.progress.preprocess || file.progress.postprocess);
  396. const uploadStartedFiles = files.filter(file => file.progress.uploadStarted);
  397. const pausedFiles = files.filter(file => file.isPaused);
  398. const completeFiles = files.filter(file => file.progress.uploadComplete);
  399. const erroredFiles = files.filter(file => file.error);
  400. const inProgressNotPausedFiles = inProgressFiles.filter(file => !file.isPaused);
  401. const processingFiles = files.filter(file => file.progress.preprocess || file.progress.postprocess);
  402. return {
  403. newFiles,
  404. startedFiles,
  405. uploadStartedFiles,
  406. pausedFiles,
  407. completeFiles,
  408. erroredFiles,
  409. inProgressFiles,
  410. inProgressNotPausedFiles,
  411. processingFiles,
  412. isUploadStarted: uploadStartedFiles.length > 0,
  413. isAllComplete: totalProgress === 100 && completeFiles.length === files.length && processingFiles.length === 0,
  414. isAllErrored: !!error && erroredFiles.length === files.length,
  415. isAllPaused: inProgressFiles.length !== 0 && pausedFiles.length === inProgressFiles.length,
  416. isUploadInProgress: inProgressFiles.length > 0,
  417. isSomeGhost: files.some(file => file.isGhost)
  418. };
  419. }
  420. /*
  421. * @constructs
  422. * @param { Error } error
  423. * @param { undefined } file
  424. */
  425. /*
  426. * @constructs
  427. * @param { RestrictionError } error
  428. * @param { UppyFile | undefined } file
  429. */
  430. validateRestrictions(file, files) {
  431. if (files === void 0) {
  432. files = this.getFiles();
  433. }
  434. // TODO: directly return the Restriction error in next major version.
  435. // we create RestrictionError's just to discard immediately, which doesn't make sense.
  436. try {
  437. _classPrivateFieldLooseBase(this, _restricter)[_restricter].validate(file, files);
  438. return {
  439. result: true
  440. };
  441. } catch (err) {
  442. return {
  443. result: false,
  444. reason: err.message
  445. };
  446. }
  447. }
  448. checkIfFileAlreadyExists(fileID) {
  449. const {
  450. files
  451. } = this.getState();
  452. if (files[fileID] && !files[fileID].isGhost) {
  453. return true;
  454. }
  455. return false;
  456. }
  457. /**
  458. * Create a file state object based on user-provided `addFile()` options.
  459. *
  460. * Note this is extremely side-effectful and should only be done when a file state object
  461. * will be added to state immediately afterward!
  462. *
  463. * The `files` value is passed in because it may be updated by the caller without updating the store.
  464. */
  465. /**
  466. * Add a new file to `state.files`. This will run `onBeforeFileAdded`,
  467. * try to guess file type in a clever way, check file against restrictions,
  468. * and start an upload if `autoProceed === true`.
  469. *
  470. * @param {object} file object to add
  471. * @returns {string} id for the added file
  472. */
  473. addFile(file) {
  474. _classPrivateFieldLooseBase(this, _assertNewUploadAllowed)[_assertNewUploadAllowed](file);
  475. const {
  476. files
  477. } = this.getState();
  478. let newFile = _classPrivateFieldLooseBase(this, _checkAndCreateFileStateObject)[_checkAndCreateFileStateObject](files, file); // Users are asked to re-select recovered files without data,
  479. // and to keep the progress, meta and everthing else, we only replace said data
  480. if (files[newFile.id] && files[newFile.id].isGhost) {
  481. newFile = { ...files[newFile.id],
  482. data: file.data,
  483. isGhost: false
  484. };
  485. this.log(`Replaced the blob in the restored ghost file: ${newFile.name}, ${newFile.id}`);
  486. }
  487. this.setState({
  488. files: { ...files,
  489. [newFile.id]: newFile
  490. }
  491. });
  492. this.emit('file-added', newFile);
  493. this.emit('files-added', [newFile]);
  494. this.log(`Added file: ${newFile.name}, ${newFile.id}, mime type: ${newFile.type}`);
  495. _classPrivateFieldLooseBase(this, _startIfAutoProceed)[_startIfAutoProceed]();
  496. return newFile.id;
  497. }
  498. /**
  499. * Add multiple files to `state.files`. See the `addFile()` documentation.
  500. *
  501. * If an error occurs while adding a file, it is logged and the user is notified.
  502. * This is good for UI plugins, but not for programmatic use.
  503. * Programmatic users should usually still use `addFile()` on individual files.
  504. */
  505. addFiles(fileDescriptors) {
  506. _classPrivateFieldLooseBase(this, _assertNewUploadAllowed)[_assertNewUploadAllowed](); // create a copy of the files object only once
  507. const files = { ...this.getState().files
  508. };
  509. const newFiles = [];
  510. const errors = [];
  511. for (let i = 0; i < fileDescriptors.length; i++) {
  512. try {
  513. let newFile = _classPrivateFieldLooseBase(this, _checkAndCreateFileStateObject)[_checkAndCreateFileStateObject](files, fileDescriptors[i]); // Users are asked to re-select recovered files without data,
  514. // and to keep the progress, meta and everthing else, we only replace said data
  515. if (files[newFile.id] && files[newFile.id].isGhost) {
  516. newFile = { ...files[newFile.id],
  517. data: fileDescriptors[i].data,
  518. isGhost: false
  519. };
  520. this.log(`Replaced blob in a ghost file: ${newFile.name}, ${newFile.id}`);
  521. }
  522. files[newFile.id] = newFile;
  523. newFiles.push(newFile);
  524. } catch (err) {
  525. if (!err.isRestriction) {
  526. errors.push(err);
  527. }
  528. }
  529. }
  530. this.setState({
  531. files
  532. });
  533. newFiles.forEach(newFile => {
  534. this.emit('file-added', newFile);
  535. });
  536. this.emit('files-added', newFiles);
  537. if (newFiles.length > 5) {
  538. this.log(`Added batch of ${newFiles.length} files`);
  539. } else {
  540. Object.keys(newFiles).forEach(fileID => {
  541. this.log(`Added file: ${newFiles[fileID].name}\n id: ${newFiles[fileID].id}\n type: ${newFiles[fileID].type}`);
  542. });
  543. }
  544. if (newFiles.length > 0) {
  545. _classPrivateFieldLooseBase(this, _startIfAutoProceed)[_startIfAutoProceed]();
  546. }
  547. if (errors.length > 0) {
  548. let message = 'Multiple errors occurred while adding files:\n';
  549. errors.forEach(subError => {
  550. message += `\n * ${subError.message}`;
  551. });
  552. this.info({
  553. message: this.i18n('addBulkFilesFailed', {
  554. smart_count: errors.length
  555. }),
  556. details: message
  557. }, 'error', this.opts.infoTimeout);
  558. if (typeof AggregateError === 'function') {
  559. throw new AggregateError(errors, message);
  560. } else {
  561. const err = new Error(message);
  562. err.errors = errors;
  563. throw err;
  564. }
  565. }
  566. }
  567. removeFiles(fileIDs, reason) {
  568. const {
  569. files,
  570. currentUploads
  571. } = this.getState();
  572. const updatedFiles = { ...files
  573. };
  574. const updatedUploads = { ...currentUploads
  575. };
  576. const removedFiles = Object.create(null);
  577. fileIDs.forEach(fileID => {
  578. if (files[fileID]) {
  579. removedFiles[fileID] = files[fileID];
  580. delete updatedFiles[fileID];
  581. }
  582. }); // Remove files from the `fileIDs` list in each upload.
  583. function fileIsNotRemoved(uploadFileID) {
  584. return removedFiles[uploadFileID] === undefined;
  585. }
  586. Object.keys(updatedUploads).forEach(uploadID => {
  587. const newFileIDs = currentUploads[uploadID].fileIDs.filter(fileIsNotRemoved); // Remove the upload if no files are associated with it anymore.
  588. if (newFileIDs.length === 0) {
  589. delete updatedUploads[uploadID];
  590. return;
  591. }
  592. const {
  593. capabilities
  594. } = this.getState();
  595. if (newFileIDs.length !== currentUploads[uploadID].fileIDs.length && !capabilities.individualCancellation) {
  596. throw new Error('individualCancellation is disabled');
  597. }
  598. updatedUploads[uploadID] = { ...currentUploads[uploadID],
  599. fileIDs: newFileIDs
  600. };
  601. });
  602. const stateUpdate = {
  603. currentUploads: updatedUploads,
  604. files: updatedFiles
  605. }; // If all files were removed - allow new uploads,
  606. // and clear recoveredState
  607. if (Object.keys(updatedFiles).length === 0) {
  608. stateUpdate.allowNewUpload = true;
  609. stateUpdate.error = null;
  610. stateUpdate.recoveredState = null;
  611. }
  612. this.setState(stateUpdate);
  613. this.calculateTotalProgress();
  614. const removedFileIDs = Object.keys(removedFiles);
  615. removedFileIDs.forEach(fileID => {
  616. this.emit('file-removed', removedFiles[fileID], reason);
  617. });
  618. if (removedFileIDs.length > 5) {
  619. this.log(`Removed ${removedFileIDs.length} files`);
  620. } else {
  621. this.log(`Removed files: ${removedFileIDs.join(', ')}`);
  622. }
  623. }
  624. removeFile(fileID, reason) {
  625. if (reason === void 0) {
  626. reason = null;
  627. }
  628. this.removeFiles([fileID], reason);
  629. }
  630. pauseResume(fileID) {
  631. if (!this.getState().capabilities.resumableUploads || this.getFile(fileID).uploadComplete) {
  632. return undefined;
  633. }
  634. const wasPaused = this.getFile(fileID).isPaused || false;
  635. const isPaused = !wasPaused;
  636. this.setFileState(fileID, {
  637. isPaused
  638. });
  639. this.emit('upload-pause', fileID, isPaused);
  640. return isPaused;
  641. }
  642. pauseAll() {
  643. const updatedFiles = { ...this.getState().files
  644. };
  645. const inProgressUpdatedFiles = Object.keys(updatedFiles).filter(file => {
  646. return !updatedFiles[file].progress.uploadComplete && updatedFiles[file].progress.uploadStarted;
  647. });
  648. inProgressUpdatedFiles.forEach(file => {
  649. const updatedFile = { ...updatedFiles[file],
  650. isPaused: true
  651. };
  652. updatedFiles[file] = updatedFile;
  653. });
  654. this.setState({
  655. files: updatedFiles
  656. });
  657. this.emit('pause-all');
  658. }
  659. resumeAll() {
  660. const updatedFiles = { ...this.getState().files
  661. };
  662. const inProgressUpdatedFiles = Object.keys(updatedFiles).filter(file => {
  663. return !updatedFiles[file].progress.uploadComplete && updatedFiles[file].progress.uploadStarted;
  664. });
  665. inProgressUpdatedFiles.forEach(file => {
  666. const updatedFile = { ...updatedFiles[file],
  667. isPaused: false,
  668. error: null
  669. };
  670. updatedFiles[file] = updatedFile;
  671. });
  672. this.setState({
  673. files: updatedFiles
  674. });
  675. this.emit('resume-all');
  676. }
  677. retryAll() {
  678. const updatedFiles = { ...this.getState().files
  679. };
  680. const filesToRetry = Object.keys(updatedFiles).filter(file => {
  681. return updatedFiles[file].error;
  682. });
  683. filesToRetry.forEach(file => {
  684. const updatedFile = { ...updatedFiles[file],
  685. isPaused: false,
  686. error: null
  687. };
  688. updatedFiles[file] = updatedFile;
  689. });
  690. this.setState({
  691. files: updatedFiles,
  692. error: null
  693. });
  694. this.emit('retry-all', filesToRetry);
  695. if (filesToRetry.length === 0) {
  696. return Promise.resolve({
  697. successful: [],
  698. failed: []
  699. });
  700. }
  701. const uploadID = _classPrivateFieldLooseBase(this, _createUpload)[_createUpload](filesToRetry, {
  702. forceAllowNewUpload: true // create new upload even if allowNewUpload: false
  703. });
  704. return _classPrivateFieldLooseBase(this, _runUpload)[_runUpload](uploadID);
  705. }
  706. cancelAll(_temp) {
  707. let {
  708. reason = 'user'
  709. } = _temp === void 0 ? {} : _temp;
  710. this.emit('cancel-all', {
  711. reason
  712. }); // Only remove existing uploads if user is canceling
  713. if (reason === 'user') {
  714. const {
  715. files
  716. } = this.getState();
  717. const fileIDs = Object.keys(files);
  718. if (fileIDs.length) {
  719. this.removeFiles(fileIDs, 'cancel-all');
  720. }
  721. this.setState({
  722. totalProgress: 0,
  723. error: null,
  724. recoveredState: null
  725. });
  726. }
  727. }
  728. retryUpload(fileID) {
  729. this.setFileState(fileID, {
  730. error: null,
  731. isPaused: false
  732. });
  733. this.emit('upload-retry', fileID);
  734. const uploadID = _classPrivateFieldLooseBase(this, _createUpload)[_createUpload]([fileID], {
  735. forceAllowNewUpload: true // create new upload even if allowNewUpload: false
  736. });
  737. return _classPrivateFieldLooseBase(this, _runUpload)[_runUpload](uploadID);
  738. } // todo remove in next major. what is the point of the reset method when we have cancelAll or vice versa?
  739. reset() {
  740. this.cancelAll(...arguments);
  741. }
  742. logout() {
  743. this.iteratePlugins(plugin => {
  744. if (plugin.provider && plugin.provider.logout) {
  745. plugin.provider.logout();
  746. }
  747. });
  748. }
  749. calculateProgress(file, data) {
  750. if (file == null || !this.getFile(file.id)) {
  751. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  752. return;
  753. } // bytesTotal may be null or zero; in that case we can't divide by it
  754. const canHavePercentage = Number.isFinite(data.bytesTotal) && data.bytesTotal > 0;
  755. this.setFileState(file.id, {
  756. progress: { ...this.getFile(file.id).progress,
  757. bytesUploaded: data.bytesUploaded,
  758. bytesTotal: data.bytesTotal,
  759. percentage: canHavePercentage ? Math.round(data.bytesUploaded / data.bytesTotal * 100) : 0
  760. }
  761. });
  762. this.calculateTotalProgress();
  763. }
  764. calculateTotalProgress() {
  765. // calculate total progress, using the number of files currently uploading,
  766. // multiplied by 100 and the summ of individual progress of each file
  767. const files = this.getFiles();
  768. const inProgress = files.filter(file => {
  769. return file.progress.uploadStarted || file.progress.preprocess || file.progress.postprocess;
  770. });
  771. if (inProgress.length === 0) {
  772. this.emit('progress', 0);
  773. this.setState({
  774. totalProgress: 0
  775. });
  776. return;
  777. }
  778. const sizedFiles = inProgress.filter(file => file.progress.bytesTotal != null);
  779. const unsizedFiles = inProgress.filter(file => file.progress.bytesTotal == null);
  780. if (sizedFiles.length === 0) {
  781. const progressMax = inProgress.length * 100;
  782. const currentProgress = unsizedFiles.reduce((acc, file) => {
  783. return acc + file.progress.percentage;
  784. }, 0);
  785. const totalProgress = Math.round(currentProgress / progressMax * 100);
  786. this.setState({
  787. totalProgress
  788. });
  789. return;
  790. }
  791. let totalSize = sizedFiles.reduce((acc, file) => {
  792. return acc + file.progress.bytesTotal;
  793. }, 0);
  794. const averageSize = totalSize / sizedFiles.length;
  795. totalSize += averageSize * unsizedFiles.length;
  796. let uploadedSize = 0;
  797. sizedFiles.forEach(file => {
  798. uploadedSize += file.progress.bytesUploaded;
  799. });
  800. unsizedFiles.forEach(file => {
  801. uploadedSize += averageSize * (file.progress.percentage || 0) / 100;
  802. });
  803. let totalProgress = totalSize === 0 ? 0 : Math.round(uploadedSize / totalSize * 100); // hot fix, because:
  804. // uploadedSize ended up larger than totalSize, resulting in 1325% total
  805. if (totalProgress > 100) {
  806. totalProgress = 100;
  807. }
  808. this.setState({
  809. totalProgress
  810. });
  811. this.emit('progress', totalProgress);
  812. }
  813. /**
  814. * Registers listeners for all global actions, like:
  815. * `error`, `file-removed`, `upload-progress`
  816. */
  817. updateOnlineStatus() {
  818. const online = typeof window.navigator.onLine !== 'undefined' ? window.navigator.onLine : true;
  819. if (!online) {
  820. this.emit('is-offline');
  821. this.info(this.i18n('noInternetConnection'), 'error', 0);
  822. this.wasOffline = true;
  823. } else {
  824. this.emit('is-online');
  825. if (this.wasOffline) {
  826. this.emit('back-online');
  827. this.info(this.i18n('connectedToInternet'), 'success', 3000);
  828. this.wasOffline = false;
  829. }
  830. }
  831. }
  832. getID() {
  833. return this.opts.id;
  834. }
  835. /**
  836. * Registers a plugin with Core.
  837. *
  838. * @param {object} Plugin object
  839. * @param {object} [opts] object with options to be passed to Plugin
  840. * @returns {object} self for chaining
  841. */
  842. // eslint-disable-next-line no-shadow
  843. use(Plugin, opts) {
  844. if (typeof Plugin !== 'function') {
  845. const msg = `Expected a plugin class, but got ${Plugin === null ? 'null' : typeof Plugin}.` + ' Please verify that the plugin was imported and spelled correctly.';
  846. throw new TypeError(msg);
  847. } // Instantiate
  848. const plugin = new Plugin(this, opts);
  849. const pluginId = plugin.id;
  850. if (!pluginId) {
  851. throw new Error('Your plugin must have an id');
  852. }
  853. if (!plugin.type) {
  854. throw new Error('Your plugin must have a type');
  855. }
  856. const existsPluginAlready = this.getPlugin(pluginId);
  857. if (existsPluginAlready) {
  858. const msg = `Already found a plugin named '${existsPluginAlready.id}'. ` + `Tried to use: '${pluginId}'.\n` + 'Uppy plugins must have unique `id` options. See https://uppy.io/docs/plugins/#id.';
  859. throw new Error(msg);
  860. }
  861. if (Plugin.VERSION) {
  862. this.log(`Using ${pluginId} v${Plugin.VERSION}`);
  863. }
  864. if (plugin.type in _classPrivateFieldLooseBase(this, _plugins)[_plugins]) {
  865. _classPrivateFieldLooseBase(this, _plugins)[_plugins][plugin.type].push(plugin);
  866. } else {
  867. _classPrivateFieldLooseBase(this, _plugins)[_plugins][plugin.type] = [plugin];
  868. }
  869. plugin.install();
  870. return this;
  871. }
  872. /**
  873. * Find one Plugin by name.
  874. *
  875. * @param {string} id plugin id
  876. * @returns {BasePlugin|undefined}
  877. */
  878. getPlugin(id) {
  879. for (const plugins of Object.values(_classPrivateFieldLooseBase(this, _plugins)[_plugins])) {
  880. const foundPlugin = plugins.find(plugin => plugin.id === id);
  881. if (foundPlugin != null) return foundPlugin;
  882. }
  883. return undefined;
  884. }
  885. [_Symbol$for](type) {
  886. return _classPrivateFieldLooseBase(this, _plugins)[_plugins][type];
  887. }
  888. /**
  889. * Iterate through all `use`d plugins.
  890. *
  891. * @param {Function} method that will be run on each plugin
  892. */
  893. iteratePlugins(method) {
  894. Object.values(_classPrivateFieldLooseBase(this, _plugins)[_plugins]).flat(1).forEach(method);
  895. }
  896. /**
  897. * Uninstall and remove a plugin.
  898. *
  899. * @param {object} instance The plugin instance to remove.
  900. */
  901. removePlugin(instance) {
  902. this.log(`Removing plugin ${instance.id}`);
  903. this.emit('plugin-remove', instance);
  904. if (instance.uninstall) {
  905. instance.uninstall();
  906. }
  907. const list = _classPrivateFieldLooseBase(this, _plugins)[_plugins][instance.type]; // list.indexOf failed here, because Vue3 converted the plugin instance
  908. // to a Proxy object, which failed the strict comparison test:
  909. // obj !== objProxy
  910. const index = list.findIndex(item => item.id === instance.id);
  911. if (index !== -1) {
  912. list.splice(index, 1);
  913. }
  914. const state = this.getState();
  915. const updatedState = {
  916. plugins: { ...state.plugins,
  917. [instance.id]: undefined
  918. }
  919. };
  920. this.setState(updatedState);
  921. }
  922. /**
  923. * Uninstall all plugins and close down this Uppy instance.
  924. */
  925. close(_temp2) {
  926. let {
  927. reason
  928. } = _temp2 === void 0 ? {} : _temp2;
  929. this.log(`Closing Uppy instance ${this.opts.id}: removing all files and uninstalling plugins`);
  930. this.cancelAll({
  931. reason
  932. });
  933. _classPrivateFieldLooseBase(this, _storeUnsubscribe)[_storeUnsubscribe]();
  934. this.iteratePlugins(plugin => {
  935. this.removePlugin(plugin);
  936. });
  937. if (typeof window !== 'undefined' && window.removeEventListener) {
  938. window.removeEventListener('online', _classPrivateFieldLooseBase(this, _updateOnlineStatus)[_updateOnlineStatus]);
  939. window.removeEventListener('offline', _classPrivateFieldLooseBase(this, _updateOnlineStatus)[_updateOnlineStatus]);
  940. }
  941. }
  942. hideInfo() {
  943. const {
  944. info
  945. } = this.getState();
  946. this.setState({
  947. info: info.slice(1)
  948. });
  949. this.emit('info-hidden');
  950. }
  951. /**
  952. * Set info message in `state.info`, so that UI plugins like `Informer`
  953. * can display the message.
  954. *
  955. * @param {string | object} message Message to be displayed by the informer
  956. * @param {string} [type]
  957. * @param {number} [duration]
  958. */
  959. info(message, type, duration) {
  960. if (type === void 0) {
  961. type = 'info';
  962. }
  963. if (duration === void 0) {
  964. duration = 3000;
  965. }
  966. const isComplexMessage = typeof message === 'object';
  967. this.setState({
  968. info: [...this.getState().info, {
  969. type,
  970. message: isComplexMessage ? message.message : message,
  971. details: isComplexMessage ? message.details : null
  972. }]
  973. });
  974. setTimeout(() => this.hideInfo(), duration);
  975. this.emit('info-visible');
  976. }
  977. /**
  978. * Passes messages to a function, provided in `opts.logger`.
  979. * If `opts.logger: Uppy.debugLogger` or `opts.debug: true`, logs to the browser console.
  980. *
  981. * @param {string|object} message to log
  982. * @param {string} [type] optional `error` or `warning`
  983. */
  984. log(message, type) {
  985. const {
  986. logger
  987. } = this.opts;
  988. switch (type) {
  989. case 'error':
  990. logger.error(message);
  991. break;
  992. case 'warning':
  993. logger.warn(message);
  994. break;
  995. default:
  996. logger.debug(message);
  997. break;
  998. }
  999. }
  1000. /**
  1001. * Restore an upload by its ID.
  1002. */
  1003. restore(uploadID) {
  1004. this.log(`Core: attempting to restore upload "${uploadID}"`);
  1005. if (!this.getState().currentUploads[uploadID]) {
  1006. _classPrivateFieldLooseBase(this, _removeUpload)[_removeUpload](uploadID);
  1007. return Promise.reject(new Error('Nonexistent upload'));
  1008. }
  1009. return _classPrivateFieldLooseBase(this, _runUpload)[_runUpload](uploadID);
  1010. }
  1011. /**
  1012. * Create an upload for a bunch of files.
  1013. *
  1014. * @param {Array<string>} fileIDs File IDs to include in this upload.
  1015. * @returns {string} ID of this upload.
  1016. */
  1017. [_Symbol$for2]() {
  1018. return _classPrivateFieldLooseBase(this, _createUpload)[_createUpload](...arguments);
  1019. }
  1020. /**
  1021. * Add data to an upload's result object.
  1022. *
  1023. * @param {string} uploadID The ID of the upload.
  1024. * @param {object} data Data properties to add to the result object.
  1025. */
  1026. addResultData(uploadID, data) {
  1027. if (!_classPrivateFieldLooseBase(this, _getUpload)[_getUpload](uploadID)) {
  1028. this.log(`Not setting result for an upload that has been removed: ${uploadID}`);
  1029. return;
  1030. }
  1031. const {
  1032. currentUploads
  1033. } = this.getState();
  1034. const currentUpload = { ...currentUploads[uploadID],
  1035. result: { ...currentUploads[uploadID].result,
  1036. ...data
  1037. }
  1038. };
  1039. this.setState({
  1040. currentUploads: { ...currentUploads,
  1041. [uploadID]: currentUpload
  1042. }
  1043. });
  1044. }
  1045. /**
  1046. * Remove an upload, eg. if it has been canceled or completed.
  1047. *
  1048. * @param {string} uploadID The ID of the upload.
  1049. */
  1050. /**
  1051. * Start an upload for all the files that are not currently being uploaded.
  1052. *
  1053. * @returns {Promise}
  1054. */
  1055. upload() {
  1056. var _classPrivateFieldLoo;
  1057. if (!((_classPrivateFieldLoo = _classPrivateFieldLooseBase(this, _plugins)[_plugins].uploader) != null && _classPrivateFieldLoo.length)) {
  1058. this.log('No uploader type plugins are used', 'warning');
  1059. }
  1060. let {
  1061. files
  1062. } = this.getState();
  1063. const onBeforeUploadResult = this.opts.onBeforeUpload(files);
  1064. if (onBeforeUploadResult === false) {
  1065. return Promise.reject(new Error('Not starting the upload because onBeforeUpload returned false'));
  1066. }
  1067. if (onBeforeUploadResult && typeof onBeforeUploadResult === 'object') {
  1068. files = onBeforeUploadResult; // Updating files in state, because uploader plugins receive file IDs,
  1069. // and then fetch the actual file object from state
  1070. this.setState({
  1071. files
  1072. });
  1073. }
  1074. return Promise.resolve().then(() => _classPrivateFieldLooseBase(this, _restricter)[_restricter].validateMinNumberOfFiles(files)).catch(err => {
  1075. _classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](err);
  1076. throw err;
  1077. }).then(() => {
  1078. if (!_classPrivateFieldLooseBase(this, _checkRequiredMetaFields)[_checkRequiredMetaFields](files)) {
  1079. throw new _Restricter.RestrictionError(this.i18n('missingRequiredMetaField'));
  1080. }
  1081. }).catch(err => {
  1082. // Doing this in a separate catch because we already emited and logged
  1083. // all the errors in `checkRequiredMetaFields` so we only throw a generic
  1084. // missing fields error here.
  1085. throw err;
  1086. }).then(() => {
  1087. const {
  1088. currentUploads
  1089. } = this.getState(); // get a list of files that are currently assigned to uploads
  1090. const currentlyUploadingFiles = Object.values(currentUploads).flatMap(curr => curr.fileIDs);
  1091. const waitingFileIDs = [];
  1092. Object.keys(files).forEach(fileID => {
  1093. const file = this.getFile(fileID); // if the file hasn't started uploading and hasn't already been assigned to an upload..
  1094. if (!file.progress.uploadStarted && currentlyUploadingFiles.indexOf(fileID) === -1) {
  1095. waitingFileIDs.push(file.id);
  1096. }
  1097. });
  1098. const uploadID = _classPrivateFieldLooseBase(this, _createUpload)[_createUpload](waitingFileIDs);
  1099. return _classPrivateFieldLooseBase(this, _runUpload)[_runUpload](uploadID);
  1100. }).catch(err => {
  1101. this.emit('error', err);
  1102. this.log(err, 'error');
  1103. throw err;
  1104. });
  1105. }
  1106. }
  1107. function _informAndEmit2(error, file) {
  1108. const {
  1109. message,
  1110. details = ''
  1111. } = error;
  1112. if (error.isRestriction) {
  1113. this.emit('restriction-failed', file, error);
  1114. } else {
  1115. this.emit('error', error);
  1116. }
  1117. this.info({
  1118. message,
  1119. details
  1120. }, 'error', this.opts.infoTimeout);
  1121. this.log(`${message} ${details}`.trim(), 'error');
  1122. }
  1123. function _checkRequiredMetaFieldsOnFile2(file) {
  1124. const {
  1125. missingFields,
  1126. error
  1127. } = _classPrivateFieldLooseBase(this, _restricter)[_restricter].getMissingRequiredMetaFields(file);
  1128. if (missingFields.length > 0) {
  1129. this.setFileState(file.id, {
  1130. missingRequiredMetaFields: missingFields
  1131. });
  1132. this.log(error.message);
  1133. this.emit('restriction-failed', file, error);
  1134. return false;
  1135. }
  1136. return true;
  1137. }
  1138. function _checkRequiredMetaFields2(files) {
  1139. let success = true;
  1140. for (const file of Object.values(files)) {
  1141. if (!_classPrivateFieldLooseBase(this, _checkRequiredMetaFieldsOnFile)[_checkRequiredMetaFieldsOnFile](file)) {
  1142. success = false;
  1143. }
  1144. }
  1145. return success;
  1146. }
  1147. function _assertNewUploadAllowed2(file) {
  1148. const {
  1149. allowNewUpload
  1150. } = this.getState();
  1151. if (allowNewUpload === false) {
  1152. const error = new _Restricter.RestrictionError(this.i18n('noMoreFilesAllowed'));
  1153. _classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](error, file);
  1154. throw error;
  1155. }
  1156. }
  1157. function _checkAndCreateFileStateObject2(files, fileDescriptor) {
  1158. const fileType = getFileType(fileDescriptor);
  1159. const fileName = getFileName(fileType, fileDescriptor);
  1160. const fileExtension = getFileNameAndExtension(fileName).extension;
  1161. const isRemote = Boolean(fileDescriptor.isRemote);
  1162. const fileID = generateFileID({ ...fileDescriptor,
  1163. type: fileType
  1164. });
  1165. if (this.checkIfFileAlreadyExists(fileID)) {
  1166. const error = new _Restricter.RestrictionError(this.i18n('noDuplicates', {
  1167. fileName
  1168. }));
  1169. _classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](error, fileDescriptor);
  1170. throw error;
  1171. }
  1172. const meta = fileDescriptor.meta || {};
  1173. meta.name = fileName;
  1174. meta.type = fileType; // `null` means the size is unknown.
  1175. const size = Number.isFinite(fileDescriptor.data.size) ? fileDescriptor.data.size : null;
  1176. let newFile = {
  1177. source: fileDescriptor.source || '',
  1178. id: fileID,
  1179. name: fileName,
  1180. extension: fileExtension || '',
  1181. meta: { ...this.getState().meta,
  1182. ...meta
  1183. },
  1184. type: fileType,
  1185. data: fileDescriptor.data,
  1186. progress: {
  1187. percentage: 0,
  1188. bytesUploaded: 0,
  1189. bytesTotal: size,
  1190. uploadComplete: false,
  1191. uploadStarted: null
  1192. },
  1193. size,
  1194. isRemote,
  1195. remote: fileDescriptor.remote || '',
  1196. preview: fileDescriptor.preview
  1197. };
  1198. const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, files);
  1199. if (onBeforeFileAddedResult === false) {
  1200. // Don’t show UI info for this error, as it should be done by the developer
  1201. const error = new _Restricter.RestrictionError('Cannot add the file because onBeforeFileAdded returned false.');
  1202. this.emit('restriction-failed', fileDescriptor, error);
  1203. throw error;
  1204. } else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) {
  1205. newFile = onBeforeFileAddedResult;
  1206. }
  1207. try {
  1208. const filesArray = Object.keys(files).map(i => files[i]);
  1209. _classPrivateFieldLooseBase(this, _restricter)[_restricter].validate(newFile, filesArray);
  1210. } catch (err) {
  1211. _classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](err, newFile);
  1212. throw err;
  1213. }
  1214. return newFile;
  1215. }
  1216. function _startIfAutoProceed2() {
  1217. if (this.opts.autoProceed && !this.scheduledAutoProceed) {
  1218. this.scheduledAutoProceed = setTimeout(() => {
  1219. this.scheduledAutoProceed = null;
  1220. this.upload().catch(err => {
  1221. if (!err.isRestriction) {
  1222. this.log(err.stack || err.message || err);
  1223. }
  1224. });
  1225. }, 4);
  1226. }
  1227. }
  1228. function _addListeners2() {
  1229. /**
  1230. * @param {Error} error
  1231. * @param {object} [file]
  1232. * @param {object} [response]
  1233. */
  1234. const errorHandler = (error, file, response) => {
  1235. let errorMsg = error.message || 'Unknown error';
  1236. if (error.details) {
  1237. errorMsg += ` ${error.details}`;
  1238. }
  1239. this.setState({
  1240. error: errorMsg
  1241. });
  1242. if (file != null && file.id in this.getState().files) {
  1243. this.setFileState(file.id, {
  1244. error: errorMsg,
  1245. response
  1246. });
  1247. }
  1248. };
  1249. this.on('error', errorHandler);
  1250. this.on('upload-error', (file, error, response) => {
  1251. errorHandler(error, file, response);
  1252. if (typeof error === 'object' && error.message) {
  1253. const newError = new Error(error.message);
  1254. newError.details = error.message;
  1255. if (error.details) {
  1256. newError.details += ` ${error.details}`;
  1257. }
  1258. newError.message = this.i18n('failedToUpload', {
  1259. file: file == null ? void 0 : file.name
  1260. });
  1261. _classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](newError);
  1262. } else {
  1263. _classPrivateFieldLooseBase(this, _informAndEmit)[_informAndEmit](error);
  1264. }
  1265. });
  1266. this.on('upload', () => {
  1267. this.setState({
  1268. error: null
  1269. });
  1270. });
  1271. this.on('upload-started', file => {
  1272. if (file == null || !this.getFile(file.id)) {
  1273. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  1274. return;
  1275. }
  1276. this.setFileState(file.id, {
  1277. progress: {
  1278. uploadStarted: Date.now(),
  1279. uploadComplete: false,
  1280. percentage: 0,
  1281. bytesUploaded: 0,
  1282. bytesTotal: file.size
  1283. }
  1284. });
  1285. });
  1286. this.on('upload-progress', this.calculateProgress);
  1287. this.on('upload-success', (file, uploadResp) => {
  1288. if (file == null || !this.getFile(file.id)) {
  1289. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  1290. return;
  1291. }
  1292. const currentProgress = this.getFile(file.id).progress;
  1293. this.setFileState(file.id, {
  1294. progress: { ...currentProgress,
  1295. postprocess: _classPrivateFieldLooseBase(this, _postProcessors)[_postProcessors].size > 0 ? {
  1296. mode: 'indeterminate'
  1297. } : null,
  1298. uploadComplete: true,
  1299. percentage: 100,
  1300. bytesUploaded: currentProgress.bytesTotal
  1301. },
  1302. response: uploadResp,
  1303. uploadURL: uploadResp.uploadURL,
  1304. isPaused: false
  1305. }); // Remote providers sometimes don't tell us the file size,
  1306. // but we can know how many bytes we uploaded once the upload is complete.
  1307. if (file.size == null) {
  1308. this.setFileState(file.id, {
  1309. size: uploadResp.bytesUploaded || currentProgress.bytesTotal
  1310. });
  1311. }
  1312. this.calculateTotalProgress();
  1313. });
  1314. this.on('preprocess-progress', (file, progress) => {
  1315. if (file == null || !this.getFile(file.id)) {
  1316. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  1317. return;
  1318. }
  1319. this.setFileState(file.id, {
  1320. progress: { ...this.getFile(file.id).progress,
  1321. preprocess: progress
  1322. }
  1323. });
  1324. });
  1325. this.on('preprocess-complete', file => {
  1326. if (file == null || !this.getFile(file.id)) {
  1327. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  1328. return;
  1329. }
  1330. const files = { ...this.getState().files
  1331. };
  1332. files[file.id] = { ...files[file.id],
  1333. progress: { ...files[file.id].progress
  1334. }
  1335. };
  1336. delete files[file.id].progress.preprocess;
  1337. this.setState({
  1338. files
  1339. });
  1340. });
  1341. this.on('postprocess-progress', (file, progress) => {
  1342. if (file == null || !this.getFile(file.id)) {
  1343. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  1344. return;
  1345. }
  1346. this.setFileState(file.id, {
  1347. progress: { ...this.getState().files[file.id].progress,
  1348. postprocess: progress
  1349. }
  1350. });
  1351. });
  1352. this.on('postprocess-complete', file => {
  1353. if (file == null || !this.getFile(file.id)) {
  1354. this.log(`Not setting progress for a file that has been removed: ${file == null ? void 0 : file.id}`);
  1355. return;
  1356. }
  1357. const files = { ...this.getState().files
  1358. };
  1359. files[file.id] = { ...files[file.id],
  1360. progress: { ...files[file.id].progress
  1361. }
  1362. };
  1363. delete files[file.id].progress.postprocess;
  1364. this.setState({
  1365. files
  1366. });
  1367. });
  1368. this.on('restored', () => {
  1369. // Files may have changed--ensure progress is still accurate.
  1370. this.calculateTotalProgress();
  1371. });
  1372. this.on('dashboard:file-edit-complete', file => {
  1373. if (file) {
  1374. _classPrivateFieldLooseBase(this, _checkRequiredMetaFieldsOnFile)[_checkRequiredMetaFieldsOnFile](file);
  1375. }
  1376. }); // show informer if offline
  1377. if (typeof window !== 'undefined' && window.addEventListener) {
  1378. window.addEventListener('online', _classPrivateFieldLooseBase(this, _updateOnlineStatus)[_updateOnlineStatus]);
  1379. window.addEventListener('offline', _classPrivateFieldLooseBase(this, _updateOnlineStatus)[_updateOnlineStatus]);
  1380. setTimeout(_classPrivateFieldLooseBase(this, _updateOnlineStatus)[_updateOnlineStatus], 3000);
  1381. }
  1382. }
  1383. function _createUpload2(fileIDs, opts) {
  1384. if (opts === void 0) {
  1385. opts = {};
  1386. }
  1387. // uppy.retryAll sets this to true — when retrying we want to ignore `allowNewUpload: false`
  1388. const {
  1389. forceAllowNewUpload = false
  1390. } = opts;
  1391. const {
  1392. allowNewUpload,
  1393. currentUploads
  1394. } = this.getState();
  1395. if (!allowNewUpload && !forceAllowNewUpload) {
  1396. throw new Error('Cannot create a new upload: already uploading.');
  1397. }
  1398. const uploadID = (0, _nonSecure.nanoid)();
  1399. this.emit('upload', {
  1400. id: uploadID,
  1401. fileIDs
  1402. });
  1403. this.setState({
  1404. allowNewUpload: this.opts.allowMultipleUploadBatches !== false && this.opts.allowMultipleUploads !== false,
  1405. currentUploads: { ...currentUploads,
  1406. [uploadID]: {
  1407. fileIDs,
  1408. step: 0,
  1409. result: {}
  1410. }
  1411. }
  1412. });
  1413. return uploadID;
  1414. }
  1415. function _getUpload2(uploadID) {
  1416. const {
  1417. currentUploads
  1418. } = this.getState();
  1419. return currentUploads[uploadID];
  1420. }
  1421. function _removeUpload2(uploadID) {
  1422. const currentUploads = { ...this.getState().currentUploads
  1423. };
  1424. delete currentUploads[uploadID];
  1425. this.setState({
  1426. currentUploads
  1427. });
  1428. }
  1429. async function _runUpload2(uploadID) {
  1430. let {
  1431. currentUploads
  1432. } = this.getState();
  1433. let currentUpload = currentUploads[uploadID];
  1434. const restoreStep = currentUpload.step || 0;
  1435. const steps = [..._classPrivateFieldLooseBase(this, _preProcessors)[_preProcessors], ..._classPrivateFieldLooseBase(this, _uploaders)[_uploaders], ..._classPrivateFieldLooseBase(this, _postProcessors)[_postProcessors]];
  1436. try {
  1437. for (let step = restoreStep; step < steps.length; step++) {
  1438. if (!currentUpload) {
  1439. break;
  1440. }
  1441. const fn = steps[step];
  1442. const updatedUpload = { ...currentUpload,
  1443. step
  1444. };
  1445. this.setState({
  1446. currentUploads: { ...currentUploads,
  1447. [uploadID]: updatedUpload
  1448. }
  1449. }); // TODO give this the `updatedUpload` object as its only parameter maybe?
  1450. // Otherwise when more metadata may be added to the upload this would keep getting more parameters
  1451. await fn(updatedUpload.fileIDs, uploadID); // Update currentUpload value in case it was modified asynchronously.
  1452. currentUploads = this.getState().currentUploads;
  1453. currentUpload = currentUploads[uploadID];
  1454. }
  1455. } catch (err) {
  1456. _classPrivateFieldLooseBase(this, _removeUpload)[_removeUpload](uploadID);
  1457. throw err;
  1458. } // Set result data.
  1459. if (currentUpload) {
  1460. // Mark postprocessing step as complete if necessary; this addresses a case where we might get
  1461. // stuck in the postprocessing UI while the upload is fully complete.
  1462. // If the postprocessing steps do not do any work, they may not emit postprocessing events at
  1463. // all, and never mark the postprocessing as complete. This is fine on its own but we
  1464. // introduced code in the @uppy/core upload-success handler to prepare postprocessing progress
  1465. // state if any postprocessors are registered. That is to avoid a "flash of completed state"
  1466. // before the postprocessing plugins can emit events.
  1467. //
  1468. // So, just in case an upload with postprocessing plugins *has* completed *without* emitting
  1469. // postprocessing completion, we do it instead.
  1470. currentUpload.fileIDs.forEach(fileID => {
  1471. const file = this.getFile(fileID);
  1472. if (file && file.progress.postprocess) {
  1473. this.emit('postprocess-complete', file);
  1474. }
  1475. });
  1476. const files = currentUpload.fileIDs.map(fileID => this.getFile(fileID));
  1477. const successful = files.filter(file => !file.error);
  1478. const failed = files.filter(file => file.error);
  1479. await this.addResultData(uploadID, {
  1480. successful,
  1481. failed,
  1482. uploadID
  1483. }); // Update currentUpload value in case it was modified asynchronously.
  1484. currentUploads = this.getState().currentUploads;
  1485. currentUpload = currentUploads[uploadID];
  1486. } // Emit completion events.
  1487. // This is in a separate function so that the `currentUploads` variable
  1488. // always refers to the latest state. In the handler right above it refers
  1489. // to an outdated object without the `.result` property.
  1490. let result;
  1491. if (currentUpload) {
  1492. result = currentUpload.result;
  1493. this.emit('complete', result);
  1494. _classPrivateFieldLooseBase(this, _removeUpload)[_removeUpload](uploadID);
  1495. }
  1496. if (result == null) {
  1497. this.log(`Not setting result for an upload that has been removed: ${uploadID}`);
  1498. }
  1499. return result;
  1500. }
  1501. Uppy.VERSION = packageJson.version;
  1502. module.exports = Uppy;