From 1ea84e4dc8bbb907a0c86cc1e301ed07aec4b65e Mon Sep 17 00:00:00 2001 From: Marcel Date: Thu, 26 Mar 2026 11:37:28 +0100 Subject: [PATCH] feat(upload): show progress bar in drop zone during upload Replaces fetch with XMLHttpRequest to get upload progress events. The drop zone shows a filling progress bar and percentage while files are uploading, then reverts to the normal hint when done. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/routes/+page.svelte | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 777abb48..71a5c93b 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -25,6 +25,7 @@ let isDragging = $state(false); let windowDragging = $state(false); let dragCounter = 0; let isUploading = $state(false); +let uploadProgress = $state(0); let uploadMessages = $state<{ text: string; isError: boolean; link?: string }[]>([]); let fileInput: HTMLInputElement; @@ -85,19 +86,26 @@ async function uploadFiles(files: File[]) { } isUploading = true; + uploadProgress = 0; try { const formData = new FormData(); for (const file of valid) { formData.append('files', file); } - const res = await fetch('/api/documents/quick-upload', { - method: 'POST', - body: formData + const { ok, body } = await new Promise<{ ok: boolean; body: string }>((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/api/documents/quick-upload'); + xhr.upload.addEventListener('progress', (e) => { + if (e.lengthComputable) uploadProgress = Math.round((e.loaded / e.total) * 100); + }); + xhr.addEventListener('load', () => resolve({ ok: xhr.status < 300, body: xhr.responseText })); + xhr.addEventListener('error', () => reject(new Error('Network error'))); + xhr.send(formData); }); - if (res.ok) { - const result = await res.json(); + if (ok) { + const result = JSON.parse(body); if (result.created?.length > 0) { messages.push({ text: m.upload_success({ count: result.created.length }), isError: false }); } @@ -122,6 +130,7 @@ async function uploadFiles(files: File[]) { } } finally { isUploading = false; + uploadProgress = 0; uploadMessages = messages; } } @@ -372,10 +381,20 @@ $effect(() => { - - {isUploading ? '…' : m.upload_drop_hint()} - - {m.upload_accepted_types()} + {#if isUploading} +
+
+
+
+ {uploadProgress}% +
+ {:else} + {m.upload_drop_hint()} + {m.upload_accepted_types()} + {/if} {#if uploadMessages.length > 0}