feat(frontend): implement file upload with drag-and-drop and per-file progress
- client.ts: add uploadWithProgress() using XHR for upload progress events - FileUpload.svelte: drag-drop zone wrapper, multi-file queue with individual progress bars, success/error status, MIME rejection message, dismiss panel - Header.svelte: optional onUpload prop renders upload icon button - files/+page.svelte: wire upload button, prepend uploaded files to grid - vite-mock-plugin.ts: handle POST /files, unshift new file into mock array - Fix crypto.randomUUID() crash on non-secure HTTP context (use Date.now + Math.random) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -89,6 +89,43 @@ async function request<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
/** Upload with XHR so we can track progress via onProgress(0–100). */
|
||||
export function uploadWithProgress<T>(
|
||||
path: string,
|
||||
formData: FormData,
|
||||
onProgress: (pct: number) => void,
|
||||
): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const token = get(authStore).accessToken;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('POST', BASE + path);
|
||||
if (token) xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
|
||||
xhr.upload.onprogress = (e) => {
|
||||
if (e.lengthComputable) onProgress(Math.round((e.loaded / e.total) * 100));
|
||||
};
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
try {
|
||||
resolve(JSON.parse(xhr.responseText) as T);
|
||||
} catch {
|
||||
resolve(undefined as T);
|
||||
}
|
||||
} else {
|
||||
let body: { code?: string; message?: string } = {};
|
||||
try {
|
||||
body = JSON.parse(xhr.responseText);
|
||||
} catch { /* ignore */ }
|
||||
reject(new ApiError(xhr.status, body.code ?? 'error', body.message ?? xhr.statusText));
|
||||
}
|
||||
};
|
||||
|
||||
xhr.onerror = () => reject(new ApiError(0, 'network_error', 'Network error'));
|
||||
xhr.send(formData);
|
||||
});
|
||||
}
|
||||
|
||||
export const api = {
|
||||
get: <T>(path: string) => request<T>(path),
|
||||
post: <T>(path: string, body?: unknown) =>
|
||||
|
||||
Reference in New Issue
Block a user