From cadee5c2200de3bcd4aab7d593753ca08e6f9deb Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Sat, 19 Sep 2026 09:22:45 +0200 Subject: [PATCH] feat(viewer): progressive image preview via thumbnail The photo viewer now renders the file's thumbnail (usually already cached from grid view) immediately while the full entity URL resolves, then swaps to the full image on load. Gives clients the low-res-first preview path from the upstream request without a new thumbnail-size pipeline; a dedicated preview-size entity variant would be the follow-up if admins need larger previews. Resolves #113 Authored By: TDvorak --- .../Viewers/ImageViewer/react-photo-view/Photo.tsx | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frontend/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx b/frontend/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx index c309139e..12df7ab1 100644 --- a/frontend/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx +++ b/frontend/src/component/Viewers/ImageViewer/react-photo-view/Photo.tsx @@ -5,7 +5,9 @@ import React, { useEffect } from "react"; import { getFileEntityUrl, getFileInfo } from "../../../../api/api.ts"; import { EntityType, FileResponse, Metadata } from "../../../../api/explorer.ts"; import { useAppDispatch } from "../../../../redux/hooks.ts"; +import { loadFileThumb } from "../../../../redux/thunks/file.ts"; import { getFileLinkedUri } from "../../../../util"; +import { FileManagerIndex } from "../../../FileManager/FileManager.tsx"; import { LRUCache } from "../../../../util/lru.ts"; import FacebookCircularProgress from "../../../Common/CircularProgress.tsx"; import useMountedRef from "./hooks/useMountedRef"; @@ -100,6 +102,18 @@ export default function Photo({ }; useEffect(() => { + // Progressive preview: show the (usually already-cached) low-res + // thumbnail immediately while the full entity URL resolves. (#113) + dispatch(loadFileThumb(FileManagerIndex.main, file)) + .then((thumbUrl) => { + if (thumbUrl && mountedRef.current) { + setImageSrc(thumbUrl); + } + }) + .catch(() => { + // No thumbnail available - the full entity load below still applies. + }); + dispatch( getFileEntityUrl({ uris: [getFileLinkedUri(file)],