const boxPanel = document.querySelector(".box-panel[data-box-id]"); const boxStatus = document.querySelector(".box-statusbar span:first-child"); const boxAddress = document.querySelector("#box-address"); const boxExpiryMeta = document.querySelector(".box-meta[data-expires-at]"); const boxExpiryText = document.querySelector("#box-expiry-text"); const contextMenu = document.querySelector("#box-context-menu"); const docPopup = document.querySelector("#doc-popup"); const docPopupTitle = document.querySelector("#doc-popup-title"); const docPopupBody = document.querySelector("#doc-popup-body"); const docPopupClose = document.querySelector("#doc-popup-close"); const modalBackdrop = document.querySelector("#modal-backdrop"); const toast = document.querySelector("#toast"); const zipOnly = boxPanel && boxPanel.dataset.zipOnly === "true"; let contextFile = null; let lastStatusSignature = ""; function htmlEscape(value) { return String(value || "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function showToast(message, type = "info") { window.WarpBoxUI.toast(message, type, { target: toast }); } function openPopup(title, html, options = {}) { window.WarpBoxUI.openPopup(title, html, { ...options, popup: docPopup, title: docPopupTitle, body: docPopupBody, backdrop: modalBackdrop, }); } function closePopup() { window.WarpBoxUI.closePopup({ popup: docPopup, backdrop: modalBackdrop }); } function currentExpiryDate() { const value = boxExpiryMeta?.dataset.expiresAt || ""; if (!value) return null; const date = new Date(value); return Number.isNaN(date.getTime()) ? null : date; } function formatDuration(ms) { if (ms <= 0) return "expired"; const totalSeconds = Math.ceil(ms / 1000); const days = Math.floor(totalSeconds / 86400); const hours = Math.floor((totalSeconds % 86400) / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; if (days) return `${days}d ${hours}h ${minutes}m`; if (hours) return `${hours}h ${minutes}m ${seconds}s`; if (minutes) return `${minutes}m ${seconds}s`; return `${seconds}s`; } function updateExpiryCountdown() { if (!boxExpiryText || !boxExpiryMeta) return; const expiry = currentExpiryDate(); if (!expiry) { boxExpiryText.textContent = "Expires after one-time download"; return; } boxExpiryText.textContent = `Expires in ${formatDuration(expiry.getTime() - Date.now())}`; boxExpiryText.title = `Expires at ${expiry.toLocaleString()}`; } function closeContextMenu() { contextMenu?.classList.remove("is-visible"); contextMenu?.setAttribute("aria-hidden", "true"); } function showContextMenu(file, x, y) { if (!contextMenu) return; contextFile = file; contextMenu.style.left = `${Math.min(x, window.innerWidth - 190)}px`; contextMenu.style.top = `${Math.min(y, window.innerHeight - 98)}px`; contextMenu.classList.add("is-visible"); contextMenu.setAttribute("aria-hidden", "false"); } function fileData(item) { return { id: item.dataset.fileId || "", name: item.dataset.name || item.querySelector(".box-file-name")?.textContent || "", size: item.dataset.size || "", mime: item.dataset.mime || "", status: item.dataset.status || "", statusLabel: item.querySelector(".box-file-meta")?.textContent || "", downloadPath: item.dataset.downloadPath || item.getAttribute("href") || "", thumbnail: item.dataset.thumbnail || "", canDownload: item.getAttribute("aria-disabled") !== "true" && item.getAttribute("href") !== "#", }; } function downloadFile(item) { const data = fileData(item); if (!data.canDownload) { showToast(zipOnly ? "Individual file downloads are disabled for one-time boxes. Use Download Zip." : "This file is not ready for download yet.", "warning"); return; } window.location.href = data.downloadPath; setTimeout(refreshBoxStatus, 900); } function previewURL(data) { return data.canDownload ? data.downloadPath : ""; } async function previewFile(item) { const data = fileData(item); if (zipOnly) { showToast("Previews are disabled for one-time boxes. Use Download Zip.", "warning"); return; } const url = previewURL(data); if (!url) { showToast("This file is not ready to preview yet.", "warning"); return; } const mime = data.mime.toLowerCase(); const name = htmlEscape(data.name); if (mime.startsWith("image/")) { openPopup(`${data.name} preview`, `${name}`, { preview: true }); return; } if (mime.startsWith("video/")) { openPopup(`${data.name} preview`, ``, { preview: true }); return; } if (mime.startsWith("audio/")) { openPopup(`${data.name} preview`, ``, { preview: true }); return; } if (mime === "application/pdf") { openPopup(`${data.name} preview`, ``, { preview: true }); return; } if (mime.startsWith("text/") || /\.(txt|md|json|csv|log|html|css|js)$/i.test(data.name)) { try { const response = await fetch(url); if (!response.ok) throw new Error("Preview failed"); const text = await response.text(); openPopup(`${data.name} preview`, `
${htmlEscape(text.slice(0, 120000))}
`, { preview: true }); } catch (_) { showToast("The browser could not load a text preview.", "error"); } return; } showToast("This file type cannot be previewed in the browser.", "warning"); } function showProperties(item) { const data = fileData(item); const url = data.downloadPath ? new URL(data.downloadPath, window.location.origin).toString() : "Not ready"; openPopup(`${data.name} Properties`, `

${htmlEscape(data.name)}

Name
${htmlEscape(data.name)}
Size
${htmlEscape(data.size || "Unknown")}
Type
${htmlEscape(data.mime || "Unknown")}
Status
${htmlEscape(data.statusLabel || data.status || "Unknown")}
File ID
${htmlEscape(data.id)}
Location
${htmlEscape(url)}
`, { properties: true }); } function updateBoxFile(file) { const item = document.querySelector(`.box-file[data-file-id="${file.id}"]`); if (!item) return; const meta = item.querySelector(".box-file-meta"); const icon = item.querySelector(".box-file-icon"); const isComplete = file.status === "complete"; const isFailed = file.status === "failed"; item.classList.toggle("is-complete", isComplete); item.classList.toggle("is-failed", isFailed); item.classList.toggle("is-loading", !isComplete && !isFailed); item.classList.toggle("has-thumbnail", Boolean(file.thumbnail_path)); item.dataset.status = file.status; item.dataset.name = file.name || item.dataset.name || ""; item.dataset.size = file.size_label || item.dataset.size || ""; item.dataset.mime = file.mime_type || item.dataset.mime || ""; item.dataset.downloadPath = file.download_path || item.dataset.downloadPath || ""; item.dataset.thumbnail = file.thumbnail_path || ""; item.title = file.title; if (isComplete && !zipOnly) { item.href = file.download_path; item.setAttribute("download", ""); item.removeAttribute("aria-disabled"); } else { item.href = "#"; item.removeAttribute("download"); item.setAttribute("aria-disabled", "true"); } if (meta) meta.textContent = `${file.status_label} ยท ${file.size_label}`; if (icon) icon.src = file.thumbnail_path || file.icon_path; } async function refreshBoxStatus() { if (!boxPanel) return false; const boxID = boxPanel.dataset.boxId; const response = await fetch(`/box/${boxID}/status`); if (!response.ok) return { changed: false, hasLoadingFiles: true }; const result = await response.json(); const signature = statusSignature(result); const changed = signature !== lastStatusSignature; lastStatusSignature = signature; if (boxExpiryMeta && typeof result.expires_at === "string") { boxExpiryMeta.dataset.expiresAt = result.expires_at; updateExpiryCountdown(); } result.files.forEach(updateBoxFile); if (boxStatus) { const completeCount = result.files.filter((file) => file.status === "complete").length; boxStatus.textContent = `${completeCount}/${result.files.length} ready`; } const hasLoadingFiles = result.files.some((file) => { const isUploading = file.status === "pending" || file.status === "uploading"; const isWaitingForThumbnail = file.status === "complete" && !file.thumbnail_status && !file.thumbnail_path; return isUploading || isWaitingForThumbnail || file.thumbnail_status === "processing"; }); return { changed, hasLoadingFiles }; } function statusSignature(result) { const files = Array.isArray(result.files) ? result.files : []; return JSON.stringify({ expiresAt: result.expires_at || "", files: files.map((file) => ({ id: file.id, status: file.status, size: file.size, thumbnailPath: file.thumbnail_path || "", thumbnailStatus: file.thumbnail_status || "", downloadPath: file.download_path || "", })), }); } function pollingStages(baseMS) { return [ { interval: baseMS, attempts: 10 }, { interval: baseMS * 2, attempts: 20 }, { interval: baseMS * 10, attempts: 100 }, ]; } function startStagedPolling(baseMS) { const stages = pollingStages(baseMS); let stageIndex = 0; let attemptsInStage = 0; let stopped = false; const tick = async () => { if (stopped) return; const stage = stages[stageIndex]; try { const result = await refreshBoxStatus(); if (result.changed) { stageIndex = 0; attemptsInStage = 0; } else { attemptsInStage += 1; if (attemptsInStage >= stage.attempts) { stageIndex += 1; attemptsInStage = 0; if (stageIndex >= stages.length) { stopped = true; return; } } } } catch (_) { attemptsInStage += 1; } if (!stopped) { window.setTimeout(tick, stages[stageIndex].interval); } }; window.setTimeout(tick, stages[0].interval); } document.addEventListener("click", (event) => { const action = event.target.closest("[data-action]")?.dataset.action; if (action === "fake-close") showToast("Close clicked. The download window is emotionally attached.", "warning"); if (action === "minimize") showToast("Minimize clicked. WarpBox refuses to disappear quietly."); if (action === "toggle-fit") { document.body.classList.toggle("fit-window"); showToast("Maximize clicked. The window is doing its best."); } const contextAction = event.target.closest("[data-context-action]")?.dataset.contextAction; if (contextAction && contextFile) { event.preventDefault(); const item = contextFile; closeContextMenu(); if (contextAction === "preview") previewFile(item); if (contextAction === "download") downloadFile(item); if (contextAction === "properties") showProperties(item); return; } if (!event.target.closest("#box-context-menu")) closeContextMenu(); }); document.querySelectorAll(".box-file").forEach((item) => { item.addEventListener("click", (event) => { if (item.getAttribute("aria-disabled") === "true") { event.preventDefault(); showToast(zipOnly ? "Individual file downloads are disabled for one-time boxes. Use Download Zip." : "This file is not ready yet.", "warning"); return; } setTimeout(refreshBoxStatus, 900); }); item.addEventListener("contextmenu", (event) => { event.preventDefault(); showContextMenu(item, event.clientX, event.clientY); }); }); boxAddress?.addEventListener("click", async () => { try { await navigator.clipboard.writeText(window.location.href); showToast("Current box URL copied."); } catch (_) { openPopup("Copy box URL", `

Clipboard access failed. Copy this URL manually.

`); } }); docPopupClose?.addEventListener("click", closePopup); modalBackdrop?.addEventListener("click", closePopup); document.addEventListener("keydown", (event) => { if (event.key === "Escape") { closePopup(); closeContextMenu(); } }); updateExpiryCountdown(); setInterval(updateExpiryCountdown, 1000); if (boxPanel) { const pollMS = Number.parseInt(boxPanel.dataset.pollMs, 10) || 5000; startStagedPolling(pollMS); }