From 93cf8a91f108f3dd0041bb47a5f9d43dcbd778dc Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Thu, 29 Jan 2026 13:58:55 +0530 Subject: [PATCH 1/9] fix: use protected download link --- .../file-manager/src/routes/(protected)/files/+page.svelte | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/platforms/file-manager/src/routes/(protected)/files/+page.svelte b/platforms/file-manager/src/routes/(protected)/files/+page.svelte index eef74c97..79f13641 100644 --- a/platforms/file-manager/src/routes/(protected)/files/+page.svelte +++ b/platforms/file-manager/src/routes/(protected)/files/+page.svelte @@ -136,6 +136,7 @@ let uploadModalDragOver = $state(false); let previewFile = $state(null); let previewUrl = $state(null); + let downloadUrl = $state(null); let breadcrumbs = $state>([ { id: null, name: "My Files" }, ]); @@ -765,6 +766,7 @@ // Add auth token as query parameter for img/iframe tags const token = localStorage.getItem("file_manager_auth_token"); previewUrl = `${API_BASE_URL}/api/files/${file.id}/preview?token=${token || ""}`; + downloadUrl = `${API_BASE_URL}/api/files/${file.id}/download?token=${token || ""}`; return; } // If not previewable, go to detail page @@ -774,6 +776,7 @@ function closePreview() { previewFile = null; previewUrl = null; + downloadUrl = null; } function getFileIcon(mimeType: string): string { @@ -1960,7 +1963,7 @@ {/if}
From f33017006622e45fd66b8118c7c385924016c72a Mon Sep 17 00:00:00 2001 From: SoSweetHam Date: Thu, 29 Jan 2026 14:16:18 +0530 Subject: [PATCH 2/9] feat: concurrent downloads --- .../src/routes/(protected)/files/+page.svelte | 179 +++++++++++++++++- 1 file changed, 175 insertions(+), 4 deletions(-) diff --git a/platforms/file-manager/src/routes/(protected)/files/+page.svelte b/platforms/file-manager/src/routes/(protected)/files/+page.svelte index 79f13641..c6a6f7f3 100644 --- a/platforms/file-manager/src/routes/(protected)/files/+page.svelte +++ b/platforms/file-manager/src/routes/(protected)/files/+page.svelte @@ -137,6 +137,10 @@ let previewFile = $state(null); let previewUrl = $state(null); let downloadUrl = $state(null); + + // Multi-file selection for download + const MAX_DOWNLOAD_LIMIT = 10; // Browser typically limits simultaneous downloads + let selectedFileIds = $state>(new Set()); let breadcrumbs = $state>([ { id: null, name: "My Files" }, ]); @@ -696,6 +700,7 @@ async function navigateToFolder(folderId: string | null) { currentFolderId = folderId; + clearSelection(); // Clear selection when navigating await loadFiles(); await updateBreadcrumbs(); } @@ -704,6 +709,7 @@ if (currentView === view) return; // Don't reload if already on this view currentView = view; currentFolderId = null; // Reset to root when switching views + clearSelection(); // Clear selection when switching views await loadFiles(); // Reload files when switching views await updateBreadcrumbs(); // Update breadcrumbs with correct root name } @@ -872,6 +878,81 @@ ); }); }); + + // Multi-file selection derived states + const selectableFiles = $derived( + allItems.filter((item) => item.type === "file") + ); + const isDownloadLimitReached = $derived( + selectedFileIds.size >= MAX_DOWNLOAD_LIMIT + ); + const allFilesSelected = $derived( + selectableFiles.length > 0 && + selectableFiles.every((f) => selectedFileIds.has(f.id)) + ); + + function toggleFileSelection(fileId: string, event: Event) { + event.stopPropagation(); + const newSet = new Set(selectedFileIds); + if (newSet.has(fileId)) { + newSet.delete(fileId); + } else if (newSet.size < MAX_DOWNLOAD_LIMIT) { + newSet.add(fileId); + } + selectedFileIds = newSet; + } + + function toggleSelectAll(event: Event) { + event.stopPropagation(); + if (allFilesSelected) { + // Deselect all + selectedFileIds = new Set(); + } else { + // Select up to the limit + const newSet = new Set(); + for (const file of selectableFiles) { + if (newSet.size >= MAX_DOWNLOAD_LIMIT) break; + newSet.add(file.id); + } + selectedFileIds = newSet; + } + } + + function clearSelection() { + selectedFileIds = new Set(); + } + + async function downloadSelectedFiles() { + if (selectedFileIds.size === 0) return; + + const token = localStorage.getItem("file_manager_auth_token"); + const filesToDownload = selectableFiles.filter((f) => + selectedFileIds.has(f.id) + ); + + // Download files with a small delay between each to avoid browser blocking + for (let i = 0; i < filesToDownload.length; i++) { + const file = filesToDownload[i]; + const url = `${API_BASE_URL}/api/files/${file.id}/download?token=${token || ""}`; + + // Create a temporary link and trigger download + const link = document.createElement("a"); + link.href = url; + link.download = file.displayName || file.name; + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + // Small delay between downloads to prevent browser from blocking + if (i < filesToDownload.length - 1) { + await new Promise((resolve) => setTimeout(resolve, 200)); + } + } + + toast.success(`Started downloading ${filesToDownload.length} file(s)`); + clearSelection(); + }
@@ -900,6 +981,47 @@
+ {#if selectedFileIds.size > 0} + + + {/if} {#if currentView === "my-files"}
{/if} + + {#if selectedFileIds.size > 0} +
+
+ + {selectedFileIds.size} of {MAX_DOWNLOAD_LIMIT} files selected + + {#if isDownloadLimitReached} + (limit reached) + {/if} +
+
+ +
+
+ {/if} +
+ @@ -1047,7 +1203,7 @@ class="hover:bg-gray-50 transition-colors cursor-pointer {item.type === 'folder' ? '' - : ''}" + : ''} {item.type === 'file' && selectedFileIds.has(item.id) ? 'bg-blue-50' : ''}" onclick={(e) => { e.stopPropagation(); if (item.type === "folder") { @@ -1057,6 +1213,21 @@ } }} > + +