"use client"; import { type CSSProperties, type PointerEvent as ReactPointerEvent, useEffect, useMemo, useRef, useState, } from "react"; import Image from "next/image"; import { ChevronLeft, ChevronRight, X } from "lucide-react"; import type { PrivateAlbum } from "@/data/schemas/private-room"; import { getResistedGalleryDragDistance, resolveGallerySwipeDirection, } from "../private-album-gallery-motion"; import { getPrivateAlbumDisplayImages } from "../private-album-images"; import styles from "../private-room-screen.module.css"; export interface PrivateAlbumGalleryProps { album: PrivateAlbum; imageIndex: number; onClose: () => void; onImageIndexChange: (index: number) => void; } interface ActivePointerGesture { pointerId: number; startX: number; startTime: number; startPosition: number; viewportWidth: number; } type GallerySlideStyle = CSSProperties & { "--gallery-slide-offset": string; "--gallery-drag-offset": string; }; export function PrivateAlbumGallery({ album, imageIndex, onClose, onImageIndexChange, }: PrivateAlbumGalleryProps) { const displayImages = useMemo( () => getPrivateAlbumDisplayImages(album), [album], ); const [activeSourceIndex, setActiveSourceIndex] = useState(imageIndex); const [dragOffset, setDragOffset] = useState(0); const [isDragging, setIsDragging] = useState(false); const gestureRef = useRef(null); const activePosition = displayImages.findIndex( (image) => image.sourceIndex === activeSourceIndex, ); const currentImage = displayImages[activePosition]; const hasPrevious = activePosition > 0; const hasNext = activePosition >= 0 && activePosition < displayImages.length - 1; useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") onClose(); if (event.key === "ArrowLeft" && hasPrevious) { const previousImage = displayImages[activePosition - 1]; if (previousImage) { setActiveSourceIndex(previousImage.sourceIndex); onImageIndexChange(previousImage.sourceIndex); } } if (event.key === "ArrowRight" && hasNext) { const nextImage = displayImages[activePosition + 1]; if (nextImage) { setActiveSourceIndex(nextImage.sourceIndex); onImageIndexChange(nextImage.sourceIndex); } } }; document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [ activePosition, displayImages, hasNext, hasPrevious, onClose, onImageIndexChange, ]); if (!currentImage) return null; const changeToPosition = (position: number) => { const nextImage = displayImages[position]; if (!nextImage || position === activePosition) return; setActiveSourceIndex(nextImage.sourceIndex); setDragOffset(0); setIsDragging(false); onImageIndexChange(nextImage.sourceIndex); }; const handlePointerDown = (event: ReactPointerEvent) => { if (event.button !== 0 || displayImages.length <= 1) return; const viewportWidth = event.currentTarget.clientWidth || window.innerWidth; gestureRef.current = { pointerId: event.pointerId, startX: event.clientX, startTime: event.timeStamp, startPosition: activePosition, viewportWidth, }; event.currentTarget.setPointerCapture(event.pointerId); setDragOffset(0); setIsDragging(true); }; const handlePointerMove = (event: ReactPointerEvent) => { const gesture = gestureRef.current; if (!gesture || gesture.pointerId !== event.pointerId) return; event.preventDefault(); const distance = event.clientX - gesture.startX; setDragOffset( getResistedGalleryDragDistance( distance, gesture.startPosition > 0, gesture.startPosition < displayImages.length - 1, ), ); }; const finishPointerGesture = ( event: ReactPointerEvent, cancelled: boolean, ) => { const gesture = gestureRef.current; if (!gesture || gesture.pointerId !== event.pointerId) return; gestureRef.current = null; if (event.currentTarget.hasPointerCapture(event.pointerId)) { event.currentTarget.releasePointerCapture(event.pointerId); } const distance = event.clientX - gesture.startX; const direction = cancelled ? 0 : resolveGallerySwipeDirection({ distance, durationMs: event.timeStamp - gesture.startTime, viewportWidth: gesture.viewportWidth, hasPrevious: gesture.startPosition > 0, hasNext: gesture.startPosition < displayImages.length - 1, }); setIsDragging(false); setDragOffset(0); if (direction !== 0) { changeToPosition(gesture.startPosition + direction); } }; return (
finishPointerGesture(event, false)} onPointerCancel={(event) => finishPointerGesture(event, true)} onLostPointerCapture={(event) => finishPointerGesture(event, true)} > {displayImages.map((image, position) => { if (Math.abs(position - activePosition) > 1) return null; const slideStyle = { "--gallery-slide-offset": `${(position - activePosition) * 100}%`, "--gallery-drag-offset": `${dragOffset}px`, } as GallerySlideStyle; return (
{`${album.title
); })}
{activePosition + 1} / {displayImages.length} {hasPrevious ? ( ) : null} {hasNext ? ( ) : null}
); }