modularize build and file browser and add more precautions for downloading from file transfer.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
PREVIEW_IMAGE_EXTS,
|
||||
PREVIEW_MAX_BYTES,
|
||||
escapeHtml,
|
||||
formatBytes,
|
||||
getFileExt,
|
||||
getPreviewMimeType,
|
||||
} from "./filebrowser-utils.js";
|
||||
|
||||
export function createFilePreviewModal({
|
||||
clientId,
|
||||
notifyToast,
|
||||
onDownload,
|
||||
}) {
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user