Skip to content

Commit a152913

Browse files
committed
Improve progress in UI
- only show progress percent and total bytes for files that we know the size of. (but all files will still be included in number of files) - use `null` as an unknown value for progress and ETA, allowing us to remove ETA from UI when unknown - `percentage` make use of `undefined` when progress is not yet known - don't show percentage in UI when unknown - add a new state field `progress` that's the same as `totalProgress` but can also be `null`
1 parent c2e74d6 commit a152913

File tree

10 files changed

+87
-61
lines changed

10 files changed

+87
-61
lines changed

packages/@uppy/core/src/Uppy.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ describe('src/Core', () => {
282282
meta: {},
283283
plugins: {},
284284
totalProgress: 0,
285+
progress: null,
285286
recoveredState: null,
286287
}
287288

@@ -316,6 +317,7 @@ describe('src/Core', () => {
316317
meta: {},
317318
plugins: {},
318319
totalProgress: 0,
320+
progress: null,
319321
recoveredState: null,
320322
})
321323
// new state
@@ -335,6 +337,7 @@ describe('src/Core', () => {
335337
meta: {},
336338
plugins: {},
337339
totalProgress: 0,
340+
progress: null,
338341
recoveredState: null,
339342
})
340343
})
@@ -386,6 +389,7 @@ describe('src/Core', () => {
386389
meta: {},
387390
plugins: {},
388391
totalProgress: 0,
392+
progress: null,
389393
recoveredState: null,
390394
})
391395
})
@@ -577,6 +581,7 @@ describe('src/Core', () => {
577581
meta: {},
578582
plugins: {},
579583
totalProgress: 0,
584+
progress: null,
580585
recoveredState: null,
581586
})
582587
expect(plugin.mocks.uninstall.mock.calls.length).toEqual(1)
@@ -1776,7 +1781,6 @@ describe('src/Core', () => {
17761781
bytesUploaded: 0,
17771782
// null indicates unsized
17781783
bytesTotal: null,
1779-
percentage: 0,
17801784
})
17811785

17821786
// @ts-ignore
@@ -1849,8 +1853,8 @@ describe('src/Core', () => {
18491853
// @ts-ignore
18501854
core[Symbol.for('uppy test: updateTotalProgress')]()
18511855

1852-
// foo.jpg at 35%, bar.jpg at 0%
1853-
expect(core.getState().totalProgress).toBe(18)
1856+
// foo.jpg at 35%, bar.jpg has unknown size and will not be counted
1857+
expect(core.getState().totalProgress).toBe(36)
18541858

18551859
core.destroy()
18561860
})

packages/@uppy/core/src/Uppy.ts

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,8 @@ export interface State<M extends Meta, B extends Body>
232232
details?: string | Record<string, string> | null
233233
}>
234234
plugins: Plugins
235-
totalProgress: number
235+
totalProgress: number // todo remove backward compat
236+
progress: number | null
236237
companion?: Record<string, string>
237238
}
238239

@@ -361,6 +362,7 @@ type OmitFirstArg<T> = T extends [any, ...infer U] ? U : never
361362

362363
const defaultUploadState = {
363364
totalProgress: 0,
365+
progress: null,
364366
allowNewUpload: true,
365367
error: null,
366368
recoveredState: null,
@@ -758,7 +760,7 @@ export class Uppy<
758760
isUploadInProgress: boolean
759761
isSomeGhost: boolean
760762
} {
761-
const { files: filesObject, totalProgress, error } = this.getState()
763+
const { files: filesObject, progress: totalProgress, error } = this.getState()
762764
const files = Object.values(filesObject)
763765

764766
const inProgressFiles: UppyFile<M, B>[] = []
@@ -1445,15 +1447,15 @@ export class Uppy<
14451447
progress.bytesTotal > 0
14461448
) ?
14471449
Math.round((progress.bytesUploaded / progress.bytesTotal) * 100)
1448-
: 0,
1450+
: undefined,
14491451
}
14501452

14511453
if (fileInState.progress.uploadStarted != null) {
14521454
this.setFileState(file.id, {
14531455
progress: {
14541456
...fileInState.progress,
1455-
bytesUploaded: progress.bytesUploaded,
14561457
...newProgress,
1458+
bytesUploaded: progress.bytesUploaded,
14571459
},
14581460
})
14591461
} else {
@@ -1469,12 +1471,19 @@ export class Uppy<
14691471
}
14701472

