Files
overlord/Overlord-Server/public/assets/filebrowser-preview-modal.js
T
2026-06-03 21:04:14 -05:00

172 lines
6.5 KiB
JavaScript

import {
PREVIEW_IMAGE_EXTS,
PREVIEW_MAX_BYTES,
escapeHtml,
formatBytes,
getFileExt,
getPreviewMimeType,
} from "./filebrowser-utils.js";
export function createFilePreviewModal({
clientId,
notifyToast,
onDownload,
beforeFileRead,
}) {
const filePreviewModal = document.getElementById("file-preview-modal");
const previewFileNameEl = document.getElementById("preview-file-name");
const previewContent = document.getElementById("preview-content");
const previewCloseBtn = document.getElementById("preview-close-btn");
const previewDownloadBtn = document.getElementById("preview-download-btn");
const previewStatusEl = document.getElementById("preview-status");
let currentPreviewBlobUrl = null;
let currentPreviewPath = null;
function closePreview() {
if (filePreviewModal) filePreviewModal.classList.remove("show");
if (currentPreviewBlobUrl) {
URL.revokeObjectURL(currentPreviewBlobUrl);
currentPreviewBlobUrl = null;
}
if (previewContent) previewContent.innerHTML = "";
if (previewStatusEl) previewStatusEl.textContent = "";
currentPreviewPath = null;
}
async function openFilePreview(path, knownSize) {
const fileName = path.split(/[\/\\]/).pop() || "";
const mimeType = getPreviewMimeType(fileName);
if (!mimeType) return;
if (typeof knownSize === "number" && knownSize > PREVIEW_MAX_BYTES) {
notifyToast(`File too large to preview (${formatBytes(knownSize)})`, "info", 4000);
return;
}
if (beforeFileRead && !(await beforeFileRead(path, "preview file"))) {
return;
}
currentPreviewPath = path;
if (previewFileNameEl) previewFileNameEl.textContent = fileName;
if (previewContent) previewContent.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin text-slate-400 text-2xl"></i>';
if (previewStatusEl) previewStatusEl.textContent = "Loading...";
if (filePreviewModal) filePreviewModal.classList.add("show");
try {
const requestRes = await fetch("/api/file/download/request", {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ clientId, path }),
});
if (!requestRes.ok) {
const text = await requestRes.text();
if (currentPreviewPath !== path) return;
if (previewContent) previewContent.innerHTML = `<div class="text-red-400 text-sm text-center p-6"><i class="fa-solid fa-exclamation-triangle mr-2"></i>${escapeHtml(text || "Failed to load preview")}</div>`;
if (previewStatusEl) previewStatusEl.textContent = "";
return;
}
const requestData = await requestRes.json();
const downloadUrl = typeof requestData?.downloadUrl === "string"
? requestData.downloadUrl
: (requestData?.downloadId
? `/api/file/download/${encodeURIComponent(requestData.downloadId)}`
: "");
if (!downloadUrl) {
if (currentPreviewPath !== path) return;
if (previewContent) previewContent.innerHTML = `<div class="text-red-400 text-sm text-center p-6"><i class="fa-solid fa-exclamation-triangle mr-2"></i>Failed to load preview</div>`;
if (previewStatusEl) previewStatusEl.textContent = "";
return;
}
const res = await fetch(downloadUrl, {
method: "GET",
credentials: "include",
});
if (!res.ok) {
const text = await res.text();
if (currentPreviewPath !== path) return;
if (previewContent) previewContent.innerHTML = `<div class="text-red-400 text-sm text-center p-6"><i class="fa-solid fa-exclamation-triangle mr-2"></i>${escapeHtml(text || "Failed to load preview")}</div>`;
if (previewStatusEl) previewStatusEl.textContent = "";
return;
}
const contentLength = Number(res.headers.get("Content-Length") || 0);
if (contentLength > PREVIEW_MAX_BYTES) {
if (currentPreviewPath !== path) return;
if (previewContent) previewContent.innerHTML = `<div class="text-slate-400 text-sm text-center p-6"><i class="fa-solid fa-file mr-2"></i>File too large to preview (${escapeHtml(formatBytes(contentLength))})</div>`;
if (previewStatusEl) previewStatusEl.textContent = "";
return;
}
const arrayBuffer = await res.arrayBuffer();
if (currentPreviewPath !== path) return;
if (arrayBuffer.byteLength > PREVIEW_MAX_BYTES) {
if (previewContent) previewContent.innerHTML = `<div class="text-slate-400 text-sm text-center p-6"><i class="fa-solid fa-file mr-2"></i>File too large to preview (${escapeHtml(formatBytes(arrayBuffer.byteLength))})</div>`;
if (previewStatusEl) previewStatusEl.textContent = "";
return;
}
if (currentPreviewBlobUrl) {
URL.revokeObjectURL(currentPreviewBlobUrl);
currentPreviewBlobUrl = null;
}
const blob = new Blob([arrayBuffer], { type: mimeType });
currentPreviewBlobUrl = URL.createObjectURL(blob);
if (previewContent) {
previewContent.innerHTML = "";
if (PREVIEW_IMAGE_EXTS.has(getFileExt(fileName))) {
const img = document.createElement("img");
img.src = currentPreviewBlobUrl;
img.alt = "";
previewContent.appendChild(img);
} else {
const obj = document.createElement("object");
obj.data = currentPreviewBlobUrl;
obj.type = "application/pdf";
previewContent.appendChild(obj);
}
}
if (previewStatusEl) previewStatusEl.textContent = "";
} catch (err) {
if (currentPreviewPath !== path) return;
if (previewContent) previewContent.innerHTML = `<div class="text-red-400 text-sm text-center p-6"><i class="fa-solid fa-exclamation-triangle mr-2"></i>${escapeHtml(err.message || "Failed to load preview")}</div>`;
if (previewStatusEl) previewStatusEl.textContent = "";
}
}
function bindControls() {
if (previewCloseBtn) previewCloseBtn.addEventListener("click", closePreview);
if (previewDownloadBtn) previewDownloadBtn.addEventListener("click", () => {
if (currentPreviewPath) onDownload(currentPreviewPath);
});
if (filePreviewModal) {
filePreviewModal.addEventListener("click", (e) => {
if (e.target === filePreviewModal) closePreview();
});
}
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && filePreviewModal && filePreviewModal.classList.contains("show")) {
closePreview();
}
});
}
return {
bindControls,
closePreview,
openFilePreview,
};
}