From 1c98dae3d2f87b2fc6c7b0fed02e6296014eb946 Mon Sep 17 00:00:00 2001 From: stefiosif Date: Wed, 24 Jun 2026 19:22:48 +0300 Subject: [PATCH] feat(frontend): add image preview and prev/next navigation --- .../src/lib/components/FilePreview.svelte | 114 +++++++++++++++--- .../src/lib/components/ImagePreview.svelte | 5 + frontend/src/routes/+page.svelte | 30 ++++- 3 files changed, 134 insertions(+), 15 deletions(-) create mode 100644 frontend/src/lib/components/ImagePreview.svelte diff --git a/frontend/src/lib/components/FilePreview.svelte b/frontend/src/lib/components/FilePreview.svelte index aead4a7..f119eaa 100644 --- a/frontend/src/lib/components/FilePreview.svelte +++ b/frontend/src/lib/components/FilePreview.svelte @@ -2,16 +2,45 @@ import type { FileRecord } from '$lib/types'; import { fade } from 'svelte/transition'; import VideoPreview from './VideoPreview.svelte'; + import ImagePreview from './ImagePreview.svelte'; + + let { + file, + onclose, + onprev, + onnext, + hasPrev = false, + hasNext = false + }: { + file: FileRecord; + onclose: () => void; + onprev?: () => void; + onnext?: () => void; + hasPrev?: boolean; + hasNext?: boolean; + } = $props(); - let { file, onclose }: { file: FileRecord; onclose: () => void } = $props(); const isVideo = $derived(file.fileType.includes('video')); - + const isImage = $derived(file.fileType.includes('image')); - { - if (e.key === 'Escape') onclose(); - }} -/> + $effect(() => { + function onKeydown(e: KeyboardEvent) { + if (e.key === 'Escape') { + onclose(); + } else if (e.key === 'ArrowLeft' && hasPrev) { + e.preventDefault(); + e.stopPropagation(); + onprev?.(); + } else if (e.key === 'ArrowRight' && hasNext) { + e.preventDefault(); + e.stopPropagation(); + onnext?.(); + } + } + window.addEventListener('keydown', onKeydown, { capture: true }); + return () => window.removeEventListener('keydown', onKeydown, { capture: true }); + }); + @@ -20,12 +49,73 @@ onclick={onclose} transition:fade={{ duration: 150 }} > + {#if hasPrev} + + {/if} + + {#if hasNext} + + {/if} +
e.stopPropagation()} > - {#if isVideo} - + {#key file.id} + {#if isVideo} + + {:else if isImage} + + {:else} +
+ No preview available +
+ {/if} + {/key} + + {#if isVideo || isImage}
@@ -52,10 +142,6 @@
- {:else} -
- No preview available -
{/if}
diff --git a/frontend/src/lib/components/ImagePreview.svelte b/frontend/src/lib/components/ImagePreview.svelte new file mode 100644 index 0000000..7668d72 --- /dev/null +++ b/frontend/src/lib/components/ImagePreview.svelte @@ -0,0 +1,5 @@ + + + diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index 91e1edc..822ddcf 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -29,6 +29,27 @@ fileRecords.filter((f) => f.name.toLowerCase().includes(search.toLowerCase())) ); + let previewIndex = $derived.by(() => { + if (!previewFile) { + return -1; + } + const id = previewFile.id; + return filteredFileRecords.findIndex((f) => f.id === id); + }); + let hasPrev = $derived(previewIndex > 0); + let hasNext = $derived(previewIndex >= 0 && previewIndex < filteredFileRecords.length - 1); + + function goPrev() { + if (hasPrev) { + previewFile = filteredFileRecords[previewIndex - 1]; + } + } + function goNext() { + if (hasNext) { + previewFile = filteredFileRecords[previewIndex + 1]; + } + } + let fileInput: HTMLInputElement; async function handleUpload(event: Event) { @@ -214,5 +235,12 @@ {#if previewFile} - (previewFile = null)} /> + (previewFile = null)} + onprev={goPrev} + onnext={goNext} + {hasPrev} + {hasNext} + /> {/if}