The Folders section in the LeftSidebar previously rendered the flat
list of source roots — actual subdirectories were invisible. Now it
shows the full nested tree, click any node to filter, drop targets
work at every depth.
Backend
- New GET /folders/tree returning a list of root nodes (one per
active SourceRoot). Each node is { id, name, path, photo_count,
children: [...] } with children sorted alphabetically at every
level. Walks Folder rows whose source_root_id matches and whose
path is at or beneath the source root, then attaches them by
parent path so partial scans don't break the tree.
- The source root's display label is overlaid on the root folder
node so the top-level entry reads as "Library" instead of
"/photos".
- list_photos folder_id filter now does descendant matching: when
a Folder id is given, it includes the folder itself and every
Folder whose path is a sep-prefixed descendant. Matches the
Lightroom mental model: clicking "Library" or any parent folder
shows everything beneath it. The existing source-root-id branch
is unchanged.
Frontend
- New types/api.ts FolderTreeNode interface and sourceFolders.tree()
helper.
- New hooks/useFolderTreeQuery.ts with a 30s staleTime and a
findFolderInTree() walker for id-based name lookups.
- LeftSidebar drops the flat foldersData list and uses the tree
query. folderNodeToTreeItem recursively maps backend nodes into
the existing TreeItem shape; renderTreeItem already knew how to
recurse into children, so the tree just works at any depth.
Drop targets, drag-to-move, drag-to-copy, double-click rename,
and active-state highlighting all carry over to nested folders.
- The renameMutation now also invalidates ['folders', 'tree'] so a
source-root rename refreshes the tree label immediately.
- ActiveFilterChips switches to the tree query and uses the new
findFolderInTree walker so the chip label resolves correctly for
sub-folder filters too — not just top-level source roots.
- The "Scan all folders" button visibility now keys off the tree
length instead of the flat folders length.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
27 lines
739 B
TypeScript
27 lines
739 B
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
import { sourceFolders, type FolderTreeNode } from '../services/api'
|
|
|
|
export const FOLDER_TREE_QUERY_KEY = ['folders', 'tree'] as const
|
|
|
|
export function useFolderTreeQuery() {
|
|
return useQuery<FolderTreeNode[]>({
|
|
queryKey: FOLDER_TREE_QUERY_KEY,
|
|
queryFn: sourceFolders.tree,
|
|
staleTime: 30_000,
|
|
})
|
|
}
|
|
|
|
/** Walk the tree to find a node by id. Used for chip name lookups. */
|
|
export function findFolderInTree(
|
|
tree: FolderTreeNode[] | undefined,
|
|
id: string
|
|
): FolderTreeNode | null {
|
|
if (!tree) return null
|
|
for (const node of tree) {
|
|
if (node.id === id) return node
|
|
const child = findFolderInTree(node.children, id)
|
|
if (child) return child
|
|
}
|
|
return null
|
|
}
|