From d1c7a777ed25ded472813b79fe80a4fc87445d5a Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Fri, 13 Jun 2025 07:39:24 -0400 Subject: [PATCH] Phot next/prev buttons follow you --- .../photos/[albumSlug]/[photoId]/+page.svelte | 229 +++++++++++++++--- src/routes/photos/p/[id]/+page.svelte | 213 +++++++++++++--- test-db.ts | 15 ++ 3 files changed, 378 insertions(+), 79 deletions(-) create mode 100644 test-db.ts diff --git a/src/routes/photos/[albumSlug]/[photoId]/+page.svelte b/src/routes/photos/[albumSlug]/[photoId]/+page.svelte index fa2a455..aa2031f 100644 --- a/src/routes/photos/[albumSlug]/[photoId]/+page.svelte +++ b/src/routes/photos/[albumSlug]/[photoId]/+page.svelte @@ -5,6 +5,7 @@ import { generateMetaTags, generateCreativeWorkJsonLd } from '$lib/utils/metadata' import { page } from '$app/stores' import { goto } from '$app/navigation' + import { spring } from 'svelte/motion' import type { PageData } from './$types' import ArrowLeft from '$icons/arrow-left.svg' import ArrowRight from '$icons/arrow-right.svg' @@ -16,6 +17,25 @@ const navigation = $derived(data.navigation) const error = $derived(data.error) + // Hover tracking for arrow buttons + let isHoveringLeft = $state(false) + let isHoveringRight = $state(false) + + // Spring stores for smooth button movement + const leftButtonCoords = spring({ x: 0, y: 0 }, { + stiffness: 0.3, + damping: 0.8 + }) + + const rightButtonCoords = spring({ x: 0, y: 0 }, { + stiffness: 0.3, + damping: 0.8 + }) + + // Default button positions (will be set once photo loads) + let defaultLeftX = 0 + let defaultRightX = 0 + const pageUrl = $derived($page.url.href) @@ -63,6 +83,122 @@ }) : null ) + + // Set default button positions when component mounts + $effect(() => { + if (!photo) return + + // Wait for DOM to update and image to load + const checkAndSetPositions = () => { + const pageContainer = document.querySelector('.photo-page') as HTMLElement + const photoImage = pageContainer?.querySelector('.photo-content-wrapper img') as HTMLElement + + if (photoImage && photoImage.complete) { + const imageRect = photoImage.getBoundingClientRect() + const pageRect = pageContainer.getBoundingClientRect() + + // Calculate default positions relative to the image + // Add 24px (half button width) since we're using translate(-50%, -50%) + defaultLeftX = (imageRect.left - pageRect.left) - 24 - 16 // half button width + gap + defaultRightX = (imageRect.right - pageRect.left) + 24 + 16 // half button width + gap + + // Set initial positions at the vertical center of the image + const centerY = (imageRect.top - pageRect.top) + (imageRect.height / 2) + leftButtonCoords.set({ x: defaultLeftX, y: centerY }, { hard: true }) + rightButtonCoords.set({ x: defaultRightX, y: centerY }, { hard: true }) + + // Check if mouse is already in a hover zone + checkInitialMousePosition(pageContainer, imageRect, pageRect) + } else { + // If image not loaded yet, try again + setTimeout(checkAndSetPositions, 50) + } + } + + checkAndSetPositions() + }) + + // We'll just remove the initial check for now + function checkInitialMousePosition(pageContainer: HTMLElement, imageRect: DOMRect, pageRect: DOMRect) { + // This will be handled by the first mouse move + } + + // Mouse tracking for hover areas + function handleMouseMove(event: MouseEvent) { + const pageContainer = event.currentTarget as HTMLElement + const photoWrapper = pageContainer.querySelector('.photo-content-wrapper') as HTMLElement + + if (!photoWrapper) return + + // Get the actual image element inside PhotoView + const photoImage = photoWrapper.querySelector('img') as HTMLElement + if (!photoImage) return + + const pageRect = pageContainer.getBoundingClientRect() + const photoRect = photoImage.getBoundingClientRect() + + const x = event.clientX + const mouseX = event.clientX - pageRect.left + const mouseY = event.clientY - pageRect.top + + // Check if mouse is in the left or right margin (outside the photo) + const wasHoveringLeft = isHoveringLeft + const wasHoveringRight = isHoveringRight + + isHoveringLeft = x < photoRect.left + isHoveringRight = x > photoRect.right + + // Calculate image center Y position + const imageCenterY = (photoRect.top - pageRect.top) + (photoRect.height / 2) + + // Update button positions + if (isHoveringLeft) { + leftButtonCoords.set({ x: mouseX, y: mouseY }) + } else if (wasHoveringLeft && !isHoveringLeft) { + // Reset left button to default + leftButtonCoords.set({ x: defaultLeftX, y: imageCenterY }) + } + + if (isHoveringRight) { + rightButtonCoords.set({ x: mouseX, y: mouseY }) + } else if (wasHoveringRight && !isHoveringRight) { + // Reset right button to default + rightButtonCoords.set({ x: defaultRightX, y: imageCenterY }) + } + } + + function handleMouseLeave() { + isHoveringLeft = false + isHoveringRight = false + + // Reset buttons to default positions + const pageContainer = document.querySelector('.photo-page') as HTMLElement + const photoImage = pageContainer?.querySelector('.photo-content-wrapper img') as HTMLElement + + if (photoImage && pageContainer) { + const imageRect = photoImage.getBoundingClientRect() + const pageRect = pageContainer.getBoundingClientRect() + const centerY = (imageRect.top - pageRect.top) + (imageRect.height / 2) + + leftButtonCoords.set({ x: defaultLeftX, y: centerY }) + rightButtonCoords.set({ x: defaultRightX, y: centerY }) + } + } + + // Keyboard navigation + function handleKeydown(e: KeyboardEvent) { + if (e.key === 'ArrowLeft' && navigation?.prevPhoto) { + goto(`/photos/${album.slug}/${navigation.prevPhoto.id}`) + } else if (e.key === 'ArrowRight' && navigation?.nextPhoto) { + goto(`/photos/${album.slug}/${navigation.nextPhoto.id}`) + } + } + + // Set up keyboard listener + $effect(() => { + window.addEventListener('keydown', handleKeydown) + return () => window.removeEventListener('keydown', handleKeydown) + }) @@ -102,7 +238,11 @@ {:else} -
+
+
- -
- {#if navigation.prevPhoto} - - {/if} + +
+ {#if navigation.prevPhoto} + + {/if} - {#if navigation.nextPhoto} - - {/if} -
+ {#if navigation.nextPhoto} + + {/if}
diff --git a/src/routes/photos/p/[id]/+page.svelte b/src/routes/photos/p/[id]/+page.svelte index be2170c..e9e44fc 100644 --- a/src/routes/photos/p/[id]/+page.svelte +++ b/src/routes/photos/p/[id]/+page.svelte @@ -6,6 +6,7 @@ import { page } from '$app/stores' import { goto } from '$app/navigation' import { onMount } from 'svelte' + import { spring } from 'svelte/motion' import type { PageData } from './$types' import { isAlbum } from '$lib/types/photos' import ArrowLeft from '$icons/arrow-left.svg' @@ -18,6 +19,25 @@ const photoItems = $derived(data.photoItems || []) const currentPhotoId = $derived(data.currentPhotoId) + // Hover tracking for arrow buttons + let isHoveringLeft = $state(false) + let isHoveringRight = $state(false) + + // Spring stores for smooth button movement + const leftButtonCoords = spring({ x: 0, y: 0 }, { + stiffness: 0.3, + damping: 0.8 + }) + + const rightButtonCoords = spring({ x: 0, y: 0 }, { + stiffness: 0.3, + damping: 0.8 + }) + + // Default button positions (will be set once photo loads) + let defaultLeftX = 0 + let defaultRightX = 0 + const pageUrl = $derived($page.url.href) // Generate metadata @@ -95,6 +115,107 @@ } } + // Set default button positions when component mounts + $effect(() => { + if (!photo) return + + // Wait for DOM to update and image to load + const checkAndSetPositions = () => { + const pageContainer = document.querySelector('.photo-page') as HTMLElement + const photoImage = pageContainer?.querySelector('.photo-content-wrapper img') as HTMLElement + + if (photoImage && photoImage.complete) { + const imageRect = photoImage.getBoundingClientRect() + const pageRect = pageContainer.getBoundingClientRect() + + // Calculate default positions relative to the image + // Add 24px (half button width) since we're using translate(-50%, -50%) + defaultLeftX = (imageRect.left - pageRect.left) - 24 - 16 // half button width + gap + defaultRightX = (imageRect.right - pageRect.left) + 24 + 16 // half button width + gap + + // Set initial positions at the vertical center of the image + const centerY = (imageRect.top - pageRect.top) + (imageRect.height / 2) + leftButtonCoords.set({ x: defaultLeftX, y: centerY }, { hard: true }) + rightButtonCoords.set({ x: defaultRightX, y: centerY }, { hard: true }) + + // Check if mouse is already in a hover zone + checkInitialMousePosition(pageContainer, imageRect, pageRect) + } else { + // If image not loaded yet, try again + setTimeout(checkAndSetPositions, 50) + } + } + + checkAndSetPositions() + }) + + // We'll just remove the initial check for now + function checkInitialMousePosition(pageContainer: HTMLElement, imageRect: DOMRect, pageRect: DOMRect) { + // This will be handled by the first mouse move + } + + // Mouse tracking for hover areas + function handleMouseMove(event: MouseEvent) { + const pageContainer = event.currentTarget as HTMLElement + const photoWrapper = pageContainer.querySelector('.photo-content-wrapper') as HTMLElement + + if (!photoWrapper) return + + // Get the actual image element inside PhotoView + const photoImage = photoWrapper.querySelector('img') as HTMLElement + if (!photoImage) return + + const pageRect = pageContainer.getBoundingClientRect() + const photoRect = photoImage.getBoundingClientRect() + + const x = event.clientX + const mouseX = event.clientX - pageRect.left + const mouseY = event.clientY - pageRect.top + + // Check if mouse is in the left or right margin (outside the photo) + const wasHoveringLeft = isHoveringLeft + const wasHoveringRight = isHoveringRight + + isHoveringLeft = x < photoRect.left + isHoveringRight = x > photoRect.right + + // Calculate image center Y position + const imageCenterY = (photoRect.top - pageRect.top) + (photoRect.height / 2) + + // Update button positions + if (isHoveringLeft) { + leftButtonCoords.set({ x: mouseX, y: mouseY }) + } else if (wasHoveringLeft && !isHoveringLeft) { + // Reset left button to default + leftButtonCoords.set({ x: defaultLeftX, y: imageCenterY }) + } + + if (isHoveringRight) { + rightButtonCoords.set({ x: mouseX, y: mouseY }) + } else if (wasHoveringRight && !isHoveringRight) { + // Reset right button to default + rightButtonCoords.set({ x: defaultRightX, y: imageCenterY }) + } + } + + function handleMouseLeave() { + isHoveringLeft = false + isHoveringRight = false + + // Reset buttons to default positions + const pageContainer = document.querySelector('.photo-page') as HTMLElement + const photoImage = pageContainer?.querySelector('.photo-content-wrapper img') as HTMLElement + + if (photoImage && pageContainer) { + const imageRect = photoImage.getBoundingClientRect() + const pageRect = pageContainer.getBoundingClientRect() + const centerY = (imageRect.top - pageRect.top) + (imageRect.height / 2) + + leftButtonCoords.set({ x: defaultLeftX, y: centerY }) + rightButtonCoords.set({ x: defaultRightX, y: centerY }) + } + } + // Set up keyboard listener $effect(() => { window.addEventListener('keydown', handleKeydown) @@ -134,7 +255,11 @@
{:else if photo} -
+
+
- -
- {#if adjacentPhotos().prev} - - {/if} + +
+ {#if adjacentPhotos().prev} + + {/if} - {#if adjacentPhotos().next} - - {/if} -
+ {#if adjacentPhotos().next} + + {/if}
diff --git a/test-db.ts b/test-db.ts new file mode 100644 index 0000000..f08af93 --- /dev/null +++ b/test-db.ts @@ -0,0 +1,15 @@ +import { PrismaClient } from '@prisma/client' + +const prisma = new PrismaClient() + +async function testDb() { + try { + const count = await prisma.media.count() + console.log('Total media entries:', count) + await prisma.$disconnect() + } catch (error) { + console.error('Database error:', error) + } +} + +testDb() \ No newline at end of file