diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 56593f5..6a2ceca 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,4 @@ import { useState } from 'react' -import { useQueryClient } from '@tanstack/react-query' import { Timeline } from './components/timeline/Timeline' import { LeftSidebar } from './components/layout/LeftSidebar' import { RightSidebar } from './components/layout/RightSidebar' @@ -7,36 +6,37 @@ import { TopBar } from './components/layout/TopBar' import { ScanProgress } from './components/ScanProgress' import { ToastContainer } from './components/ToastContainer' import { KeyboardHints } from './components/KeyboardHints' -import { LoupeView } from './components/loupe/LoupeView' +import { PreviewView } from './components/preview/PreviewView' import { FilterBar } from './components/filter/FilterBar' import { ActiveFilterChips } from './components/filter/ActiveFilterChips' import { usePhotoStore } from './store/photoStore' import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts' import { useFilterUrlSync } from './hooks/useFilterUrlSync' -import type { Photo } from './types/photo' +import { usePhotosQuery } from './hooks/usePhotosQuery' function App() { const [leftSidebarOpen, setLeftSidebarOpen] = useState(true) const [rightSidebarOpen, setRightSidebarOpen] = useState(false) const selectedPhotos = usePhotoStore((state) => state.selectedPhotos) const viewMode = usePhotoStore((state) => state.viewMode) - const queryClient = useQueryClient() // Bidirectional sync of filter store with URL query params. useFilterUrlSync() + // Subscribe to the same photos query the Timeline uses, so the keyboard + // "open preview on first photo" path can read from the live cache regardless + // of what filter key it's stored under. + const { data: allPhotos } = usePhotosQuery() + // Set up global keyboard shortcuts useKeyboardShortcuts({ onToggleLeftSidebar: () => setLeftSidebarOpen(!leftSidebarOpen), onToggleRightSidebar: () => setRightSidebarOpen(!rightSidebarOpen), - getFirstPhotoId: () => { - const photos = queryClient.getQueryData(['photos']) - return photos && photos.length > 0 ? photos[0].id : null - }, + getFirstPhotoId: () => allPhotos?.[0]?.id ?? null, }) // Auto-show right sidebar when photos are selected — but only in grid mode, - // so leaving loupe doesn't fight the user's prior sidebar state. + // so leaving the preview doesn't fight the user's prior sidebar state. if (viewMode === 'grid') { if (selectedPhotos.length > 0 && !rightSidebarOpen) { setRightSidebarOpen(true) @@ -85,8 +85,8 @@ function App() { {/* Toast Notifications */} - {/* Loupe overlay — covers TopBar when active */} - {viewMode === 'loupe' && } + {/* Preview overlay — covers TopBar when active */} + {viewMode === 'preview' && } ) } diff --git a/frontend/src/components/KeyboardHints.tsx b/frontend/src/components/KeyboardHints.tsx index a278318..6fa2a10 100644 --- a/frontend/src/components/KeyboardHints.tsx +++ b/frontend/src/components/KeyboardHints.tsx @@ -4,23 +4,23 @@ export function KeyboardHints() { const selectedCount = usePhotoStore((state) => state.selectedPhotos.length) const viewMode = usePhotoStore((state) => state.viewMode) - // In loupe mode the photo viewer has its own context, so the grid hints + // In preview mode the viewer has its own context, so the grid hints // would just be confusing. Hide them. - if (viewMode === 'loupe') return null + if (viewMode === 'preview') return null const hints = selectedCount > 0 ? [ { key: '1-5', action: 'Rate' }, { key: 'P', action: 'Pick' }, { key: 'X', action: 'Trash' }, - { key: 'E', action: 'Loupe' }, + { key: 'E / Space', action: 'Preview' }, { key: 'Esc', action: 'Deselect' }, ] : [ { key: '↑↓←→', action: 'Navigate' }, { key: 'Click', action: 'Select' }, { key: 'Shift+Click', action: 'Range' }, - { key: 'Space', action: 'Preview' }, + { key: 'E / Space', action: 'Preview' }, { key: '\\', action: 'Filters' }, { key: '/', action: 'Search' }, ] diff --git a/frontend/src/components/loupe/LoupeFilmstrip.tsx b/frontend/src/components/preview/PreviewFilmstrip.tsx similarity index 92% rename from frontend/src/components/loupe/LoupeFilmstrip.tsx rename to frontend/src/components/preview/PreviewFilmstrip.tsx index bc627d0..e044da8 100644 --- a/frontend/src/components/loupe/LoupeFilmstrip.tsx +++ b/frontend/src/components/preview/PreviewFilmstrip.tsx @@ -3,7 +3,7 @@ import clsx from 'clsx' import type { Photo } from '../../types/photo' import { photos as photosApi } from '../../services/api' -interface LoupeFilmstripProps { +interface PreviewFilmstripProps { photos: Photo[] currentIndex: number onSelect: (id: string) => void @@ -11,7 +11,7 @@ interface LoupeFilmstripProps { const CELL_SIZE = 72 -export function LoupeFilmstrip({ photos, currentIndex, onSelect }: LoupeFilmstripProps) { +export function PreviewFilmstrip({ photos, currentIndex, onSelect }: PreviewFilmstripProps) { const activeRef = useRef(null) useEffect(() => { diff --git a/frontend/src/components/loupe/LoupeImage.tsx b/frontend/src/components/preview/PreviewImage.tsx similarity index 81% rename from frontend/src/components/loupe/LoupeImage.tsx rename to frontend/src/components/preview/PreviewImage.tsx index 897ec98..3f4af97 100644 --- a/frontend/src/components/loupe/LoupeImage.tsx +++ b/frontend/src/components/preview/PreviewImage.tsx @@ -1,15 +1,14 @@ import { useState, useEffect, useRef, useCallback } from 'react' import { useHotkeys } from 'react-hotkeys-hook' -import clsx from 'clsx' import type { Photo } from '../../types/photo' import { - getLoupeImageSrc, - getLoupeFallbackSrc, + getPreviewImageSrc, + getPreviewFallbackSrc, getVideoSrc, isVideo, -} from './loupeSrc' +} from './previewSrc' -interface LoupeImageProps { +interface PreviewImageProps { photo: Photo } @@ -17,14 +16,14 @@ const MIN_SCALE = 1 const MAX_SCALE = 8 const WHEEL_STEP = 1.15 -export function LoupeImage({ photo }: LoupeImageProps) { +export function PreviewImage({ photo }: PreviewImageProps) { if (isVideo(photo)) { - return + return } - return + return } -function LoupeVideo({ photo }: { photo: Photo }) { +function PreviewVideo({ photo }: { photo: Photo }) { return (