feat: add thumbnail metadata and download endpoint

- Extend `BoxFile` with thumbnail path/status fields and internal URL
- Populate `ThumbnailURL` when a thumbnail path is present during decoration
- Add `/box/:id/thumbnails/:file_id` route and handler to serve JPEG thumbnails
- Introduce thumbnail status constants to standardize processing state reportingfeat: add thumbnail metadata and download endpoint

- Extend `BoxFile` with thumbnail path/status fields and internal URL
- Populate `ThumbnailURL` when a thumbnail path is present during decoration
- Add `/box/:id/thumbnails/:file_id` route and handler to serve JPEG thumbnails
- Introduce thumbnail status constants to standardize processing state reporting
This commit is contained in:
2026-04-28 18:44:16 +03:00
parent c1489d1fbb
commit f1600faa8d
12 changed files with 400 additions and 17 deletions

View File

@@ -19,6 +19,14 @@ let selectedFiles = [];
let statusTimer = null;
let shareURL = "";
function revokePreviewURLs() {
selectedFiles.forEach((selectedFile) => {
if (selectedFile.previewURL) {
URL.revokeObjectURL(selectedFile.previewURL);
}
});
}
function formatBytes(bytes) {
const units = ["B", "KB", "MB", "GB"];
let size = bytes;
@@ -167,10 +175,11 @@ function setRowProgress(row, percent) {
function createFileRow(selectedFile) {
const row = document.createElement("div");
row.className = "upload-file-row";
row.classList.toggle("has-thumbnail", Boolean(selectedFile.previewURL));
const icon = document.createElement("img");
icon.className = "upload-file-icon";
icon.src = iconForFile(selectedFile.file);
icon.src = selectedFile.previewURL || iconForFile(selectedFile.file);
icon.alt = "";
icon.setAttribute("aria-hidden", "true");
@@ -197,8 +206,10 @@ function createFileRow(selectedFile) {
}
function updateSelectedFiles(files) {
revokePreviewURLs();
selectedFiles = Array.from(files || []).map((file) => ({
file,
previewURL: file.type.startsWith("image/") ? URL.createObjectURL(file) : "",
loaded: 0,
row: null,
uploaded: false,
@@ -438,7 +449,10 @@ if (uploadForm) {
selectedFile.boxID = box.box_id;
selectedFile.boxFile = box.files[index];
const icon = selectedFile.row.querySelector(".upload-file-icon");
if (icon && selectedFile.boxFile.icon_path) {
if (icon && selectedFile.boxFile.thumbnail_path) {
selectedFile.row.classList.add("has-thumbnail");
icon.src = selectedFile.boxFile.thumbnail_path;
} else if (icon && selectedFile.boxFile.icon_path && !selectedFile.previewURL) {
icon.src = selectedFile.boxFile.icon_path;
}
});
@@ -489,3 +503,5 @@ if (shareButton) {
}
});
}
window.addEventListener("beforeunload", revokePreviewURLs);