14711473
#updateTotalProgress() {
1472-
let totalProgress = Math.round(this.#calculateTotalProgress() * 100)
1473-
if (totalProgress > 100) totalProgress = 100
1474-
else if (totalProgress < 0) totalProgress = 0
1474+
const totalProgress = this.#calculateTotalProgress()
1475+
let totalProgressPercent: number | null = null;
1476+
if (totalProgress != null) {
1477+
totalProgressPercent = Math.round(totalProgress * 100)
1478+
if (totalProgressPercent > 100) totalProgressPercent = 100
1479+
else if (totalProgressPercent < 0) totalProgressPercent = 0
1480+
}
14751481

1476-
this.emit('progress', totalProgress)
1477-
this.setState({ totalProgress })
1482+
this.emit('progress', totalProgressPercent ?? 0) // todo remove `?? 0` in next major
1483+
this.setState({
1484+
totalProgress: totalProgressPercent ?? 0, // todo remove backward compat in next major
1485+
progress: totalProgressPercent,
1486+
})
14781487
}
14791488

14801489
// ___Why throttle at 500ms?
@@ -1512,35 +1521,31 @@ export class Uppy<
15121521
return 0
15131522
}
15141523

1515-
const sizedFiles = filesInProgress.filter(
1516-
(file) => file.progress.bytesTotal != null,
1517-
)
1518-
const unsizedFiles = filesInProgress.filter(
1519-
(file) => file.progress.bytesTotal == null,
1524+
const sizedFilesInProgress = filesInProgress.filter(
1525+
(file) => file.progress.bytesTotal != null && file.progress.bytesTotal !== 0,
15201526
)
15211527

1522-
if (sizedFiles.length === 0) {
1523-
const totalUnsizedProgress = unsizedFiles.reduce(
1524-
(acc, file) => acc + (file.progress.percentage ?? 0) / 100,
1525-
0,
1526-
)
1528+
if (sizedFilesInProgress.length === 0) {
1529+
return null // we don't have any files that we can know the percentage progress of
1530+
}
15271531

1528-
return totalUnsizedProgress / unsizedFiles.length
1532+
if (sizedFilesInProgress.every((file) => file.progress.uploadComplete)) {
1533+
// If every uploading file is complete, and we're still getting progress, it means either
1534+
// 1. there's a bug somewhere in some progress reporting code (maybe not even ours)
1535+
// and we're still getting progress, so let's just ignore it
1536+
// 2. there are files with unknown size (bytesTotal == null), still uploading,
1537+
// and we cannot say anything about their progress
1538+
// In any case, return null because it doesn't make any sense to show a progress
1539+
return null
15291540
}
15301541

1531-
let totalFilesSize = sizedFiles.reduce((acc, file) => {
1532-
return (acc + (file.progress.bytesTotal ?? 0)) as number
1533-
}, 0)
1534-
const averageSize = totalFilesSize / sizedFiles.length
1535-
totalFilesSize += averageSize * unsizedFiles.length
1542+
const totalFilesSize = sizedFilesInProgress.reduce((acc, file) => (
1543+
acc + (file.progress.bytesTotal ?? 0)
1544+
), 0)
15361545

1537-
let totalUploadedSize = 0
1538-
sizedFiles.forEach((file) => {
1539-
totalUploadedSize += file.progress.bytesUploaded || 0
1540-
})
1541-
unsizedFiles.forEach((file) => {
1542-
totalUploadedSize += averageSize * ((file.progress.percentage ?? 0) / 100)
1543-
})
1546+
const totalUploadedSize = sizedFilesInProgress.reduce((acc, file) => (
1547+
acc + (file.progress.bytesUploaded || 0)
1548+
), 0)
15441549

15451550
return totalFilesSize === 0 ? 0 : totalUploadedSize / totalFilesSize
15461551
}
@@ -1628,7 +1633,6 @@ export class Uppy<
16281633
progress: {
16291634
uploadStarted: Date.now(),
16301635
uploadComplete: false,
1631-
percentage: 0,
16321636
bytesUploaded: 0,
16331637
bytesTotal: file.size,
16341638
} as FileProgressStarted,

packages/@uppy/dashboard/src/Dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,7 @@ export default class Dashboard<M extends Meta, B extends Body> extends UIPlugin<
12021202
isAllComplete,
12031203
isAllPaused,
12041204
totalFileCount: Object.keys(files).length,
1205-
totalProgress: state.totalProgress,
1205+
totalProgress: state.progress,
12061206
allowNewUpload,
12071207
acquirers,
12081208
theme,

packages/@uppy/dashboard/src/components/Dashboard.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ type DashboardUIProps<M extends Meta, B extends Body> = {
4444
isAllComplete: boolean
4545
isAllPaused: boolean
4646
totalFileCount: number
47-
totalProgress: number
47+
totalProgress: number | null
4848
allowNewUpload: boolean
4949
acquirers: TargetWithRender[]
5050
theme: string

packages/@uppy/progress-bar/src/ProgressBar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ export default class ProgressBar<
4040
}
4141

4242
render(state: State<M, B>): ComponentChild {
43-
const progress = state.totalProgress || 0
43+
const { progress } = state
4444
// before starting and after finish should be hidden if specified in the options
4545
const isHidden =
46-
(progress === 0 || progress === 100) && this.opts.hideAfterFinish
46+
(progress == null || progress === 0 || progress === 100) && this.opts.hideAfterFinish
4747
return (
4848
<div
4949
className="uppy uppy-ProgressBar"

packages/@uppy/status-bar/src/Components.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ interface ProgressDetailsProps {
266266
complete: number
267267
totalUploadedSize: number
268268
totalSize: number
269-
totalETA: number
269+
totalETA: number | null
270270
}
271271

272272
function ProgressDetails(props: ProgressDetailsProps) {
@@ -275,6 +275,9 @@ function ProgressDetails(props: ProgressDetailsProps) {
275275

276276
const ifShowFilesUploadedOfTotal = numUploads > 1
277277

278+
const totalUploadedSizeStr = prettierBytes(totalUploadedSize)
279+
280+
278281
return (
279282
<div className="uppy-StatusBar-statusSecondary">
280283
{ifShowFilesUploadedOfTotal &&
@@ -289,14 +292,14 @@ function ProgressDetails(props: ProgressDetailsProps) {
289292
*/}
290293
{ifShowFilesUploadedOfTotal && renderDot()}
291294

292-
{i18n('dataUploadedOfTotal', {
293-
complete: prettierBytes(totalUploadedSize),
295+
{totalSize !== 0 ? i18n('dataUploadedOfTotal', {
296+
complete: totalUploadedSizeStr,
294297
total: prettierBytes(totalSize),
295-
})}
298+
}) : totalUploadedSizeStr}
296299

297300
{renderDot()}
298301

299-
{i18n('xTimeLeft', {
302+
{totalETA != null && i18n('xTimeLeft', {
300303
time: prettyETA(totalETA),
301304
})}
302305
</span>
@@ -355,7 +358,7 @@ function UploadNewlyAddedFiles(props: UploadNewlyAddedFilesProps) {
355358
interface ProgressBarUploadingProps {
356359
i18n: I18n
357360
supportsUploadProgress: boolean
358-
totalProgress: number
361+
totalProgress: number | null
359362
showProgressDetails: boolean | undefined
360363
isUploadStarted: boolean
361364
isAllComplete: boolean
@@ -365,7 +368,7 @@ interface ProgressBarUploadingProps {
365368
complete: number
366369
totalUploadedSize: number
367370
totalSize: number
368-
totalETA: number
371+
totalETA: number | null
369372
startUpload: () => void
370373
}
371374

@@ -427,7 +430,7 @@ function ProgressBarUploading(props: ProgressBarUploadingProps) {
427430
: null}
428431
<div className="uppy-StatusBar-status">
429432
<div className="uppy-StatusBar-statusPrimary">
430-
{supportsUploadProgress ? `${title}: ${totalProgress}%` : title}
433+
{supportsUploadProgress && totalProgress != null ? `${title}: ${totalProgress}%` : title}
431434
</div>
432435

433436
{renderProgressDetails()}

packages/@uppy/status-bar/src/StatusBar.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,15 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
102102

103103
#computeSmoothETA(totalBytes: {
104104
uploaded: number
105-
total: number
106-
remaining: number
107-
}): number {
108-
if (totalBytes.total === 0 || totalBytes.remaining === 0) {
109-
return 0
105+
total: number | null // null means indeterminate
106+
}) {
107+
if (totalBytes.total == null || totalBytes.total === 0) {
108+
return null
109+
}
110+
111+
const remaining = totalBytes.total - totalBytes.uploaded
112+
if (remaining <= 0) {
113+
return null
110114
}
111115

112116
// When state is restored, lastUpdateTime is still nullish at this point.
@@ -131,7 +135,7 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
131135
currentSpeed
132136
: emaFilter(currentSpeed, this.#previousSpeed, speedFilterHalfLife, dt)
133137
this.#previousSpeed = filteredSpeed
134-
const instantETA = totalBytes.remaining / filteredSpeed
138+
const instantETA = remaining / filteredSpeed
135139

136140
const updatedPreviousETA = Math.max(this.#previousETA! - dt, 0)
137141
const filteredETA =
@@ -155,7 +159,7 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
155159
capabilities,
156160
files,
157161
allowNewUpload,
158-
totalProgress,
162+
progress: totalProgress,
159163
error,
160164
recoveredState,
161165
} = state
@@ -182,14 +186,21 @@ export default class StatusBar<M extends Meta, B extends Body> extends UIPlugin<
182186
let totalSize = 0
183187
let totalUploadedSize = 0
184188

189+
// If at least one file has an unknown size, it doesn't make sense to display a total size
190+
if (startedFiles.every((f) => f.progress.bytesTotal != null && f.progress.bytesTotal !== 0)) {
191+
startedFiles.forEach((file) => {
192+
totalSize += file.progress.bytesTotal || 0
193+
totalUploadedSize += file.progress.bytesUploaded || 0
194+
})
195+
}
196+
185197
startedFiles.forEach((file) => {
186-
totalSize += file.progress.bytesTotal || 0
187198
totalUploadedSize += file.progress.bytesUploaded || 0
188199
})
200+
189201
const totalETA = this.#computeSmoothETA({
190202
uploaded: totalUploadedSize,
191203
total: totalSize,
192-
remaining: totalSize - totalUploadedSize,
193204
})
194205

195206
return StatusBarUI({

packages/@uppy/status-bar/src/StatusBarUI.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export interface StatusBarUIProps<M extends Meta, B extends Body> {
4040
hideRetryButton?: boolean
4141
recoveredState: State<M, B>['recoveredState']
4242
uploadState: (typeof statusBarStates)[keyof typeof statusBarStates]
43-
totalProgress: number
43+
totalProgress: number | null
4444
files: Record<string, UppyFile<M, B>>
4545
supportsUploadProgress: boolean
4646
hideAfterFinish?: boolean
@@ -55,7 +55,7 @@ export interface StatusBarUIProps<M extends Meta, B extends Body> {
5555
numUploads: number
5656
complete: number
5757
totalSize: number
58-
totalETA: number
58+
totalETA: number | null
5959
totalUploadedSize: number
6060
}
6161

packages/@uppy/utils/src/FileProgress.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ export type FileProcessingInfo =
1616
interface FileProgressBase {
1717
uploadComplete?: boolean
1818
percentage?: number
19+
// note that Companion will send `bytesTotal` 0 if unknown size (not `null`).
20+
// this is not perfect because some files can actually have a size of 0,
21+
// and then we might think those files have an unknown size
22+
// todo we should change this in companion
1923
bytesTotal: number | null
2024
preprocess?: FileProcessingInfo
2125
postprocess?: FileProcessingInfo

packages/@uppy/utils/src/emitSocketProgress.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { Uppy } from '@uppy/core'
1+
import type { Uppy } from '@uppy/core/src/Uppy.js'
22
import type { UppyFile } from './UppyFile.ts'
33

44
function emitSocketProgress(
55
uploader: { uppy: Uppy<any, any> },
66
progressData: {
7-
progress: string // pre-formatted percentage
7+
progress: string // pre-formatted percentage number as a string
88
bytesTotal: number
99
bytesUploaded: number
1010
},

0 commit comments

Comments
 (0)