FixThumbnailRegeneration (#6134)

This commit is contained in:
Anthony Stirling
2026-04-22 14:33:38 +01:00
committed by GitHub
parent 97e2dc2c68
commit d71a2c3d81

View File

@@ -3,6 +3,7 @@ import { StirlingFileStub } from "@app/types/fileContext";
import { useIndexedDB } from "@app/contexts/IndexedDBContext";
import { generateThumbnailForFile } from "@app/utils/thumbnailUtils";
import { FileId } from "@app/types/fileContext";
import { useFileManagement } from "@app/contexts/FileContext";
/**
* Hook for IndexedDB-aware thumbnail loading
@@ -17,6 +18,7 @@ export function useIndexedDBThumbnail(
const [thumb, setThumb] = useState<string | null>(null);
const [generating, setGenerating] = useState(false);
const indexedDB = useIndexedDB();
const { updateStirlingFileStub } = useFileManagement();
useEffect(() => {
let cancelled = false;
@@ -27,58 +29,59 @@ export function useIndexedDBThumbnail(
return;
}
// First priority: use stored thumbnail
// Tier 1: stored thumbnail on the stub.
if (file.thumbnailUrl) {
setThumb(file.thumbnailUrl);
return;
}
// Second priority: generate thumbnail for files under 100MB
if (file.size < 100 * 1024 * 1024 && !generating) {
setGenerating(true);
try {
let fileObject: File;
// Try to load file from IndexedDB using new context
if (file.id && indexedDB) {
const loadedFile = await indexedDB.loadFile(file.id as FileId);
if (!loadedFile) {
throw new Error("File not found in IndexedDB");
}
fileObject = loadedFile;
} else {
throw new Error(
"File ID not available or IndexedDB context not available",
);
}
// Use the universal thumbnail generator
const thumbnail = await generateThumbnailForFile(fileObject);
if (!cancelled) {
setThumb(thumbnail);
// Save thumbnail to IndexedDB for persistence
if (file.id && indexedDB && thumbnail) {
try {
await indexedDB.updateThumbnail(file.id as FileId, thumbnail);
} catch (error) {
console.warn("Failed to save thumbnail to IndexedDB:", error);
}
}
}
} catch (error) {
console.warn(
"Failed to generate thumbnail for file",
file.name,
error,
);
if (!cancelled) setThumb(null);
} finally {
if (!cancelled) setGenerating(false);
}
} else {
// Large files - no thumbnail
// >=100MB files are skipped entirely — no thumbnail.
if (file.size >= 100 * 1024 * 1024) {
setThumb(null);
return;
}
// Tier 2: generate on demand from the File bytes in IndexedDB.
// Re-entry guard is handled by the effect's cleanup/cancelled pattern —
// `generating` is NOT in the deps, so setGenerating() does not trigger
// the effect to re-run and cancel itself mid-flight.
setGenerating(true);
try {
if (!file.id || !indexedDB) {
throw new Error(
`missing prerequisite fileId=${file.id} indexedDB=${Boolean(indexedDB)}`,
);
}
const loadedFile = await indexedDB.loadFile(file.id as FileId);
if (!loadedFile) {
throw new Error("not in IndexedDB (likely remote-only stub)");
}
const thumbnail = await generateThumbnailForFile(loadedFile);
if (cancelled) return;
setThumb(thumbnail);
if (file.id && indexedDB && thumbnail) {
try {
await indexedDB.updateThumbnail(file.id as FileId, thumbnail);
// Also sync the in-memory stub so subsequent re-mounts hit tier 1
// instead of regenerating. IndexedDB persistence alone only helps
// the next page load; the current session reads file.thumbnailUrl
// from the FileContext stub.
updateStirlingFileStub(file.id as FileId, {
thumbnailUrl: thumbnail,
});
} catch (error) {
console.warn("Failed to persist thumbnail:", error);
}
}
} catch (error) {
console.warn("Failed to generate thumbnail for file", file.name, error);
if (!cancelled) setThumb(null);
} finally {
if (!cancelled) setGenerating(false);
}
}
@@ -86,7 +89,11 @@ export function useIndexedDBThumbnail(
return () => {
cancelled = true;
};
}, [file, file?.thumbnailUrl, file?.id, indexedDB, generating]);
// `generating` is intentionally NOT in the deps — it's an internal flag
// set by this effect, and including it caused the effect to cancel
// itself mid-flight (orphaning the render and leaving generating=true
// stuck forever).
}, [file, file?.thumbnailUrl, file?.id, indexedDB, updateStirlingFileStub]);
return { thumbnail: thumb, isGenerating: generating };
}