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.
This commit is contained in:
parent
fdf8ab2a39
commit
854afd4c41
|
|
@ -148,7 +148,6 @@ const FolderViewerPage = () => {
|
||||||
const handlePhotoClick = (item: FileSystemItem, index: number) => {
|
const handlePhotoClick = (item: FileSystemItem, index: number) => {
|
||||||
if (item.type === 'photo' && item.id) {
|
if (item.type === 'photo' && item.id) {
|
||||||
setSelectedPhoto(item);
|
setSelectedPhoto(item);
|
||||||
setCurrentPhotoIndex(index);
|
|
||||||
setIsPhotoViewerOpen(true);
|
setIsPhotoViewerOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -158,16 +157,40 @@ const FolderViewerPage = () => {
|
||||||
setSelectedPhoto(null);
|
setSelectedPhoto(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [currentItems, setCurrentItems] = useState<FileSystemItem[]>([]);
|
||||||
|
|
||||||
const handleNextPhoto = () => {
|
const handleNextPhoto = () => {
|
||||||
// This would need to be implemented with the current items list
|
// Navigate to next photo, skipping videos
|
||||||
// For now, just close the viewer
|
const photos = currentItems.filter(item => item.type === 'photo' && item.id);
|
||||||
handleClosePhotoViewer();
|
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 = () => {
|
const handlePrevPhoto = () => {
|
||||||
// This would need to be implemented with the current items list
|
// Navigate to previous photo, skipping videos
|
||||||
// For now, just close the viewer
|
const photos = currentItems.filter(item => item.type === 'photo' && item.id);
|
||||||
handleClosePhotoViewer();
|
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) {
|
if (!path) {
|
||||||
|
|
@ -200,6 +223,7 @@ const FolderViewerPage = () => {
|
||||||
onBreadcrumbClick={handleBreadcrumbClick}
|
onBreadcrumbClick={handleBreadcrumbClick}
|
||||||
breadcrumbs={getBreadcrumbs(path)}
|
breadcrumbs={getBreadcrumbs(path)}
|
||||||
libraries={libraries}
|
libraries={libraries}
|
||||||
|
onItemsLoaded={setCurrentItems}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Photo Viewer */}
|
{/* Photo Viewer */}
|
||||||
|
|
@ -209,7 +233,7 @@ const FolderViewerPage = () => {
|
||||||
onClose={handleClosePhotoViewer}
|
onClose={handleClosePhotoViewer}
|
||||||
onNext={handleNextPhoto}
|
onNext={handleNextPhoto}
|
||||||
onPrev={handlePrevPhoto}
|
onPrev={handlePrevPhoto}
|
||||||
showNavigation={false}
|
showNavigation={true}
|
||||||
showBookmarks={false}
|
showBookmarks={false}
|
||||||
showRatings={false}
|
showRatings={false}
|
||||||
formatFileSize={formatFileSize}
|
formatFileSize={formatFileSize}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,49 @@ interface Photo {
|
||||||
export default function PhotosPage() {
|
export default function PhotosPage() {
|
||||||
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);
|
const [selectedPhoto, setSelectedPhoto] = useState<Photo | null>(null);
|
||||||
const [isViewerOpen, setIsViewerOpen] = useState(false);
|
const [isViewerOpen, setIsViewerOpen] = useState(false);
|
||||||
|
const [photosList, setPhotosList] = useState<Photo[]>([]);
|
||||||
|
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
||||||
|
|
||||||
const handlePhotoClick = (photo: Photo) => {
|
const handlePhotoClick = (photo: Photo, index?: number) => {
|
||||||
setSelectedPhoto(photo);
|
setSelectedPhoto(photo);
|
||||||
|
if (index !== undefined) {
|
||||||
|
setCurrentPhotoIndex(index);
|
||||||
|
}
|
||||||
setIsViewerOpen(true);
|
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 = () => {
|
const handleCloseViewer = () => {
|
||||||
setIsViewerOpen(false);
|
setIsViewerOpen(false);
|
||||||
setSelectedPhoto(null);
|
setSelectedPhoto(null);
|
||||||
|
|
@ -66,6 +103,7 @@ export default function PhotosPage() {
|
||||||
onBookmark={handleBookmark}
|
onBookmark={handleBookmark}
|
||||||
onUnbookmark={handleUnbookmark}
|
onUnbookmark={handleUnbookmark}
|
||||||
onRate={handleRate}
|
onRate={handleRate}
|
||||||
|
onDataUpdate={handlePhotosData}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Photo Viewer */}
|
{/* Photo Viewer */}
|
||||||
|
|
@ -73,6 +111,9 @@ export default function PhotosPage() {
|
||||||
photo={selectedPhoto!}
|
photo={selectedPhoto!}
|
||||||
isOpen={isViewerOpen}
|
isOpen={isViewerOpen}
|
||||||
onClose={handleCloseViewer}
|
onClose={handleCloseViewer}
|
||||||
|
onNext={handleNextPhoto}
|
||||||
|
onPrev={handlePrevPhoto}
|
||||||
|
showNavigation={true}
|
||||||
showBookmarks={true}
|
showBookmarks={true}
|
||||||
showRatings={true}
|
showRatings={true}
|
||||||
onBookmark={handleBookmark}
|
onBookmark={handleBookmark}
|
||||||
|
|
|
||||||
|
|
@ -21,10 +21,11 @@ interface MediaItem {
|
||||||
|
|
||||||
interface InfiniteVirtualGridProps {
|
interface InfiniteVirtualGridProps {
|
||||||
type: 'video' | 'photo' | 'bookmark';
|
type: 'video' | 'photo' | 'bookmark';
|
||||||
onItemClick: (item: MediaItem) => void;
|
onItemClick: (item: MediaItem, index?: number) => void;
|
||||||
onBookmark: (id: number) => Promise<void>;
|
onBookmark: (id: number) => Promise<void>;
|
||||||
onUnbookmark: (id: number) => Promise<void>;
|
onUnbookmark: (id: number) => Promise<void>;
|
||||||
onRate: (id: number, rating: number) => Promise<void>;
|
onRate: (id: number, rating: number) => Promise<void>;
|
||||||
|
onDataUpdate?: (items: MediaItem[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ITEM_HEIGHT = 220;
|
const ITEM_HEIGHT = 220;
|
||||||
|
|
@ -36,7 +37,8 @@ export default function InfiniteVirtualGrid({
|
||||||
onItemClick,
|
onItemClick,
|
||||||
onBookmark,
|
onBookmark,
|
||||||
onUnbookmark,
|
onUnbookmark,
|
||||||
onRate
|
onRate,
|
||||||
|
onDataUpdate
|
||||||
}: InfiniteVirtualGridProps) {
|
}: InfiniteVirtualGridProps) {
|
||||||
const [totalItems, setTotalItems] = useState(0);
|
const [totalItems, setTotalItems] = useState(0);
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
|
@ -324,6 +326,23 @@ export default function InfiniteVirtualGrid({
|
||||||
fetchItems(nextBatchStart, batchEnd);
|
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]);
|
}, [totalItems, fetchItems, getColumnCount]);
|
||||||
|
|
||||||
const Cell = ({ columnIndex, rowIndex, style }: any) => {
|
const Cell = ({ columnIndex, rowIndex, style }: any) => {
|
||||||
|
|
@ -352,7 +371,7 @@ export default function InfiniteVirtualGrid({
|
||||||
<div style={style} className="p-2">
|
<div style={style} className="p-2">
|
||||||
<Card
|
<Card
|
||||||
className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border overflow-hidden h-full"
|
className="group hover:shadow-lg transition-all duration-300 hover:-translate-y-1 cursor-pointer border-border overflow-hidden h-full"
|
||||||
onClick={() => onItemClick(item)}
|
onClick={() => onItemClick(item, index)}
|
||||||
>
|
>
|
||||||
<div className="relative overflow-hidden bg-muted aspect-video">
|
<div className="relative overflow-hidden bg-muted aspect-video">
|
||||||
<img
|
<img
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ interface VirtualizedFolderGridProps {
|
||||||
onBreadcrumbClick: (path: string) => void;
|
onBreadcrumbClick: (path: string) => void;
|
||||||
breadcrumbs: BreadcrumbItem[];
|
breadcrumbs: BreadcrumbItem[];
|
||||||
libraries: {id: number, path: string}[];
|
libraries: {id: number, path: string}[];
|
||||||
|
onItemsLoaded?: (items: FileSystemItem[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ITEM_HEIGHT = 280; // Increased for folder cards
|
const ITEM_HEIGHT = 280; // Increased for folder cards
|
||||||
|
|
@ -45,7 +46,8 @@ export default function VirtualizedFolderGrid({
|
||||||
onBackClick,
|
onBackClick,
|
||||||
onBreadcrumbClick,
|
onBreadcrumbClick,
|
||||||
breadcrumbs,
|
breadcrumbs,
|
||||||
libraries
|
libraries,
|
||||||
|
onItemsLoaded
|
||||||
}: VirtualizedFolderGridProps) {
|
}: VirtualizedFolderGridProps) {
|
||||||
const [items, setItems] = useState<FileSystemItem[]>([]);
|
const [items, setItems] = useState<FileSystemItem[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
@ -203,6 +205,12 @@ export default function VirtualizedFolderGrid({
|
||||||
}
|
}
|
||||||
}, [currentPath, fetchItems]);
|
}, [currentPath, fetchItems]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (onItemsLoaded) {
|
||||||
|
onItemsLoaded(items);
|
||||||
|
}
|
||||||
|
}, [items, onItemsLoaded]);
|
||||||
|
|
||||||
const Cell = ({ columnIndex, rowIndex, style }: any) => {
|
const Cell = ({ columnIndex, rowIndex, style }: any) => {
|
||||||
const columnCount = getColumnCount();
|
const columnCount = getColumnCount();
|
||||||
const index = rowIndex * columnCount + columnIndex;
|
const index = rowIndex * columnCount + columnIndex;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue