feat: show live progress while downloading named files

Bunny/cross-origin downloads are fetched into a blob before saving, which
on large files or slow connections looked stuck (spinner only). Stream the
body through a counting transform and show real byte progress in a toast:
per-file percent for single downloads and file N/M + percent for bulk.

- Progress is measured from Content-Length + received bytes (not estimated).
- The blob is assembled by the browser from the stream (can be disk-backed),
  so we don't accumulate chunks in the JS heap.
- Only the blob path shows a toast; same-origin (R2/S3/MinIO) and the >10 GB
  fallback use the browser's native download UI.
This commit is contained in:
yusufipk
2026-07-10 22:27:53 +07:00
parent 8845c2c643
commit 5821f73d38
4 changed files with 157 additions and 21 deletions
@@ -222,9 +222,26 @@ export function ProjectContentClient({
return;
}
toast.info(`Starting download of ${manifest.totalFiles} files…`);
await runProjectDownloadManifest(manifest);
toast.success(`Started ${manifest.totalFiles} file downloads`);
const downloadToastId = `project-download-${projectId}`;
toast.loading(`Downloading ${manifest.totalFiles} files…`, {
id: downloadToastId,
duration: Infinity,
});
await runProjectDownloadManifest(manifest, (p) => {
const pct =
p.totalBytes && p.totalBytes > 0
? ` · ${Math.min(100, Math.floor((p.receivedBytes / p.totalBytes) * 100))}%`
: '';
toast.loading(`Downloading file ${p.index}/${p.total}`, {
id: downloadToastId,
description: `${p.fileName}${pct}`,
duration: Infinity,
});
});
toast.success(`Downloaded ${manifest.totalFiles} files`, {
id: downloadToastId,
duration: 4000,
});
} catch {
toast.error('Failed to start project download');
} finally {