fix: upload-dialog was heavy on the gpu
Massimo Melina committed
May 5, 2025 at 00:48 UTC
2d92329b96e45f3e3b630e9ec753a17d25f70a18
3 files changed
+16
-12
frontend/src/menu.ts
+2
-2
@@ -25,7 +25,7 @@ const { t, useI18N } = i18n
25
export function MenuPanel() {
26
const { showFilter, remoteSearch, stopSearch, searchManuallyInterrupted, selected, props } = useSnapState()
27
const { can_upload, can_delete, can_archive } = props ? { ...defaultPerms, ...props } : {} as VfsPerms
28
- const { uploading, qs } = useSnapshot(uploadState)
28
+ const { uploading, qs, uploadDialogIsOpen } = useSnapshot(uploadState)
29
useEffect(() => {
30
if (!showFilter)
31
state.selected = {}
@@ -77,7 +77,7 @@ export function MenuPanel() {
77
label: t`Upload`,
78
disabled: !changingButton,
79
tabIndex: changingButton ? undefined : -1,
80
- className: changingButton ? 'show-sliding ' + (uploading ? 'ani-working' : '') : 'before-sliding',
80
+ className: changingButton ? 'show-sliding ' + (uploading && !uploadDialogIsOpen ? 'ani-working' : '') : 'before-sliding',
81
onClick: showUpload,
82
}),
83
h(Btn, showFilter && can_delete ? {
frontend/src/upload.ts
+3
-3
@@ -166,21 +166,21 @@ function FileList({ entries, actions }: { entries: ToUpload[], actions: { [icon:
166
useEffect(() => setAll(false), [entries.length])
167
const MAX = all ? Infinity : _.round(_.clamp(100 * cpuSpeedIndex, 10, 100))
168
const rest = Math.max(0, snapEntries.length - MAX)
169
+ const title = formatPerc(progress)
170
return !snapEntries.length ? null : h('table', { className: 'upload-list', width: '100%' },
171
h('tbody', {},
172
snapEntries.slice(0, MAX).map((e, i) => {
173
const working = e.file === uploading?.file // e is a proxy, so we check 'file' as it's a ref
173
- const title = formatPerc(progress)
174
return h(Fragment, { key: i },
175
h('tr', {},
176
h('td', { className: 'nowrap '}, ..._.map(actions, (cb, icon) =>
177
cb && iconBtn(icon, () => cb(entries[i]), { className: `action-${icon}` })) ),
178
h('td', {}, formatBytes(e.file.size)),
179
h('td', {},
180
- h('span', { className: working ? 'ani-working' : undefined }, e.name || getFilePath(entries[i].file)),
180
+ h('span', {}, e.name || getFilePath(entries[i].file)),
181
working && h('span', { className: 'upload-progress', title }, formatBytes(partial)),
182
working && hashing && h('span', { className: 'upload-hashing' }, t`Considering resume`, ' (', formatPerc(hashing), ')'),
183
- working && h('progress', { className: 'upload-progress-bar', title, value: progress, max: 1 }),
183
+ working && h('progress', { className: 'upload-progress-bar', title, max: 1, value: _.round(progress, 3) }), // round for fewer dom updates
184
),
185
),
186
e.comment && h('tr', {}, h('td', { colSpan: 3 }, h('div', { className: 'entry-comment' }, e.comment)) )
frontend/src/uploadQueue.ts
+11
-7
@@ -59,7 +59,6 @@ let bytesSent = 0
59
setInterval(() => {
60
const now = Date.now()
61
const passed = (now - bytesSentTimestamp) / 1000
62
- if (passed < 3 && uploadState.speed) return
62
uploadState.speed = bytesSent / passed
63
if (now - stuckSince >= 10_000) { // this will normally cause the upload to be retried after 10+10 seconds of no progress
64
overrideStatus = RETRY_UPLOAD // try again
@@ -72,9 +71,9 @@ setInterval(() => {
71
const qBytes = _.sumBy(uploadState.qs, q => _.sumBy(q.entries, x => x.file.size))
72
const left = (qBytes - uploadState.partial)
73
uploadState.eta = uploadState.speed && Math.round(left / uploadState.speed)
75
-}, 5_000)
74
+}, 2_000)
75
77
-let req: XMLHttpRequest | undefined
76
+let currentReq: XMLHttpRequest | undefined
77
let overrideStatus = 0
78
let notificationChannel = ''
79
let notificationSource: EventSource | undefined
@@ -100,11 +99,14 @@ export async function startUpload(toUpload: ToUpload, to: string, resume=0) {
99
let offset = resume
100
let stopLooping = false
101
do { // at least one iteration, even for empty files
103
- req = new XMLHttpRequest()
102
+ const req = currentReq = new XMLHttpRequest()
103
+ req.timeout = 10_000
104
const finished = pendingPromise()
105
+ let aborted = false
106
+ req.onabort = () => aborted = true
107
req.onloadend = () => {
108
finished.resolve()
107
- if (req?.readyState !== 4) return
109
+ currentReq = undefined
110
if (overrideStatus === RETRY_UPLOAD) {
111
overrideStatus = 0
112
stopLooping = true
@@ -119,10 +121,12 @@ export async function startUpload(toUpload: ToUpload, to: string, resume=0) {
121
stopLooping = true
122
return
123
}
122
- if (!status || status === HTTP_CONFLICT) // 0 = user-aborted, HTTP_CONFLICT = skipped because existing
124
+ if (aborted || status === HTTP_CONFLICT) // 0 = user-aborted, HTTP_CONFLICT = skipped because existing
125
uploadState.skipped.push(toUpload)
126
else if (status >= 400)
127
error(status)
128
+ else if (!status) // since no aborted, the request failed at a network level, so try again
129
+ return
130
else {
131
if (splitSize) {
132
offset += splitSize
@@ -255,7 +259,7 @@ export async function startUpload(toUpload: ToUpload, to: string, resume=0) {
259
}
260
261
export function abortCurrentUpload() {
258
- req?.abort()
262
+ currentReq?.abort()
263
}
264
subscribe(uploadState, () => {
265
const [cur] = uploadState.qs