diff --git a/package-lock.json b/package-lock.json index 2f2d8f3..0c1c7eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,8 @@ "redis": "^4.7.0", "sharp": "^0.34.2", "steamapi": "^3.0.11", + "svelte-medium-image-zoom": "^0.2.6", + "svelte-portal": "^2.2.1", "svelte-tiptap": "^2.1.0", "svgo": "^3.3.2", "tinyduration": "^3.3.1", @@ -7832,6 +7834,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/svelte-medium-image-zoom": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/svelte-medium-image-zoom/-/svelte-medium-image-zoom-0.2.6.tgz", + "integrity": "sha512-PJAm9R8IgzcMmUEdCmLMYtSU6Qtzr0nh5OTKrQi/RTOrcQ/tRb7w+AGVvVyFKaR40fD5cr8986EOxjBSdD3vfg==", + "peerDependencies": { + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-portal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/svelte-portal/-/svelte-portal-2.2.1.tgz", + "integrity": "sha512-uF7is5sM4aq5iN7QF/67XLnTUvQCf2iiG/B1BHTqLwYVY1dsVmTeXZ/LeEyU6dLjApOQdbEG9lkqHzxiQtOLEQ==" + }, "node_modules/svelte-preprocess": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", diff --git a/package.json b/package.json index 5d67226..3bc6a95 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,8 @@ "redis": "^4.7.0", "sharp": "^0.34.2", "steamapi": "^3.0.11", + "svelte-medium-image-zoom": "^0.2.6", + "svelte-portal": "^2.2.1", "svelte-tiptap": "^2.1.0", "svgo": "^3.3.2", "tinyduration": "^3.3.1", diff --git a/src/routes/photos/p/[id]/+page.svelte b/src/routes/photos/p/[id]/+page.svelte index 0f1df28..f3ba536 100644 --- a/src/routes/photos/p/[id]/+page.svelte +++ b/src/routes/photos/p/[id]/+page.svelte @@ -6,6 +6,8 @@ import { onMount } from 'svelte' import type { PageData } from './$types' import { isAlbum } from '$lib/types/photos' + import Zoom from 'svelte-medium-image-zoom' + import 'svelte-medium-image-zoom/dist/styles.css' let { data }: { data: PageData } = $props() @@ -13,8 +15,6 @@ const error = $derived(data.error) const photoItems = $derived(data.photoItems || []) const currentPhotoId = $derived(data.currentPhotoId) - - let showModal = $state(false) const pageUrl = $derived($page.url.href) @@ -69,77 +69,42 @@ photo?.exifData && typeof photo.exifData === 'object' ? photo.exifData : null ) - // Get adjacent photos for filmstrip - always show 5 when possible - const filmstripItems = $derived(() => { - if (!photoItems.length || !currentPhotoId) return [] + // Get previous and next photos (excluding albums) + const adjacentPhotos = $derived(() => { + if (!photoItems.length || !currentPhotoId) return { prev: null, next: null } - const currentIndex = photoItems.findIndex(item => item.id === currentPhotoId) - if (currentIndex === -1) return [] + // Filter out albums - we only want photos + const photosOnly = photoItems.filter(item => !isAlbum(item)) + const currentIndex = photosOnly.findIndex(item => item.id === currentPhotoId) - const targetCount = 5 - const halfCount = Math.floor(targetCount / 2) + if (currentIndex === -1) return { prev: null, next: null } - let start = currentIndex - halfCount - let end = currentIndex + halfCount + 1 - - // Adjust if we're near the beginning - if (start < 0) { - end = Math.min(photoItems.length, end - start) - start = 0 + return { + prev: currentIndex > 0 ? photosOnly[currentIndex - 1] : null, + next: currentIndex < photosOnly.length - 1 ? photosOnly[currentIndex + 1] : null } - - // Adjust if we're near the end - if (end > photoItems.length) { - start = Math.max(0, start - (end - photoItems.length)) - end = photoItems.length - } - - // Ensure we always get up to targetCount items if available - const itemsCount = end - start - if (itemsCount < targetCount && photoItems.length >= targetCount) { - if (start === 0) { - end = Math.min(targetCount, photoItems.length) - } else { - start = Math.max(0, photoItems.length - targetCount) - } - } - - return photoItems.slice(start, end) }) - // Handle filmstrip navigation - function handleFilmstripClick(item: any) { - if (isAlbum(item)) { - goto(`/photos/${item.slug}`) - } else { - const photoId = item.id.replace('photo-', '') - goto(`/photos/p/${photoId}`) - } - } - - // Modal handlers - function openModal() { - showModal = true - document.body.style.overflow = 'hidden' - } - - function closeModal() { - showModal = false - document.body.style.overflow = '' + // Handle photo navigation + function navigateToPhoto(item: any) { + if (!item) return + const photoId = item.id.replace('photo-', '') + goto(`/photos/p/${photoId}`) } function handleKeydown(e: KeyboardEvent) { - if (e.key === 'Escape' && showModal) { - closeModal() + // Arrow key navigation for photos + if (e.key === 'ArrowLeft' && adjacentPhotos().prev) { + navigateToPhoto(adjacentPhotos().prev) + } else if (e.key === 'ArrowRight' && adjacentPhotos().next) { + navigateToPhoto(adjacentPhotos().next) } } // Set up keyboard listener $effect(() => { - if (showModal) { - window.addEventListener('keydown', handleKeydown) - return () => window.removeEventListener('keydown', handleKeydown) - } + window.addEventListener('keydown', handleKeydown) + return () => window.removeEventListener('keydown', handleKeydown) }) @@ -176,9 +141,50 @@ {:else if photo}