ui: rework selection/heap visuals, contextual shortcut hints, inline scan activity
- Selection now reads as a blue ring + tint with a springy scale-down, hover stays a subtle gray ring so keyboard-driven and mouse-driven states are tellable apart. - Heap membership is signalled with a green tint only (no badge, no ring, no scale). - Discard/restore is optimistic and non-yanking: photos stay in the grid greyed out until the next reload, X toggles based on the current state, and the same treatment applies in preview. - Filmstrip mirrors the grid styling (selection blue, heap green, discarded grey). - Preview close restores the LAST viewed photo as the focused/selected one in the grid. - Right sidebar collapses on view change and re-opens when a photo is in focus; Esc clears active selection so the panel collapses too. - Keyboard hints panel is context-aware (grid / preview / discarded section), collapsible with H, persisted, and rendered inside the preview column above the filmstrip. - "Pick (P)" renamed to "Select (S)" everywhere. - Needs review moved into the Flag pill dropdown. - Fixed vertical videos overflowing the preview column (min-h-0). - Replaced the bottom-right ScanProgress popover with an inline spinner next to the FOLDERS sidebar header (and on the specific folder row being scanned). ScanProgress is now a headless invalidator; useScanActivity exposes the live status. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -182,21 +182,26 @@ export function Timeline() {
|
||||
const prevViewModeRef = useRef(viewMode)
|
||||
|
||||
// Auto-focus the first photo on initial grid load so arrow-key nav
|
||||
// works immediately without a pre-click. Only fires when there's no
|
||||
// current active photo — we never clobber the user's selection or
|
||||
// the one they restored by navigating back from preview.
|
||||
// works immediately without a pre-click. One-shot — after the user
|
||||
// explicitly clears the selection (Escape), we don't re-focus, so
|
||||
// the metadata sidebar can collapse and stay collapsed.
|
||||
const didAutoFocusRef = useRef(false)
|
||||
useEffect(() => {
|
||||
if (didAutoFocusRef.current) return
|
||||
if (viewMode !== 'grid') return
|
||||
if (activePhotoId) return
|
||||
if (activePhotoId) {
|
||||
didAutoFocusRef.current = true
|
||||
return
|
||||
}
|
||||
if (photos.length === 0) return
|
||||
didAutoFocusRef.current = true
|
||||
selectPhoto(photos[0].id)
|
||||
}, [viewMode, activePhotoId, photos, selectPhoto])
|
||||
|
||||
// Membership in the active heap (for the basket affordance). Subscribed
|
||||
// once at this level so we don't have hundreds of thumbnails each
|
||||
// subscribing to the same query.
|
||||
const { memberIds: activeHeapMembers, activeHeap } = useActiveHeapMembers()
|
||||
const activeHeapName = activeHeap?.name ?? null
|
||||
// Membership in the active heap (drives the green tint on each
|
||||
// thumbnail). Subscribed once at this level so we don't have hundreds
|
||||
// of thumbnails each subscribing to the same query.
|
||||
const { memberIds: activeHeapMembers } = useActiveHeapMembers()
|
||||
|
||||
// Build the flat virtualizer items: a mix of group headers and rows of
|
||||
// photos. Date headers appear only in the main timeline (groupBy='date').
|
||||
@@ -783,7 +788,6 @@ export function Timeline() {
|
||||
fill
|
||||
isSelected={selectedPhotos.includes(photo.id)}
|
||||
isInActiveHeap={activeHeapMembers.has(photo.id)}
|
||||
activeHeapName={activeHeapName}
|
||||
onClick={(e) => {
|
||||
if (e.shiftKey) {
|
||||
selectRange(photo.id)
|
||||
|
||||
Reference in New Issue
Block a user