-
Notifications
You must be signed in to change notification settings - Fork 48
Add download all button to shared folder page #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,28 +54,53 @@ export default function SharedPage({ | |
| ); | ||
|
|
||
| const getDownloadUrl = trpc.shares.getDownloadUrl.useMutation(); | ||
| const [isDownloadingAll, setIsDownloadingAll] = useState(false); | ||
|
|
||
| const downloadFile = async (fileId?: string) => { | ||
| const result = await getDownloadUrl.mutateAsync({ | ||
| token, | ||
| fileId, | ||
| password: enteredPassword, | ||
| }); | ||
| const response = await fetch(result.url); | ||
| if (!response.ok) throw new Error(`Download failed (${response.status})`); | ||
| const blob = await response.blob(); | ||
| const blobUrl = URL.createObjectURL(blob); | ||
| const a = document.createElement("a"); | ||
| a.href = blobUrl; | ||
| a.download = result.filename; | ||
| a.click(); | ||
| setTimeout(() => URL.revokeObjectURL(blobUrl), 100); | ||
| }; | ||
|
|
||
| const handleDownload = async (fileId?: string) => { | ||
| try { | ||
| const result = await getDownloadUrl.mutateAsync({ | ||
| token, | ||
| fileId, | ||
| password: enteredPassword, | ||
| }); | ||
| const response = await fetch(result.url); | ||
| if (!response.ok) throw new Error(`Download failed (${response.status})`); | ||
| const blob = await response.blob(); | ||
| const blobUrl = URL.createObjectURL(blob); | ||
| const a = document.createElement("a"); | ||
| a.href = blobUrl; | ||
| a.download = result.filename; | ||
| a.click(); | ||
| setTimeout(() => URL.revokeObjectURL(blobUrl), 100); | ||
| await downloadFile(fileId); | ||
| } catch (err) { | ||
| toast.error((err as Error).message); | ||
| } | ||
| }; | ||
|
|
||
| const handleDownloadAll = async (files: { id: string }[]) => { | ||
| setIsDownloadingAll(true); | ||
| let failed = 0; | ||
| // Sequential: each file consumes one of the link's maxDownloads budget, | ||
| // and parallel blob clicks get throttled by the browser. | ||
| for (const file of files) { | ||
| try { | ||
| await downloadFile(file.id); | ||
| } catch { | ||
| failed++; | ||
| } | ||
| } | ||
| setIsDownloadingAll(false); | ||
| if (failed > 0) { | ||
| toast.error( | ||
| `${failed} of ${files.length} ${failed === 1 ? "file" : "files"} failed to download`, | ||
| ); | ||
| } | ||
| }; | ||
|
|
||
| const navigateToFolder = (folderId: string) => { | ||
| setCurrentFolderId(folderId); | ||
| }; | ||
|
|
@@ -231,6 +256,21 @@ export default function SharedPage({ | |
| ))} | ||
| </div> | ||
|
|
||
| {displayFiles && displayFiles.length > 0 && ( | ||
| <Button | ||
| variant="outline" | ||
| size="sm" | ||
| className="w-full" | ||
| disabled={isDownloadingAll} | ||
| onClick={() => handleDownloadAll(displayFiles)} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the user moves directly between subfolders, the query can retain the previous folder's data while fetching the next folder. Because this button remains enabled with those stale file IDs, clicking it during the transition downloads files from the previous folder and consumes their download allowance. |
||
| > | ||
| <Download className="size-3.5" /> | ||
| {isDownloadingAll | ||
| ? "Downloading…" | ||
| : `Download all (${displayFiles.length})`} | ||
| </Button> | ||
| )} | ||
|
|
||
| {browseQuery.isLoading && isBrowsing ? ( | ||
| <div className="border rounded-sm p-6 text-center"> | ||
| <div className="skeleton h-4 w-24 mx-auto rounded-sm" /> | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Browsers can block or prompt for repeated automatic downloads after the first asynchronous
a.click(), but a blocked click does not rejectdownloadFile. The loop then reports success even when only some files were delivered, while each requested URL has already consumed the share link's download allowance.