From eff68769f364074daf40c03f5fca9bd4a4a32464 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 4 Apr 2023 23:09:12 +0900 Subject: [PATCH 01/19] show how many files are added when loading remake of https://github.com/transloadit/uppy/pull/4388 --- packages/@uppy/core/src/locale.js | 1 + packages/@uppy/locales/src/en_US.js | 1 + packages/@uppy/provider-views/src/Loader.jsx | 5 ++++- .../@uppy/provider-views/src/ProviderView/ProviderView.jsx | 3 ++- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/@uppy/core/src/locale.js b/packages/@uppy/core/src/locale.js index 25783f26f9..fdd0e0987f 100644 --- a/packages/@uppy/core/src/locale.js +++ b/packages/@uppy/core/src/locale.js @@ -51,6 +51,7 @@ export default { search: 'Search', resetSearch: 'Reset search', emptyFolderAdded: 'No files were added from empty folder', + addedNumFiles: 'Added %{numFiles} files', folderAlreadyAdded: 'The folder "%{folder}" was already added', folderAdded: { 0: 'Added %{smart_count} file from %{folder}', diff --git a/packages/@uppy/locales/src/en_US.js b/packages/@uppy/locales/src/en_US.js index c29bbadfba..63c0a2711d 100644 --- a/packages/@uppy/locales/src/en_US.js +++ b/packages/@uppy/locales/src/en_US.js @@ -62,6 +62,7 @@ en_US.strings = { editFileWithFilename: 'Edit file %{file}', editing: 'Editing %{file}', emptyFolderAdded: 'No files were added from empty folder', + addedNumFiles: 'Added %{numFiles} files', encoding: 'Encoding...', enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file', enterTextToSearch: 'Enter text to search for images', diff --git a/packages/@uppy/provider-views/src/Loader.jsx b/packages/@uppy/provider-views/src/Loader.jsx index 0572ad2aae..dde05f4829 100644 --- a/packages/@uppy/provider-views/src/Loader.jsx +++ b/packages/@uppy/provider-views/src/Loader.jsx @@ -1,9 +1,12 @@ import { h } from 'preact' -export default ({ i18n }) => { +export default ({ i18n, loading }) => { return (
{i18n('loading')} + {typeof loading === 'string' && ( + {loading} + )}
) } diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index 3690cf6348..9b6a28fbff 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx @@ -280,6 +280,7 @@ export default class ProviderView extends View { if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) { newFiles.push(fileInFolder) numNewFiles++ + this.setLoading(this.plugin.uppy.i18n('addedNumFiles', { numFiles: numNewFiles })) } isEmpty = false } @@ -384,7 +385,7 @@ export default class ProviderView extends View { if (loading) { return ( - + ) } From d67a4a59ee4378d637a7dee9fbda733c01decd0c Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Tue, 4 Apr 2023 23:11:03 +0900 Subject: [PATCH 02/19] add french (cherry pick) --- packages/@uppy/core/src/locale.js | 2 +- packages/@uppy/locales/src/en_US.js | 2 +- packages/@uppy/locales/src/fr_FR.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/@uppy/core/src/locale.js b/packages/@uppy/core/src/locale.js index fdd0e0987f..34d229541e 100644 --- a/packages/@uppy/core/src/locale.js +++ b/packages/@uppy/core/src/locale.js @@ -51,7 +51,7 @@ export default { search: 'Search', resetSearch: 'Reset search', emptyFolderAdded: 'No files were added from empty folder', - addedNumFiles: 'Added %{numFiles} files', + addedNumFiles: 'Added %{numFiles} file(s)', folderAlreadyAdded: 'The folder "%{folder}" was already added', folderAdded: { 0: 'Added %{smart_count} file from %{folder}', diff --git a/packages/@uppy/locales/src/en_US.js b/packages/@uppy/locales/src/en_US.js index 63c0a2711d..01decba47d 100644 --- a/packages/@uppy/locales/src/en_US.js +++ b/packages/@uppy/locales/src/en_US.js @@ -12,6 +12,7 @@ en_US.strings = { '0': 'Failed to add %{smart_count} file due to an internal error', '1': 'Failed to add %{smart_count} files due to internal errors', }, + addedNumFiles: 'Added %{numFiles} file(s)', addingMoreFiles: 'Adding more files', addMore: 'Add more', addMoreFiles: 'Add more files', @@ -62,7 +63,6 @@ en_US.strings = { editFileWithFilename: 'Edit file %{file}', editing: 'Editing %{file}', emptyFolderAdded: 'No files were added from empty folder', - addedNumFiles: 'Added %{numFiles} files', encoding: 'Encoding...', enterCorrectUrl: 'Incorrect URL: Please make sure you are entering a direct link to a file', enterTextToSearch: 'Enter text to search for images', diff --git a/packages/@uppy/locales/src/fr_FR.js b/packages/@uppy/locales/src/fr_FR.js index 748e2934d2..2781de60fd 100644 --- a/packages/@uppy/locales/src/fr_FR.js +++ b/packages/@uppy/locales/src/fr_FR.js @@ -12,9 +12,10 @@ fr_FR.strings = { '0': 'L\'ajout de %{smart_count} fichier a échoué', '1': 'L\'ajout de %{smart_count} fichiers a échoué', }, + addedNumFiles: '%{numFiles} fichier(s) ajouté(s)', + addingMoreFiles: 'En train d\'ajouter des fichiers', addMore: 'Ajouter d\'autres', addMoreFiles: 'Ajouter d\'autres fichiers', - addingMoreFiles: 'En train d\'ajouter des fichiers', allFilesFromFolderNamed: 'Tous les fichiers du dossier %{name}', allowAccessDescription: 'Pour prendre des photos ou enregistrer une vidéo avec votre caméra, veuillez autoriser l\'accès à votre caméra pour ce site.', allowAccessTitle: 'Veuillez autoriser l\'accès à votre caméra', From 435c71bc06e1f8e850a7e543c179c0bf708efd24 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 5 Apr 2023 16:35:50 +0900 Subject: [PATCH 03/19] implement concurrent file listing --- packages/@uppy/provider-views/package.json | 1 + .../src/ProviderView/ProviderView.jsx | 56 +++++++++++-------- yarn.lock | 20 ++++++- 3 files changed, 52 insertions(+), 25 deletions(-) diff --git a/packages/@uppy/provider-views/package.json b/packages/@uppy/provider-views/package.json index 92ec8111b3..7d3e0d92ff 100644 --- a/packages/@uppy/provider-views/package.json +++ b/packages/@uppy/provider-views/package.json @@ -23,6 +23,7 @@ "@uppy/utils": "workspace:^", "classnames": "^2.2.6", "nanoid": "^4.0.0", + "p-queue": "^7.3.4", "preact": "^10.5.13" }, "peerDependencies": { diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index 9b6a28fbff..fa5ff17b00 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx @@ -1,4 +1,6 @@ import { h } from 'preact' +// eslint-disable-next-line import/no-unresolved +import PQueue from 'p-queue' import { getSafeFileId } from '@uppy/utils/lib/generateFileID' @@ -235,23 +237,23 @@ export default class ProviderView extends View { } } - async* recursivelyListAllFiles (path) { + async recursivelyListAllFiles (path, queue, onFiles) { let curPath = path - // need to repeat the list call until there are no more pages while (curPath) { const res = await this.provider.list(curPath) + curPath = res.nextPagePath - for (const item of res.items) { - if (item.isFolder) { - // recursively call self for folder - yield* this.recursivelyListAllFiles(item.requestPath) - } else { - yield item - } - } + const files = res.items.filter((item) => !item.isFolder) + const folders = res.items.filter((item) => item.isFolder) - curPath = res.nextPagePath + onFiles(files) + + // recursively queue call to self for each folder + const promises = folders.map(async (folder) => queue.add(async () => ( + this.recursivelyListAllFiles(folder.requestPath, queue, onFiles) + ))) + await Promise.all(promises) // in case we get an error } } @@ -263,28 +265,34 @@ export default class ProviderView extends View { const messages = [] const newFiles = [] - // eslint-disable-next-line no-unreachable-loop for (const file of currentSelection) { if (file.isFolder) { const { requestPath, name } = file let isEmpty = true let numNewFiles = 0 - for await (const fileInFolder of this.recursivelyListAllFiles(requestPath)) { - const tagFile = this.getTagFile(fileInFolder) - const id = getSafeFileId(tagFile) - // If the same folder is added again, we don't want to send - // X amount of duplicate file notifications, we want to say - // the folder was already added. This checks if all files are duplicate, - // if that's the case, we don't add the files. - if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) { - newFiles.push(fileInFolder) - numNewFiles++ - this.setLoading(this.plugin.uppy.i18n('addedNumFiles', { numFiles: numNewFiles })) + const queue = new PQueue({ concurrency: 6 }) + + const onFiles = (files) => { + for (const newFile of files) { + const tagFile = this.getTagFile(newFile) + const id = getSafeFileId(tagFile) + // If the same folder is added again, we don't want to send + // X amount of duplicate file notifications, we want to say + // the folder was already added. This checks if all files are duplicate, + // if that's the case, we don't add the files. + if (!this.plugin.uppy.checkIfFileAlreadyExists(id)) { + newFiles.push(newFile) + numNewFiles++ + this.setLoading(this.plugin.uppy.i18n('addedNumFiles', { numFiles: numNewFiles })) + } + isEmpty = false } - isEmpty = false } + await this.recursivelyListAllFiles(requestPath, queue, onFiles) + await queue.onIdle() + let message if (isEmpty) { message = this.plugin.uppy.i18n('emptyFolderAdded') diff --git a/yarn.lock b/yarn.lock index fb4aacb9fa..03ca86263f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8952,6 +8952,7 @@ __metadata: "@uppy/utils": "workspace:^" classnames: ^2.2.6 nanoid: ^4.0.0 + p-queue: ^7.3.4 preact: ^10.5.13 peerDependencies: "@uppy/core": "workspace:^" @@ -16688,7 +16689,7 @@ __metadata: languageName: node linkType: hard -"eventemitter3@npm:^4.0.0": +"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.7": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374 @@ -25996,6 +25997,16 @@ __metadata: languageName: node linkType: hard +"p-queue@npm:^7.3.4": + version: 7.3.4 + resolution: "p-queue@npm:7.3.4" + dependencies: + eventemitter3: ^4.0.7 + p-timeout: ^5.0.2 + checksum: a21b8a4dd75f64a4988e4468cc344d1b45132506ddd2c771932d3de446d108ee68713b629e0d3f0809c227bc10eafc613edde6ae741d9f60db89b6031e40921c + languageName: node + linkType: hard + "p-retry@npm:^4.5.0": version: 4.6.2 resolution: "p-retry@npm:4.6.2" @@ -26015,6 +26026,13 @@ __metadata: languageName: node linkType: hard +"p-timeout@npm:^5.0.2": + version: 5.1.0 + resolution: "p-timeout@npm:5.1.0" + checksum: f5cd4e17301ff1ff1d8dbf2817df0ad88c6bba99349fc24d8d181827176ad4f8aca649190b8a5b1a428dfd6ddc091af4606835d3e0cb0656e04045da5c9e270c + languageName: node + linkType: hard + "p-try@npm:^1.0.0": version: 1.0.0 resolution: "p-try@npm:1.0.0" From 133600d05322a2b2bd91fe575d1c1313d5be8855 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 5 Apr 2023 23:43:33 +0900 Subject: [PATCH 04/19] refactor / fix lint --- .../dashboard/src/components/Dashboard.jsx | 24 ++++- .../dashboard/src/components/FileList.jsx | 87 ++++++++++--------- 2 files changed, 66 insertions(+), 45 deletions(-) diff --git a/packages/@uppy/dashboard/src/components/Dashboard.jsx b/packages/@uppy/dashboard/src/components/Dashboard.jsx index afdb0e42f0..ee937e2805 100644 --- a/packages/@uppy/dashboard/src/components/Dashboard.jsx +++ b/packages/@uppy/dashboard/src/components/Dashboard.jsx @@ -136,8 +136,28 @@ export default function Dashboard (props) { {showFileList ? ( diff --git a/packages/@uppy/dashboard/src/components/FileList.jsx b/packages/@uppy/dashboard/src/components/FileList.jsx index 9e248a8e50..74f2f5aab5 100644 --- a/packages/@uppy/dashboard/src/components/FileList.jsx +++ b/packages/@uppy/dashboard/src/components/FileList.jsx @@ -1,4 +1,5 @@ import { h } from 'preact' +import { useMemo } from 'preact/hooks' import FileItem from './FileItem/index.jsx' import VirtualList from './VirtualList.jsx' @@ -17,50 +18,28 @@ function chunks (list, size) { return chunked } -export default (props) => { +export default ({ + id, error, i18n, uppy, files, acquirers, resumableUploads, hideRetryButton, hidePauseResumeButton, hideCancelButton, + showLinkToFileUploadResult, showRemoveButtonAfterComplete, isWide, metaFields, singleFile, toggleFileCard, + handleRequestThumbnail, handleCancelThumbnail, recoveredState, individualCancellation, itemsPerRow, openFileEditor, + canEditFile, toggleAddFilesPanel, +}) => { // It's not great that this is hardcoded! // It's ESPECIALLY not great that this is checking against `itemsPerRow`! - const rowHeight = props.itemsPerRow === 1 + const rowHeight = itemsPerRow === 1 // Mobile ? 71 // 190px height + 2 * 5px margin : 200 - const fileProps = { - // FIXME This is confusing, it's actually the Dashboard's plugin ID - id: props.id, - error: props.error, - // TODO move this to context - i18n: props.i18n, - uppy: props.uppy, - // features - acquirers: props.acquirers, - resumableUploads: props.resumableUploads, - individualCancellation: props.individualCancellation, - // visual options - hideRetryButton: props.hideRetryButton, - hidePauseResumeButton: props.hidePauseResumeButton, - hideCancelButton: props.hideCancelButton, - showLinkToFileUploadResult: props.showLinkToFileUploadResult, - showRemoveButtonAfterComplete: props.showRemoveButtonAfterComplete, - isWide: props.isWide, - metaFields: props.metaFields, - recoveredState: props.recoveredState, - singleFile: props.singleFile, - // callbacks - toggleFileCard: props.toggleFileCard, - handleRequestThumbnail: props.handleRequestThumbnail, - handleCancelThumbnail: props.handleCancelThumbnail, - } - - const sortByGhostComesFirst = (file1, file2) => { - return props.files[file2].isGhost - props.files[file1].isGhost - } - // Sort files by file.isGhost, ghost files first, only if recoveredState is present - const files = Object.keys(props.files) - if (props.recoveredState) files.sort(sortByGhostComesFirst) - const rows = chunks(files, props.itemsPerRow) + const rows = useMemo(() => { + const sortByGhostComesFirst = (file1, file2) => files[file2].isGhost - files[file1].isGhost + + const fileIds = Object.keys(files) + if (recoveredState) fileIds.sort(sortByGhostComesFirst) + return chunks(fileIds, itemsPerRow) + }, [files, itemsPerRow, recoveredState]) const renderRow = (row) => ( // The `role="presentation` attribute ensures that the list items are properly @@ -70,19 +49,41 @@ export default (props) => { {row.map((fileID) => ( ))} ) - if (props.singleFile) { + if (singleFile) { return (
{renderRow(rows[0])} From 47e2980d5f559ee0ac823fe7922742fcdf9b4e86 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Thu, 6 Apr 2023 00:16:25 +0900 Subject: [PATCH 05/19] refactor/reduce duplication --- packages/@uppy/core/src/Uppy.js | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index ce45394923..46a55810b4 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -516,6 +516,17 @@ class Uppy { throw err } + // Users are asked to re-select recovered files without data, + // and to keep the progress, meta and everthing else, we only replace said data + if (files[newFile.id] && files[newFile.id].isGhost) { + newFile = { + ...files[newFile.id], + data: fileDescriptor.data, + isGhost: false, + } + this.log(`Replaced the blob in the restored ghost file: ${newFile.name}, ${newFile.id}`) + } + return newFile } @@ -545,18 +556,7 @@ class Uppy { this.#assertNewUploadAllowed(file) const { files } = this.getState() - let newFile = this.#checkAndCreateFileStateObject(files, file) - - // Users are asked to re-select recovered files without data, - // and to keep the progress, meta and everthing else, we only replace said data - if (files[newFile.id] && files[newFile.id].isGhost) { - newFile = { - ...files[newFile.id], - data: file.data, - isGhost: false, - } - this.log(`Replaced the blob in the restored ghost file: ${newFile.name}, ${newFile.id}`) - } + const newFile = this.#checkAndCreateFileStateObject(files, file) this.setState({ files: { @@ -590,17 +590,8 @@ class Uppy { const errors = [] for (let i = 0; i < fileDescriptors.length; i++) { try { - let newFile = this.#checkAndCreateFileStateObject(files, fileDescriptors[i]) - // Users are asked to re-select recovered files without data, - // and to keep the progress, meta and everthing else, we only replace said data - if (files[newFile.id] && files[newFile.id].isGhost) { - newFile = { - ...files[newFile.id], - data: fileDescriptors[i].data, - isGhost: false, - } - this.log(`Replaced blob in a ghost file: ${newFile.name}, ${newFile.id}`) - } + const newFile = this.#checkAndCreateFileStateObject(files, fileDescriptors[i]) + files[newFile.id] = newFile newFiles.push(newFile) } catch (err) { From 966d3e72907d89ca349c458b8e23de1d569bc638 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Thu, 6 Apr 2023 01:04:58 +0900 Subject: [PATCH 06/19] pull out totals validation don't do it for every file added, as it's very slow instead do the check at the end when all files are added. this allows us to easily work with 10k+ files fixes #4389 --- packages/@uppy/core/src/Restricter.js | 41 ++++++++++------- packages/@uppy/core/src/Uppy.js | 63 +++++++++++++++++---------- 2 files changed, 64 insertions(+), 40 deletions(-) diff --git a/packages/@uppy/core/src/Restricter.js b/packages/@uppy/core/src/Restricter.js index 7e90545d57..38838c31eb 100644 --- a/packages/@uppy/core/src/Restricter.js +++ b/packages/@uppy/core/src/Restricter.js @@ -30,16 +30,37 @@ class Restricter { } } - validate (file, files) { - const { maxFileSize, minFileSize, maxTotalFileSize, maxNumberOfFiles, allowedFileTypes } = this.getOpts().restrictions + validateTotals (existingFiles, newFiles) { + const { maxTotalFileSize, maxNumberOfFiles } = this.getOpts().restrictions if (maxNumberOfFiles) { - const nonGhostFiles = files.filter(f => !f.isGhost) - if (nonGhostFiles.length + 1 > maxNumberOfFiles) { + const nonGhostFiles = existingFiles.filter(f => !f.isGhost) + if (nonGhostFiles.length + newFiles.length > maxNumberOfFiles) { throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles })}`) } } + if (maxTotalFileSize) { + let totalFilesSize = existingFiles.reduce((total, f) => (total + f.size), 0) + + for (const newFile of newFiles) { + if (newFile.size != null) { // We can't check maxTotalFileSize if the size is unknown. + totalFilesSize += newFile.size + + if (totalFilesSize > maxTotalFileSize) { + throw new RestrictionError(this.i18n('exceedsSize', { + size: prettierBytes(maxTotalFileSize), + file: newFile.name, + })) + } + } + } + } + } + + validate (file) { + const { maxFileSize, minFileSize, allowedFileTypes } = this.getOpts().restrictions + if (allowedFileTypes) { const isCorrectFileType = allowedFileTypes.some((type) => { // check if this is a mime-type @@ -61,18 +82,6 @@ class Restricter { } } - // We can't check maxTotalFileSize if the size is unknown. - if (maxTotalFileSize && file.size != null) { - const totalFilesSize = files.reduce((total, f) => (total + f.size), file.size) - - if (totalFilesSize > maxTotalFileSize) { - throw new RestrictionError(this.i18n('exceedsSize', { - size: prettierBytes(maxTotalFileSize), - file: file.name, - })) - } - } - // We can't check maxFileSize if the size is unknown. if (maxFileSize && file.size != null && file.size > maxFileSize) { throw new RestrictionError(this.i18n('exceedsSize', { diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 46a55810b4..ca0322c10d 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -384,7 +384,8 @@ class Uppy { validateRestrictions (file, files = this.getFiles()) { try { - this.#restricter.validate(file, files) + this.#restricter.validate(file) + this.#restricter.validateTotals(files, [file]) } catch (err) { return err } @@ -508,13 +509,7 @@ class Uppy { newFile = onBeforeFileAddedResult } - try { - const filesArray = Object.keys(files).map(i => files[i]) - this.#restricter.validate(newFile, filesArray) - } catch (err) { - this.#informAndEmit(err, newFile) - throw err - } + this.#restricter.validate(newFile) // Users are asked to re-select recovered files without data, // and to keep the progress, meta and everthing else, we only replace said data @@ -556,7 +551,16 @@ class Uppy { this.#assertNewUploadAllowed(file) const { files } = this.getState() - const newFile = this.#checkAndCreateFileStateObject(files, file) + + let newFile + + try { + newFile = this.#checkAndCreateFileStateObject(files, file) + this.#restricter.validateTotals(Object.values(files), [newFile]) + } catch (err) { + this.#informAndEmit(err, file) + throw err + } this.setState({ files: { @@ -589,36 +593,47 @@ class Uppy { const newFiles = [] const errors = [] for (let i = 0; i < fileDescriptors.length; i++) { + const fileDescriptor = fileDescriptors[i] try { - const newFile = this.#checkAndCreateFileStateObject(files, fileDescriptors[i]) + const newFile = this.#checkAndCreateFileStateObject(files, fileDescriptor) files[newFile.id] = newFile newFiles.push(newFile) } catch (err) { + this.#informAndEmit(err, fileDescriptor) if (!err.isRestriction) { errors.push(err) } } } - this.setState({ files }) - - newFiles.forEach((newFile) => { - this.emit('file-added', newFile) - }) + try { + this.#restricter.validateTotals(Object.values(this.getState().files), newFiles) - this.emit('files-added', newFiles) + this.setState({ files }) - if (newFiles.length > 5) { - this.log(`Added batch of ${newFiles.length} files`) - } else { - Object.keys(newFiles).forEach(fileID => { - this.log(`Added file: ${newFiles[fileID].name}\n id: ${newFiles[fileID].id}\n type: ${newFiles[fileID].type}`) + newFiles.forEach((newFile) => { + this.emit('file-added', newFile) }) - } - if (newFiles.length > 0) { - this.#startIfAutoProceed() + this.emit('files-added', newFiles) + + if (newFiles.length > 5) { + this.log(`Added batch of ${newFiles.length} files`) + } else { + Object.keys(newFiles).forEach(fileID => { + this.log(`Added file: ${newFiles[fileID].name}\n id: ${newFiles[fileID].id}\n type: ${newFiles[fileID].type}`) + }) + } + + if (newFiles.length > 0) { + this.#startIfAutoProceed() + } + } catch (err) { + this.#informAndEmit(err, fileDescriptors[0]) + if (!err.isRestriction) { + errors.push(err) + } } if (errors.length > 0) { From 94a338543749cbcfb58cffbdb34aff885532735b Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Thu, 6 Apr 2023 13:11:18 +0900 Subject: [PATCH 07/19] Update packages/@uppy/core/src/Uppy.js Co-authored-by: Antoine du Hamel --- packages/@uppy/core/src/Uppy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index ca0322c10d..028935a991 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -513,7 +513,7 @@ class Uppy { // Users are asked to re-select recovered files without data, // and to keep the progress, meta and everthing else, we only replace said data - if (files[newFile.id] && files[newFile.id].isGhost) { + if (files[newFile.id]?.isGhost) { newFile = { ...files[newFile.id], data: fileDescriptor.data, From 9f53afe110fa82e47295f5409ea9ca8f35e3195c Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Thu, 6 Apr 2023 19:14:16 +0900 Subject: [PATCH 08/19] make restricter.validate validate everything instead make more specific methods for sub-validation also rename validateTotals to validateAggregateRestrictions --- packages/@uppy/core/src/Restricter.js | 22 ++-- packages/@uppy/core/src/Uppy.js | 143 +++++++++++++------------- 2 files changed, 89 insertions(+), 76 deletions(-) diff --git a/packages/@uppy/core/src/Restricter.js b/packages/@uppy/core/src/Restricter.js index 38838c31eb..db11f32922 100644 --- a/packages/@uppy/core/src/Restricter.js +++ b/packages/@uppy/core/src/Restricter.js @@ -30,12 +30,13 @@ class Restricter { } } - validateTotals (existingFiles, newFiles) { + // Because these operations are slow, we cannot run them for every file (if we are adding multiple files) + validateAggregateRestrictions (existingFiles, addingFiles) { const { maxTotalFileSize, maxNumberOfFiles } = this.getOpts().restrictions if (maxNumberOfFiles) { const nonGhostFiles = existingFiles.filter(f => !f.isGhost) - if (nonGhostFiles.length + newFiles.length > maxNumberOfFiles) { + if (nonGhostFiles.length + addingFiles.length > maxNumberOfFiles) { throw new RestrictionError(`${this.i18n('youCanOnlyUploadX', { smart_count: maxNumberOfFiles })}`) } } @@ -43,14 +44,14 @@ class Restricter { if (maxTotalFileSize) { let totalFilesSize = existingFiles.reduce((total, f) => (total + f.size), 0) - for (const newFile of newFiles) { - if (newFile.size != null) { // We can't check maxTotalFileSize if the size is unknown. - totalFilesSize += newFile.size + for (const addingFile of addingFiles) { + if (addingFile.size != null) { // We can't check maxTotalFileSize if the size is unknown. + totalFilesSize += addingFile.size if (totalFilesSize > maxTotalFileSize) { throw new RestrictionError(this.i18n('exceedsSize', { size: prettierBytes(maxTotalFileSize), - file: newFile.name, + file: addingFile.name, })) } } @@ -58,7 +59,7 @@ class Restricter { } } - validate (file) { + validateSingleFile (file) { const { maxFileSize, minFileSize, allowedFileTypes } = this.getOpts().restrictions if (allowedFileTypes) { @@ -98,6 +99,13 @@ class Restricter { } } + validate (existingFiles, addingFiles) { + addingFiles.forEach((addingFile) => { + this.validateSingleFile(addingFile) + }) + this.validateAggregateRestrictions(existingFiles, addingFiles) + } + validateMinNumberOfFiles (files) { const { minNumberOfFiles } = this.getOpts().restrictions if (Object.keys(files).length < minNumberOfFiles) { diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 028935a991..597ca1c7fa 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -384,8 +384,7 @@ class Uppy { validateRestrictions (file, files = this.getFiles()) { try { - this.#restricter.validate(file) - this.#restricter.validateTotals(files, [file]) + this.#restricter.validate(files, [file]) } catch (err) { return err } @@ -498,7 +497,7 @@ class Uppy { preview: fileDescriptor.preview, } - const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, files) + const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, { ...files }) if (onBeforeFileAddedResult === false) { // Don’t show UI info for this error, as it should be done by the developer @@ -509,7 +508,7 @@ class Uppy { newFile = onBeforeFileAddedResult } - this.#restricter.validate(newFile) + this.#restricter.validateSingleFile(newFile) // Users are asked to re-select recovered files without data, // and to keep the progress, meta and everthing else, we only replace said data @@ -517,7 +516,7 @@ class Uppy { newFile = { ...files[newFile.id], data: fileDescriptor.data, - isGhost: false, + isGhost: false, // todo document why we do this } this.log(`Replaced the blob in the restored ghost file: ${newFile.name}, ${newFile.id}`) } @@ -539,6 +538,47 @@ class Uppy { } } + #checkAndCreateFileStateObjects (filesToAdd) { + const { files: existingFiles } = this.getState() + + // create a copy of the files object only once + const newFilesState = { ...existingFiles } + const validFilesToAdd = [] + const errors = [] + + for (const fileToAdd of filesToAdd) { + try { + const newFile = this.#checkAndCreateFileStateObject(newFilesState, fileToAdd) + + newFilesState[newFile.id] = newFile + validFilesToAdd.push(newFile) + } catch (err) { + this.#informAndEmit(err, fileToAdd) + errors.push(err) + } + } + + try { + this.#restricter.validateAggregateRestrictions(Object.values(existingFiles), validFilesToAdd) + } catch (err) { + this.#informAndEmit(err, filesToAdd[0]) + errors.push(err) + + // If we have any aggregate error, don't allow adding this batch + return { + newFilesState: existingFiles, + validFilesToAdd: [], + errors, + } + } + + return { + newFilesState, + validFilesToAdd, + errors, + } + } + /** * Add a new file to `state.files`. This will run `onBeforeFileAdded`, * try to guess file type in a clever way, check file against restrictions, @@ -550,32 +590,21 @@ class Uppy { addFile (file) { this.#assertNewUploadAllowed(file) - const { files } = this.getState() + const { newFilesState, validFilesToAdd, errors } = this.#checkAndCreateFileStateObjects([file]) - let newFile + if (errors.length > 0) throw errors[0] - try { - newFile = this.#checkAndCreateFileStateObject(files, file) - this.#restricter.validateTotals(Object.values(files), [newFile]) - } catch (err) { - this.#informAndEmit(err, file) - throw err - } + const [validFileToAdd] = validFilesToAdd - this.setState({ - files: { - ...files, - [newFile.id]: newFile, - }, - }) + this.setState({ files: newFilesState }) - this.emit('file-added', newFile) - this.emit('files-added', [newFile]) - this.log(`Added file: ${newFile.name}, ${newFile.id}, mime type: ${newFile.type}`) + this.emit('file-added', validFileToAdd) + this.emit('files-added', validFilesToAdd) + this.log(`Added file: ${validFileToAdd.name}, ${validFileToAdd.id}, mime type: ${validFileToAdd.type}`) this.#startIfAutoProceed() - return newFile.id + return validFileToAdd.id } /** @@ -588,70 +617,46 @@ class Uppy { addFiles (fileDescriptors) { this.#assertNewUploadAllowed() - // create a copy of the files object only once - const files = { ...this.getState().files } - const newFiles = [] - const errors = [] - for (let i = 0; i < fileDescriptors.length; i++) { - const fileDescriptor = fileDescriptors[i] - try { - const newFile = this.#checkAndCreateFileStateObject(files, fileDescriptor) + const { newFilesState, validFilesToAdd, errors } = this.#checkAndCreateFileStateObjects(fileDescriptors) - files[newFile.id] = newFile - newFiles.push(newFile) - } catch (err) { - this.#informAndEmit(err, fileDescriptor) - if (!err.isRestriction) { - errors.push(err) - } - } - } + const nonRestrictionErrors = errors.filter((error) => !error.isRestriction) - try { - this.#restricter.validateTotals(Object.values(this.getState().files), newFiles) - - this.setState({ files }) + this.setState({ files: newFilesState }) - newFiles.forEach((newFile) => { - this.emit('file-added', newFile) - }) + validFilesToAdd.forEach((file) => { + this.emit('file-added', file) + }) - this.emit('files-added', newFiles) + this.emit('files-added', validFilesToAdd) - if (newFiles.length > 5) { - this.log(`Added batch of ${newFiles.length} files`) - } else { - Object.keys(newFiles).forEach(fileID => { - this.log(`Added file: ${newFiles[fileID].name}\n id: ${newFiles[fileID].id}\n type: ${newFiles[fileID].type}`) - }) - } + if (validFilesToAdd.length > 5) { + this.log(`Added batch of ${validFilesToAdd.length} files`) + } else { + Object.values(validFilesToAdd).forEach((file) => { + this.log(`Added file: ${file.name}\n id: ${file.id}\n type: ${file.type}`) + }) + } - if (newFiles.length > 0) { - this.#startIfAutoProceed() - } - } catch (err) { - this.#informAndEmit(err, fileDescriptors[0]) - if (!err.isRestriction) { - errors.push(err) - } + if (validFilesToAdd.length > 0) { + this.#startIfAutoProceed() } - if (errors.length > 0) { + if (nonRestrictionErrors.length > 0) { let message = 'Multiple errors occurred while adding files:\n' - errors.forEach((subError) => { + nonRestrictionErrors.forEach((subError) => { message += `\n * ${subError.message}` }) this.info({ - message: this.i18n('addBulkFilesFailed', { smart_count: errors.length }), + message: this.i18n('addBulkFilesFailed', { smart_count: nonRestrictionErrors.length }), details: message, }, 'error', this.opts.infoTimeout) if (typeof AggregateError === 'function') { - throw new AggregateError(errors, message) + throw new AggregateError(nonRestrictionErrors, message) } else { const err = new Error(message) - err.errors = errors + err.errors = nonRestrictionErrors throw err } } From 734a456b4dd35fa07e095f029f509f0b1543ae1d Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Thu, 6 Apr 2023 19:58:48 +0900 Subject: [PATCH 09/19] improve errors and user feedback - handle errors centrally so that we can limit the amount of toasts (informers) sent to the users (prevent flooding hundreds/thousands of them) - introduce FileRestrictionError which is a restriction error for a specific file - introduce isUserFacing field for RestrictionError --- packages/@uppy/core/src/Restricter.js | 24 ++++++--- packages/@uppy/core/src/Uppy.js | 52 +++++++++++-------- .../src/ProviderView/ProviderView.jsx | 3 ++ 3 files changed, 50 insertions(+), 29 deletions(-) diff --git a/packages/@uppy/core/src/Restricter.js b/packages/@uppy/core/src/Restricter.js index db11f32922..93c2660a78 100644 --- a/packages/@uppy/core/src/Restricter.js +++ b/packages/@uppy/core/src/Restricter.js @@ -13,9 +13,21 @@ const defaultOptions = { } class RestrictionError extends Error { + constructor (message, { isUserFacing = true } = {}) { + super(message) + this.isUserFacing = isUserFacing + } + isRestriction = true } +class FileRestrictionError extends RestrictionError { + constructor (message, { file, ...opts }) { + super(message, opts) + this.file = file + } +} + class Restricter { constructor (getOpts, i18n) { this.i18n = i18n @@ -79,23 +91,23 @@ class Restricter { if (!isCorrectFileType) { const allowedFileTypesString = allowedFileTypes.join(', ') - throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString })) + throw new FileRestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString }), { file }) } } // We can't check maxFileSize if the size is unknown. if (maxFileSize && file.size != null && file.size > maxFileSize) { - throw new RestrictionError(this.i18n('exceedsSize', { + throw new FileRestrictionError(this.i18n('exceedsSize', { size: prettierBytes(maxFileSize), file: file.name, - })) + }), { file }) } // We can't check minFileSize if the size is unknown. if (minFileSize && file.size != null && file.size < minFileSize) { - throw new RestrictionError(this.i18n('inferiorSize', { + throw new FileRestrictionError(this.i18n('inferiorSize', { size: prettierBytes(minFileSize), - })) + }), { file }) } } @@ -128,4 +140,4 @@ class Restricter { } } -export { Restricter, defaultOptions, RestrictionError } +export { Restricter, defaultOptions, RestrictionError, FileRestrictionError } diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 597ca1c7fa..2f1f40bfd7 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -16,6 +16,7 @@ import { Restricter, defaultOptions as defaultRestrictionOptions, RestrictionError, + FileRestrictionError, } from './Restricter.js' import packageJson from '../package.json' @@ -362,24 +363,29 @@ class Uppy { /* * @constructs - * @param { Error } error + * @param { Error[] } errors * @param { undefined } file */ /* * @constructs * @param { RestrictionError } error - * @param { UppyFile | undefined } file */ - #informAndEmit (error, file) { - const { message, details = '' } = error + #informAndEmit (errors) { + for (const error of errors) { + const { file, isRestriction } = error - if (error.isRestriction) { - this.emit('restriction-failed', file, error) - } else { - this.emit('error', error) + if (isRestriction) { + this.emit('restriction-failed', file, error) + } else { + this.emit('error', error) + } + this.log(error, 'warning') } - this.info({ message, details }, 'error', this.opts.infoTimeout) - this.log(error, 'warning') + + // don't flood the user: only show the first 5 toasts + errors.filter((error) => error.isUserFacing).slice(0, 5).forEach(({ message, details = '' }) => { + this.info({ message, details }, 'error', this.opts.infoTimeout) + }) } validateRestrictions (file, files = this.getFiles()) { @@ -417,8 +423,8 @@ class Uppy { const { allowNewUpload } = this.getState() if (allowNewUpload === false) { - const error = new RestrictionError(this.i18n('noMoreFilesAllowed')) - this.#informAndEmit(error, file) + const error = new FileRestrictionError(this.i18n('noMoreFilesAllowed'), { file }) + this.#informAndEmit([error]) throw error } } @@ -461,9 +467,7 @@ class Uppy { const id = getSafeFileId(fileDescriptor) if (this.checkIfFileAlreadyExists(id)) { - const error = new RestrictionError(this.i18n('noDuplicates', { fileName })) - this.#informAndEmit(error, fileDescriptor) - throw error + throw new FileRestrictionError(this.i18n('noDuplicates', { fileName }), { file: fileDescriptor }) } const meta = fileDescriptor.meta || {} @@ -501,9 +505,7 @@ class Uppy { if (onBeforeFileAddedResult === false) { // Don’t show UI info for this error, as it should be done by the developer - const error = new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.') - this.emit('restriction-failed', fileDescriptor, error) - throw error + throw new FileRestrictionError('Cannot add the file because onBeforeFileAdded returned false.', { isUserFacing: false, file: fileDescriptor }) } else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) { newFile = onBeforeFileAddedResult } @@ -553,7 +555,6 @@ class Uppy { newFilesState[newFile.id] = newFile validFilesToAdd.push(newFile) } catch (err) { - this.#informAndEmit(err, fileToAdd) errors.push(err) } } @@ -561,7 +562,6 @@ class Uppy { try { this.#restricter.validateAggregateRestrictions(Object.values(existingFiles), validFilesToAdd) } catch (err) { - this.#informAndEmit(err, filesToAdd[0]) errors.push(err) // If we have any aggregate error, don't allow adding this batch @@ -592,6 +592,9 @@ class Uppy { const { newFilesState, validFilesToAdd, errors } = this.#checkAndCreateFileStateObjects([file]) + const restrictionErrors = errors.filter((error) => error.isRestriction) + this.#informAndEmit(restrictionErrors) + if (errors.length > 0) throw errors[0] const [validFileToAdd] = validFilesToAdd @@ -620,6 +623,8 @@ class Uppy { const { newFilesState, validFilesToAdd, errors } = this.#checkAndCreateFileStateObjects(fileDescriptors) const nonRestrictionErrors = errors.filter((error) => !error.isRestriction) + const restrictionErrors = errors.filter((error) => error.isRestriction) + this.#informAndEmit(restrictionErrors) this.setState({ files: newFilesState }) @@ -976,14 +981,15 @@ class Uppy { if (typeof error === 'object' && error.message) { const newError = new Error(error.message) + newError.isUserFacing = true // todo maybe don't do this with all errors? newError.details = error.message if (error.details) { newError.details += ` ${error.details}` } newError.message = this.i18n('failedToUpload', { file: file?.name }) - this.#informAndEmit(newError) + this.#informAndEmit([newError]) } else { - this.#informAndEmit(error) + this.#informAndEmit([error]) } }) @@ -1542,7 +1548,7 @@ class Uppy { return Promise.resolve() .then(() => this.#restricter.validateMinNumberOfFiles(files)) .catch((err) => { - this.#informAndEmit(err) + this.#informAndEmit([err]) throw err }) .then(() => { diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index fa5ff17b00..cb3d01492d 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx @@ -301,6 +301,9 @@ export default class ProviderView extends View { folder: name, }) } else { + // TODO we don't really know at this point whether any files were actually added + // (only later after addFiles has been called) so we should probably rewrite this. + // Example: If all files fail to add due to restriction error, it will still say "Added 100 files from folder" message = this.plugin.uppy.i18n('folderAdded', { smart_count: numNewFiles, folder: name, }) From 4142923d56f779811328855adcf0feeb5c63158f Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Thu, 6 Apr 2023 20:10:26 +0900 Subject: [PATCH 10/19] fix performance issue reintroduced --- packages/@uppy/core/src/Uppy.js | 2 +- packages/@uppy/core/src/Uppy.test.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 2f1f40bfd7..9a453816b1 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -501,7 +501,7 @@ class Uppy { preview: fileDescriptor.preview, } - const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, { ...files }) + const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, files) if (onBeforeFileAddedResult === false) { // Don’t show UI info for this error, as it should be done by the developer diff --git a/packages/@uppy/core/src/Uppy.test.js b/packages/@uppy/core/src/Uppy.test.js index fd8faff2b4..83f6781c41 100644 --- a/packages/@uppy/core/src/Uppy.test.js +++ b/packages/@uppy/core/src/Uppy.test.js @@ -684,8 +684,10 @@ describe('src/Core', () => { describe('adding a file', () => { it('should call onBeforeFileAdded if it was specified in the options when initialising the class', () => { const onBeforeFileAdded = jest.fn() + const core = new Core({ - onBeforeFileAdded, + // need to capture a snapshot of files, because files will change in the next tick, thus failing the expect below + onBeforeFileAdded: (file, files) => onBeforeFileAdded(file, { ...files }), }) core.addFile({ From a8fb99ce1739476a51be0599a09bd76f54ba3658 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Fri, 7 Apr 2023 00:13:28 +0900 Subject: [PATCH 11/19] improvements - show "%{count} additional restrictions were not fulfilled" for any restriction errors more than 4 - refactor/rename methods - improve ghost logic/comments --- packages/@uppy/core/src/Uppy.js | 133 +++++++++++++++------------- packages/@uppy/core/src/locale.js | 1 + packages/@uppy/locales/src/en_US.js | 1 + 3 files changed, 73 insertions(+), 62 deletions(-) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 9a453816b1..517c8b4705 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -382,10 +382,19 @@ class Uppy { this.log(error, 'warning') } - // don't flood the user: only show the first 5 toasts - errors.filter((error) => error.isUserFacing).slice(0, 5).forEach(({ message, details = '' }) => { + const userFacingErrors = errors.filter((error) => error.isUserFacing) + + // don't flood the user: only show the first 4 toasts + const maxNumToShow = 4 + const firstErrors = userFacingErrors.slice(0, maxNumToShow) + const additionalErrors = userFacingErrors.slice(maxNumToShow) + firstErrors.forEach(({ message, details = '' }) => { this.info({ message, details }, 'error', this.opts.infoTimeout) }) + + if (additionalErrors.length > 0) { + this.info({ message: this.i18n('additionalRestrictionsFailed', { count: additionalErrors.length }) }) + } } validateRestrictions (file, files = this.getFiles()) { @@ -423,7 +432,10 @@ class Uppy { const { allowNewUpload } = this.getState() if (allowNewUpload === false) { - const error = new FileRestrictionError(this.i18n('noMoreFilesAllowed'), { file }) + const error = file != null + ? new FileRestrictionError(this.i18n('noMoreFilesAllowed'), { file }) + : new RestrictionError(this.i18n('noMoreFilesAllowed')) + this.#informAndEmit([error]) throw error } @@ -440,25 +452,17 @@ class Uppy { /** * Create a file state object based on user-provided `addFile()` options. - * - * Note this is extremely side-effectful and should only be done when a file state object - * will be added to state immediately afterward! - * - * The `files` value is passed in because it may be updated by the caller without updating the store. */ - #checkAndCreateFileStateObject (files, fileDescriptor) { + #transformFile (fileDescriptorOrFile) { // Uppy expects files in { name, type, size, data } format. // If the actual File object is passed from input[type=file] or drag-drop, // we normalize it to match Uppy file object - if (fileDescriptor instanceof File) { - // eslint-disable-next-line no-param-reassign - fileDescriptor = { - name: fileDescriptor.name, - type: fileDescriptor.type, - size: fileDescriptor.size, - data: fileDescriptor, - } - } + const fileDescriptor = fileDescriptorOrFile instanceof File ? { + name: fileDescriptorOrFile.name, + type: fileDescriptorOrFile.type, + size: fileDescriptorOrFile.size, + data: fileDescriptorOrFile, + } : fileDescriptorOrFile const fileType = getFileType(fileDescriptor) const fileName = getFileName(fileType, fileDescriptor) @@ -466,10 +470,6 @@ class Uppy { const isRemote = Boolean(fileDescriptor.isRemote) const id = getSafeFileId(fileDescriptor) - if (this.checkIfFileAlreadyExists(id)) { - throw new FileRestrictionError(this.i18n('noDuplicates', { fileName }), { file: fileDescriptor }) - } - const meta = fileDescriptor.meta || {} meta.name = fileName meta.type = fileType @@ -477,7 +477,7 @@ class Uppy { // `null` means the size is unknown. const size = Number.isFinite(fileDescriptor.data.size) ? fileDescriptor.data.size : null - let newFile = { + return { source: fileDescriptor.source || '', id, name: fileName, @@ -500,30 +500,6 @@ class Uppy { remote: fileDescriptor.remote || '', preview: fileDescriptor.preview, } - - const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, files) - - if (onBeforeFileAddedResult === false) { - // Don’t show UI info for this error, as it should be done by the developer - throw new FileRestrictionError('Cannot add the file because onBeforeFileAdded returned false.', { isUserFacing: false, file: fileDescriptor }) - } else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) { - newFile = onBeforeFileAddedResult - } - - this.#restricter.validateSingleFile(newFile) - - // Users are asked to re-select recovered files without data, - // and to keep the progress, meta and everthing else, we only replace said data - if (files[newFile.id]?.isGhost) { - newFile = { - ...files[newFile.id], - data: fileDescriptor.data, - isGhost: false, // todo document why we do this - } - this.log(`Replaced the blob in the restored ghost file: ${newFile.name}, ${newFile.id}`) - } - - return newFile } // Schedule an upload if `autoProceed` is enabled. @@ -540,19 +516,48 @@ class Uppy { } } - #checkAndCreateFileStateObjects (filesToAdd) { + #checkAndUpdateFileState (filesToAdd) { const { files: existingFiles } = this.getState() // create a copy of the files object only once - const newFilesState = { ...existingFiles } + const nextFilesState = { ...existingFiles } const validFilesToAdd = [] const errors = [] for (const fileToAdd of filesToAdd) { try { - const newFile = this.#checkAndCreateFileStateObject(newFilesState, fileToAdd) + let newFile = this.#transformFile(fileToAdd) + + // If a file has been recovered (Golden Retriever), but we were unable to recover its data (probably too large), + // users are asked to re-select these half-recovered files and then this method will be called again. + // In order to keep the progress, meta and everthing else, we keep the existing file, + // but we replace `data`, and we remove `isGhost`, because the file is no longer a ghost now + if (existingFiles[newFile.id]?.isGhost) { + const { isGhost, ...existingFileState } = existingFiles[newFile.id] + newFile = { + ...existingFileState, + data: fileToAdd.data, + } + this.log(`Replaced the blob in the restored ghost file: ${newFile.name}, ${newFile.id}`) + } + + if (this.checkIfFileAlreadyExists(newFile.id)) { + throw new FileRestrictionError(this.i18n('noDuplicates', { fileName: newFile.name }), { file: fileToAdd }) + } - newFilesState[newFile.id] = newFile + const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, nextFilesState) + + if (onBeforeFileAddedResult === false) { + // Don’t show UI info for this error, as it should be done by the developer + throw new FileRestrictionError('Cannot add the file because onBeforeFileAdded returned false.', { isUserFacing: false, file: fileToAdd }) + } else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) { + newFile = onBeforeFileAddedResult + } + + this.#restricter.validateSingleFile(newFile) + + // need to add it to the new local state immediately, so we can use the state to validate the next files too + nextFilesState[newFile.id] = newFile validFilesToAdd.push(newFile) } catch (err) { errors.push(err) @@ -560,20 +565,22 @@ class Uppy { } try { + // need to run this separately because it's much more slow, so if we run it inside the for-loop it will be very slow + // when many files are added this.#restricter.validateAggregateRestrictions(Object.values(existingFiles), validFilesToAdd) } catch (err) { errors.push(err) // If we have any aggregate error, don't allow adding this batch return { - newFilesState: existingFiles, + nextFilesState: existingFiles, validFilesToAdd: [], errors, } } return { - newFilesState, + nextFilesState, validFilesToAdd, errors, } @@ -590,24 +597,24 @@ class Uppy { addFile (file) { this.#assertNewUploadAllowed(file) - const { newFilesState, validFilesToAdd, errors } = this.#checkAndCreateFileStateObjects([file]) + const { nextFilesState, validFilesToAdd, errors } = this.#checkAndUpdateFileState([file]) const restrictionErrors = errors.filter((error) => error.isRestriction) this.#informAndEmit(restrictionErrors) if (errors.length > 0) throw errors[0] - const [validFileToAdd] = validFilesToAdd + this.setState({ files: nextFilesState }) - this.setState({ files: newFilesState }) + const [firstValidFileToAdd] = validFilesToAdd - this.emit('file-added', validFileToAdd) + this.emit('file-added', firstValidFileToAdd) this.emit('files-added', validFilesToAdd) - this.log(`Added file: ${validFileToAdd.name}, ${validFileToAdd.id}, mime type: ${validFileToAdd.type}`) + this.log(`Added file: ${firstValidFileToAdd.name}, ${firstValidFileToAdd.id}, mime type: ${firstValidFileToAdd.type}`) this.#startIfAutoProceed() - return validFileToAdd.id + return firstValidFileToAdd.id } /** @@ -620,13 +627,15 @@ class Uppy { addFiles (fileDescriptors) { this.#assertNewUploadAllowed() - const { newFilesState, validFilesToAdd, errors } = this.#checkAndCreateFileStateObjects(fileDescriptors) + const { nextFilesState, validFilesToAdd, errors } = this.#checkAndUpdateFileState(fileDescriptors) - const nonRestrictionErrors = errors.filter((error) => !error.isRestriction) const restrictionErrors = errors.filter((error) => error.isRestriction) this.#informAndEmit(restrictionErrors) - this.setState({ files: newFilesState }) + const nonRestrictionErrors = errors.filter((error) => !error.isRestriction) + + // todo shouldn't we only setState if we are not throwing an error? (see below) + this.setState({ files: nextFilesState }) validFilesToAdd.forEach((file) => { this.emit('file-added', file) diff --git a/packages/@uppy/core/src/locale.js b/packages/@uppy/core/src/locale.js index 34d229541e..a3ca237906 100644 --- a/packages/@uppy/core/src/locale.js +++ b/packages/@uppy/core/src/locale.js @@ -57,5 +57,6 @@ export default { 0: 'Added %{smart_count} file from %{folder}', 1: 'Added %{smart_count} files from %{folder}', }, + additionalRestrictionsFailed: '%{count} additional restrictions were not fulfilled', }, } diff --git a/packages/@uppy/locales/src/en_US.js b/packages/@uppy/locales/src/en_US.js index 01decba47d..e1a5fd35c6 100644 --- a/packages/@uppy/locales/src/en_US.js +++ b/packages/@uppy/locales/src/en_US.js @@ -81,6 +81,7 @@ en_US.strings = { '0': 'Added %{smart_count} file from %{folder}', '1': 'Added %{smart_count} files from %{folder}', }, + additionalRestrictionsFailed: '%{count} additional restrictions were not fulfilled', folderAlreadyAdded: 'The folder "%{folder}" was already added', generatingThumbnails: 'Generating thumbnails...', import: 'Import', From bb0079f03446d159b0a8fbaa1a27a98559fbdffb Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Sun, 9 Apr 2023 23:17:26 +0900 Subject: [PATCH 12/19] improve performance when uploading - introduce new event "upload-start" that can contain multiple files - make a new patchFilesState method to allow updating more files - unify "upload-start" logic in all plugins (send it before files start uploading) - defer slicing buffer until we need the data - refactor to reuse code --- .../aws-s3-multipart/src/MultipartUploader.js | 28 ++++-- packages/@uppy/aws-s3-multipart/src/index.js | 73 +++++++------- packages/@uppy/aws-s3/src/index.js | 12 ++- packages/@uppy/core/src/Uppy.js | 97 +++++++++++++------ packages/@uppy/core/src/Uppy.test.js | 12 ++- packages/@uppy/dashboard/src/Dashboard.jsx | 2 + packages/@uppy/tus/src/index.js | 57 ++++------- packages/@uppy/utils/package.json | 1 + packages/@uppy/utils/src/fileFilters.js | 10 ++ packages/@uppy/utils/src/settle.js | 1 + packages/@uppy/xhr-upload/src/index.js | 57 +++++------ private/dev/Dashboard.js | 6 +- 12 files changed, 195 insertions(+), 161 deletions(-) create mode 100644 packages/@uppy/utils/src/fileFilters.js diff --git a/packages/@uppy/aws-s3-multipart/src/MultipartUploader.js b/packages/@uppy/aws-s3-multipart/src/MultipartUploader.js index 34a5ca2255..4c7a2a6975 100644 --- a/packages/@uppy/aws-s3-multipart/src/MultipartUploader.js +++ b/packages/@uppy/aws-s3-multipart/src/MultipartUploader.js @@ -70,19 +70,29 @@ class MultipartUploader { // Upload zero-sized files in one zero-sized chunk if (this.#data.size === 0) { - this.#chunks = [this.#data] - this.#data.onProgress = this.#onPartProgress(0) - this.#data.onComplete = this.#onPartComplete(0) + this.#chunks = [{ + getData: () => this.#data, + onProgress: this.#onPartProgress(0), + onComplete: this.#onPartComplete(0), + }] } else { const arraySize = Math.ceil(fileSize / chunkSize) this.#chunks = Array(arraySize) - let j = 0 - for (let i = 0; i < fileSize; i += chunkSize) { + + for (let i = 0, j = 0; i < fileSize; i += chunkSize, j++) { const end = Math.min(fileSize, i + chunkSize) - const chunk = this.#data.slice(i, end) - chunk.onProgress = this.#onPartProgress(j) - chunk.onComplete = this.#onPartComplete(j) - this.#chunks[j++] = chunk + + // Defer data fetching/slicing until we actually need the data, because it's slow if we have a lot of files + const getData = () => { + const i2 = i + return this.#data.slice(i2, end) + } + + this.#chunks[j] = { + getData, + onProgress: this.#onPartProgress(j), + onComplete: this.#onPartComplete(j), + } } } diff --git a/packages/@uppy/aws-s3-multipart/src/index.js b/packages/@uppy/aws-s3-multipart/src/index.js index 13e5ae31b0..e2b1acd05f 100644 --- a/packages/@uppy/aws-s3-multipart/src/index.js +++ b/packages/@uppy/aws-s3-multipart/src/index.js @@ -4,7 +4,7 @@ import EventTracker from '@uppy/utils/lib/EventTracker' import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress' import getSocketHost from '@uppy/utils/lib/getSocketHost' import { RateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' - +import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/utils/lib/fileFilters' import { createAbortError } from '@uppy/utils/lib/AbortController' import packageJson from '../package.json' import MultipartUploader from './MultipartUploader.js' @@ -200,17 +200,23 @@ class HTTPCommunicationQueue { return this.#sendCompletionRequest(file, { key, uploadId, parts, signal }).abortOn(signal) } - async uploadChunk (file, partNumber, body, signal) { + async uploadChunk (file, partNumber, chunk, signal) { throwIfAborted(signal) const { uploadId, key } = await this.getUploadId(file, signal) throwIfAborted(signal) for (;;) { - const signature = await this.#fetchSignature(file, { uploadId, key, partNumber, body, signal }).abortOn(signal) + const chunkData = chunk.getData() + const { onProgress, onComplete } = chunk + + const signature = await this.#fetchSignature(file, { + uploadId, key, partNumber, body: chunkData, signal, + }).abortOn(signal) + throwIfAborted(signal) try { return { PartNumber: partNumber, - ...await this.#uploadPartBytes(signature, body, signal).abortOn(signal), + ...await this.#uploadPartBytes({ signature, body: chunkData, onProgress, onComplete, signal }).abortOn(signal), } } catch (err) { if (!await this.#shouldRetry(err)) throw err @@ -258,8 +264,6 @@ export default class AwsS3Multipart extends BasePlugin { } } - this.upload = this.upload.bind(this) - /** * Simultaneous upload limiting is shared across all uploads with this plugin. * @@ -369,7 +373,7 @@ export default class AwsS3Multipart extends BasePlugin { .then(assertServerError) } - static async uploadPartBytes ({ url, expires, headers }, body, signal) { + static async uploadPartBytes ({ signature: { url, expires, headers }, body, onProgress, onComplete, signal }) { throwIfAborted(signal) if (url == null) { @@ -397,7 +401,7 @@ export default class AwsS3Multipart extends BasePlugin { } signal.addEventListener('abort', onabort) - xhr.upload.addEventListener('progress', body.onProgress) + xhr.upload.addEventListener('progress', onProgress) xhr.addEventListener('abort', () => { cleanup() @@ -427,7 +431,7 @@ export default class AwsS3Multipart extends BasePlugin { return } - body.onProgress?.(body.size) + onProgress?.(body.size) // NOTE This must be allowed by CORS. const etag = ev.target.getResponseHeader('ETag') @@ -437,7 +441,7 @@ export default class AwsS3Multipart extends BasePlugin { return } - body.onComplete?.(etag) + onComplete?.(etag) resolve({ ETag: etag, }) @@ -466,8 +470,10 @@ export default class AwsS3Multipart extends BasePlugin { }) } - uploadFile (file) { + #uploadFile (file) { return new Promise((resolve, reject) => { + const getFile = () => this.uppy.getFile(file.id) || file + const onProgress = (bytesUploaded, bytesTotal) => { this.uppy.emit('upload-progress', file, { uploader: this, @@ -485,7 +491,6 @@ export default class AwsS3Multipart extends BasePlugin { } const onSuccess = (result) => { - const uploadObject = upload // eslint-disable-line no-use-before-define const uploadResp = { body: { ...result, @@ -495,23 +500,17 @@ export default class AwsS3Multipart extends BasePlugin { this.resetUploaderReferences(file.id) - const cFile = this.uppy.getFile(file.id) - this.uppy.emit('upload-success', cFile || file, uploadResp) + this.uppy.emit('upload-success', getFile(), uploadResp) if (result.location) { this.uppy.log(`Download ${file.name} from ${result.location}`) } - resolve(uploadObject) + resolve() } const onPartComplete = (part) => { - const cFile = this.uppy.getFile(file.id) - if (!cFile) { - return - } - - this.uppy.emit('s3-multipart:part-uploaded', cFile, part) + this.uppy.emit('s3-multipart:part-uploaded', getFile(), part) } const upload = new MultipartUploader(file.data, { @@ -564,11 +563,7 @@ export default class AwsS3Multipart extends BasePlugin { upload.start() }) - // Don't double-emit upload-started for Golden Retriever-restored files that were already started - if (!file.progress.uploadStarted || !file.isRestored) { - upload.start() - this.uppy.emit('upload-started', file) - } + upload.start() }) } @@ -597,14 +592,9 @@ export default class AwsS3Multipart extends BasePlugin { // NOTE! Keep this duplicated code in sync with other plugins // TODO we should probably abstract this into a common function - async uploadRemote (file) { + async #uploadRemote (file) { this.resetUploaderReferences(file.id) - // Don't double-emit upload-started for Golden Retriever-restored files that were already started - if (!file.progress.uploadStarted || !file.isRestored) { - this.uppy.emit('upload-started', file) - } - try { if (file.serverToken) { return await this.connectToServerSocket(file) @@ -733,15 +723,20 @@ export default class AwsS3Multipart extends BasePlugin { }) } - async upload (fileIDs) { + #upload = async (fileIDs) => { if (fileIDs.length === 0) return undefined - const promises = fileIDs.map((id) => { - const file = this.uppy.getFile(id) + const files = this.uppy.getFilesByIds(fileIDs) + + const filesFiltered = filterNonFailedFiles(files) + const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered) + this.uppy.emit('upload-start', filesToEmit) + + const promises = filesFiltered.map((file) => { if (file.isRemote) { - return this.uploadRemote(file) + return this.#uploadRemote(file) } - return this.uploadFile(file) + return this.#uploadFile(file) }) return Promise.all(promises) @@ -810,7 +805,7 @@ export default class AwsS3Multipart extends BasePlugin { }, }) this.uppy.addPreProcessor(this.#setCompanionHeaders) - this.uppy.addUploader(this.upload) + this.uppy.addUploader(this.#upload) } uninstall () { @@ -822,6 +817,6 @@ export default class AwsS3Multipart extends BasePlugin { }, }) this.uppy.removePreProcessor(this.#setCompanionHeaders) - this.uppy.removeUploader(this.upload) + this.uppy.removeUploader(this.#upload) } } diff --git a/packages/@uppy/aws-s3/src/index.js b/packages/@uppy/aws-s3/src/index.js index 7c34dd6370..cb0c267f7a 100644 --- a/packages/@uppy/aws-s3/src/index.js +++ b/packages/@uppy/aws-s3/src/index.js @@ -28,6 +28,7 @@ import BasePlugin from '@uppy/core/lib/BasePlugin.js' import { RateLimitedQueue, internalRateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' import { RequestClient } from '@uppy/companion-client' +import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/utils/lib/fileFilters' import packageJson from '../package.json' import MiniXHRUpload from './MiniXHRUpload.js' @@ -163,7 +164,7 @@ export default class AwsS3 extends BasePlugin { .then(assertServerError) } - #handleUpload = (fileIDs) => { + #handleUpload = async (fileIDs) => { /** * keep track of `getUploadParameters()` responses * so we can cancel the calls individually using just a file ID @@ -178,10 +179,11 @@ export default class AwsS3 extends BasePlugin { } this.uppy.on('file-removed', onremove) - fileIDs.forEach((id) => { - const file = this.uppy.getFile(id) - this.uppy.emit('upload-started', file) - }) + const files = this.uppy.getFilesByIds(fileIDs) + + const filesFiltered = filterNonFailedFiles(files) + const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered) + this.uppy.emit('upload-start', filesToEmit) const getUploadParameters = this.#requests.wrapPromiseFunction((file) => { return this.opts.getUploadParameters(file) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 517c8b4705..ab64e2889a 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -179,6 +179,23 @@ class Uppy { return this.store.getState() } + patchFilesState (filesWithNewState) { + const existingFilesState = this.getState().files + + this.setState({ + files: { + ...existingFilesState, + ...Object.fromEntries(Object.entries(filesWithNewState).map(([fileID, newFileState]) => ([ + fileID, + { + ...existingFilesState[fileID], + ...newFileState, + }, + ]))), + }, + }) + } + /** * Shorthand to set state for a specific file. */ @@ -187,9 +204,7 @@ class Uppy { throw new Error(`Can’t set state for ${fileID} (the file could have been removed)`) } - this.setState({ - files: { ...this.getState().files, [fileID]: { ...this.getState().files[fileID], ...state } }, - }) + this.patchFilesState({ [fileID]: state }) } i18nInit () { @@ -324,6 +339,10 @@ class Uppy { return Object.values(files) } + getFilesByIds (ids) { + return ids.map((id) => this.getFile(id)) + } + getObjectOfFilesPerState () { const { files: filesObject, totalProgress, error } = this.getState() const files = Object.values(filesObject) @@ -1019,20 +1038,35 @@ class Uppy { this.setState({ error: null }) }) - this.on('upload-started', (file) => { - if (file == null || !this.getFile(file.id)) { - this.log(`Not setting progress for a file that has been removed: ${file?.id}`) - return - } - this.setFileState(file.id, { - progress: { - uploadStarted: Date.now(), - uploadComplete: false, - percentage: 0, - bytesUploaded: 0, - bytesTotal: file.size, + const onUploadStarted = (files) => { + const filesFiltered = files.filter((file) => { + const exists = (file != null && this.getFile(file.id)) + if (!exists) this.log(`Not setting progress for a file that has been removed: ${file?.id}`) + return exists + }) + + const filesState = Object.fromEntries(filesFiltered.map((file) => ([ + file.id, + { + progress: { + uploadStarted: Date.now(), + uploadComplete: false, + percentage: 0, + bytesUploaded: 0, + bytesTotal: file.size, + }, }, + ]))) + + this.patchFilesState(filesState) + } + + this.on('upload-start', (files) => { + files.forEach((file) => { + // todo backward compat, remove this event in a next major + this.emit('upload-started', file) }) + onUploadStarted(files) }) this.on('upload-progress', this.calculateProgress) @@ -1441,9 +1475,12 @@ class Uppy { * @private */ async #runUpload (uploadID) { - let { currentUploads } = this.getState() - let currentUpload = currentUploads[uploadID] - const restoreStep = currentUpload.step || 0 + const getCurrentUpload = () => { + const { currentUploads } = this.getState() + return currentUploads[uploadID] + } + + let currentUpload = getCurrentUpload() const steps = [ ...this.#preProcessors, @@ -1451,31 +1488,30 @@ class Uppy { ...this.#postProcessors, ] try { - for (let step = restoreStep; step < steps.length; step++) { + for (let step = currentUpload.step || 0; step < steps.length; step++) { if (!currentUpload) { break } const fn = steps[step] - const updatedUpload = { - ...currentUpload, - step, - } - this.setState({ currentUploads: { - ...currentUploads, - [uploadID]: updatedUpload, + ...this.getState().currentUploads, + [uploadID]: { + ...currentUpload, + step, + }, }, }) + const { fileIDs } = currentUpload + // 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 - await fn(updatedUpload.fileIDs, uploadID) + await fn(fileIDs, uploadID) // Update currentUpload value in case it was modified asynchronously. - currentUploads = this.getState().currentUploads - currentUpload = currentUploads[uploadID] + currentUpload = getCurrentUpload() } } catch (err) { this.#removeUpload(uploadID) @@ -1507,8 +1543,7 @@ class Uppy { await this.addResultData(uploadID, { successful, failed, uploadID }) // Update currentUpload value in case it was modified asynchronously. - currentUploads = this.getState().currentUploads - currentUpload = currentUploads[uploadID] + currentUpload = getCurrentUpload() } // Emit completion events. // This is in a separate function so that the `currentUploads` variable diff --git a/packages/@uppy/core/src/Uppy.test.js b/packages/@uppy/core/src/Uppy.test.js index 83f6781c41..a3141c09c8 100644 --- a/packages/@uppy/core/src/Uppy.test.js +++ b/packages/@uppy/core/src/Uppy.test.js @@ -1430,7 +1430,7 @@ describe('src/Core', () => { const promise = new Promise((resolve) => { proceedUpload = resolve }) const finishPromise = new Promise((resolve) => { finishUpload = resolve }) core.addUploader(async ([id]) => { - core.emit('upload-started', core.getFile(id)) + core.emit('upload-start', [core.getFile(id)]) await promise core.emit('upload-progress', core.getFile(id), { bytesTotal: 3456, @@ -1450,7 +1450,11 @@ describe('src/Core', () => { core.calculateTotalProgress() const uploadPromise = core.upload() - await new Promise((resolve) => core.once('upload-started', resolve)) + await Promise.all([ + new Promise((resolve) => core.once('upload-start', resolve)), + // todo backward compat: remove in next major + new Promise((resolve) => core.once('upload-started', resolve)), + ]) expect(core.getFiles()[0].size).toBeNull() expect(core.getFiles()[0].progress).toMatchObject({ @@ -1493,7 +1497,7 @@ describe('src/Core', () => { const core = new Core() core.once('file-added', (file) => { - core.emit('upload-started', file) + core.emit('upload-start', [file]) core.emit('upload-progress', file, { bytesTotal: 3456, bytesUploaded: 1234, @@ -1507,7 +1511,7 @@ describe('src/Core', () => { }) core.once('file-added', (file) => { - core.emit('upload-started', file) + core.emit('upload-start', [file]) core.emit('upload-progress', file, { bytesTotal: null, bytesUploaded: null, diff --git a/packages/@uppy/dashboard/src/Dashboard.jsx b/packages/@uppy/dashboard/src/Dashboard.jsx index 582efcc8d6..d7d38b2dbe 100644 --- a/packages/@uppy/dashboard/src/Dashboard.jsx +++ b/packages/@uppy/dashboard/src/Dashboard.jsx @@ -653,6 +653,8 @@ export default class Dashboard extends UIPlugin { } } + this.uppy.log('[Dashboard] Processing dropped files') + // Add all dropped files const files = await getDroppedFiles(event.dataTransfer, { logDropError }) if (files.length > 0) { diff --git a/packages/@uppy/tus/src/index.js b/packages/@uppy/tus/src/index.js index a1f0c4feeb..0d254d65e9 100644 --- a/packages/@uppy/tus/src/index.js +++ b/packages/@uppy/tus/src/index.js @@ -3,12 +3,12 @@ import * as tus from 'tus-js-client' import { Provider, RequestClient, Socket } from '@uppy/companion-client' import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress' import getSocketHost from '@uppy/utils/lib/getSocketHost' -import settle from '@uppy/utils/lib/settle' import EventTracker from '@uppy/utils/lib/EventTracker' import NetworkError from '@uppy/utils/lib/NetworkError' import isNetworkError from '@uppy/utils/lib/isNetworkError' import { RateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' import hasProperty from '@uppy/utils/lib/hasProperty' +import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/utils/lib/fileFilters' import getFingerprint from './getFingerprint.js' import packageJson from '../package.json' @@ -102,7 +102,6 @@ export default class Tus extends BasePlugin { this.uploaderSockets = Object.create(null) this.handleResetProgress = this.handleResetProgress.bind(this) - this.handleUpload = this.handleUpload.bind(this) this.#queueRequestSocketToken = this.requests.wrapPromiseFunction(this.#requestSocketToken, { priority: -1 }) } @@ -183,7 +182,7 @@ export default class Tus extends BasePlugin { * @param {UppyFile} file for use with upload * @returns {Promise} */ - upload (file) { + #upload (file) { this.resetUploaderReferences(file.id) // Create a new tus upload @@ -192,8 +191,6 @@ export default class Tus extends BasePlugin { let qRequest let upload - this.uppy.emit('upload-started', file) - const opts = { ...this.opts, ...(file.tus || {}), @@ -462,14 +459,9 @@ export default class Tus extends BasePlugin { * @param {UppyFile} file for use with upload * @returns {Promise} */ - async uploadRemote (file) { + async #uploadRemote (file) { this.resetUploaderReferences(file.id) - // Don't double-emit upload-started for Golden Retriever-restored files that were already started - if (!file.progress.uploadStarted || !file.isRestored) { - this.uppy.emit('upload-started', file) - } - try { if (file.serverToken) { return await this.connectToServerSocket(file) @@ -730,39 +722,29 @@ export default class Tus extends BasePlugin { /** * @param {(UppyFile | FailedUppyFile)[]} files */ - uploadFiles (files) { - const promises = files.map((file, i) => { + async #uploadFiles (files) { + const filesFiltered = filterNonFailedFiles(files) + const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered) + this.uppy.emit('upload-start', filesToEmit) + + await Promise.allSettled(filesFiltered.map((file, i) => { const current = i + 1 const total = files.length - if ('error' in file && file.error) { - return Promise.reject(new Error(file.error)) - } if (file.isRemote) { - // We emit upload-started here, so that it's also emitted for files - // that have to wait due to the `limit` option. - // Don't double-emit upload-started for Golden Retriever-restored files that were already started - if (!file.progress.uploadStarted || !file.isRestored) { - this.uppy.emit('upload-started', file) - } - return this.uploadRemote(file, current, total) - } - // Don't double-emit upload-started for Golden Retriever-restored files that were already started - if (!file.progress.uploadStarted || !file.isRestored) { - this.uppy.emit('upload-started', file) + if (file.isRemote) { + return this.#uploadRemote(file, current, total) } - return this.upload(file, current, total) - }) - - return settle(promises) + return this.#upload(file, current, total) + })) } /** * @param {string[]} fileIDs */ - handleUpload (fileIDs) { + #handleUpload = async (fileIDs) => { if (fileIDs.length === 0) { this.uppy.log('[Tus] No files to upload') - return Promise.resolve() + return } if (this.opts.limit === 0) { @@ -773,17 +755,16 @@ export default class Tus extends BasePlugin { } this.uppy.log('[Tus] Uploading...') - const filesToUpload = fileIDs.map((fileID) => this.uppy.getFile(fileID)) + const filesToUpload = this.uppy.getFilesByIds(fileIDs) - return this.uploadFiles(filesToUpload) - .then(() => null) + await this.#uploadFiles(filesToUpload) } install () { this.uppy.setState({ capabilities: { ...this.uppy.getState().capabilities, resumableUploads: true }, }) - this.uppy.addUploader(this.handleUpload) + this.uppy.addUploader(this.#handleUpload) this.uppy.on('reset-progress', this.handleResetProgress) } @@ -792,6 +773,6 @@ export default class Tus extends BasePlugin { this.uppy.setState({ capabilities: { ...this.uppy.getState().capabilities, resumableUploads: false }, }) - this.uppy.removeUploader(this.handleUpload) + this.uppy.removeUploader(this.#handleUpload) } } diff --git a/packages/@uppy/utils/package.json b/packages/@uppy/utils/package.json index ae1db2a0b1..86767e0b11 100644 --- a/packages/@uppy/utils/package.json +++ b/packages/@uppy/utils/package.json @@ -61,6 +61,7 @@ "./lib/mimeTypes": "./lib/mimeTypes.js", "./lib/getDroppedFiles": "./lib/getDroppedFiles/index.js", "./lib/FOCUSABLE_ELEMENTS.js": "./lib/FOCUSABLE_ELEMENTS.js", + "./lib/fileFilters": "./lib/fileFilters.js", "./src/microtip.scss": "./src/microtip.scss" }, "dependencies": { diff --git a/packages/@uppy/utils/src/fileFilters.js b/packages/@uppy/utils/src/fileFilters.js new file mode 100644 index 0000000000..f4164cc6f4 --- /dev/null +++ b/packages/@uppy/utils/src/fileFilters.js @@ -0,0 +1,10 @@ +export function filterNonFailedFiles (files) { + const hasError = (file) => 'error' in file && file.error + + return files.filter((file) => !hasError(file)) +} + +// Don't double-emit upload-started for Golden Retriever-restored files that were already started +export function filterFilesToEmitUploadStarted (files) { + return files.filter((file) => !file.progress.uploadStarted || !file.isRestored) +} diff --git a/packages/@uppy/utils/src/settle.js b/packages/@uppy/utils/src/settle.js index d7b3235bea..2a57f6c3d6 100644 --- a/packages/@uppy/utils/src/settle.js +++ b/packages/@uppy/utils/src/settle.js @@ -1,3 +1,4 @@ +// TODO remove (no longer in use) export default function settle (promises) { const resolutions = [] const rejections = [] diff --git a/packages/@uppy/xhr-upload/src/index.js b/packages/@uppy/xhr-upload/src/index.js index 65e297ad11..b95d71f195 100644 --- a/packages/@uppy/xhr-upload/src/index.js +++ b/packages/@uppy/xhr-upload/src/index.js @@ -3,12 +3,12 @@ import { nanoid } from 'nanoid/non-secure' import { Provider, RequestClient, Socket } from '@uppy/companion-client' import emitSocketProgress from '@uppy/utils/lib/emitSocketProgress' import getSocketHost from '@uppy/utils/lib/getSocketHost' -import settle from '@uppy/utils/lib/settle' import EventTracker from '@uppy/utils/lib/EventTracker' import ProgressTimeout from '@uppy/utils/lib/ProgressTimeout' import { RateLimitedQueue, internalRateLimitedQueue } from '@uppy/utils/lib/RateLimitedQueue' import NetworkError from '@uppy/utils/lib/NetworkError' import isNetworkError from '@uppy/utils/lib/isNetworkError' +import { filterNonFailedFiles, filterFilesToEmitUploadStarted } from '@uppy/utils/lib/fileFilters' import packageJson from '../package.json' import locale from './locale.js' @@ -113,8 +113,6 @@ export default class XHRUpload extends BasePlugin { this.opts = { ...defaultOptions, ...opts } this.i18nInit() - this.handleUpload = this.handleUpload.bind(this) - // Simultaneous upload limiting is shared across all uploads with this plugin. if (internalRateLimitedQueue in this.opts) { this.requests = this.opts[internalRateLimitedQueue] @@ -214,13 +212,11 @@ export default class XHRUpload extends BasePlugin { return formPost } - upload (file, current, total) { + async #upload (file, current, total) { const opts = this.getOptions(file) this.uppy.log(`uploading ${current} of ${total}`) return new Promise((resolve, reject) => { - this.uppy.emit('upload-started', file) - const data = opts.formData ? this.createFormDataUpload(file, opts) : file.data @@ -317,8 +313,6 @@ export default class XHRUpload extends BasePlugin { } queuedRequest = this.requests.run(() => { - this.uppy.emit('upload-started', file) - // When using an authentication system like JWT, the bearer token goes as a header. This // header needs to be fresh each time the token is refreshed so computing and setting the // headers just before the upload starts enables this kind of authentication to work properly. @@ -375,10 +369,9 @@ export default class XHRUpload extends BasePlugin { // NOTE! Keep this duplicated code in sync with other plugins // TODO we should probably abstract this into a common function - async uploadRemote (file) { + async #uploadRemote (file) { // TODO: we could rewrite this to use server-sent events instead of creating WebSockets. try { - this.uppy.emit('upload-started', file) if (file.serverToken) { return await this.connectToServerSocket(file) } @@ -496,7 +489,7 @@ export default class XHRUpload extends BasePlugin { }) } - uploadBundle (files) { + #uploadBundle (files) { return new Promise((resolve, reject) => { const { endpoint } = this.opts const { method } = this.opts @@ -587,27 +580,19 @@ export default class XHRUpload extends BasePlugin { }) xhr.send(formData) - - files.forEach((file) => { - this.uppy.emit('upload-started', file) - }) }) } - uploadFiles (files) { - const promises = files.map((file, i) => { + async #uploadFiles (files) { + await Promise.allSettled(files.map((file, i) => { const current = parseInt(i, 10) + 1 const total = files.length - if (file.error) { - return Promise.reject(new Error(file.error)) - } if (file.isRemote) { - return this.uploadRemote(file, current, total) + if (file.isRemote) { + return this.#uploadRemote(file, current, total) } - return this.upload(file, current, total) - }) - - return settle(promises) + return this.#upload(file, current, total) + })) } onFileRemove (fileID, cb) { @@ -638,10 +623,10 @@ export default class XHRUpload extends BasePlugin { }) } - handleUpload (fileIDs) { + #handleUpload = async (fileIDs) => { if (fileIDs.length === 0) { this.uppy.log('[XHRUpload] No files to upload!') - return Promise.resolve() + return } // No limit configured by the user, and no RateLimitedQueue passed in by a "parent" plugin @@ -654,11 +639,15 @@ export default class XHRUpload extends BasePlugin { } this.uppy.log('[XHRUpload] Uploading...') - const files = fileIDs.map((fileID) => this.uppy.getFile(fileID)) + const files = this.uppy.getFilesByIds(fileIDs) + + const filesFiltered = filterNonFailedFiles(files) + const filesToEmit = filterFilesToEmitUploadStarted(filesFiltered) + this.uppy.emit('upload-start', filesToEmit) if (this.opts.bundle) { // if bundle: true, we don’t support remote uploads - const isSomeFileRemote = files.some(file => file.isRemote) + const isSomeFileRemote = filesFiltered.some(file => file.isRemote) if (isSomeFileRemote) { throw new Error('Can’t upload remote files when the `bundle: true` option is set') } @@ -667,10 +656,10 @@ export default class XHRUpload extends BasePlugin { throw new TypeError('`headers` may not be a function when the `bundle: true` option is set') } - return this.uploadBundle(files) + await this.#uploadBundle(filesFiltered) + } else { + await this.#uploadFiles(filesFiltered) } - - return this.uploadFiles(files).then(() => null) } install () { @@ -684,7 +673,7 @@ export default class XHRUpload extends BasePlugin { }) } - this.uppy.addUploader(this.handleUpload) + this.uppy.addUploader(this.#handleUpload) } uninstall () { @@ -698,6 +687,6 @@ export default class XHRUpload extends BasePlugin { }) } - this.uppy.removeUploader(this.handleUpload) + this.uppy.removeUploader(this.#handleUpload) } } diff --git a/private/dev/Dashboard.js b/private/dev/Dashboard.js index 0e98db614a..ed6933b9c5 100644 --- a/private/dev/Dashboard.js +++ b/private/dev/Dashboard.js @@ -40,6 +40,7 @@ console.log(import.meta.env) // DEV CONFIG: enable or disable Golden Retriever const RESTORE = false +const COMPRESS = false async function assemblyOptions () { return generateSignatureIfSecret(TRANSLOADIT_SECRET, { @@ -108,7 +109,10 @@ export default () => { .use(DropTarget, { target: document.body, }) - .use(Compressor) + + if (COMPRESS) { + uppyDashboard.use(Compressor) + } switch (UPLOADER) { case 'tus': From ff3e580c0f34ddd635adb666e88a1482c7ce7293 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Sun, 9 Apr 2023 23:59:45 +0900 Subject: [PATCH 13/19] fix e2e build issue --- packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index cb3d01492d..8c8871eb97 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx @@ -1,6 +1,6 @@ import { h } from 'preact' // eslint-disable-next-line import/no-unresolved -import PQueue from 'p-queue' +import PQueue from 'p-queue/dist' // https://github.com/sindresorhus/p-queue/issues/145 import { getSafeFileId } from '@uppy/utils/lib/generateFileID' From b43864fb95b6a41a375fe0b92c02384cbc048980 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Mon, 10 Apr 2023 15:32:40 +0900 Subject: [PATCH 14/19] try to upgrade cypress maybe it fixes the error --- e2e/package.json | 2 +- yarn.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index 2fe1ef2582..a15dc61689 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -45,7 +45,7 @@ "@uppy/zoom": "workspace:^" }, "devDependencies": { - "cypress": "^10.0.0", + "cypress": "^12.9.0", "cypress-terminal-report": "^4.1.2", "deep-freeze": "^0.0.1", "execa": "^6.1.0", diff --git a/yarn.lock b/yarn.lock index 03ca86263f..b7180fcf33 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13928,9 +13928,9 @@ __metadata: languageName: node linkType: hard -"cypress@npm:^10.0.0": - version: 10.7.0 - resolution: "cypress@npm:10.7.0" +"cypress@npm:^12.9.0": + version: 12.9.0 + resolution: "cypress@npm:12.9.0" dependencies: "@cypress/request": ^2.88.10 "@cypress/xvfb": ^1.2.4 @@ -13949,9 +13949,9 @@ __metadata: commander: ^5.1.0 common-tags: ^1.8.0 dayjs: ^1.10.4 - debug: ^4.3.2 + debug: ^4.3.4 enquirer: ^2.3.6 - eventemitter2: ^6.4.3 + eventemitter2: 6.4.7 execa: 4.1.0 executable: ^4.1.1 extract-zip: 2.0.1 @@ -13976,7 +13976,7 @@ __metadata: yauzl: ^2.10.0 bin: cypress: bin/cypress - checksum: ef8a5ae54f3404f7926e1e248ba7a27c7f66e654a9603bf9df6366731d8dd75455f6ec9cc34d7e55e1a8c3cb8e0e36ce59add81b7b14466f2063cfe45e6c00f4 + checksum: aad2278310fe4897b2c4e1f23e22f28992c83bcac945a6c350d077b1b2fb1805e44d5c58e19e2a9d63ca04aa3f9eabdd3c661cab0ce5dddd75c72702262ac89e languageName: node linkType: hard @@ -14854,7 +14854,7 @@ __metadata: "@uppy/webcam": "workspace:^" "@uppy/xhr-upload": "workspace:^" "@uppy/zoom": "workspace:^" - cypress: ^10.0.0 + cypress: ^12.9.0 cypress-terminal-report: ^4.1.2 deep-freeze: ^0.0.1 execa: ^6.1.0 @@ -16682,7 +16682,7 @@ __metadata: languageName: node linkType: hard -"eventemitter2@npm:^6.4.3": +"eventemitter2@npm:6.4.7": version: 6.4.7 resolution: "eventemitter2@npm:6.4.7" checksum: 1b36a77e139d6965ebf3a36c01fa00c089ae6b80faa1911e52888f40b3a7057b36a2cc45dcd1ad87cda3798fe7b97a0aabcbb8175a8b96092a23bb7d0f039e66 From a84e3f61d28e4784f118e8e36bfc84a3c16a83dd Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Mon, 10 Apr 2023 15:33:06 +0900 Subject: [PATCH 15/19] Revert "fix e2e build issue" This reverts commit ff3e580c0f34ddd635adb666e88a1482c7ce7293. --- packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx index 8c8871eb97..cb3d01492d 100644 --- a/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx +++ b/packages/@uppy/provider-views/src/ProviderView/ProviderView.jsx @@ -1,6 +1,6 @@ import { h } from 'preact' // eslint-disable-next-line import/no-unresolved -import PQueue from 'p-queue/dist' // https://github.com/sindresorhus/p-queue/issues/145 +import PQueue from 'p-queue' import { getSafeFileId } from '@uppy/utils/lib/generateFileID' From 00bd1460534621a665990dfafc3d5af39336b114 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Mon, 10 Apr 2023 17:25:21 +0900 Subject: [PATCH 16/19] upgrade parcel --- e2e/package.json | 2 +- package.json | 2 +- yarn.lock | 1773 +++++++++++++++++++++++++++++++++------------- 3 files changed, 1291 insertions(+), 486 deletions(-) diff --git a/e2e/package.json b/e2e/package.json index a15dc61689..0afe2839fb 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -49,7 +49,7 @@ "cypress-terminal-report": "^4.1.2", "deep-freeze": "^0.0.1", "execa": "^6.1.0", - "parcel": "^2.0.1", + "parcel": "2.0.0-nightly.1278", "prompts": "^2.4.2", "react": "^18.1.0", "react-dom": "^18.1.0", diff --git a/package.json b/package.json index e387474109..8cd0b4c08f 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "@babel/preset-env": "^7.14.7", "@babel/register": "^7.10.5", "@babel/types": "^7.17.0", - "@parcel/transformer-vue": "2.7.0", + "@parcel/transformer-vue": "2.8.4-nightly.2903+5b901a317", "@types/jasmine": "file:./private/@types/jasmine", "@types/jasminewd2": "file:./private/@types/jasmine", "@typescript-eslint/eslint-plugin": "^5.0.0", diff --git a/yarn.lock b/yarn.lock index b7180fcf33..d661ee4b82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4332,109 +4332,196 @@ __metadata: languageName: node linkType: hard -"@parcel/bundler-default@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/bundler-default@npm:2.7.0" +"@parcel/bundler-default@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/bundler-default@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/graph": 2.8.4-nightly.2903+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + nullthrows: ^1.1.1 + checksum: 936a3ea9adac2641cac0059a2f5927ab660e8f9e5dba946c92e4300beac4804743f642a76545c1d11ad71f6f5e362c654b33af2185e7e222c3a81616c487e29a + languageName: node + linkType: hard + +"@parcel/bundler-default@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/bundler-default@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/graph": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 nullthrows: ^1.1.1 - checksum: 0efb78be2e8873d951549f0e6e25fe441ff7a0220bc8414430c8ddd4a7cb3ae84b100a5cc761277af699bf736e3631eb0fa456bdec1141a7bddc5f0f52312522 + checksum: 219b2be341cad20991659b7a3031454a081ce0787c161a4da8a73ae8a4af4468667b284caea9488e869b162763d308cfd6495ab35fe386413b14325d6667ea86 languageName: node linkType: hard -"@parcel/cache@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/cache@npm:2.7.0" +"@parcel/cache@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/cache@npm:2.0.0-nightly.1280" dependencies: - "@parcel/fs": 2.7.0 - "@parcel/logger": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/fs": 2.0.0-nightly.1280+5b901a317 + "@parcel/logger": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 lmdb: 2.5.2 peerDependencies: - "@parcel/core": ^2.7.0 - checksum: 7aa6a6883b1c62a8f1fa13d8841adbed3e2d969b72d67f51f833c6a14464ad5fc5a6368883759e817bcda44a4f0f617ece068a93d389bc68697c326e7651c8f6 + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: 4ae837045250e1415eb7c73787c66a08138cb2e559b3a836a63ede0708a127c6030a7278508020830b0830cd5e2688d0ef70dc90271f24792d1b469f74658b76 languageName: node linkType: hard -"@parcel/codeframe@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/codeframe@npm:2.7.0" +"@parcel/cache@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/cache@npm:2.8.3" + dependencies: + "@parcel/fs": 2.8.3 + "@parcel/logger": 2.8.3 + "@parcel/utils": 2.8.3 + lmdb: 2.5.2 + peerDependencies: + "@parcel/core": ^2.8.3 + checksum: cd679053d229f8d06536a8fc9d857e5fa58905492e1a97c4f6b1da82de0dcef202a609c1e36206d3cdb32e5da3a214525f868b98dfd7aa671a53dacceb004fd9 + languageName: node + linkType: hard + +"@parcel/codeframe@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/codeframe@npm:2.0.0-nightly.1280" dependencies: chalk: ^4.1.0 - checksum: 169f305518f567019d893ef3865ccc29f38d93652441d1345ef93f5c5c0533c99f37493917514c2be051939643e62faea213a52c0d797e078120f40cfe90138d + checksum: af0d7b78b4779f980c1c4fc1965d085926183c84b1d071c270df37ba973de20bb9e3e8db864ffba0778e8fb8ac41dfc9e2af1faa3eb9fd7f474864b3e77229ae languageName: node linkType: hard -"@parcel/compressor-raw@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/compressor-raw@npm:2.7.0" +"@parcel/codeframe@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/codeframe@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - checksum: 6b9c009fe45ff461b4c7b6ec1ac723da6d09ea3d9af2b1b9a8bacefa97fd7c597fc2f71fc47328f97b26c7bd77cf6beedaa46797a26c5692bb8a63ea1cb5937b + chalk: ^4.1.0 + checksum: a6e82c30e6251dcae14f247a14f6cb265f766b8bf18b62dd6a1c4a103cfae364a08897b36c5c379d0d867169647cb72962266f77571f718ff68ef70a16b81c02 languageName: node linkType: hard -"@parcel/config-default@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/config-default@npm:2.7.0" - dependencies: - "@parcel/bundler-default": 2.7.0 - "@parcel/compressor-raw": 2.7.0 - "@parcel/namer-default": 2.7.0 - "@parcel/optimizer-css": 2.7.0 - "@parcel/optimizer-htmlnano": 2.7.0 - "@parcel/optimizer-image": 2.7.0 - "@parcel/optimizer-svgo": 2.7.0 - "@parcel/optimizer-terser": 2.7.0 - "@parcel/packager-css": 2.7.0 - "@parcel/packager-html": 2.7.0 - "@parcel/packager-js": 2.7.0 - "@parcel/packager-raw": 2.7.0 - "@parcel/packager-svg": 2.7.0 - "@parcel/reporter-dev-server": 2.7.0 - "@parcel/resolver-default": 2.7.0 - "@parcel/runtime-browser-hmr": 2.7.0 - "@parcel/runtime-js": 2.7.0 - "@parcel/runtime-react-refresh": 2.7.0 - "@parcel/runtime-service-worker": 2.7.0 - "@parcel/transformer-babel": 2.7.0 - "@parcel/transformer-css": 2.7.0 - "@parcel/transformer-html": 2.7.0 - "@parcel/transformer-image": 2.7.0 - "@parcel/transformer-js": 2.7.0 - "@parcel/transformer-json": 2.7.0 - "@parcel/transformer-postcss": 2.7.0 - "@parcel/transformer-posthtml": 2.7.0 - "@parcel/transformer-raw": 2.7.0 - "@parcel/transformer-react-refresh-wrap": 2.7.0 - "@parcel/transformer-svg": 2.7.0 - peerDependencies: - "@parcel/core": ^2.7.0 - checksum: 165d4ec08907d20759267160d7ab6c455aa05d855211dbd4d5f973de3c6b4bcccc41a039f819df76bf39d348cf0448efda06bc8916c4daf4d6c626e05d731a01 - languageName: node - linkType: hard - -"@parcel/core@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/core@npm:2.7.0" +"@parcel/compressor-raw@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/compressor-raw@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + checksum: ca3b8a4f60e5193cffaa8041e709513df9c6cb54f32c9d20fef993a9af2d84f1e2d8bf8f4092220a8abaec24679498f854e683511876187f35b4f94a5852cf85 + languageName: node + linkType: hard + +"@parcel/compressor-raw@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/compressor-raw@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + checksum: 02465b01f57504afcb6819fd58f74e969f3db4467aebd63ddf24657241583dbf4d9cd56e5e8d76bdb9ab77518fb53756e9f9317e311937f077f9ce0aba56c281 + languageName: node + linkType: hard + +"@parcel/config-default@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/config-default@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/bundler-default": 2.0.0-nightly.1280+5b901a317 + "@parcel/compressor-raw": 2.8.4-nightly.2903+5b901a317 + "@parcel/namer-default": 2.0.0-nightly.1280+5b901a317 + "@parcel/optimizer-css": 2.8.4-nightly.2903+5b901a317 + "@parcel/optimizer-htmlnano": 2.0.0-nightly.1280+5b901a317 + "@parcel/optimizer-image": 2.8.4-nightly.2903+5b901a317 + "@parcel/optimizer-svgo": 2.8.4-nightly.2903+5b901a317 + "@parcel/optimizer-swc": 2.8.4-nightly.2903+5b901a317 + "@parcel/packager-css": 2.0.0-nightly.1280+5b901a317 + "@parcel/packager-html": 2.0.0-nightly.1280+5b901a317 + "@parcel/packager-js": 2.0.0-nightly.1280+5b901a317 + "@parcel/packager-raw": 2.0.0-nightly.1280+5b901a317 + "@parcel/packager-svg": 2.8.4-nightly.2903+5b901a317 + "@parcel/reporter-dev-server": 2.0.0-nightly.1280+5b901a317 + "@parcel/resolver-default": 2.0.0-nightly.1280+5b901a317 + "@parcel/runtime-browser-hmr": 2.0.0-nightly.1280+5b901a317 + "@parcel/runtime-js": 2.0.0-nightly.1280+5b901a317 + "@parcel/runtime-react-refresh": 2.0.0-nightly.1280+5b901a317 + "@parcel/runtime-service-worker": 2.8.4-nightly.2903+5b901a317 + "@parcel/transformer-babel": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-css": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-html": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-image": 2.8.4-nightly.2903+5b901a317 + "@parcel/transformer-js": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-json": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-postcss": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-posthtml": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-raw": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-react-refresh-wrap": 2.0.0-nightly.1280+5b901a317 + "@parcel/transformer-svg": 2.8.4-nightly.2903+5b901a317 + peerDependencies: + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: 3d72c1fec22e5e2ddbe1ca11b1460f08247253d6c5e21863f47f31058075fb1804c69c40d99cdab9b47f0a1d9f668b7bd8bef3d87c1e15cb11220f0a32847a15 + languageName: node + linkType: hard + +"@parcel/config-default@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/config-default@npm:2.8.3" + dependencies: + "@parcel/bundler-default": 2.8.3 + "@parcel/compressor-raw": 2.8.3 + "@parcel/namer-default": 2.8.3 + "@parcel/optimizer-css": 2.8.3 + "@parcel/optimizer-htmlnano": 2.8.3 + "@parcel/optimizer-image": 2.8.3 + "@parcel/optimizer-svgo": 2.8.3 + "@parcel/optimizer-terser": 2.8.3 + "@parcel/packager-css": 2.8.3 + "@parcel/packager-html": 2.8.3 + "@parcel/packager-js": 2.8.3 + "@parcel/packager-raw": 2.8.3 + "@parcel/packager-svg": 2.8.3 + "@parcel/reporter-dev-server": 2.8.3 + "@parcel/resolver-default": 2.8.3 + "@parcel/runtime-browser-hmr": 2.8.3 + "@parcel/runtime-js": 2.8.3 + "@parcel/runtime-react-refresh": 2.8.3 + "@parcel/runtime-service-worker": 2.8.3 + "@parcel/transformer-babel": 2.8.3 + "@parcel/transformer-css": 2.8.3 + "@parcel/transformer-html": 2.8.3 + "@parcel/transformer-image": 2.8.3 + "@parcel/transformer-js": 2.8.3 + "@parcel/transformer-json": 2.8.3 + "@parcel/transformer-postcss": 2.8.3 + "@parcel/transformer-posthtml": 2.8.3 + "@parcel/transformer-raw": 2.8.3 + "@parcel/transformer-react-refresh-wrap": 2.8.3 + "@parcel/transformer-svg": 2.8.3 + peerDependencies: + "@parcel/core": ^2.8.3 + checksum: 08c700a7a253f39e84e1d341b3e0f558a2410bb27bf8a128113d8d157c32a7ef6b6ebd95e2c26d9f35c1040b98ff229ab56782247746189b4c41b925d1efd251 + languageName: node + linkType: hard + +"@parcel/core@npm:2.0.0-nightly.1278+5b901a317": + version: 2.0.0-nightly.1278 + resolution: "@parcel/core@npm:2.0.0-nightly.1278" dependencies: "@mischnic/json-sourcemap": ^0.1.0 - "@parcel/cache": 2.7.0 - "@parcel/diagnostic": 2.7.0 - "@parcel/events": 2.7.0 - "@parcel/fs": 2.7.0 - "@parcel/graph": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/logger": 2.7.0 - "@parcel/package-manager": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 - "@parcel/workers": 2.7.0 + "@parcel/cache": 2.0.0-nightly.1280+5b901a317 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/events": 2.0.0-nightly.1280+5b901a317 + "@parcel/fs": 2.0.0-nightly.1280+5b901a317 + "@parcel/graph": 2.8.4-nightly.2903+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/logger": 2.0.0-nightly.1280+5b901a317 + "@parcel/package-manager": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 abortcontroller-polyfill: ^1.1.9 base-x: ^3.0.8 browserslist: ^4.6.6 @@ -4445,672 +4532,1155 @@ __metadata: msgpackr: ^1.5.4 nullthrows: ^1.1.1 semver: ^5.7.1 - checksum: 615903871f7e3d03f19342a6d1b231612f1ac780a0079d4a37c82d4819a75140d5cf287172b9d40fad1c7aad3c12198c27718f838b543db75d90702ac558b15e + checksum: a3348495e20dec77aff5126ac8feaaedf409dd079e85807316696880d2d8d1a96be7e0f2e6f85d831cf675c6bd2a36b5720eaf7eb65f583c5a8215a08ec455c0 languageName: node linkType: hard -"@parcel/css-darwin-arm64@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-darwin-arm64@npm:1.13.0" - conditions: os=darwin & cpu=arm64 +"@parcel/core@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/core@npm:2.8.3" + dependencies: + "@mischnic/json-sourcemap": ^0.1.0 + "@parcel/cache": 2.8.3 + "@parcel/diagnostic": 2.8.3 + "@parcel/events": 2.8.3 + "@parcel/fs": 2.8.3 + "@parcel/graph": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/logger": 2.8.3 + "@parcel/package-manager": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 + "@parcel/workers": 2.8.3 + abortcontroller-polyfill: ^1.1.9 + base-x: ^3.0.8 + browserslist: ^4.6.6 + clone: ^2.1.1 + dotenv: ^7.0.0 + dotenv-expand: ^5.1.0 + json5: ^2.2.0 + msgpackr: ^1.5.4 + nullthrows: ^1.1.1 + semver: ^5.7.1 + checksum: 68adceb1b041208fe922bb52da218e6be90d6e016322f4eac5a5dbfbac72838080cf9bbce51785d65556a258293c02dffba4482217dbd9b723258101d925fb0e languageName: node linkType: hard -"@parcel/css-darwin-x64@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-darwin-x64@npm:1.13.0" - conditions: os=darwin & cpu=x64 +"@parcel/diagnostic@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/diagnostic@npm:2.0.0-nightly.1280" + dependencies: + "@mischnic/json-sourcemap": ^0.1.0 + nullthrows: ^1.1.1 + checksum: 07918b760d4ce474b5d4a39fd10f9febb82dc6b4562ea4476ae42b972e0cd5bb2d7c782cffa593cd8d3a85b5fcf8f9dc331f755076037010750a20a56c8ea74b languageName: node linkType: hard -"@parcel/css-linux-arm-gnueabihf@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-linux-arm-gnueabihf@npm:1.13.0" - conditions: os=linux & cpu=arm +"@parcel/diagnostic@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/diagnostic@npm:2.8.3" + dependencies: + "@mischnic/json-sourcemap": ^0.1.0 + nullthrows: ^1.1.1 + checksum: c24d98a2dbf068ef334c595d51504cd063310c0327477b5d7bcf817af3f8ad79d56593cdf91d8d45cb4a41a48baf9090ae4100a96d2c197d4ed20bc5db9df2d9 languageName: node linkType: hard -"@parcel/css-linux-arm64-gnu@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-linux-arm64-gnu@npm:1.13.0" - conditions: os=linux & cpu=arm64 & libc=glibc +"@parcel/events@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/events@npm:2.0.0-nightly.1280" + checksum: 8208e6d05faddef14d19b3e46526ce0b2e5a6ab8878e3d84ba818f8b2191c22369b30ccad0e19f16b756748f3d88d095d1c8b34f1dd443570ed9019193d71551 languageName: node linkType: hard -"@parcel/css-linux-arm64-musl@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-linux-arm64-musl@npm:1.13.0" - conditions: os=linux & cpu=arm64 & libc=musl +"@parcel/events@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/events@npm:2.8.3" + checksum: 9d23c6663e9afce1d1094c46d38eba0b0171835201140258c1dcd33f63cfbc20bb1abdc163cbb7a01d407a8cf06c8742c10035c8a835ebca261b19d8ee0fbf7e languageName: node linkType: hard -"@parcel/css-linux-x64-gnu@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-linux-x64-gnu@npm:1.13.0" - conditions: os=linux & cpu=x64 & libc=glibc +"@parcel/fs-search@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/fs-search@npm:2.8.3" + dependencies: + detect-libc: ^1.0.3 + checksum: 25e8eda6942fbf28e02cef1f5e94acafb3e33275a20b0a3e553402f04d2d24026be796b645728e872949dc8555b03a7d26d615a4f8eeed03a3af76aed535cc10 languageName: node linkType: hard -"@parcel/css-linux-x64-musl@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-linux-x64-musl@npm:1.13.0" - conditions: os=linux & cpu=x64 & libc=musl +"@parcel/fs-search@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/fs-search@npm:2.8.4-nightly.2903" + checksum: 1e0c5c2d612396c789426a5cc2af201f81a22e55c22f73b04f4372b9437833489beb5043ac62342f474b2d69a3fa0d5a7d46d28d8066f75cc0b9f4584b4f072a languageName: node linkType: hard -"@parcel/css-win32-x64-msvc@npm:1.13.0": - version: 1.13.0 - resolution: "@parcel/css-win32-x64-msvc@npm:1.13.0" - conditions: os=win32 & cpu=x64 +"@parcel/fs@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/fs@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/fs-search": 2.8.4-nightly.2903+5b901a317 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@parcel/watcher": ^2.0.7 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 + peerDependencies: + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: c8460a191854c39777e3da71f09dfe2e57f134202f8c89c801850e2f28e5a0bdbf71a5e0d4ebc5af8365f6c90f76191abf911420aa0f54933c52c42d6d607298 languageName: node linkType: hard -"@parcel/css@npm:^1.12.2": - version: 1.13.0 - resolution: "@parcel/css@npm:1.13.0" - dependencies: - "@parcel/css-darwin-arm64": 1.13.0 - "@parcel/css-darwin-x64": 1.13.0 - "@parcel/css-linux-arm-gnueabihf": 1.13.0 - "@parcel/css-linux-arm64-gnu": 1.13.0 - "@parcel/css-linux-arm64-musl": 1.13.0 - "@parcel/css-linux-x64-gnu": 1.13.0 - "@parcel/css-linux-x64-musl": 1.13.0 - "@parcel/css-win32-x64-msvc": 1.13.0 - detect-libc: ^1.0.3 - dependenciesMeta: - "@parcel/css-darwin-arm64": - optional: true - "@parcel/css-darwin-x64": - optional: true - "@parcel/css-linux-arm-gnueabihf": - optional: true - "@parcel/css-linux-arm64-gnu": - optional: true - "@parcel/css-linux-arm64-musl": - optional: true - "@parcel/css-linux-x64-gnu": - optional: true - "@parcel/css-linux-x64-musl": - optional: true - "@parcel/css-win32-x64-msvc": - optional: true - checksum: 97f319baeec4d628bc84fd293cc048645d66f31c370fad9f1537cd775291e1ac56b3892daeee4d4a2cf9b39653cdfed2e19a63d8d051f6d7279683ae3becbf7f +"@parcel/fs@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/fs@npm:2.8.3" + dependencies: + "@parcel/fs-search": 2.8.3 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 + "@parcel/watcher": ^2.0.7 + "@parcel/workers": 2.8.3 + peerDependencies: + "@parcel/core": ^2.8.3 + checksum: cc421552daef3c7676030867a1b4ed45d64d5f4221b0b12d487a86183a39544290fd3e7ed9104b1b58c05f2a6b5ec0698ce37a9cd49c484d94ed6b445f26d598 languageName: node linkType: hard -"@parcel/diagnostic@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/diagnostic@npm:2.7.0" +"@parcel/graph@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/graph@npm:2.8.3" dependencies: - "@mischnic/json-sourcemap": ^0.1.0 nullthrows: ^1.1.1 - checksum: a41cc65cb1815b90256f767eb118fcf3bf84b6bed4a31ef61627f2a1cc26e9aa9b9ad6fadb7cc7ce443b985b154b36ac3b10adcc0d0bbd42b0092129324a5fef + checksum: ceed8445f5a23396cca001a54ee0620bd7d6ecbb455977c16bd2f446da14c1791817ed715a4cf70d6ba66310991eeee44d692f15f70ff52e75b98b629da25a88 languageName: node linkType: hard -"@parcel/events@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/events@npm:2.7.0" - checksum: 9477cc8eefa2d5bed9cae39b9aed6379f3686735ea7ab7ffcc78148f2f83ed356663c9dd07ccf93edee5e59f877cb0072ae876c5a03e754cf6d2263638788875 +"@parcel/graph@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/graph@npm:2.8.4-nightly.2903" + dependencies: + nullthrows: ^1.1.1 + checksum: 639aadb5c859aa09ba914b8889fd13e4705a4dbb1f76a03ce6c8bd5c54f57a498a97f4102a0c5ecc873619cd84e9b8f5de26cbc4d067843db4cfa35ba2c94d91 languageName: node linkType: hard -"@parcel/fs-search@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/fs-search@npm:2.7.0" +"@parcel/hash@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/hash@npm:2.8.3" dependencies: detect-libc: ^1.0.3 - checksum: ed49a19d86b21ff5f399d3d137ebd38885d523f34a8753c59a63565d438bb107578ff1f76bdcac2e7d93090553c262dbecdc39e3c2640a12a9eb19416ee18c32 + xxhash-wasm: ^0.4.2 + checksum: 29cef199feda672756c930a8b45ee91e46607aa1b6659c38658758fe2f88870c20e0d4e8738d96ca8b44df60c1b767b5593110e2d24b99382214158c759258d0 languageName: node linkType: hard -"@parcel/fs@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/fs@npm:2.7.0" +"@parcel/hash@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/hash@npm:2.8.4-nightly.2903" dependencies: - "@parcel/fs-search": 2.7.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 - "@parcel/watcher": ^2.0.0 - "@parcel/workers": 2.7.0 - peerDependencies: - "@parcel/core": ^2.7.0 - checksum: 176c21f4a94e0189615582b3d4aca3611012f5e790fcd97ba8d7fe8fdd79ce6e51e0239510698381568aa04a3ec76afbfe689cd7e14c0ee971d9ff8dd3339707 + xxhash-wasm: ^0.4.2 + checksum: 4e1994f815d94a0a0325d899b5cf71fb8fb76cd1156b57b8bc26404a8a10e110ebbbfb40b6ab2f9722964598c6a1efd131045e93f7a3517730f197a0b1191345 languageName: node linkType: hard -"@parcel/graph@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/graph@npm:2.7.0" +"@parcel/logger@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/logger@npm:2.0.0-nightly.1280" dependencies: - "@parcel/utils": 2.7.0 - nullthrows: ^1.1.1 - checksum: 55db3972df4a1246410e1b1a9683b2336497c8d323e3b69c2161d9884bce45170d2f5791211c05ccba6bce181e2c9ffe9fd07401220ef2ed5b7ec031fc87f38a + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/events": 2.0.0-nightly.1280+5b901a317 + checksum: 68ef2004b53f6cd86370a4861e5ab6d19b9e0980993448d0b604e7142ac33c388e580e77bf890741050b3d9ecdac796796b1a3e0d8032c14457ad4f2dbba0b0a languageName: node linkType: hard -"@parcel/hash@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/hash@npm:2.7.0" +"@parcel/logger@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/logger@npm:2.8.3" dependencies: - detect-libc: ^1.0.3 - xxhash-wasm: ^0.4.2 - checksum: 42cad499e60c5c8ff989f644e51d629ddb175574b2c53aae1c89ee3a9827f6d69012c5652459b7f3460a7fe94a9e49a59432ee12250bcebb149ca46faa4b5b0f + "@parcel/diagnostic": 2.8.3 + "@parcel/events": 2.8.3 + checksum: 04fd46313138ea8e38c5bd051cd79ee245ad0a7bb6d5d12a892cafa79755af81ec1b6ddc83a79224bb74170bc1323f016cf849981326adb391f43920976ec9dc languageName: node linkType: hard -"@parcel/logger@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/logger@npm:2.7.0" +"@parcel/markdown-ansi@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/markdown-ansi@npm:2.0.0-nightly.1280" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/events": 2.7.0 - checksum: 85e959a8edc408750260fd908bf3cc886e2d6178977ae93ca6a0907c2a53f14751d7f4ab9fd682dbbe4c3b158001a3f39ee98f98dcdc182ebec41c6128cab7f7 + chalk: ^4.1.0 + checksum: f30557ba0f0143c3013b6d935386641ab3d1bcdf2a468da9686ddf47c7f567a52e2012145094473494d205210b2427e316cc565070e430493ec9f75ad48774f0 languageName: node linkType: hard -"@parcel/markdown-ansi@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/markdown-ansi@npm:2.7.0" +"@parcel/markdown-ansi@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/markdown-ansi@npm:2.8.3" dependencies: chalk: ^4.1.0 - checksum: a9f19091c5a663e71a6ee7936a76c25759eb30af4271883c51468ef193ba0bebe54619d2b6b9d70fb3d9262e8e60a8bbe20f74404e41fbcf3dc0ce4a82d63896 + checksum: 1985f149b2ac08347f954230922fdcc45d7ceedba9b7f458078843a018d950a56cb512fb951537b4f995e861b9290b0757cfc0eadf542a13b124175b5ef02945 languageName: node linkType: hard -"@parcel/namer-default@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/namer-default@npm:2.7.0" +"@parcel/namer-default@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/namer-default@npm:2.0.0-nightly.1280" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 nullthrows: ^1.1.1 - checksum: 0012b78fc95646c8f201476534eefaa354039a0fcd03cf04f2e33cf8e0c457010cfd78cc9071718f72b7e4d848e077b89f72e58baae3ea5cacc97045b83e8a24 + checksum: b5e47d1c3b2ee910805165b9cdde5aac909e0105076f8ad772ce7454f200e174123b01e9efaf4d4bc389786dcd9b70af70552281d3c912cd7335142b8c56de3b languageName: node linkType: hard -"@parcel/node-resolver-core@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/node-resolver-core@npm:2.7.0" +"@parcel/namer-default@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/namer-default@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + nullthrows: ^1.1.1 + checksum: 7c2c3434460d8fa6c9d482a9bfc681e47322ad82c8beef193eee9e45831374860d0f89de4c69e2e5cf41301cad19c7e87f5b536ca7d684aa383e783bcce02ef1 + languageName: node + linkType: hard + +"@parcel/node-resolver-core@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/node-resolver-core@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/utils": 2.8.3 nullthrows: ^1.1.1 semver: ^5.7.1 - checksum: bc0a94ce3c2423ded844dcf13e61208a3adbffaa13d8e84646023bf48b837ecb0f9b4c39eb667d2bf1080a1b7ee1094164afd3710a943a8828e39da35e28449f + checksum: 4976d3ecc9acc6ee05c7709291f4576c269bc84f896c8bf9e6171ce6f9fbd9c2dd7e3db4e11542b3b29093c73f5451724c94bf7b0735b9920ddcdeecf1809968 languageName: node linkType: hard -"@parcel/optimizer-css@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/optimizer-css@npm:2.7.0" +"@parcel/node-resolver-core@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/node-resolver-core@npm:2.8.4-nightly.2903" dependencies: - "@parcel/css": ^1.12.2 - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@mischnic/json-sourcemap": ^0.1.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/fs": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + nullthrows: ^1.1.1 + semver: ^5.7.1 + checksum: 716a08216910fc114c3efc068a2ae0958d17064a15d74324d0b7ea819b5722840f4f53c2f506c57b46ca90048e506263478db2c70837c722e1e9425282b9ab89 + languageName: node + linkType: hard + +"@parcel/optimizer-css@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/optimizer-css@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 browserslist: ^4.6.6 + lightningcss: ^1.16.1 nullthrows: ^1.1.1 - checksum: 3799e128cb1cafc57c4de384a9b7339c5be7d4a0205887424a7265d31205439bce25195b1c06036e91acb32cb354edd9ccf9116f5a46a04db72e16b4e185e664 + checksum: ffac43a2c20243d57b8627257b5a74462ebc0f4aa780b3117237240c9c3e9ca37ddcc8312296be9fe571a78f5a44cc14fa47ca9490d3796d673d8313d6cd8c9a languageName: node linkType: hard -"@parcel/optimizer-htmlnano@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/optimizer-htmlnano@npm:2.7.0" +"@parcel/optimizer-css@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/optimizer-css@npm:2.8.4-nightly.2903" dependencies: - "@parcel/plugin": 2.7.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + browserslist: ^4.6.6 + lightningcss: ^1.16.1 + nullthrows: ^1.1.1 + checksum: c25cf960eda5e3bcd066aa3c7cca990d938bb6cc2387bbe384060f60b23c8a0ff4085627c70ad226c09ca81cc6ee611da788cc2e7f0a780dfef7ccaf4a5510f6 + languageName: node + linkType: hard + +"@parcel/optimizer-htmlnano@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/optimizer-htmlnano@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 htmlnano: ^2.0.0 nullthrows: ^1.1.1 posthtml: ^0.16.5 svgo: ^2.4.0 - checksum: a5fa890bc36c7f4c8ebb5f8ff4ba88b15a0a9a489fd99badabfe0dfec8e4381cc04c6ee59b856bd17bdc1933529a851d0567c26d7e7df2fc19d6d623c5670e2a + checksum: 1daacb2b5727a0f88d60df56daef565e1d49ee38f0a53e6cbf1545d8890c2240779bf473cc76118619308161f3fb7443bd459daa9e246a583425637e62480963 languageName: node linkType: hard -"@parcel/optimizer-image@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/optimizer-image@npm:2.7.0" +"@parcel/optimizer-htmlnano@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/optimizer-htmlnano@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + htmlnano: ^2.0.0 + nullthrows: ^1.1.1 + posthtml: ^0.16.5 + svgo: ^2.4.0 + checksum: ca1cab7b1ecc16f209ad867fbdd8b2f446fd831d8688db068491fa22786a5aa3a0debb4290e0f003830c6b06c6f3a4c3a3cd9cdb033e7fa6cded8a19887d5f23 + languageName: node + linkType: hard + +"@parcel/optimizer-image@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/optimizer-image@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 - "@parcel/workers": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + "@parcel/workers": 2.8.3 detect-libc: ^1.0.3 - checksum: ca12701e1c7080c4bff15971b6be7ff6c9633ed6ef69d925eedd06021cbedf02c326862fa5899072d35ce01a34018de265ccb811ff4f31556a9b931dcbff835b + checksum: 72c5acffaea833237f62e23c8fb183eb85863ccddeb11304b2299b28973b957daba1e34854d347314edf35d83cba695c0d7600e1ae125dec4cc3151abd8f2e31 languageName: node linkType: hard -"@parcel/optimizer-svgo@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/optimizer-svgo@npm:2.7.0" +"@parcel/optimizer-image@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/optimizer-image@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 + checksum: 9805f058025f2614355cf9ecf493ee37a20dbabb9de29e236060b592ecb436b891ed52c889752a3ba259b3cbaf2f7e1b6f8b135488c3192bd4b526acfc36a918 + languageName: node + linkType: hard + +"@parcel/optimizer-svgo@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/optimizer-svgo@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 svgo: ^2.4.0 - checksum: 305024d23c9bb049cce91cd902d8f9053b93cac9e5f7eb8f7b5d6a9b5d419d67e2321b3194689c09b20c3999087a11975e6cd95a669c5a5c61952be9a2a866c0 + checksum: b3544c08fac4009de1ec6f88136a58cdec70b072433b13fb99f9e6584dc4731afea82ae13d27e4121ed5aaec9e4481225a54251ce52b6ece835908300c26fa33 languageName: node linkType: hard -"@parcel/optimizer-terser@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/optimizer-terser@npm:2.7.0" +"@parcel/optimizer-svgo@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/optimizer-svgo@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + svgo: ^2.4.0 + checksum: a7a961360bc8d31fe8cd4630285aa8008fbf2e26bc12895357d8129fa0d36ad8b6c0cea592ac4564ed69f0fc436e5de8a79d6a57b6d7720de6bfa25b03d6168e + languageName: node + linkType: hard + +"@parcel/optimizer-swc@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/optimizer-swc@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@swc/core": ^1.3.36 + nullthrows: ^1.1.1 + checksum: 37d3fadef5a7ac754187c057e8d4ae577ee683a7b806932739dcc86e68774a883c1c1e66bc3ccee55a687ee5cabb9e325d14667f0048e321be27cc25c7494295 + languageName: node + linkType: hard + +"@parcel/optimizer-terser@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/optimizer-terser@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 nullthrows: ^1.1.1 terser: ^5.2.0 - checksum: 20eddbcaa947c380909ba95337d3db436b748c0baf5525e3a59756fa05219a612f30070d9618aef7570037a3a926e5bacc5ac9c221603bfab19753b864aca202 + checksum: ee1959f5965c7eee8ad1519f9d2554810030f326e959dd5e44aa014c29a51c2ab777dfbbf604a6b4436b75176a8694b7b8c9d99f945d57dea7828225762c8823 languageName: node linkType: hard -"@parcel/package-manager@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/package-manager@npm:2.7.0" - dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/fs": 2.7.0 - "@parcel/logger": 2.7.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 - "@parcel/workers": 2.7.0 +"@parcel/package-manager@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/package-manager@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/fs": 2.0.0-nightly.1280+5b901a317 + "@parcel/logger": 2.0.0-nightly.1280+5b901a317 + "@parcel/node-resolver-core": 2.8.4-nightly.2903+5b901a317 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 semver: ^5.7.1 peerDependencies: - "@parcel/core": ^2.7.0 - checksum: f4817d1aae84e3f4a7758a3e51a1f2a35996649450ef32b6f8de680b77ff98b138d917652f4802a49cb8ab562a737f661bb28ceadd18132f05170805b5bb145d + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: e455aeaab735c22dbaa0491cc593b14ba3b54fff707e7cf20481c826da8b3750fb2b95f61b0b284361313295520108f07c68024e0de81800180b3860e888ec2b languageName: node linkType: hard -"@parcel/packager-css@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/packager-css@npm:2.7.0" +"@parcel/package-manager@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/package-manager@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/fs": 2.8.3 + "@parcel/logger": 2.8.3 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 + "@parcel/workers": 2.8.3 + semver: ^5.7.1 + peerDependencies: + "@parcel/core": ^2.8.3 + checksum: 572a5aacfd7bc545d9aa35ff2125f1231226b550f9b0fe2c36d68a82ec8ffb047035e25fdb883bc2331a6eaf69c98bb5d6752644546d962de7bf544c6243a959 + languageName: node + linkType: hard + +"@parcel/packager-css@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/packager-css@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 nullthrows: ^1.1.1 - checksum: 0a7fbedb8860626c8cd70ed8c8977b65b80856e2180a71ec988809f8b15721dec328bcdc56200b1224f292ce122efdafff5ef6ef5402d3b0e251dbe74ca6790a + checksum: 368784dc531481717ec463ecb21d32948afd2bd8255cdc6f7d24886d321e13e6d8fd61e98bd62904018a9606e4c05e04e38e84d37c54af920acb9e402ba7770b languageName: node linkType: hard -"@parcel/packager-html@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/packager-html@npm:2.7.0" +"@parcel/packager-css@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/packager-css@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 + nullthrows: ^1.1.1 + checksum: bb28fc9f02df83a1fd8eac7043466debb67398190819282a40a52ff299b0f4c3f474bfa7806be776ce36a66cc89574128a9fa210d2c0c9cb905bbb4dbbd2b926 + languageName: node + linkType: hard + +"@parcel/packager-html@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/packager-html@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 nullthrows: ^1.1.1 posthtml: ^0.16.5 - checksum: 9ca4f91112bfce9cf99ec34241f15775f964a74ea293b9e526d4bd64c60fa837e1385bec8c8a3a9315f66fe980e3e05c16b32793a2c98c9837482a3f79319269 + checksum: 4206ce709f5de492e46422def0056bbe27e62719bcf173e8cf9281ed0bb2f500129243af96f58bbe1d69300fc8d9e3cde203c5da58f2ac075a180b434b61b138 languageName: node linkType: hard -"@parcel/packager-js@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/packager-js@npm:2.7.0" +"@parcel/packager-html@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/packager-html@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.8.3 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 + nullthrows: ^1.1.1 + posthtml: ^0.16.5 + checksum: 631f98fca0fdd3f11fe4cfbc1e0ad73b86f7fb00be7164fef5633c600a13282ae592b8f7d9aa31d4f66bd645ae57ce27e67db51a81b2a91c286ed5c8b36a4a87 + languageName: node + linkType: hard + +"@parcel/packager-js@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/packager-js@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 globals: ^13.2.0 nullthrows: ^1.1.1 - checksum: 8a65b8f82c40f3e429d11ce5df73454c90233ada8dd00a8913e945b8cbc85a021ddb0c75c2f8bd2bdc2424105874a86f0cb748eb5c8f157dd085b13c67f72f63 + checksum: 224cf1c29d4cb46e3ac260d7d7dadaba03a9ec2c8c9438eb6981b26629ae0031e79060d9e42bc63ab9660b77454fdb006c145a63ea8c621d8258e0a7130a980d languageName: node linkType: hard -"@parcel/packager-raw@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/packager-raw@npm:2.7.0" +"@parcel/packager-js@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/packager-js@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - checksum: d895ed703e3f71b57bb1d2f6e2f9c574fb259790622afd7818e6ac174b69c8dead78d8cc15abf19df566b9642d7ea5cd7db22dd62256cfc3e86bb2b838e6c2d8 + "@parcel/diagnostic": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 + globals: ^13.2.0 + nullthrows: ^1.1.1 + checksum: 92ac88244b6104c5905ab95d882b755134042654ab48106ca84ab18441fb7240b66f049e407146958aead0812345823da729a4a37f32be17afd2b44cbdebc926 languageName: node linkType: hard -"@parcel/packager-svg@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/packager-svg@npm:2.7.0" +"@parcel/packager-raw@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/packager-raw@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + checksum: a47bd334442d9c00b90968f8ec91148520573b29512a3d8ba311fd8ebcb86ae45866a42bbf639b9a820b3a9193a85060441338c4089387936b8954337f8ce55f + languageName: node + linkType: hard + +"@parcel/packager-raw@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/packager-raw@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + checksum: 26236dd64624a25fc1d749fb96b1bc3a6854b14d4386109670572f55feda4bb6affde19b1c9e971c4e50bfb53fd88e32da8303c83a3cb18ceaf12dd310685c34 + languageName: node + linkType: hard + +"@parcel/packager-svg@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/packager-svg@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.8.3 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 posthtml: ^0.16.4 - checksum: 9b2c4e4cee93ecc18f852bf623eb9097e53b4d0d9dea562674942b7b1121f82c1c8b418a5ccc4a18010df7e62c4934bb039903ea21c0493732f6adedaca9ba0c + checksum: 45c966ad8e6dbb25049adca66d761089a09cda83558d8767b46501a023b8d94b050f1a2899b1c8b18eac6cf87d2605ad5aa9a4cb2f9d90474794576dafb2e4fc languageName: node linkType: hard -"@parcel/plugin@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/plugin@npm:2.7.0" +"@parcel/packager-svg@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/packager-svg@npm:2.8.4-nightly.2903" dependencies: - "@parcel/types": 2.7.0 - checksum: 930befafa01c179b27d34d6a63e5f67fe4998724654163fc07a618a432e8d20397cc3f424093ee967ddad8c4adf4e3f0e596e72f2ca5764b3fe212211c09d321 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + posthtml: ^0.16.4 + checksum: e26c1ff07026de7e07ebc38cba480d5af09001e1d48715450f4f068380b430c8eea43af440e0e61d9f9d749fc824d38c7f46279ef387278e62b8e7d04dc21dfd languageName: node linkType: hard -"@parcel/reporter-cli@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/reporter-cli@npm:2.7.0" +"@parcel/plugin@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/plugin@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + checksum: ebf77381744e1c5f10dd021f18440f5baef3f3028dca012458150f4257b3166e794aa37c1bd37d9027bf831096bccc5e7476a3bf998287b8485dd8c17a324974 + languageName: node + linkType: hard + +"@parcel/plugin@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/plugin@npm:2.8.3" + dependencies: + "@parcel/types": 2.8.3 + checksum: a69ac66f5cc28197cf689f1c4144398457d62a086621a22b3b45fe863909a094b616dad415ec01673a9eb731b05fe9060bcb340c07efcd48343577a540fbfdf7 + languageName: node + linkType: hard + +"@parcel/reporter-cli@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/reporter-cli@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 chalk: ^4.1.0 term-size: ^2.2.1 - checksum: eff35a2ee3f2b453688fd16603c41a97b24d250a7b50fd9e7dcb2f35be743b338031c82746bf1e8cd299b7f3c4acb3345d13c02da3865bea7bd4f2aafcb9f6af + checksum: 7174c550f0fdf60daa7fc17688af9d2cb695db3a34f2e484c247417f7237a0bc2400d2cf8b5e03e890612971ecfeb467908704adccdae619041b6fcdd636286f languageName: node linkType: hard -"@parcel/reporter-dev-server@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/reporter-dev-server@npm:2.7.0" +"@parcel/reporter-cli@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/reporter-cli@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 - checksum: e315689d8d4ba094e67b0206d15c259c9faccf8554e191e14c53a4f176d9b52457ee24e9e53581fbc1685932f76ce57286613fdd9cafb81139b6e38fb57838e1 + "@parcel/plugin": 2.8.3 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 + chalk: ^4.1.0 + term-size: ^2.2.1 + checksum: 791dd4706aac706427a563455d9db5fa330b77e94fe4226b3751cd527327d8540d62400a8040e85a5fd29ecb6e673507e9d4a1fa754c093f1c005078670eef85 languageName: node linkType: hard -"@parcel/resolver-default@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/resolver-default@npm:2.7.0" +"@parcel/reporter-dev-server@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/reporter-dev-server@npm:2.0.0-nightly.1280" dependencies: - "@parcel/node-resolver-core": 2.7.0 - "@parcel/plugin": 2.7.0 - checksum: 5bc64bea956c3579c99eb13bef0c868be1b0b5d9d045184adfc3cdc02f503775d12fdb2116f093d62cc9d7e7fc506351973d2808b71b091a5ed5c038064906fe + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + checksum: 4f80c644ba093b5fcd57e1bf08a3778c0d2599a05808fa9559c9eadfa996fd03e1dd81231ba5222cfec039814370d61443b59bb7bb4968e28bed39f0921e1083 languageName: node linkType: hard -"@parcel/runtime-browser-hmr@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/runtime-browser-hmr@npm:2.7.0" +"@parcel/reporter-dev-server@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/reporter-dev-server@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 - checksum: ad736bab6972d42e0027bd5aeee4beef087b1c32a99ab1700f7c6ccc125fe566ff08d065fffa6a6565e5f2e0bd6bc256a9869f6ec98322b09c02b2f97f29bb77 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + checksum: 329db9fd0cdc3ddc36d8156a7d67747335c76b1368116c98e266218f1e1ce4ea108981441bcb78961f64e2067a2d8a1745d8aa069398d50e67278e1333293723 languageName: node linkType: hard -"@parcel/runtime-js@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/runtime-js@npm:2.7.0" +"@parcel/resolver-default@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/resolver-default@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/node-resolver-core": 2.8.4-nightly.2903+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + checksum: 5525c49b7736baed6e042580a157851e27f2dc828b04a783b47985721b23b041f9756ce242b4fed8fd28495ea3983e0b5318ee6b80ee022d6ab7d8bf13d32389 + languageName: node + linkType: hard + +"@parcel/resolver-default@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/resolver-default@npm:2.8.3" + dependencies: + "@parcel/node-resolver-core": 2.8.3 + "@parcel/plugin": 2.8.3 + checksum: 40515a62c1a301050144e1427ac7a591afedea50e89baff0ab4ed05ad8424f5df6ad4a7b5e413956a199ecef18bf8220b353fb115af72fac4187a62e8a997d1d + languageName: node + linkType: hard + +"@parcel/runtime-browser-hmr@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/runtime-browser-hmr@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + checksum: 985cd4109b2abe1e7f63251782a09e59e9dd288567206e93af7bba3a61cfb4273e80abc8a6918bb00b1c53e461f771717b31e1ac775413454a3d8c4bde5e0760 + languageName: node + linkType: hard + +"@parcel/runtime-browser-hmr@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/runtime-browser-hmr@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + checksum: 56c276c7b03bb4c301d7dbe94c5183404af4064286f67682399e848ff894bfb5ea783dad11082290d40f2f07be64252dd236b993baf2e3e8fbb30a572f95a0dc + languageName: node + linkType: hard + +"@parcel/runtime-js@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/runtime-js@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 nullthrows: ^1.1.1 - checksum: 3a22c78ce1848c37b677ca1c38d2e4eca632059d9607be65ac1cf49e985c969b393c34103ad79f0a4f372fe3d49088ddff471cbebcb100de7ab1b14bb6047ff3 + checksum: 7388643282789e623159004648fa0bb3fe79def0203c911742e418d4b5c03383690eb8fe54de321b9c8a2ddec2f11e4fc0ecb050ec1d58cbe417bec454c8c07c languageName: node linkType: hard -"@parcel/runtime-react-refresh@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/runtime-react-refresh@npm:2.7.0" +"@parcel/runtime-js@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/runtime-js@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + nullthrows: ^1.1.1 + checksum: ee5e04f84d522a6f53753c3956d37cacb2bdabb2539e2f40e640762b3cc43b20efc495331fe254d92d82a06c3e1b4690c17125090a12300d75ad7c3a9ca3e2f0 + languageName: node + linkType: hard + +"@parcel/runtime-react-refresh@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/runtime-react-refresh@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 react-error-overlay: 6.0.9 react-refresh: ^0.9.0 - checksum: ea4c240185f00266d2820393b9c3faa95e7854b92ea9b18191eca5329e5ac53e287a32a967c9001890af4a78741eba3dc0173323af2bdbe65ec72f0bfa70a055 + checksum: face88be0b0e741d7ca4d28e7da4a52b83483b7100c0603a04f9c1e38641f301edce329db87f3b9e60ad657852d27bfcae7c57b085a58eb43b3200d5277dcd3e languageName: node linkType: hard -"@parcel/runtime-service-worker@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/runtime-service-worker@npm:2.7.0" +"@parcel/runtime-react-refresh@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/runtime-react-refresh@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + react-error-overlay: 6.0.9 + react-refresh: ^0.9.0 + checksum: 327159be0c8183f1cff139de973e8e8ca6b83dc2fc94846a89415fabf8cd8535e95ed3ae9750ac08e73a303de57c18c4e5da959ecbe73af75f1d3c9a98f5c20b + languageName: node + linkType: hard + +"@parcel/runtime-service-worker@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/runtime-service-worker@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 nullthrows: ^1.1.1 - checksum: 13eb0ba276e104c5b412d139fbf8b15a3f7ef836fec386d9f7982d0f3eac1e0942be87c62e1d4926efcdb15919547c32df4e6a54b228a5f67da6241e058a5c32 + checksum: 0646fee9a9378c0844c223d0eaf1c46e817738e70b2e993434717fb6aab998339b37a32c5bd9db891fcb8bc44dc3d7530564f84a5cd978d6dd47f204f18bd44a languageName: node linkType: hard -"@parcel/source-map@npm:^2.0.0": - version: 2.1.0 - resolution: "@parcel/source-map@npm:2.1.0" +"@parcel/runtime-service-worker@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/runtime-service-worker@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + nullthrows: ^1.1.1 + checksum: fa5b993337727ce46d04065c9303c5eaa29167382d226980c2b6a9fd67c174154ff6cdbbb8d1dc4291ab9600749765cf2514a6a448d52b32ec9bc7abcd0e8bd0 + languageName: node + linkType: hard + +"@parcel/source-map@npm:^2.1.1": + version: 2.1.1 + resolution: "@parcel/source-map@npm:2.1.1" dependencies: detect-libc: ^1.0.3 - checksum: 7ec2cfec01148d1813030615e525b7ce27ee6a80781769aba52149677286f02b6b6e387e8c602fd67abe5e0f815b07a5b2ee94e1cc8bf1714301c575436aaaa6 + checksum: 1fa27a7047ec08faf7fe1dd0e2ae95a27b84697ecfaed029d0b7d06e46d84ed8f98a9dc9d308fe623655f3c985052dcf7622de479bfa6103c44884fb7f6c810a languageName: node linkType: hard -"@parcel/transformer-babel@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-babel@npm:2.7.0" +"@parcel/transformer-babel@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-babel@npm:2.0.0-nightly.1280" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 browserslist: ^4.6.6 json5: ^2.2.0 nullthrows: ^1.1.1 semver: ^5.7.0 - checksum: aa269b12913667175919322faaea9e192fd2259765cc482b3902a050ecbfc48c212b4d999cda976c7095bfbe78f94d38333f5a66719eb5989fbe4b32963a9a07 + checksum: 7f19bbda39f6f643b72bbe131565e3a7ab2a9b7f218103509066482f2598eaa456fd4ffbae8a9f6c734169e71c36ad1f12f053c3410bcf208e33b2cdfeac0e28 languageName: node linkType: hard -"@parcel/transformer-css@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-css@npm:2.7.0" +"@parcel/transformer-babel@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-babel@npm:2.8.3" dependencies: - "@parcel/css": ^1.12.2 - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 browserslist: ^4.6.6 + json5: ^2.2.0 nullthrows: ^1.1.1 - checksum: 47cf7cd9a40b8e957075442fb56c3d52882572f3b65ccc2f67ae3190706d51f93cf783fb1275e01a9562538d94f570f7f1922f9f943faa83f87b8a1f36663943 + semver: ^5.7.0 + checksum: a27bbe8d893854a77d9a8c9b45490728b2db81ad0782b7d9085d00c50155840477dd4ada8e382e0b02f9f5f8761da48bd6d3feb62ddd582e6608f92d4468df80 languageName: node linkType: hard -"@parcel/transformer-html@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-html@npm:2.7.0" +"@parcel/transformer-css@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-css@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + browserslist: ^4.6.6 + lightningcss: ^1.16.1 + nullthrows: ^1.1.1 + checksum: a93a1cb011c4bcd41d33fd670618d9236ef98b1ec9d8d769cd32539d5697497b94df18250e7b2b846ecc91976f387f1bb4d1fc7ddfa03aabf44621ebbd7783d3 + languageName: node + linkType: hard + +"@parcel/transformer-css@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-css@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 + browserslist: ^4.6.6 + lightningcss: ^1.16.1 + nullthrows: ^1.1.1 + checksum: 31375a140550968a36f7a8eb998c03f20200d202b7c62c98fb49b05f719777ca545d08f356dec9ca6d9a601ba0020abce5cf4672fe425bc99a540dccf262a6cc + languageName: node + linkType: hard + +"@parcel/transformer-html@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-html@npm:2.0.0-nightly.1280" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/plugin": 2.7.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 nullthrows: ^1.1.1 posthtml: ^0.16.5 posthtml-parser: ^0.10.1 posthtml-render: ^3.0.0 semver: ^5.7.1 - checksum: 4028ffa5ece1b30cd801b97d371eba142eed52267d527c06a02c5ec9b83960353dd0e6143d408c3351fc5acb3bfbceb1755bed07827e619119a4be77863471d9 + srcset: 4 + checksum: 885e4105ba1a5b3feec22dd10372c13c37c077ca6b0644222a6c3668e5fdc171002d95edfb1140f7d8030c1f05e77ac4e557d47a5c3f4dd2e8e2175b28edbb8a languageName: node linkType: hard -"@parcel/transformer-image@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-image@npm:2.7.0" +"@parcel/transformer-html@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-html@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 - "@parcel/workers": 2.7.0 + "@parcel/diagnostic": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/plugin": 2.8.3 + nullthrows: ^1.1.1 + posthtml: ^0.16.5 + posthtml-parser: ^0.10.1 + posthtml-render: ^3.0.0 + semver: ^5.7.1 + srcset: 4 + checksum: 21600a3e0ac9e05aa6f6066ef94f8ba7e0de62a8ae59a812230907f5731dcf73dc5308fb74b32bfb6dab16089d13f72043965e1e87e8c4daec8447a9081af8eb + languageName: node + linkType: hard + +"@parcel/transformer-image@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-image@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + "@parcel/workers": 2.8.3 nullthrows: ^1.1.1 peerDependencies: - "@parcel/core": ^2.7.0 - checksum: 1decacc1a4b037de9c7838fce34057338262c03e16a199d00e9641be0ad80885ff77119bd571765268e5f0d2f1f2d1863b759aaaaba6de2f64fe5b2c44b69b4c + "@parcel/core": ^2.8.3 + checksum: f4b3464828e1b3d44e7da5c7a71272f5f53f830d9bb371e8dd8b2f32040f4426f3efeae12949947e34b39f7755a253f0b48c6eeec6d86ad43baf0b30717f1f47 languageName: node linkType: hard -"@parcel/transformer-js@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-js@npm:2.7.0" - dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 - "@parcel/workers": 2.7.0 - "@swc/helpers": ^0.4.2 +"@parcel/transformer-image@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/transformer-image@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 + nullthrows: ^1.1.1 + peerDependencies: + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: a53ac35c288bfc48b839684d52f09eb4d15735a02a4c6e7ae0603bc9ec6a18c95ea0814a05a41f71ba8dbe5745c4cb5d119aa252786993300b69c71e0f341ccc + languageName: node + linkType: hard + +"@parcel/transformer-js@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-js@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 + "@swc/helpers": ^0.4.12 + browserslist: ^4.6.6 + nullthrows: ^1.1.1 + regenerator-runtime: ^0.13.7 + semver: ^5.7.1 + peerDependencies: + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: da67f0a9d510b0bf117a733ccffd3329efb7f23a6c589555cff191697d06e2f10f34dc69edacb02032b9566a05c11dd8f324d909521c0b90a7a584987becc037 + languageName: node + linkType: hard + +"@parcel/transformer-js@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-js@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.8.3 + "@parcel/workers": 2.8.3 + "@swc/helpers": ^0.4.12 browserslist: ^4.6.6 detect-libc: ^1.0.3 nullthrows: ^1.1.1 regenerator-runtime: ^0.13.7 semver: ^5.7.1 peerDependencies: - "@parcel/core": ^2.7.0 - checksum: 76669ef593672cf3936aafd68676ba9f23562556dafd3594f5714bbb8fdebae83e0b2ad51cef47bedf0304cce1a55a7c344679f20747306a7a111caae535704a + "@parcel/core": ^2.8.3 + checksum: 29fb203502309e11452837e4ae60589300c0d91fae35cf4774e70959e9f4532960ef4619959ce9c95f0060020faabbcfd024b076f41c7d5f7e126c3547244ff6 languageName: node linkType: hard -"@parcel/transformer-json@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-json@npm:2.7.0" +"@parcel/transformer-json@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-json@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 json5: ^2.2.0 - checksum: d9d82148ee69b623129596408f97269199aabbce0ced71bbb501addf9fb76761eb3da49b3e1286a3d08806caf01156b97fdac766212e38e62823db34b1433bfa + checksum: 5b6d4a980987d14b7ef1814fae804830cd2d9581d9494a887b0aaea177261f9a0f7aa3d8e7ab787fcae22e3848a025744ea42f89e07722e16f10500ab004e652 languageName: node linkType: hard -"@parcel/transformer-postcss@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-postcss@npm:2.7.0" +"@parcel/transformer-json@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-json@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.8.3 + json5: ^2.2.0 + checksum: 04da28b0f0ff1ec1d7c6383b880daa2918f85ba1375351690a9a07ea4de102531d5f6addb3091ae5109623e270e1d2cdf582661f4a0805bd982a653a06d26890 + languageName: node + linkType: hard + +"@parcel/transformer-postcss@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-postcss@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 clone: ^2.1.1 nullthrows: ^1.1.1 postcss-value-parser: ^4.2.0 semver: ^5.7.1 - checksum: 15bd9ac0510d1ec822891f64aadbb7a9e22e7a86619863863fe0de056f1e9da8f910590ed0664575950bf483b58196640634278beecdc492c65ea675a832f3e5 + checksum: e4a4052177caa0a18ee4123bf674808382ec0293a6d734f51cfa616c1f8cd28e78f1205db51efc357ea894f17882a592885f76402b173daa8f50d7651d038bcf languageName: node linkType: hard -"@parcel/transformer-posthtml@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-posthtml@npm:2.7.0" +"@parcel/transformer-postcss@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-postcss@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + clone: ^2.1.1 + nullthrows: ^1.1.1 + postcss-value-parser: ^4.2.0 + semver: ^5.7.1 + checksum: 2c75cb5cec7112d12a28ac5cddc9f2e939f76e006929757804431b266e7541aae5df6ba8601727c33c7b53f0f971a6df5dfb4394fa0baf284bd2c6fc9b507650 + languageName: node + linkType: hard + +"@parcel/transformer-posthtml@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-posthtml@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 nullthrows: ^1.1.1 posthtml: ^0.16.5 posthtml-parser: ^0.10.1 posthtml-render: ^3.0.0 semver: ^5.7.1 - checksum: 04642838ce10f3a54b42c95115afc59f852095b8370482c6b3728db46e047443a7663b6f28d76ad1eeb18b81828d5dda0b8e650ebbfb1cc65e09d725b901f8f4 + checksum: 1323de92963a7c8d625ba69364fe73dc26e3237ac777f79dccb5f9f6779e5f860807b3c85023a7f921952b7c65b778e1cae20d7123cf64b75de911d929788ce7 languageName: node linkType: hard -"@parcel/transformer-raw@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-raw@npm:2.7.0" +"@parcel/transformer-posthtml@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-posthtml@npm:2.8.3" dependencies: - "@parcel/plugin": 2.7.0 - checksum: 942a5ddbed9453fe905310f22430ef643e544d4a262f317a2ca4452080aad18484ea7dd1953e1a6c7ef049b42366c46820297d981cec9ac76b980df424248b4c + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + nullthrows: ^1.1.1 + posthtml: ^0.16.5 + posthtml-parser: ^0.10.1 + posthtml-render: ^3.0.0 + semver: ^5.7.1 + checksum: 130c95782aebb2491f2d89685db573b3b85ed1f7d9862684db2ab9d11fe8148995185a4e144b818de06d596cf687c5bd57b6b8648d2856cf830a9674c2ec3237 languageName: node linkType: hard -"@parcel/transformer-react-refresh-wrap@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-react-refresh-wrap@npm:2.7.0" +"@parcel/transformer-raw@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-raw@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + checksum: cb8b34174d4e4449c1c560419e8bc051fc96f6b25e71f11cc0c227a4fba553644454d6a994263cdb9e91fc52f6b34801c01b25e2fb72915138483eedb25d282a + languageName: node + linkType: hard + +"@parcel/transformer-raw@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-raw@npm:2.8.3" + dependencies: + "@parcel/plugin": 2.8.3 + checksum: 371263bb526373c229aa3e730f2a1d6687bd6b771203d73237c04da3a3ada86c4fcf0b534d3fb366a7b3842df0cf98ae1e033602613cafd9f702f47a6568a83c + languageName: node + linkType: hard + +"@parcel/transformer-react-refresh-wrap@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/transformer-react-refresh-wrap@npm:2.0.0-nightly.1280" dependencies: - "@parcel/plugin": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 react-refresh: ^0.9.0 - checksum: d193b9552b8e9abff7313845d02f2068112f16a15ba7eb0e7fba568937b765030ab874c84d601d8941dc6a14af9090327be8093825a95f0c4b35eabf7d36f1a3 + checksum: 95e45d237c5936ca4fc5d8180ab2a35a34a62634069332b804cca853ad92f74167826f96bcb3bc0753823c9a31245afc19cb357690a2e16e1cff9b91c220ab4c languageName: node linkType: hard -"@parcel/transformer-svg@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-svg@npm:2.7.0" +"@parcel/transformer-react-refresh-wrap@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-react-refresh-wrap@npm:2.8.3" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/plugin": 2.7.0 + "@parcel/plugin": 2.8.3 + "@parcel/utils": 2.8.3 + react-refresh: ^0.9.0 + checksum: e9648e04b7f9b29f47ec7baedfba9cc36bbb7e44be6ad4b6b4433c20d1b5a3184a3043b712add16a5cc06300289305d5fa9ebb73c6dc926d04df7c52d9bc3316 + languageName: node + linkType: hard + +"@parcel/transformer-svg@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/transformer-svg@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/plugin": 2.8.3 nullthrows: ^1.1.1 posthtml: ^0.16.5 posthtml-parser: ^0.10.1 posthtml-render: ^3.0.0 semver: ^5.7.1 - checksum: 19026a39f9757908f89d1a57ba9de027cbb80c562e055d5dd88a8ea7691c42d6d8598412b3a8b80e52001b305303b16dd6b8ae2a7708e95861a7654a682c8a2e + checksum: 1f3db309e47d07849a2b4ffe11b508fd7ae792c0c0ce7b03e442fffb25f5e7425c5027428729bf2b587309265bba0be6da635d62c51ae8ab7e54483eff3f575e languageName: node linkType: hard -"@parcel/transformer-vue@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/transformer-vue@npm:2.7.0" +"@parcel/transformer-svg@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/transformer-svg@npm:2.8.4-nightly.2903" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/plugin": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + nullthrows: ^1.1.1 + posthtml: ^0.16.5 + posthtml-parser: ^0.10.1 + posthtml-render: ^3.0.0 + semver: ^5.7.1 + checksum: 8cdb11e2661292b5cea355d6986eec0d63c63ea06b7388d181124d9b12a92f888255771a53d6b04a319184ae0f98a232e0687f46162179e315b385a778956090 + languageName: node + linkType: hard + +"@parcel/transformer-vue@npm:2.8.4-nightly.2903+5b901a317": + version: 2.8.4-nightly.2903 + resolution: "@parcel/transformer-vue@npm:2.8.4-nightly.2903" + dependencies: + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/plugin": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 "@vue/compiler-sfc": ^3.2.27 consolidate: ^0.16.0 nullthrows: ^1.1.1 semver: ^5.7.1 - checksum: 564c98af47091b2a74e4419884429e66f82cda8ec589193d96c637eb670766ba8d21d1deef16c672bd2907783b7c4659ef53ae62e938da1245406b3c74edcc94 + checksum: 44dfe9af51a9cb384dd32b7952d6e02f88e5689d7fdd27fca08b72d15c6461eef062ecbd7b6952dcabf5aabf6b903edb17782b3fdb391edf3a46824f84a9c63a languageName: node linkType: hard -"@parcel/types@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/types@npm:2.7.0" - dependencies: - "@parcel/cache": 2.7.0 - "@parcel/diagnostic": 2.7.0 - "@parcel/fs": 2.7.0 - "@parcel/package-manager": 2.7.0 - "@parcel/source-map": ^2.0.0 - "@parcel/workers": 2.7.0 +"@parcel/types@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/types@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/cache": 2.0.0-nightly.1280+5b901a317 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/fs": 2.0.0-nightly.1280+5b901a317 + "@parcel/package-manager": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 + "@parcel/workers": 2.0.0-nightly.1280+5b901a317 utility-types: ^3.10.0 - checksum: cf28c56d3e74faabefb2359259906a3f574a70ebda20633a3059b6c4c897f7a97df4e89920d89650855829b2ec3677b23a44efcef6fcc7be65c78eef7b21b96c + checksum: d654966d21665cce71f5fff8da405fa6538eb8c72916406e3ffd55002d9ed785458858b85cbb0603397d326d63740a273cc660e1df7c07ddb351b4b3f520cdd4 languageName: node linkType: hard -"@parcel/utils@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/utils@npm:2.7.0" - dependencies: - "@parcel/codeframe": 2.7.0 - "@parcel/diagnostic": 2.7.0 - "@parcel/hash": 2.7.0 - "@parcel/logger": 2.7.0 - "@parcel/markdown-ansi": 2.7.0 - "@parcel/source-map": ^2.0.0 +"@parcel/types@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/types@npm:2.8.3" + dependencies: + "@parcel/cache": 2.8.3 + "@parcel/diagnostic": 2.8.3 + "@parcel/fs": 2.8.3 + "@parcel/package-manager": 2.8.3 + "@parcel/source-map": ^2.1.1 + "@parcel/workers": 2.8.3 + utility-types: ^3.10.0 + checksum: ece0abdd5c7cce32a246155f6828f6a92830341dfbceb81c9aaf7da44e0733b87ea8a607412dfe4b5ec59d7c9a3c1b1463b94ec8a5a82b745541881952003a16 + languageName: node + linkType: hard + +"@parcel/utils@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/utils@npm:2.0.0-nightly.1280" + dependencies: + "@parcel/codeframe": 2.0.0-nightly.1280+5b901a317 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/hash": 2.8.4-nightly.2903+5b901a317 + "@parcel/logger": 2.0.0-nightly.1280+5b901a317 + "@parcel/markdown-ansi": 2.0.0-nightly.1280+5b901a317 + "@parcel/source-map": ^2.1.1 chalk: ^4.1.0 - checksum: 2645b3379deea727af4d908687b18707d721c71eafbe597275fd02da7e04ccb4ae50a191ef8afacc1cf119dcc77d33fe627cbfe56c0d210d84a1fe8b32ab9bac + nullthrows: ^1.1.1 + checksum: 35c247b0f5591924ec44196530b87851d975ca0101c5d029d06d59a4e3047a994e6443df226cb676cd0924d7b0fa57e58cc622b1ad25e6fc9f9361c033f494d3 languageName: node linkType: hard -"@parcel/watcher@npm:^2.0.0": - version: 2.0.5 - resolution: "@parcel/watcher@npm:2.0.5" +"@parcel/utils@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/utils@npm:2.8.3" + dependencies: + "@parcel/codeframe": 2.8.3 + "@parcel/diagnostic": 2.8.3 + "@parcel/hash": 2.8.3 + "@parcel/logger": 2.8.3 + "@parcel/markdown-ansi": 2.8.3 + "@parcel/source-map": ^2.1.1 + chalk: ^4.1.0 + checksum: 69edf3e7c3ef1ccd4caa6ca838a64b27b27668b213212579111405824ed969e6555857b33f0b9e793e97399a60f034904addde19b98628b37a2fcbbb9141cafa + languageName: node + linkType: hard + +"@parcel/watcher@npm:^2.0.7": + version: 2.1.0 + resolution: "@parcel/watcher@npm:2.1.0" dependencies: + is-glob: ^4.0.3 + micromatch: ^4.0.5 node-addon-api: ^3.2.1 node-gyp: latest node-gyp-build: ^4.3.0 - checksum: ddc5b073e82cfadbc3bca3c1c0d5e64f445550f77a073fa3f7b1ac4547ec545fd6474ba5cd7e37aa0121d1ea37e70535172be74f9b081a17e7458849cedffdb0 + checksum: 17f512ad6d5dbb40053ceea7091f8af754afc63786b8f050b225b89a8ba24900468aad8bc4edb25c0349b4c0c8d061f50aa19242c0af52cbc30e6ebf50c7bf4c languageName: node linkType: hard -"@parcel/workers@npm:2.7.0": - version: 2.7.0 - resolution: "@parcel/workers@npm:2.7.0" +"@parcel/workers@npm:2.0.0-nightly.1280+5b901a317": + version: 2.0.0-nightly.1280 + resolution: "@parcel/workers@npm:2.0.0-nightly.1280" dependencies: - "@parcel/diagnostic": 2.7.0 - "@parcel/logger": 2.7.0 - "@parcel/types": 2.7.0 - "@parcel/utils": 2.7.0 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/logger": 2.0.0-nightly.1280+5b901a317 + "@parcel/types": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 chrome-trace-event: ^1.0.2 nullthrows: ^1.1.1 peerDependencies: - "@parcel/core": ^2.7.0 - checksum: e3621c40300be14db72d2a201db9de08fdea121cf76c721a466960b5586052c4416b26931ea8e2df2fb7df2d942180c73759ceca4bd484499f8ac04caa104897 + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + checksum: b0a561c5fd4cfede403388ab674d3312519c5a972667af85ef3adf7f8412eb9c847144a7212d568d2aadfb2c86bc88006201f559d01b6e7ebb4f4331d7b67174 + languageName: node + linkType: hard + +"@parcel/workers@npm:2.8.3": + version: 2.8.3 + resolution: "@parcel/workers@npm:2.8.3" + dependencies: + "@parcel/diagnostic": 2.8.3 + "@parcel/logger": 2.8.3 + "@parcel/types": 2.8.3 + "@parcel/utils": 2.8.3 + chrome-trace-event: ^1.0.2 + nullthrows: ^1.1.1 + peerDependencies: + "@parcel/core": ^2.8.3 + checksum: e3168b3e9ee6bd8e92472e11af9228aca689c5d31841410c908ab31f2a11adf939481d9f4d945ae44d7d3ec1e07980fb3ca5c2f87be82e31a02a94f4655c8e01 languageName: node linkType: hard @@ -6794,12 +7364,126 @@ __metadata: languageName: node linkType: hard -"@swc/helpers@npm:^0.4.2": - version: 0.4.11 - resolution: "@swc/helpers@npm:0.4.11" +"@swc/core-darwin-arm64@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-darwin-arm64@npm:1.3.49" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-darwin-x64@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-darwin-x64@npm:1.3.49" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@swc/core-linux-arm-gnueabihf@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.49" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"@swc/core-linux-arm64-gnu@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.49" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-arm64-musl@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.49" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-linux-x64-gnu@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.49" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@swc/core-linux-x64-musl@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-linux-x64-musl@npm:1.3.49" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@swc/core-win32-arm64-msvc@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.49" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@swc/core-win32-ia32-msvc@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.49" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@swc/core-win32-x64-msvc@npm:1.3.49": + version: 1.3.49 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.49" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@swc/core@npm:^1.3.36": + version: 1.3.49 + resolution: "@swc/core@npm:1.3.49" + dependencies: + "@swc/core-darwin-arm64": 1.3.49 + "@swc/core-darwin-x64": 1.3.49 + "@swc/core-linux-arm-gnueabihf": 1.3.49 + "@swc/core-linux-arm64-gnu": 1.3.49 + "@swc/core-linux-arm64-musl": 1.3.49 + "@swc/core-linux-x64-gnu": 1.3.49 + "@swc/core-linux-x64-musl": 1.3.49 + "@swc/core-win32-arm64-msvc": 1.3.49 + "@swc/core-win32-ia32-msvc": 1.3.49 + "@swc/core-win32-x64-msvc": 1.3.49 + peerDependencies: + "@swc/helpers": ^0.5.0 + dependenciesMeta: + "@swc/core-darwin-arm64": + optional: true + "@swc/core-darwin-x64": + optional: true + "@swc/core-linux-arm-gnueabihf": + optional: true + "@swc/core-linux-arm64-gnu": + optional: true + "@swc/core-linux-arm64-musl": + optional: true + "@swc/core-linux-x64-gnu": + optional: true + "@swc/core-linux-x64-musl": + optional: true + "@swc/core-win32-arm64-msvc": + optional: true + "@swc/core-win32-ia32-msvc": + optional: true + "@swc/core-win32-x64-msvc": + optional: true + peerDependenciesMeta: + "@swc/helpers": + optional: true + checksum: 7234f38451dd765ea94cb44236f261603014e27bff6ecd133d9ba8a2d39314e9949e43bb77ffccd21f5e6c942ad7acf444de8972744ca24ac8f3ccecaea849a1 + languageName: node + linkType: hard + +"@swc/helpers@npm:^0.4.12": + version: 0.4.14 + resolution: "@swc/helpers@npm:0.4.14" dependencies: tslib: ^2.4.0 - checksum: 736857d524b41a8a4db81094e9b027f554004e0fa3e86325d85bdb38f7e6459ce022db079edb6c61ba0f46fe8583b3e663e95f7acbd13e51b8da6c34e45bba2e + checksum: 273fd3f3fc461a92f3790cc551ea054745c6d6959afbe1232e6d7aa1c722bbc114d308aab96bef5c78fc0303c85c7b472ef00e2253251cc89737f3b1af56e5a5 languageName: node linkType: hard @@ -8069,7 +8753,7 @@ __metadata: "@babel/preset-env": ^7.14.7 "@babel/register": ^7.10.5 "@babel/types": ^7.17.0 - "@parcel/transformer-vue": 2.7.0 + "@parcel/transformer-vue": 2.8.4-nightly.2903+5b901a317 "@types/jasmine": "file:./private/@types/jasmine" "@types/jasminewd2": "file:./private/@types/jasmine" "@typescript-eslint/eslint-plugin": ^5.0.0 @@ -14858,7 +15542,7 @@ __metadata: cypress-terminal-report: ^4.1.2 deep-freeze: ^0.0.1 execa: ^6.1.0 - parcel: ^2.0.1 + parcel: 2.0.0-nightly.1278 prompts: ^2.4.2 react: ^18.1.0 react-dom: ^18.1.0 @@ -22113,6 +22797,96 @@ __metadata: languageName: node linkType: hard +"lightningcss-darwin-arm64@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-darwin-arm64@npm:1.19.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"lightningcss-darwin-x64@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-darwin-x64@npm:1.19.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"lightningcss-linux-arm-gnueabihf@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-arm-gnueabihf@npm:1.19.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + +"lightningcss-linux-arm64-gnu@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-arm64-gnu@npm:1.19.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-arm64-musl@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-arm64-musl@npm:1.19.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-linux-x64-gnu@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-x64-gnu@npm:1.19.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"lightningcss-linux-x64-musl@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-linux-x64-musl@npm:1.19.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"lightningcss-win32-x64-msvc@npm:1.19.0": + version: 1.19.0 + resolution: "lightningcss-win32-x64-msvc@npm:1.19.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"lightningcss@npm:^1.16.1": + version: 1.19.0 + resolution: "lightningcss@npm:1.19.0" + dependencies: + detect-libc: ^1.0.3 + lightningcss-darwin-arm64: 1.19.0 + lightningcss-darwin-x64: 1.19.0 + lightningcss-linux-arm-gnueabihf: 1.19.0 + lightningcss-linux-arm64-gnu: 1.19.0 + lightningcss-linux-arm64-musl: 1.19.0 + lightningcss-linux-x64-gnu: 1.19.0 + lightningcss-linux-x64-musl: 1.19.0 + lightningcss-win32-x64-msvc: 1.19.0 + dependenciesMeta: + lightningcss-darwin-arm64: + optional: true + lightningcss-darwin-x64: + optional: true + lightningcss-linux-arm-gnueabihf: + optional: true + lightningcss-linux-arm64-gnu: + optional: true + lightningcss-linux-arm64-musl: + optional: true + lightningcss-linux-x64-gnu: + optional: true + lightningcss-linux-x64-musl: + optional: true + lightningcss-win32-x64-msvc: + optional: true + checksum: c51de34b7379f9da391d0c1157893bb1484357d1ce2212a8c7943690d7a4fed7f2fa0d2dd7a92004b4444662011564ec7bf31f458a1638c856c529fe07285177 + languageName: node + linkType: hard + "lilconfig@npm:2.0.5": version: 2.0.5 resolution: "lilconfig@npm:2.0.5" @@ -26113,27 +26887,51 @@ __metadata: languageName: node linkType: hard -"parcel@npm:^2.0.0, parcel@npm:^2.0.1": - version: 2.7.0 - resolution: "parcel@npm:2.7.0" - dependencies: - "@parcel/config-default": 2.7.0 - "@parcel/core": 2.7.0 - "@parcel/diagnostic": 2.7.0 - "@parcel/events": 2.7.0 - "@parcel/fs": 2.7.0 - "@parcel/logger": 2.7.0 - "@parcel/package-manager": 2.7.0 - "@parcel/reporter-cli": 2.7.0 - "@parcel/reporter-dev-server": 2.7.0 - "@parcel/utils": 2.7.0 +"parcel@npm:2.0.0-nightly.1278": + version: 2.0.0-nightly.1278 + resolution: "parcel@npm:2.0.0-nightly.1278" + dependencies: + "@parcel/config-default": 2.0.0-nightly.1280+5b901a317 + "@parcel/core": 2.0.0-nightly.1278+5b901a317 + "@parcel/diagnostic": 2.0.0-nightly.1280+5b901a317 + "@parcel/events": 2.0.0-nightly.1280+5b901a317 + "@parcel/fs": 2.0.0-nightly.1280+5b901a317 + "@parcel/logger": 2.0.0-nightly.1280+5b901a317 + "@parcel/package-manager": 2.0.0-nightly.1280+5b901a317 + "@parcel/reporter-cli": 2.0.0-nightly.1280+5b901a317 + "@parcel/reporter-dev-server": 2.0.0-nightly.1280+5b901a317 + "@parcel/utils": 2.0.0-nightly.1280+5b901a317 chalk: ^4.1.0 commander: ^7.0.0 get-port: ^4.2.0 v8-compile-cache: ^2.0.0 bin: parcel: lib/bin.js - checksum: 0584bc59daccf9c1e3afe0235d134f440fc2e08135c66d7ac4ee7b4edec12739ec94c829590cf18774f33f760ac601a78b5df26b81977842d1052e3c9b71e94b + checksum: 73a67032a7db14882c93fbc7fa1c5361af2684a50d85410c1099f57a29b98f2643c2bbca7fb5189baa366533aceda8c8023536af27dfcfd178381130342f64ec + languageName: node + linkType: hard + +"parcel@npm:^2.0.0": + version: 2.8.3 + resolution: "parcel@npm:2.8.3" + dependencies: + "@parcel/config-default": 2.8.3 + "@parcel/core": 2.8.3 + "@parcel/diagnostic": 2.8.3 + "@parcel/events": 2.8.3 + "@parcel/fs": 2.8.3 + "@parcel/logger": 2.8.3 + "@parcel/package-manager": 2.8.3 + "@parcel/reporter-cli": 2.8.3 + "@parcel/reporter-dev-server": 2.8.3 + "@parcel/utils": 2.8.3 + chalk: ^4.1.0 + commander: ^7.0.0 + get-port: ^4.2.0 + v8-compile-cache: ^2.0.0 + bin: + parcel: lib/bin.js + checksum: 09cd2dc23c2ec0417e9de93face185a08679d744c6cbb627fce6ffb507f8af1f8d0642f063e0cf771b699419a29db8ee7ca60cdb32966a65dd3b03da35473bfa languageName: node linkType: hard @@ -31266,6 +32064,13 @@ __metadata: languageName: node linkType: hard +"srcset@npm:4": + version: 4.0.0 + resolution: "srcset@npm:4.0.0" + checksum: aceb898c9281101ef43bfbf96bf04dfae828e1bf942a45df6fad74ae9f8f0a425f4bca1480e0d22879beb40dd2bc6947e0e1e5f4d307a714666196164bc5769d + languageName: node + linkType: hard + "sshpk@npm:^1.14.1": version: 1.17.0 resolution: "sshpk@npm:1.17.0" From 2474b1676a1bbdf006a1e29334776de2460cd1b9 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 12 Apr 2023 14:27:08 +0900 Subject: [PATCH 17/19] move mutation logic to end --- packages/@uppy/core/src/Uppy.js | 43 +++++++++++++++++---------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index ab64e2889a..d352d31098 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -653,27 +653,6 @@ class Uppy { const nonRestrictionErrors = errors.filter((error) => !error.isRestriction) - // todo shouldn't we only setState if we are not throwing an error? (see below) - this.setState({ files: nextFilesState }) - - validFilesToAdd.forEach((file) => { - this.emit('file-added', file) - }) - - this.emit('files-added', validFilesToAdd) - - if (validFilesToAdd.length > 5) { - this.log(`Added batch of ${validFilesToAdd.length} files`) - } else { - Object.values(validFilesToAdd).forEach((file) => { - this.log(`Added file: ${file.name}\n id: ${file.id}\n type: ${file.type}`) - }) - } - - if (validFilesToAdd.length > 0) { - this.#startIfAutoProceed() - } - if (nonRestrictionErrors.length > 0) { let message = 'Multiple errors occurred while adding files:\n' nonRestrictionErrors.forEach((subError) => { @@ -693,6 +672,28 @@ class Uppy { throw err } } + + // OK, we haven't thrown an error, we can start emitting/updating state now: + + validFilesToAdd.forEach((file) => { + this.emit('file-added', file) + }) + + this.emit('files-added', validFilesToAdd) + + if (validFilesToAdd.length > 5) { + this.log(`Added batch of ${validFilesToAdd.length} files`) + } else { + Object.values(validFilesToAdd).forEach((file) => { + this.log(`Added file: ${file.name}\n id: ${file.id}\n type: ${file.type}`) + }) + } + + if (validFilesToAdd.length > 0) { + this.#startIfAutoProceed() + } + + this.setState({ files: nextFilesState }) } removeFiles (fileIDs, reason) { From 5ebda5182186efe27d8302798d30a04b9c0ac6e2 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 12 Apr 2023 14:32:22 +0900 Subject: [PATCH 18/19] remove FileRestrictionError merge it with RestrictionError --- packages/@uppy/core/src/Restricter.js | 18 ++++++------------ packages/@uppy/core/src/Uppy.js | 10 +++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/packages/@uppy/core/src/Restricter.js b/packages/@uppy/core/src/Restricter.js index 93c2660a78..4255d47197 100644 --- a/packages/@uppy/core/src/Restricter.js +++ b/packages/@uppy/core/src/Restricter.js @@ -13,21 +13,15 @@ const defaultOptions = { } class RestrictionError extends Error { - constructor (message, { isUserFacing = true } = {}) { + constructor (message, { isUserFacing = true, file } = {}) { super(message) this.isUserFacing = isUserFacing + if (file != null) this.file = file // only some restriction errors are related to a particular file } isRestriction = true } -class FileRestrictionError extends RestrictionError { - constructor (message, { file, ...opts }) { - super(message, opts) - this.file = file - } -} - class Restricter { constructor (getOpts, i18n) { this.i18n = i18n @@ -91,13 +85,13 @@ class Restricter { if (!isCorrectFileType) { const allowedFileTypesString = allowedFileTypes.join(', ') - throw new FileRestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString }), { file }) + throw new RestrictionError(this.i18n('youCanOnlyUploadFileTypes', { types: allowedFileTypesString }), { file }) } } // We can't check maxFileSize if the size is unknown. if (maxFileSize && file.size != null && file.size > maxFileSize) { - throw new FileRestrictionError(this.i18n('exceedsSize', { + throw new RestrictionError(this.i18n('exceedsSize', { size: prettierBytes(maxFileSize), file: file.name, }), { file }) @@ -105,7 +99,7 @@ class Restricter { // We can't check minFileSize if the size is unknown. if (minFileSize && file.size != null && file.size < minFileSize) { - throw new FileRestrictionError(this.i18n('inferiorSize', { + throw new RestrictionError(this.i18n('inferiorSize', { size: prettierBytes(minFileSize), }), { file }) } @@ -140,4 +134,4 @@ class Restricter { } } -export { Restricter, defaultOptions, RestrictionError, FileRestrictionError } +export { Restricter, defaultOptions, RestrictionError } diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index d352d31098..0f7ad9b564 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -16,7 +16,6 @@ import { Restricter, defaultOptions as defaultRestrictionOptions, RestrictionError, - FileRestrictionError, } from './Restricter.js' import packageJson from '../package.json' @@ -451,10 +450,7 @@ class Uppy { const { allowNewUpload } = this.getState() if (allowNewUpload === false) { - const error = file != null - ? new FileRestrictionError(this.i18n('noMoreFilesAllowed'), { file }) - : new RestrictionError(this.i18n('noMoreFilesAllowed')) - + const error = new RestrictionError(this.i18n('noMoreFilesAllowed'), { file }) this.#informAndEmit([error]) throw error } @@ -561,14 +557,14 @@ class Uppy { } if (this.checkIfFileAlreadyExists(newFile.id)) { - throw new FileRestrictionError(this.i18n('noDuplicates', { fileName: newFile.name }), { file: fileToAdd }) + throw new RestrictionError(this.i18n('noDuplicates', { fileName: newFile.name }), { file: fileToAdd }) } const onBeforeFileAddedResult = this.opts.onBeforeFileAdded(newFile, nextFilesState) if (onBeforeFileAddedResult === false) { // Don’t show UI info for this error, as it should be done by the developer - throw new FileRestrictionError('Cannot add the file because onBeforeFileAdded returned false.', { isUserFacing: false, file: fileToAdd }) + throw new RestrictionError('Cannot add the file because onBeforeFileAdded returned false.', { isUserFacing: false, file: fileToAdd }) } else if (typeof onBeforeFileAddedResult === 'object' && onBeforeFileAddedResult !== null) { newFile = onBeforeFileAddedResult } From 33311d8cf21bcf742a3f397a821aed315d0f5bf4 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Wed, 12 Apr 2023 15:31:36 +0900 Subject: [PATCH 19/19] fix silly bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit looks like the e2e tests are doing its job 👏 --- packages/@uppy/core/src/Uppy.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@uppy/core/src/Uppy.js b/packages/@uppy/core/src/Uppy.js index 0f7ad9b564..5ee79072fd 100644 --- a/packages/@uppy/core/src/Uppy.js +++ b/packages/@uppy/core/src/Uppy.js @@ -669,7 +669,9 @@ class Uppy { } } - // OK, we haven't thrown an error, we can start emitting/updating state now: + // OK, we haven't thrown an error, we can start updating state and emitting events now: + + this.setState({ files: nextFilesState }) validFilesToAdd.forEach((file) => { this.emit('file-added', file) @@ -688,8 +690,6 @@ class Uppy { if (validFilesToAdd.length > 0) { this.#startIfAutoProceed() } - - this.setState({ files: nextFilesState }) } removeFiles (fileIDs, reason) {