diff options
| author | Adam Malczewski <[email protected]> | 2026-04-09 19:28:44 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-04-09 19:28:44 +0900 |
| commit | 1bfd3e50842fdd511fc89d2b5892f8d440914cca (patch) | |
| tree | e8fec173a185b91ef28681e8e7030cae46bd37ee /src/lib/components | |
| parent | 2a8ecc585f6dc5c3c5f98ba3821d899807d813f8 (diff) | |
| download | flashair-speedsync-1bfd3e50842fdd511fc89d2b5892f8d440914cca.tar.gz flashair-speedsync-1bfd3e50842fdd511fc89d2b5892f8d440914cca.zip | |
caching of images
Diffstat (limited to 'src/lib/components')
| -rw-r--r-- | src/lib/components/CachedThumbnail.svelte | 67 | ||||
| -rw-r--r-- | src/lib/components/ImageList.svelte | 8 | ||||
| -rw-r--r-- | src/lib/components/ImagePreview.svelte | 31 |
3 files changed, 100 insertions, 6 deletions
diff --git a/src/lib/components/CachedThumbnail.svelte b/src/lib/components/CachedThumbnail.svelte new file mode 100644 index 0000000..f0fe8bf --- /dev/null +++ b/src/lib/components/CachedThumbnail.svelte @@ -0,0 +1,67 @@ +<script lang="ts"> + import { flashair } from '../flashair'; + import { imageCache } from '../cache'; + + interface Props { + path: string; + alt: string; + } + + let { path, alt }: Props = $props(); + + let blobUrl = $state<string | undefined>(undefined); + let rawBlobUrl: string | undefined; + + $effect(() => { + const currentPath = path; + blobUrl = undefined; + + void loadThumbnail(currentPath); + + return () => { + if (rawBlobUrl !== undefined) { + URL.revokeObjectURL(rawBlobUrl); + rawBlobUrl = undefined; + } + }; + }); + + async function loadThumbnail(filePath: string) { + // Try cache first + const cached = await imageCache.get('thumbnail', filePath); + if (cached !== undefined) { + const url = URL.createObjectURL(cached.blob); + rawBlobUrl = url; + blobUrl = url; + return; + } + + // Fetch from card + const thumbUrl = flashair.thumbnailUrl(filePath); + if (thumbUrl === undefined) return; + + try { + const { blob, meta } = await flashair.fetchThumbnail(filePath); + // Store in cache (fire-and-forget) + void imageCache.put('thumbnail', filePath, blob, meta); + const url = URL.createObjectURL(blob); + rawBlobUrl = url; + blobUrl = url; + } catch { + // Thumbnail fetch failed — show placeholder + } + } +</script> + +{#if blobUrl !== undefined} + <img + src={blobUrl} + {alt} + class="w-full aspect-square rounded object-cover" + draggable="false" + /> +{:else} + <div class="w-full aspect-square rounded bg-base-300 flex items-center justify-center"> + <span class="loading loading-spinner loading-sm"></span> + </div> +{/if} diff --git a/src/lib/components/ImageList.svelte b/src/lib/components/ImageList.svelte index f338670..532cd3b 100644 --- a/src/lib/components/ImageList.svelte +++ b/src/lib/components/ImageList.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import { flashair } from '../flashair'; import type { FlashAirFileEntry } from '../flashair'; + import CachedThumbnail from './CachedThumbnail.svelte'; interface Props { images: FlashAirFileEntry[]; @@ -29,12 +30,7 @@ onclick={() => onSelect(file)} > {#if thumbUrl} - <img - src={thumbUrl} - alt={file.filename} - class="w-full aspect-square rounded object-cover" - loading="lazy" - /> + <CachedThumbnail path={file.path} alt={file.filename} /> {:else} <div class="w-full aspect-square rounded bg-base-300 flex items-center justify-center"> <span class="text-lg">🖼️</span> diff --git a/src/lib/components/ImagePreview.svelte b/src/lib/components/ImagePreview.svelte index 08be8ac..4a59d13 100644 --- a/src/lib/components/ImagePreview.svelte +++ b/src/lib/components/ImagePreview.svelte @@ -1,6 +1,7 @@ <script lang="ts"> import { flashair } from '../flashair'; import type { FlashAirFileEntry } from '../flashair'; + import { imageCache } from '../cache'; interface Props { file: FlashAirFileEntry | undefined; @@ -277,8 +278,23 @@ const url = flashair.thumbnailUrl(entry.path); if (url === undefined) return; + // Try cache first + const cached = await imageCache.get('thumbnail', entry.path); + if (cached !== undefined) { + const blobUrl = URL.createObjectURL(cached.blob); + rawThumbnailUrl = blobUrl; + thumbnailBlobUrl = blobUrl; + + if (cached.meta !== undefined && cached.meta.width !== undefined && cached.meta.height !== undefined && cached.meta.width > 0 && cached.meta.height > 0) { + imageAspectRatio = `${String(cached.meta.width)} / ${String(cached.meta.height)}`; + } + return; + } + try { const { blob, meta } = await flashair.fetchThumbnail(entry.path); + // Store in cache (fire-and-forget) + void imageCache.put('thumbnail', entry.path, blob, meta); const blobUrl = URL.createObjectURL(blob); rawThumbnailUrl = blobUrl; thumbnailBlobUrl = blobUrl; @@ -310,6 +326,17 @@ progress = 0; loadError = undefined; + // Try cache first + const cached = await imageCache.get('full', entry.path); + if (cached !== undefined && !abort.signal.aborted) { + const objectUrl = URL.createObjectURL(cached.blob); + rawObjectUrl = objectUrl; + fullObjectUrl = objectUrl; + progress = 1; + downloading = false; + return; + } + const url = flashair.fileUrl(entry.path); const totalBytes = entry.size; @@ -323,6 +350,8 @@ if (reader === undefined) { const blob = await res.blob(); if (abort.signal.aborted) return; + // Store in cache (fire-and-forget) + void imageCache.put('full', entry.path, blob); const objectUrl = URL.createObjectURL(blob); rawObjectUrl = objectUrl; fullObjectUrl = objectUrl; @@ -345,6 +374,8 @@ if (abort.signal.aborted) return; const blob = new Blob(chunks); + // Store in cache (fire-and-forget) + void imageCache.put('full', entry.path, blob); const objectUrl = URL.createObjectURL(blob); rawObjectUrl = objectUrl; fullObjectUrl = objectUrl; |
