From c0a08598dd0ad409be16ddfd767a73a765931f81 Mon Sep 17 00:00:00 2001 From: Reece Browne Date: Thu, 21 Aug 2025 17:09:51 +0100 Subject: [PATCH] Fix image thumbnail persistance --- frontend/src/contexts/IndexedDBContext.tsx | 8 +++++++- frontend/src/contexts/file/fileActions.ts | 18 +++++++++--------- frontend/src/hooks/useIndexedDBThumbnail.ts | 9 +++++++++ frontend/src/utils/thumbnailUtils.ts | 9 +++++++-- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/frontend/src/contexts/IndexedDBContext.tsx b/frontend/src/contexts/IndexedDBContext.tsx index f713c85aa..a09070403 100644 --- a/frontend/src/contexts/IndexedDBContext.tsx +++ b/frontend/src/contexts/IndexedDBContext.tsx @@ -25,6 +25,7 @@ interface IndexedDBContextValue { // Utilities getStorageStats: () => Promise<{ used: number; available: number; fileCount: number }>; + updateThumbnail: (fileId: FileId, thumbnail: string) => Promise; } const IndexedDBContext = createContext(null); @@ -174,6 +175,10 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) { return await fileStorage.getStorageStats(); }, []); + const updateThumbnail = useCallback(async (fileId: FileId, thumbnail: string): Promise => { + return await fileStorage.updateThumbnail(fileId, thumbnail); + }, []); + const value: IndexedDBContextValue = { saveFile, loadFile, @@ -182,7 +187,8 @@ export function IndexedDBProvider({ children }: IndexedDBProviderProps) { loadAllMetadata, deleteMultiple, clearAll, - getStorageStats + getStorageStats, + updateThumbnail }; return ( diff --git a/frontend/src/contexts/file/fileActions.ts b/frontend/src/contexts/file/fileActions.ts index 3c272e3af..948b4f011 100644 --- a/frontend/src/contexts/file/fileActions.ts +++ b/frontend/src/contexts/file/fileActions.ts @@ -238,15 +238,6 @@ export async function addFiles( const record = toFileRecord(file, fileId); - // Restore metadata from storage - if (metadata.thumbnail) { - record.thumbnailUrl = metadata.thumbnail; - // Track blob URLs for cleanup (images return blob URLs that need revocation) - if (metadata.thumbnail.startsWith('blob:')) { - lifecycleManager.trackBlobUrl(metadata.thumbnail); - } - } - // Generate processedFile metadata for stored files let pageCount: number = 1; @@ -271,6 +262,15 @@ export async function addFiles( if (DEBUG) console.log(`📄 addFiles(stored): Non-PDF file ${file.name}, no page count`); } + // Restore metadata from storage + if (metadata.thumbnail) { + record.thumbnailUrl = metadata.thumbnail; + // Track blob URLs for cleanup (images return blob URLs that need revocation) + if (metadata.thumbnail.startsWith('blob:')) { + lifecycleManager.trackBlobUrl(metadata.thumbnail); + } + } + // Create processedFile metadata with correct page count if (pageCount > 0) { record.processedFile = createProcessedFile(pageCount, metadata.thumbnail); diff --git a/frontend/src/hooks/useIndexedDBThumbnail.ts b/frontend/src/hooks/useIndexedDBThumbnail.ts index 3e2b6597a..4f0d77c0e 100644 --- a/frontend/src/hooks/useIndexedDBThumbnail.ts +++ b/frontend/src/hooks/useIndexedDBThumbnail.ts @@ -66,6 +66,15 @@ export function useIndexedDBThumbnail(file: FileMetadata | undefined | null): { 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, 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); diff --git a/frontend/src/utils/thumbnailUtils.ts b/frontend/src/utils/thumbnailUtils.ts index 7bf5a2194..e4a48f9fd 100644 --- a/frontend/src/utils/thumbnailUtils.ts +++ b/frontend/src/utils/thumbnailUtils.ts @@ -333,9 +333,14 @@ export async function generateThumbnailForFile(file: File): Promise { return generatePlaceholderThumbnail(file); } - // Handle image files - creates blob URL that needs cleanup by caller + // Handle image files - convert to data URL for persistence if (file.type.startsWith('image/')) { - return URL.createObjectURL(file); + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.onerror = () => reject(reader.error); + reader.readAsDataURL(file); + }); } // Handle PDF files