feat: folder navigation from sidebar
Folders in the LeftSidebar were decorative — clicking one did
nothing. Now they actually filter the timeline.
filterStore: new folderId field, setFolderId, hasActiveFilters check,
filtersToParams sends folder_id to the backend (the param was already
declared and applied server-side, just nothing was setting it).
useFilterUrlSync round-trips ?folder_id= so the filter persists in
the URL. usePhotosQuery threads it through.
LeftSidebar:
- Clicking a folder row calls clearAllFilters() then setFolderId(id)
so the user lands cleanly on that folder.
- Library virtual nodes (All Photos, Rated, Discarded) clear the
folder filter as part of their normal action.
- The active-row visual highlight is now derived from the filter
store: a folder row is selected when filterStore.folderId matches
it, "All Photos" is selected when no folder is set. Keeps the
sidebar in sync if filters change externally (URL hydrate, the
ActiveFilterChips X button, FilterBar Clear all).
ActiveFilterChips: shows "Folder: {name}" and "Heap: {name}" chips,
looking up the names from the folders / heaps queries (lazy-enabled
only when the corresponding filter is set). Clicking the X clears
the filter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,8 @@ export function LeftSidebar() {
|
||||
const clearAllFilters = useFilterStore((s) => s.clearAll)
|
||||
const setRatingMin = useFilterStore((s) => s.setRatingMin)
|
||||
const setFlag = useFilterStore((s) => s.setFlag)
|
||||
const setFolderId = useFilterStore((s) => s.setFolderId)
|
||||
const filterFolderId = useFilterStore((s) => s.folderId)
|
||||
|
||||
// Map a library tree id to a filter-store mutation. Each "virtual node" in
|
||||
// the library tree is just a saved filter preset.
|
||||
@@ -55,7 +57,16 @@ export function LeftSidebar() {
|
||||
clearAllFilters()
|
||||
setFlag('discarded')
|
||||
break
|
||||
// 'by-date' is purely visual until we add a date-grouping UI
|
||||
default:
|
||||
if (id.startsWith('folder-')) {
|
||||
// Folder rows: filter to that folder, clear other filters that
|
||||
// would compete (heap, discarded, etc.) so the user sees what they
|
||||
// expect when they click a folder.
|
||||
const folderId = id.slice('folder-'.length)
|
||||
clearAllFilters()
|
||||
setFolderId(folderId)
|
||||
}
|
||||
// 'by-date' is still visual-only.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +161,24 @@ export function LeftSidebar() {
|
||||
},
|
||||
]
|
||||
|
||||
// Derive whether a tree item is currently the "active" filter target.
|
||||
// Folder rows are selected when the filter store's folderId matches; the
|
||||
// library "All Photos" virtual node is selected when no folder/heap filter
|
||||
// is set.
|
||||
const isItemActive = (id: string): boolean => {
|
||||
if (id.startsWith('folder-')) {
|
||||
return filterFolderId === id.slice('folder-'.length)
|
||||
}
|
||||
if (id === 'all-photos') {
|
||||
return filterFolderId === null && selectedItem === 'all-photos'
|
||||
}
|
||||
return selectedItem === id
|
||||
}
|
||||
|
||||
const renderTreeItem = (item: TreeItem, depth: number = 0) => {
|
||||
const hasChildren = item.children && item.children.length > 0
|
||||
const isExpanded = expandedItems.has(item.id)
|
||||
const isSelected = selectedItem === item.id
|
||||
const isSelected = isItemActive(item.id)
|
||||
|
||||
return (
|
||||
<div key={item.id}>
|
||||
|
||||
Reference in New Issue
Block a user