feat(server): add box file listing and download routes

Introduce `/box/:id` to render a box page with file metadata, plus
endpoints to download individual files and a “download all” ZIP. Add
helpers to safely list box contents, stream files into a ZIP response,
and infer MIME types/icons for better UI and correct downloads.feat(server): add box file listing and download routes

Introduce `/box/:id` to render a box page with file metadata, plus
endpoints to download individual files and a “download all” ZIP. Add
helpers to safely list box contents, stream files into a ZIP response,
and infer MIME types/icons for better UI and correct downloads.
This commit is contained in:
2026-04-27 17:20:57 +03:00
parent b5f39d714a
commit 6a0b3dbe2f
6 changed files with 526 additions and 1 deletions

View File

@@ -5,9 +5,12 @@ const dropzone = document.querySelector(".upload-dropzone");
const uploadForm = document.querySelector(".upload-form");
const uploadStatus = document.querySelector(".upload-statusbar span:first-child");
const boxStatus = document.querySelector(".upload-statusbar span:last-child");
const boxLink = document.querySelector("#upload-box-link");
const shareButton = document.querySelector("#upload-share-button");
let selectedFiles = [];
let statusTimer = null;
let shareURL = "";
function formatBytes(bytes) {
const units = ["B", "KB", "MB", "GB"];
@@ -56,6 +59,22 @@ function setBoxStatus(message) {
}
}
function setBoxLink(path) {
shareURL = path ? new URL(path, window.location.origin).toString() : "";
if (boxLink) {
boxLink.href = shareURL || "#";
boxLink.textContent = shareURL || "Waiting for upload";
boxLink.title = shareURL;
boxLink.classList.toggle("is-empty", !shareURL);
boxLink.setAttribute("aria-disabled", shareURL ? "false" : "true");
}
if (shareButton) {
shareButton.disabled = !shareURL;
}
}
function updateFileCount() {
if (fileCount) {
fileCount.textContent = `${selectedFiles.length} ${selectedFiles.length === 1 ? "file" : "files"}`;
@@ -122,6 +141,7 @@ function updateSelectedFiles(files) {
fileList.append(emptyState);
updateStatus("Ready");
setBoxStatus("WarpBox");
setBoxLink("");
return;
}
@@ -133,6 +153,7 @@ function updateSelectedFiles(files) {
fileList.append(fragment);
updateStatus("Files selected");
setBoxStatus("WarpBox");
setBoxLink("");
}
async function createBox() {
@@ -246,6 +267,7 @@ if (uploadForm) {
try {
const box = await createBox();
setBoxStatus(box.box_url);
setBoxLink(box.box_url);
await Promise.allSettled(selectedFiles.map((selectedFile) => {
return uploadFile(box.box_id, selectedFile, () => {
@@ -267,3 +289,27 @@ if (uploadForm) {
}
});
}
if (shareButton) {
shareButton.addEventListener("click", async () => {
if (!shareURL) {
return;
}
try {
if (navigator.share) {
await navigator.share({
title: "WarpBox download",
text: "Download these files from WarpBox",
url: shareURL,
});
return;
}
await navigator.clipboard.writeText(shareURL);
updateStatus("Link copied");
} catch (error) {
updateStatus("Share cancelled");
}
});
}