From 854afd4c4154866fa9a7fd5034b803444bc23f6c Mon Sep 17 00:00:00 2001 From: tigeren Date: Fri, 29 Aug 2025 17:26:51 +0000 Subject: [PATCH] feat: enhance photo viewer navigation and state management - Implemented next and previous photo navigation in the photo viewer, allowing users to cycle through images while skipping videos. - Introduced state management for current items in the folder viewer to support navigation functionality. - Updated the InfiniteVirtualGrid and VirtualizedFolderGrid components to handle item clicks with index support for better user experience. --- src/app/folder-viewer/page.tsx | 40 ++++++++++++++++----- src/app/photos/page.tsx | 43 ++++++++++++++++++++++- src/components/infinite-virtual-grid.tsx | 25 +++++++++++-- src/components/virtualized-media-grid.tsx | 10 +++++- 4 files changed, 105 insertions(+), 13 deletions(-) diff --git a/src/app/folder-viewer/page.tsx b/src/app/folder-viewer/page.tsx index cd60f9d..b2ca3f1 100644 --- a/src/app/folder-viewer/page.tsx +++ b/src/app/folder-viewer/page.tsx @@ -148,7 +148,6 @@ const FolderViewerPage = () => { const handlePhotoClick = (item: FileSystemItem, index: number) => { if (item.type === 'photo' && item.id) { setSelectedPhoto(item); - setCurrentPhotoIndex(index); setIsPhotoViewerOpen(true); } }; @@ -158,16 +157,40 @@ const FolderViewerPage = () => { setSelectedPhoto(null); }; + const [currentItems, setCurrentItems] = useState([]); + const handleNextPhoto = () => { - // This would need to be implemented with the current items list - // For now, just close the viewer - handleClosePhotoViewer(); + // Navigate to next photo, skipping videos + const photos = currentItems.filter(item => item.type === 'photo' && item.id); + if (photos.length === 0) return; + + const currentPhotoId = selectedPhoto?.id; + if (!currentPhotoId) return; + + const currentIndexInPhotos = photos.findIndex(p => p.id === currentPhotoId); + if (currentIndexInPhotos === -1) return; + + const nextIndex = (currentIndexInPhotos + 1) % photos.length; + const nextPhoto = photos[nextIndex]; + + setSelectedPhoto(nextPhoto); }; const handlePrevPhoto = () => { - // This would need to be implemented with the current items list - // For now, just close the viewer - handleClosePhotoViewer(); + // Navigate to previous photo, skipping videos + const photos = currentItems.filter(item => item.type === 'photo' && item.id); + if (photos.length === 0) return; + + const currentPhotoId = selectedPhoto?.id; + if (!currentPhotoId) return; + + const currentIndexInPhotos = photos.findIndex(p => p.id === currentPhotoId); + if (currentIndexInPhotos === -1) return; + + const prevIndex = (currentIndexInPhotos - 1 + photos.length) % photos.length; + const prevPhoto = photos[prevIndex]; + + setSelectedPhoto(prevPhoto); }; if (!path) { @@ -200,6 +223,7 @@ const FolderViewerPage = () => { onBreadcrumbClick={handleBreadcrumbClick} breadcrumbs={getBreadcrumbs(path)} libraries={libraries} + onItemsLoaded={setCurrentItems} /> {/* Photo Viewer */} @@ -209,7 +233,7 @@ const FolderViewerPage = () => { onClose={handleClosePhotoViewer} onNext={handleNextPhoto} onPrev={handlePrevPhoto} - showNavigation={false} + showNavigation={true} showBookmarks={false} showRatings={false} formatFileSize={formatFileSize} diff --git a/src/app/photos/page.tsx b/src/app/photos/page.tsx index 2df83b9..372ce9d 100644 --- a/src/app/photos/page.tsx +++ b/src/app/photos/page.tsx @@ -19,12 +19,49 @@ interface Photo { export default function PhotosPage() { const [selectedPhoto, setSelectedPhoto] = useState(null); const [isViewerOpen, setIsViewerOpen] = useState(false); + const [photosList, setPhotosList] = useState([]); + const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0); - const handlePhotoClick = (photo: Photo) => { + const handlePhotoClick = (photo: Photo, index?: number) => { setSelectedPhoto(photo); + if (index !== undefined) { + setCurrentPhotoIndex(index); + } setIsViewerOpen(true); }; + const handlePhotosData = (photos: Photo[]) => { + setPhotosList(photos); + }; + + const handleNextPhoto = () => { + if (photosList.length === 0) return; + + const currentPhotoId = selectedPhoto?.id; + if (!currentPhotoId) return; + + const currentIndex = photosList.findIndex(p => p.id === currentPhotoId); + if (currentIndex === -1) return; + + const nextIndex = (currentIndex + 1) % photosList.length; + setSelectedPhoto(photosList[nextIndex]); + setCurrentPhotoIndex(nextIndex); + }; + + const handlePrevPhoto = () => { + if (photosList.length === 0) return; + + const currentPhotoId = selectedPhoto?.id; + if (!currentPhotoId) return; + + const currentIndex = photosList.findIndex(p => p.id === currentPhotoId); + if (currentIndex === -1) return; + + const prevIndex = (currentIndex - 1 + photosList.length) % photosList.length; + setSelectedPhoto(photosList[prevIndex]); + setCurrentPhotoIndex(prevIndex); + }; + const handleCloseViewer = () => { setIsViewerOpen(false); setSelectedPhoto(null); @@ -66,6 +103,7 @@ export default function PhotosPage() { onBookmark={handleBookmark} onUnbookmark={handleUnbookmark} onRate={handleRate} + onDataUpdate={handlePhotosData} /> {/* Photo Viewer */} @@ -73,6 +111,9 @@ export default function PhotosPage() { photo={selectedPhoto!} isOpen={isViewerOpen} onClose={handleCloseViewer} + onNext={handleNextPhoto} + onPrev={handlePrevPhoto} + showNavigation={true} showBookmarks={true} showRatings={true} onBookmark={handleBookmark} diff --git a/src/components/infinite-virtual-grid.tsx b/src/components/infinite-virtual-grid.tsx index a79f718..5aaf87e 100644 --- a/src/components/infinite-virtual-grid.tsx +++ b/src/components/infinite-virtual-grid.tsx @@ -21,10 +21,11 @@ interface MediaItem { interface InfiniteVirtualGridProps { type: 'video' | 'photo' | 'bookmark'; - onItemClick: (item: MediaItem) => void; + onItemClick: (item: MediaItem, index?: number) => void; onBookmark: (id: number) => Promise; onUnbookmark: (id: number) => Promise; onRate: (id: number, rating: number) => Promise; + onDataUpdate?: (items: MediaItem[]) => void; } const ITEM_HEIGHT = 220; @@ -36,7 +37,8 @@ export default function InfiniteVirtualGrid({ onItemClick, onBookmark, onUnbookmark, - onRate + onRate, + onDataUpdate }: InfiniteVirtualGridProps) { const [totalItems, setTotalItems] = useState(0); const [searchTerm, setSearchTerm] = useState(''); @@ -324,6 +326,23 @@ export default function InfiniteVirtualGrid({ fetchItems(nextBatchStart, batchEnd); } } + + // Collect all available items and notify parent + if (onDataUpdate) { + const allItems: MediaItem[] = []; + const sortedKeys = Array.from(dataCacheRef.current.keys()).sort((a, b) => a - b); + + for (const batchKey of sortedKeys) { + const items = dataCacheRef.current.get(batchKey); + if (items) { + allItems.push(...items); + } + } + + if (allItems.length > 0) { + onDataUpdate(allItems); + } + } }, [totalItems, fetchItems, getColumnCount]); const Cell = ({ columnIndex, rowIndex, style }: any) => { @@ -352,7 +371,7 @@ export default function InfiniteVirtualGrid({
onItemClick(item)} + onClick={() => onItemClick(item, index)} >
void; breadcrumbs: BreadcrumbItem[]; libraries: {id: number, path: string}[]; + onItemsLoaded?: (items: FileSystemItem[]) => void; } const ITEM_HEIGHT = 280; // Increased for folder cards @@ -45,7 +46,8 @@ export default function VirtualizedFolderGrid({ onBackClick, onBreadcrumbClick, breadcrumbs, - libraries + libraries, + onItemsLoaded }: VirtualizedFolderGridProps) { const [items, setItems] = useState([]); const [loading, setLoading] = useState(false); @@ -203,6 +205,12 @@ export default function VirtualizedFolderGrid({ } }, [currentPath, fetchItems]); + useEffect(() => { + if (onItemsLoaded) { + onItemsLoaded(items); + } + }, [items, onItemsLoaded]); + const Cell = ({ columnIndex, rowIndex, style }: any) => { const columnCount = getColumnCount(); const index = rowIndex * columnCount + columnIndex;