|
@@ -1279,7 +1279,7 @@ export class Uppy<
|
|
|
}
|
|
|
|
|
|
this.setState(stateUpdate)
|
|
|
- this.calculateTotalProgress()
|
|
|
+ this.#updateTotalProgressThrottled()
|
|
|
|
|
|
const removedFileIDs = Object.keys(removedFiles)
|
|
|
removedFileIDs.forEach((fileID) => {
|
|
@@ -1425,59 +1425,98 @@ export class Uppy<
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- // ___Why throttle at 500ms?
|
|
|
- // - We must throttle at >250ms for superfocus in Dashboard to work well
|
|
|
- // (because animation takes 0.25s, and we want to wait for all animations to be over before refocusing).
|
|
|
- // [Practical Check]: if thottle is at 100ms, then if you are uploading a file,
|
|
|
- // and click 'ADD MORE FILES', - focus won't activate in Firefox.
|
|
|
- // - We must throttle at around >500ms to avoid performance lags.
|
|
|
- // [Practical Check] Firefox, try to upload a big file for a prolonged period of time. Laptop will start to heat up.
|
|
|
- // todo when uploading multiple files, this will cause problems because they share the same throttle,
|
|
|
- // meaning some files might never get their progress reported (eaten up by progress events from other files)
|
|
|
- calculateProgress = throttle(
|
|
|
- (file, data) => {
|
|
|
- const fileInState = this.getFile(file?.id)
|
|
|
- if (file == null || !fileInState) {
|
|
|
- this.log(
|
|
|
- `Not setting progress for a file that has been removed: ${file?.id}`,
|
|
|
- )
|
|
|
- return
|
|
|
- }
|
|
|
+ #handleUploadProgress = (
|
|
|
+ file: UppyFile<M, B> | undefined,
|
|
|
+ progress: FileProgressStarted,
|
|
|
+ ) => {
|
|
|
+ const fileInState = file ? this.getFile(file.id) : undefined
|
|
|
+ if (file == null || !fileInState) {
|
|
|
+ this.log(
|
|
|
+ `Not setting progress for a file that has been removed: ${file?.id}`,
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- if (fileInState.progress.percentage === 100) {
|
|
|
- this.log(
|
|
|
- `Not setting progress for a file that has been already uploaded: ${file.id}`,
|
|
|
- )
|
|
|
- return
|
|
|
- }
|
|
|
+ if (fileInState.progress.percentage === 100) {
|
|
|
+ this.log(
|
|
|
+ `Not setting progress for a file that has been already uploaded: ${file.id}`,
|
|
|
+ )
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
+ const newProgress = {
|
|
|
+ bytesTotal: progress.bytesTotal,
|
|
|
// bytesTotal may be null or zero; in that case we can't divide by it
|
|
|
- const canHavePercentage =
|
|
|
- Number.isFinite(data.bytesTotal) && data.bytesTotal > 0
|
|
|
+ percentage:
|
|
|
+ (
|
|
|
+ progress.bytesTotal != null &&
|
|
|
+ Number.isFinite(progress.bytesTotal) &&
|
|
|
+ progress.bytesTotal > 0
|
|
|
+ ) ?
|
|
|
+ Math.round((progress.bytesUploaded / progress.bytesTotal) * 100)
|
|
|
+ : undefined,
|
|
|
+ }
|
|
|
+
|
|
|
+ if (fileInState.progress.uploadStarted != null) {
|
|
|
+ this.setFileState(file.id, {
|
|
|
+ progress: {
|
|
|
+ ...fileInState.progress,
|
|
|
+ ...newProgress,
|
|
|
+ bytesUploaded: progress.bytesUploaded,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ } else {
|
|
|
this.setFileState(file.id, {
|
|
|
progress: {
|
|
|
...fileInState.progress,
|
|
|
- bytesUploaded: data.bytesUploaded,
|
|
|
- bytesTotal: data.bytesTotal,
|
|
|
- percentage:
|
|
|
- canHavePercentage ?
|
|
|
- Math.round((data.bytesUploaded / data.bytesTotal) * 100)
|
|
|
- : 0,
|
|
|
+ ...newProgress,
|
|
|
},
|
|
|
})
|
|
|
+ }
|
|
|
+
|
|
|
+ this.#updateTotalProgressThrottled()
|
|
|
+ }
|
|
|
+
|
|
|
+ #updateTotalProgress() {
|
|
|
+ const totalProgress = this.#calculateTotalProgress()
|
|
|
+ let totalProgressPercent: number | null = null
|
|
|
+ if (totalProgress != null) {
|
|
|
+ totalProgressPercent = Math.round(totalProgress * 100)
|
|
|
+ if (totalProgressPercent > 100) totalProgressPercent = 100
|
|
|
+ else if (totalProgressPercent < 0) totalProgressPercent = 0
|
|
|
+ }
|
|
|
+
|
|
|
+ this.emit('progress', totalProgressPercent ?? 0)
|
|
|
+ this.setState({
|
|
|
+ totalProgress: totalProgressPercent ?? 0,
|
|
|
+ })
|
|
|
+ }
|
|
|
|
|
|
- this.calculateTotalProgress()
|
|
|
- },
|
|
|
+ // ___Why throttle at 500ms?
|
|
|
+ // - We must throttle at >250ms for superfocus in Dashboard to work well
|
|
|
+ // (because animation takes 0.25s, and we want to wait for all animations to be over before refocusing).
|
|
|
+ // [Practical Check]: if thottle is at 100ms, then if you are uploading a file,
|
|
|
+ // and click 'ADD MORE FILES', - focus won't activate in Firefox.
|
|
|
+ // - We must throttle at around >500ms to avoid performance lags.
|
|
|
+ // [Practical Check] Firefox, try to upload a big file for a prolonged period of time. Laptop will start to heat up.
|
|
|
+ #updateTotalProgressThrottled = throttle(
|
|
|
+ () => this.#updateTotalProgress(),
|
|
|
500,
|
|
|
{ leading: true, trailing: true },
|
|
|
)
|
|
|
|
|
|
- calculateTotalProgress(): void {
|
|
|
+ // eslint-disable-next-line class-methods-use-this, @typescript-eslint/explicit-module-boundary-types
|
|
|
+ private [Symbol.for('uppy test: updateTotalProgress')]() {
|
|
|
+ return this.#updateTotalProgress()
|
|
|
+ }
|
|
|
+
|
|
|
+ #calculateTotalProgress() {
|
|
|
// calculate total progress, using the number of files currently uploading,
|
|
|
- // multiplied by 100 and the summ of individual progress of each file
|
|
|
+ // between 0 and 1 and sum of individual progress of each file
|
|
|
const files = this.getFiles()
|
|
|
|
|
|
- const inProgress = files.filter((file) => {
|
|
|
+ // note: also includes files that have completed uploading:
|
|
|
+ const filesInProgress = files.filter((file) => {
|
|
|
return (
|
|
|
file.progress.uploadStarted ||
|
|
|
file.progress.preprocess ||
|
|
@@ -1485,54 +1524,48 @@ export class Uppy<
|
|
|
)
|
|
|
})
|
|
|
|
|
|
- if (inProgress.length === 0) {
|
|
|
- this.emit('progress', 0)
|
|
|
- this.setState({ totalProgress: 0 })
|
|
|
- return
|
|
|
+ if (filesInProgress.length === 0) {
|
|
|
+ return 0
|
|
|
}
|
|
|
|
|
|
- const sizedFiles = inProgress.filter(
|
|
|
- (file) => file.progress.bytesTotal != null,
|
|
|
- )
|
|
|
- const unsizedFiles = inProgress.filter(
|
|
|
- (file) => file.progress.bytesTotal == null,
|
|
|
- )
|
|
|
-
|
|
|
- if (sizedFiles.length === 0) {
|
|
|
- const progressMax = inProgress.length * 100
|
|
|
- const currentProgress = unsizedFiles.reduce((acc, file) => {
|
|
|
- return acc + (file.progress.percentage as number)
|
|
|
- }, 0)
|
|
|
- const totalProgress = Math.round((currentProgress / progressMax) * 100)
|
|
|
- this.setState({ totalProgress })
|
|
|
- return
|
|
|
+ if (filesInProgress.every((file) => file.progress.uploadComplete)) {
|
|
|
+ // If every uploading file is complete, and we're still getting progress, it probably means
|
|
|
+ // there's a bug somewhere in some progress reporting code (maybe not even our code)
|
|
|
+ // and we're still getting progress, so let's just assume it means a 100% progress
|
|
|
+ return 1
|
|
|
}
|
|
|
|
|
|
- let totalSize = sizedFiles.reduce((acc, file) => {
|
|
|
- return (acc + (file.progress.bytesTotal ?? 0)) as number
|
|
|
- }, 0)
|
|
|
- const averageSize = totalSize / sizedFiles.length
|
|
|
- totalSize += averageSize * unsizedFiles.length
|
|
|
-
|
|
|
- let uploadedSize = 0
|
|
|
- sizedFiles.forEach((file) => {
|
|
|
- uploadedSize += file.progress.bytesUploaded as number
|
|
|
- })
|
|
|
- unsizedFiles.forEach((file) => {
|
|
|
- uploadedSize += (averageSize * (file.progress.percentage || 0)) / 100
|
|
|
- })
|
|
|
+ const isSizedFile = (file: UppyFile<M, B>) =>
|
|
|
+ file.progress.bytesTotal != null && file.progress.bytesTotal !== 0
|
|
|
|
|
|
- let totalProgress =
|
|
|
- totalSize === 0 ? 0 : Math.round((uploadedSize / totalSize) * 100)
|
|
|
+ const sizedFilesInProgress = filesInProgress.filter(isSizedFile)
|
|
|
+ const unsizedFilesInProgress = filesInProgress.filter(
|
|
|
+ (file) => !isSizedFile(file),
|
|
|
+ )
|
|
|
|
|
|
- // hot fix, because:
|
|
|
- // uploadedSize ended up larger than totalSize, resulting in 1325% total
|
|
|
- if (totalProgress > 100) {
|
|
|
- totalProgress = 100
|
|
|
+ if (
|
|
|
+ sizedFilesInProgress.every((file) => file.progress.uploadComplete) &&
|
|
|
+ unsizedFilesInProgress.length > 0 &&
|
|
|
+ !unsizedFilesInProgress.every((file) => file.progress.uploadComplete)
|
|
|
+ ) {
|
|
|
+ // we are done with uploading all files of known size, however
|
|
|
+ // there is at least one file with unknown size still uploading,
|
|
|
+ // and we cannot say anything about their progress
|
|
|
+ // In any case, return null because it doesn't make any sense to show a progress
|
|
|
+ return null
|
|
|
}
|
|
|
|
|
|
- this.setState({ totalProgress })
|
|
|
- this.emit('progress', totalProgress)
|
|
|
+ const totalFilesSize = sizedFilesInProgress.reduce(
|
|
|
+ (acc, file) => acc + (file.progress.bytesTotal ?? 0),
|
|
|
+ 0,
|
|
|
+ )
|
|
|
+
|
|
|
+ const totalUploadedSize = sizedFilesInProgress.reduce(
|
|
|
+ (acc, file) => acc + (file.progress.bytesUploaded || 0),
|
|
|
+ 0,
|
|
|
+ )
|
|
|
+
|
|
|
+ return totalFilesSize === 0 ? 0 : totalUploadedSize / totalFilesSize
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -1618,7 +1651,6 @@ export class Uppy<
|
|
|
progress: {
|
|
|
uploadStarted: Date.now(),
|
|
|
uploadComplete: false,
|
|
|
- percentage: 0,
|
|
|
bytesUploaded: 0,
|
|
|
bytesTotal: file.size,
|
|
|
} as FileProgressStarted,
|
|
@@ -1631,7 +1663,7 @@ export class Uppy<
|
|
|
|
|
|
this.on('upload-start', onUploadStarted)
|
|
|
|
|
|
- this.on('upload-progress', this.calculateProgress)
|
|
|
+ this.on('upload-progress', this.#handleUploadProgress)
|
|
|
|
|
|
this.on('upload-success', (file, uploadResp) => {
|
|
|
if (file == null || !this.getFile(file.id)) {
|
|
@@ -1668,7 +1700,7 @@ export class Uppy<
|
|
|
})
|
|
|
}
|
|
|
|
|
|
- this.calculateTotalProgress()
|
|
|
+ this.#updateTotalProgressThrottled()
|
|
|
})
|
|
|
|
|
|
this.on('preprocess-progress', (file, progress) => {
|
|
@@ -1738,7 +1770,7 @@ export class Uppy<
|
|
|
|
|
|
this.on('restored', () => {
|
|
|
// Files may have changed--ensure progress is still accurate.
|
|
|
- this.calculateTotalProgress()
|
|
|
+ this.#updateTotalProgressThrottled()
|
|
|
})
|
|
|
|
|
|
// @ts-expect-error should fix itself when dashboard it typed (also this doesn't belong here)
|