From db1b2315b78d2b358fa8b375ae0216c408ed097e Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Thu, 9 Apr 2026 17:13:03 +0900 Subject: image viewer --- src/App.svelte | 183 +++++++++++++-------------------- src/lib/components/ImageList.svelte | 52 ++++++++++ src/lib/components/ImagePreview.svelte | 40 +++++++ src/lib/flashair/client.ts | 39 +++++++ 4 files changed, 203 insertions(+), 111 deletions(-) create mode 100644 src/lib/components/ImageList.svelte create mode 100644 src/lib/components/ImagePreview.svelte (limited to 'src') diff --git a/src/App.svelte b/src/App.svelte index bcac7cb..ea161bf 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,9 +1,11 @@ -
- - -
- - +
+ +
{#if loading} -
+
{:else if error} - + + +
+
+ + + +
+
+ +
+ + + +
diff --git a/src/lib/components/ImageList.svelte b/src/lib/components/ImageList.svelte new file mode 100644 index 0000000..7e6f836 --- /dev/null +++ b/src/lib/components/ImageList.svelte @@ -0,0 +1,52 @@ + + +
+ {#if images.length === 0} +
+

No images found

+
+ {:else} +
    + {#each images as file (file.path)} + {@const thumbUrl = flashair.thumbnailUrl(file.path)} + {@const isSelected = file.path === selectedPath} +
  • + +
  • + {/each} +
+ {/if} +
diff --git a/src/lib/components/ImagePreview.svelte b/src/lib/components/ImagePreview.svelte new file mode 100644 index 0000000..82115e7 --- /dev/null +++ b/src/lib/components/ImagePreview.svelte @@ -0,0 +1,40 @@ + + +
+ {#if file === undefined || imageUrl === undefined} +
+

Select a photo to preview

+
+ {:else} + {#key file.path} + {#if !imageLoaded} + + {/if} + {file.filename} { imageLoaded = true; }} + /> + {/key} + {/if} +
diff --git a/src/lib/flashair/client.ts b/src/lib/flashair/client.ts index 4e3a567..978eb60 100644 --- a/src/lib/flashair/client.ts +++ b/src/lib/flashair/client.ts @@ -77,6 +77,9 @@ function parseFileList(text: string): FlashAirFileEntry[] { /** Known image extensions the FlashAir thumbnail.cgi supports (JPEG only). */ const JPEG_EXTENSIONS = new Set(['.jpg', '.jpeg']); +/** Regex matching common image file extensions. */ +const IMAGE_EXTENSIONS = /\.(jpe?g|png|bmp|gif)$/i; + function isJpeg(filename: string): boolean { const ext = filename.slice(filename.lastIndexOf('.')).toLowerCase(); return JPEG_EXTENSIONS.has(ext); @@ -193,6 +196,42 @@ export const flashair = { return { blob, meta }; }, + /** + * Recursively list all image files on the card starting from a root directory. + * Returns files sorted by date, newest first. + */ + async listAllImages(rootDir: string): Promise { + const allImages: FlashAirFileEntry[] = []; + const dirsToVisit: string[] = [rootDir]; + + while (dirsToVisit.length > 0) { + const dir = dirsToVisit.pop(); + if (dir === undefined) break; + + let entries: FlashAirFileEntry[]; + try { + entries = await flashair.listFiles(dir); + } catch { + continue; + } + + for (const entry of entries) { + if (entry.isDirectory) { + dirsToVisit.push(entry.path); + } else if (IMAGE_EXTENSIONS.test(entry.filename)) { + allImages.push(entry); + } + } + } + + allImages.sort((a, b) => { + const dateCompare = b.rawDate - a.rawDate; + if (dateCompare !== 0) return dateCompare; + return b.rawTime - a.rawTime; + }); + + return allImages; + }, } as const; export type { FlashAirFileEntry, ThumbnailMeta } from './types'; -- cgit v1.2.3