const SETTINGS_KEY = "warpbox.upload.settings.v1"; const el = { form: document.querySelector("#upload-form"), fileInput: document.querySelector("#file-upload"), dropSurface: document.querySelector("#drop-surface"), dropzone: document.querySelector("#dropzone"), fileList: document.querySelector("#file-list"), queueLabel: document.querySelector("#queue-label"), queueSize: document.querySelector("#queue-size"), limitHint: document.querySelector("#limit-hint"), boxSpaceText: document.querySelector("#box-space-text"), boxSpaceBar: document.querySelector("#box-space-bar"), overallBar: document.querySelector("#overall-bar"), overallPercent: document.querySelector("#overall-percent"), shareLink: document.querySelector("#share-link"), copyButton: document.querySelector("#copy-button"), startButton: document.querySelector("#start-button"), statusText: document.querySelector("#status-text"), toast: document.querySelector("#toast"), terminal: document.querySelector("#terminal-box"), copyCurlButton: document.querySelector("#copy-curl-button"), docPopup: document.querySelector("#doc-popup"), modalBackdrop: document.querySelector("#modal-backdrop"), docPopupTitle: document.querySelector("#doc-popup-title"), docPopupBody: document.querySelector("#doc-popup-body"), docPopupClose: document.querySelector("#doc-popup-close"), expiry: document.querySelector("#expiry-select"), password: document.querySelector("#password-input"), optionsForm: document.querySelector("#box-options-form"), maxViews: document.querySelector("#max-views"), boxName: document.querySelector("#box-name"), customSlug: document.querySelector("#custom-slug"), downloadPage: document.querySelector("#download-page"), allowZip: document.querySelector("#allow-zip"), allowPreview: document.querySelector("#allow-preview"), keepFilenames: document.querySelector("#keep-filenames"), privateBox: document.querySelector("#private-box"), apiKeyMode: document.querySelector("#api-key-mode"), apiKeyInput: document.querySelector("#api-key-input"), apiKeyRow: document.querySelector("#api-key-row"), apiKeyState: document.querySelector("#api-key-state"), }; const uploadsEnabled = el.form?.dataset.uploadsEnabled === "true"; const defaultRetention = el.form?.dataset.defaultRetention || "10s"; const maxFileBytes = numberFromDataset(el.form?.dataset.maxFileBytes); const maxBoxBytes = numberFromDataset(el.form?.dataset.maxBoxBytes); const oneTimeRetentionKey = "one-time"; let files = []; let shareUrl = ""; let uploadLocked = false; let statusTimer = null; let pendingDuplicateFiles = []; let apiKeyTimer = null; function numberFromDataset(value) { const number = Number.parseInt(value || "0", 10); return Number.isFinite(number) && number > 0 ? number : 0; } function formatBytes(bytes) { if (!bytes) return "0 B"; const units = ["B", "KB", "MB", "GB", "TB"]; let value = bytes; let unit = 0; while (value >= 1024 && unit < units.length - 1) { value /= 1024; unit += 1; } return `${value.toFixed(value >= 10 || unit === 0 ? 0 : 1)} ${units[unit]}`; } function htmlEscape(value) { return String(value) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function shellQuote(value) { return `'${String(value).replaceAll("'", "'\\''")}'`; } function totalBytes() { return files.reduce((sum, item) => sum + item.file.size, 0); } function uploadedBytes() { return files.reduce((sum, item) => sum + item.loaded, 0); } function overallProgress() { const total = totalBytes(); return total ? Math.round((uploadedBytes() / total) * 100) : 0; } function oversizedFiles() { return maxFileBytes ? files.filter((item) => item.file.size > maxFileBytes) : []; } function isOverBoxQuota() { return maxBoxBytes ? totalBytes() > maxBoxBytes : false; } function hasQuotaError() { return isOverBoxQuota() || oversizedFiles().length > 0; } function normalizedFileName(name) { return String(name || "").trim().toLowerCase(); } function splitNameForIncrement(name) { const value = String(name || "file"); const dot = value.lastIndexOf("."); if (dot > 0 && dot < value.length - 1) return [value.slice(0, dot), value.slice(dot)]; return [value, ""]; } function nextIncrementedFileName(name, usedNames) { const [base, ext] = splitNameForIncrement(name); let index = 2; let candidate = `${base} (${index})${ext}`; while (usedNames.has(normalizedFileName(candidate))) { index += 1; candidate = `${base} (${index})${ext}`; } usedNames.add(normalizedFileName(candidate)); return candidate; } function makeQueuedFile(file, displayName = file.name) { return { file, displayName, loaded: 0, uploaded: false, failed: false, error: "", row: null, boxID: "", boxFile: null, previewURL: file.type?.startsWith("image/") ? URL.createObjectURL(file) : "", }; } function iconForFile(file) { const filename = file.name || ""; const mimeType = file.type || ""; const extension = filename.includes(".") ? filename.slice(filename.lastIndexOf(".")).toLowerCase() : ""; if (extension === ".exe") return "/static/img/icons/Program Files Icons - PNG/MSONSEXT.DLL_14_6-0.png"; if (mimeType.startsWith("image/")) return "/static/img/sprites/bitmap.png"; if (mimeType.startsWith("video/") || mimeType.startsWith("audio/")) return "/static/img/icons/netshow_notransm-1.png"; if (mimeType.startsWith("text/") || extension === ".md") return "/static/img/sprites/notepad_file-1.png"; if (mimeType.includes("zip") || mimeType.includes("compressed") || [".rar", ".7z", ".tar", ".gz"].includes(extension)) return "/static/img/icons/Windows Icons - PNG/zipfldr.dll_14_101-0.png"; if ([".ttf", ".otf", ".woff", ".woff2"].includes(extension)) return "/static/img/sprites/font.png"; if (extension === ".pdf") return "/static/img/sprites/journal.png"; if ([".html", ".css", ".js"].includes(extension)) return "/static/img/sprites/frame_web-0.png"; return "/static/img/icons/Windows Icons - PNG/ole2.dll_14_DEFICON.png"; } function setStatus(message) { if (el.statusText) el.statusText.textContent = message; } function showToast(message, type = "info") { if (!el.toast) return; el.toast.textContent = message; el.toast.classList.remove("toast-info", "toast-warning", "toast-error", "is-visible"); el.toast.classList.add(`toast-${type}`, "is-visible"); clearTimeout(showToast.timer); showToast.timer = setTimeout(() => el.toast.classList.remove("is-visible"), 2600); } function stopStatusAnimation() { if (statusTimer) { clearInterval(statusTimer); statusTimer = null; } } function animateUploadStatus(getPrefix) { let dotCount = 0; stopStatusAnimation(); statusTimer = setInterval(() => { dotCount = (dotCount % 3) + 1; setStatus(`${getPrefix()} Uploading${".".repeat(dotCount)}`); }, 350); } function setShareUrl(url) { shareUrl = url ? new URL(url, window.location.origin).toString() : ""; if (!el.shareLink || !el.copyButton) return; el.shareLink.textContent = shareUrl || "Not created yet"; el.shareLink.href = shareUrl || "#"; el.shareLink.title = shareUrl; el.shareLink.classList.toggle("is-empty", !shareUrl); el.shareLink.setAttribute("aria-disabled", shareUrl ? "false" : "true"); el.copyButton.disabled = !shareUrl; el.copyButton.dataset.disabledReason = shareUrl ? "" : "There is no share URL yet. Start an upload first."; updateTerminal(); updateCurrentStep(); } function setOverallProgress(percent) { const clamped = Math.max(0, Math.min(100, percent)); const display = `${Math.round(clamped)}%`; if (el.overallBar) el.overallBar.style.width = display; if (el.overallPercent) el.overallPercent.textContent = display; } function setRowProgress(item, percent) { const bar = item.row?.querySelector(".upload-progress-bar"); if (bar) bar.style.width = `${Math.max(0, Math.min(100, percent))}%`; } function updateCurrentStep() { const hasFiles = files.length > 0; const allDone = hasFiles && files.every((item) => item.uploaded); el.dropzone?.classList.toggle("is-current-step", uploadsEnabled && !hasFiles && !uploadLocked); el.startButton?.classList.toggle("is-current-step", uploadsEnabled && hasFiles && !allDone && !uploadLocked && !hasQuotaError()); document.querySelector(".upload-result")?.classList.toggle("is-current-step", allDone && Boolean(shareUrl)); } function quotaWarningMessage(incoming = []) { const combined = [...files, ...incoming]; const tooBig = maxFileBytes ? combined.filter((item) => item.file.size > maxFileBytes) : []; const total = combined.reduce((sum, item) => sum + item.file.size, 0); if (tooBig.length) { const list = tooBig.slice(0, 4).map((item) => `${item.displayName} (${formatBytes(item.file.size)})`).join(", "); const more = tooBig.length > 4 ? ` and ${tooBig.length - 4} more` : ""; return `These files are over the single-file limit of ${formatBytes(maxFileBytes)}: ${list}${more}. Remove them before uploading.`; } if (maxBoxBytes && total > maxBoxBytes) { return `This box is ${formatBytes(total - maxBoxBytes)} over the ${formatBytes(maxBoxBytes)} limit. Remove some files before uploading.`; } return ""; } function updateLimitHint() { if (!el.limitHint) return; const parts = []; if (maxBoxBytes) parts.push(`Max box: ${formatBytes(maxBoxBytes)}`); if (maxFileBytes) parts.push(`max file: ${formatBytes(maxFileBytes)}`); parts.push("links expire automatically"); el.limitHint.textContent = parts.join(" · "); } function updateQuota() { const used = totalBytes(); const limitText = maxBoxBytes ? ` / ${formatBytes(maxBoxBytes)}` : ""; const overQuota = isOverBoxQuota(); const overFile = oversizedFiles().length > 0; const percent = maxBoxBytes ? Math.min(100, Math.round((used / maxBoxBytes) * 100)) : 0; document.querySelector(".upload-quota")?.classList.toggle("is-quota-warning", overQuota || overFile); if (el.boxSpaceText) el.boxSpaceText.textContent = `${formatBytes(used)}${limitText}${overQuota ? " - over quota" : ""}`; if (el.boxSpaceBar) { el.boxSpaceBar.style.width = `${percent}%`; el.boxSpaceBar.classList.toggle("is-over-quota", overQuota || overFile); } } function updateQueueSummary() { const count = files.length; if (el.queueLabel) el.queueLabel.textContent = count ? `${count} file${count === 1 ? "" : "s"} selected` : "No files selected"; if (el.queueSize) el.queueSize.textContent = `${formatBytes(totalBytes())} total`; } function updateOverallProgress() { const uploadedCount = files.filter((item) => item.uploaded).length; const percent = overallProgress(); setOverallProgress(percent >= 100 && uploadedCount < files.length ? 99 : percent); } function createFileRow(item, index) { const row = document.createElement("div"); row.className = "upload-file-row"; row.dataset.index = String(index); row.classList.toggle("has-thumbnail", Boolean(item.previewURL)); row.classList.toggle("is-too-large", maxFileBytes > 0 && item.file.size > maxFileBytes); row.classList.toggle("is-working", item.loaded > 0 && !item.uploaded && !item.failed); row.classList.toggle("is-uploaded", item.uploaded); row.classList.toggle("is-failed", item.failed); row.title = item.error || ""; const icon = document.createElement("img"); icon.className = "upload-file-icon"; icon.src = item.previewURL || iconForFile(item.file); icon.alt = ""; icon.setAttribute("aria-hidden", "true"); const name = document.createElement("span"); name.className = "upload-file-name"; name.textContent = item.displayName; name.title = item.displayName; const size = document.createElement("span"); size.className = "upload-file-size"; size.textContent = formatBytes(item.file.size); const remove = document.createElement("button"); remove.className = "win98-button upload-file-remove"; remove.type = "button"; remove.textContent = "×"; remove.dataset.remove = String(index); remove.title = uploadLocked ? "This file cannot be removed because this upload box was already created." : "Remove file"; remove.disabled = uploadLocked; const progress = document.createElement("span"); progress.className = "upload-progress"; progress.setAttribute("aria-label", `Upload progress ${Math.round(item.file.size ? (item.loaded / item.file.size) * 100 : 0)} percent`); const progressBar = document.createElement("span"); progressBar.className = "upload-progress-bar"; progressBar.style.width = `${item.uploaded ? 100 : item.failed ? 100 : Math.max(0, Math.min(100, item.file.size ? (item.loaded / item.file.size) * 100 : 0))}%`; progress.append(progressBar); row.append(icon, name, size, remove, progress); item.row = row; return row; } function renderFiles() { if (!el.fileList) return; el.fileList.replaceChildren(); if (!files.length) { const empty = document.createElement("p"); empty.className = "upload-empty-state"; empty.textContent = uploadsEnabled ? "No files in the box yet. Drop files here, use File > Add files, or click the dropzone." : "Guest uploads are disabled."; el.fileList.append(empty); } else { const fragment = document.createDocumentFragment(); files.forEach((item, index) => fragment.append(createFileRow(item, index))); el.fileList.append(fragment); } updateQueueSummary(); updateQuota(); updateOverallProgress(); updateTerminal(); updateDisabledReasons(); updateCurrentStep(); } function duplicateFileReport(incoming = []) { const used = new Set(files.map((item) => normalizedFileName(item.displayName))); const duplicates = []; const unique = []; incoming.forEach((item) => { const key = normalizedFileName(item.displayName); if (used.has(key)) { duplicates.push(item); return; } used.add(key); unique.push(item); }); return { unique, duplicates }; } function addFiles(fileList) { if (!uploadsEnabled) { showToast("Guest uploads are disabled.", "warning"); return; } if (uploadLocked) { showToast("This box is sealed. Clear it to create a fresh upload.", "warning"); return; } const incoming = Array.from(fileList || []).map((file) => makeQueuedFile(file)); if (!incoming.length) return; const { unique, duplicates } = duplicateFileReport(incoming); if (unique.length) { files.push(...unique); setShareUrl(""); renderFiles(); const warning = quotaWarningMessage(); if (warning) showWarningDialog("Quota warning", warning); } if (duplicates.length) showDuplicateDialog(duplicates); if (unique.length) setStatus(`${unique.length} file${unique.length === 1 ? "" : "s"} added to queue`); if (duplicates.length && !unique.length) setStatus(`${duplicates.length} duplicate file${duplicates.length === 1 ? "" : "s"} need your choice`); } function showDuplicateDialog(duplicates) { pendingDuplicateFiles = duplicates; const list = duplicates.map((item) => `
These files have the same names as files already in the queue.
Skip them, or append numbers so they become names like file (2).zip.
This removes the current queue, resets progress, and unlocks the Start upload button.
The browser refused clipboard access. Copy it manually from the field below.
Single-file limit exceeded. Remove these files before uploading.
"); parts.push(`Box quota exceeded. Current total is ${formatBytes(totalBytes())}. The limit is ${formatBytes(maxBoxBytes)}. Remove ${formatBytes(totalBytes() - maxBoxBytes)} or more.
`); } if (!parts.length) parts.push(`${htmlEscape(message)}
`); return parts.join(""); } function showWarningDialog(title, message) { openPopup(title, `WarpBox accepts normal multipart form uploads through the compatibility endpoint:
curl \\
-F 'files=@./my-file.zip' \\
-F 'retention=1h' \\
${window.location.origin}/upload
The browser uses the manifest API: it creates a box, uploads each file, and marks failed uploads so the download page does not wait forever.
`, }, faq: { title: "Help & FAQ", html: `Can I password protect uploads?
Yes. Set a password in Box Options before starting the upload.
What happens if one file fails?
The failed row stays red, successful files remain available, and WarpBox marks the failed file in the manifest.
Are all options server-backed?
Expiry, password, ZIP download, and one-time download are sent to the backend. Notes like box name, custom slug, and API key mode are saved locally until backend support exists.
These values come from the running WarpBox configuration.
`, }, about: { title: "About WarpBox", about: true, html: `WarpBox was made by Daniel Legt.
Temporary file boxes, terminal-friendly uploads, and old-web UI charm.
`, }, examples: { title: "Examples", html: `curl \\
-F 'files=@./photo.png' \\
-F 'retention=24h' \\
${window.location.origin}/upload
curl \\
-F 'files=@./one.png' \\
-F 'files=@./two.zip' \\
-F 'retention=1h' \\
-F 'password=secret-pass' \\
${window.location.origin}/upload
`,
},
};
function openDoc(name) {
const doc = docs[name];
if (!doc) return;
openPopup(doc.title, doc.html, doc.about);
setStatus(`${doc.title} opened`);
}
document.addEventListener("click", (event) => {
const menuButton = event.target.closest(".menu-button");
if (menuButton) {
const item = menuButton.closest(".menu-item");
const isOpen = item.classList.contains("is-open");
document.querySelectorAll(".menu-item.is-open").forEach((node) => {
node.classList.remove("is-open");
node.querySelector(".menu-button")?.setAttribute("aria-expanded", "false");
});
item.classList.toggle("is-open", !isOpen);
menuButton.setAttribute("aria-expanded", String(!isOpen));
return;
}
const action = event.target.closest("[data-action]")?.dataset.action;
if (action) {
document.querySelectorAll(".menu-item.is-open").forEach((node) => node.classList.remove("is-open"));
if (action === "browse") el.fileInput?.click();
if (action === "start-upload") startUpload();
if (action === "copy-link") copyText("Share URL", shareUrl, shareUrl);
if (action === "clear") confirmClearQueue();
if (action === "toggle-delete-once" && el.expiry?.querySelector(`option[value="${oneTimeRetentionKey}"]`)) {
el.expiry.value = isOneTimeDownloadSelected() ? defaultRetention : oneTimeRetentionKey;
syncZipForRetention();
saveSettings();
syncMenuChecks();
updateTerminal();
}
if (action === "random-password") randomPassword();
if (action === "random-box-name") randomBoxName();
if (action === "clear-password" && el.password && !uploadLocked) {
el.password.value = "";
saveSettings();
updateTerminal();
}
if (action === "toggle-page" && el.downloadPage && !uploadLocked) {
el.downloadPage.checked = !el.downloadPage.checked;
saveSettings();
syncMenuChecks();
}
if (action === "help" || action === "side-help") openDoc("faq");
if (action === "terminal-help") el.terminal?.focus();
if (action === "coming-soon") showToast("That shortcut is decorative for now.");
if (action === "side-close" || action === "side-folder-close" || action === "fake-close" || action === "minimize" || action === "toggle-fit") showToast("Window controls are decorative on this page.");
return;
}
const expiry = event.target.closest("[data-expiry]")?.dataset.expiry;
if (expiry && el.expiry) {
el.expiry.value = expiry;
syncZipForRetention();
saveSettings();
syncMenuChecks();
updateTerminal();
setStatus(`Expiry set to ${event.target.textContent.trim()}`);
return;
}
const doc = event.target.closest("[data-doc]")?.dataset.doc;
if (doc) {
openDoc(doc);
return;
}
const remove = event.target.closest("[data-remove]");
if (remove) {
removeFile(Number(remove.dataset.remove));
return;
}
if (event.target.id === "duplicate-append") appendPendingDuplicates();
if (event.target.id === "duplicate-skip") {
pendingDuplicateFiles = [];
closeDoc();
showToast("Duplicate files skipped.");
}
if (event.target.id === "confirm-clear-yes") {
closeDoc();
clearQueue();
}
if (event.target.id === "confirm-clear-no" || event.target.id === "fallback-close") closeDoc();
if (!event.target.closest(".menu-item")) {
document.querySelectorAll(".menu-item.is-open").forEach((node) => {
node.classList.remove("is-open");
node.querySelector(".menu-button")?.setAttribute("aria-expanded", "false");
});
}
});
el.fileInput?.addEventListener("change", () => addFiles(el.fileInput.files));
[el.dropSurface, el.dropzone].filter(Boolean).forEach((target) => {
target.addEventListener("dragover", (event) => {
event.preventDefault();
el.dropzone?.classList.add("is-dragging");
});
target.addEventListener("dragleave", () => el.dropzone?.classList.remove("is-dragging"));
target.addEventListener("drop", (event) => {
event.preventDefault();
el.dropzone?.classList.remove("is-dragging");
addFiles(event.dataTransfer.files);
});
});
el.dropzone?.addEventListener("keydown", (event) => {
if (event.key === "Enter" || event.key === " ") {
event.preventDefault();
el.fileInput?.click();
}
});
el.form?.addEventListener("submit", (event) => {
event.preventDefault();
startUpload();
});
el.copyButton?.addEventListener("click", () => copyText("Share URL", shareUrl, shareUrl));
el.copyCurlButton?.addEventListener("click", () => copyText("cURL command", getCurlCommand({ full: true })));
el.docPopupClose?.addEventListener("click", closeDoc);
el.modalBackdrop?.addEventListener("click", closeDoc);
[el.expiry, el.password, el.maxViews, el.boxName, el.customSlug, el.downloadPage, el.allowZip, el.allowPreview, el.keepFilenames, el.privateBox, el.apiKeyMode, el.apiKeyInput].filter(Boolean).forEach((control) => {
control.addEventListener("input", () => {
if (control === el.boxName) syncSlugFromName();
if (control === el.customSlug) el.customSlug.dataset.auto = "false";
if (control === el.apiKeyInput) validateApiKeyField();
saveSettings();
updateTerminal();
});
control.addEventListener("change", () => {
if (control === el.expiry) syncZipForRetention();
if (control === el.apiKeyMode) syncApiKeyField();
saveSettings();
syncMenuChecks();
updateTerminal();
});
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closeDoc();
document.querySelectorAll(".menu-item.is-open").forEach((node) => node.classList.remove("is-open"));
}
if (event.key === "F1") {
event.preventDefault();
openDoc("faq");
}
if (event.ctrlKey && !event.shiftKey && !event.altKey) {
const key = event.key.toLowerCase();
if (key === "o") {
event.preventDefault();
el.fileInput?.click();
}
if (key === "u") {
event.preventDefault();
startUpload();
}
if (key === "k") {
event.preventDefault();
copyText("cURL command", getCurlCommand({ full: true }));
}
if (key === "l") {
event.preventDefault();
copyText("Share URL", shareUrl, shareUrl);
}
}
});
window.addEventListener("beforeunload", () => {
files.forEach((item) => {
if (item.previewURL) URL.revokeObjectURL(item.previewURL);
});
});
loadSettings();
updateLimitHint();
syncMenuChecks();
renderFiles();
updateTerminal();