feat(web): persist file tree expansion and add column visibility callbacks

Save expanded file tree paths to localStorage so they survive page reloads, and surface an onColumnVisibilityChanged callback from DataTable/ObjectTable.
This commit is contained in:
kumarabhirup 2026-03-05 19:09:34 -08:00
parent 1c21b039fc
commit 3533aa3358
No known key found for this signature in database
GPG Key ID: DB7CA2289CAB0167
3 changed files with 33 additions and 16 deletions

View File

@ -60,6 +60,7 @@ export type DataTableProps<TData, TValue> = {
enableColumnReordering?: boolean;
onColumnReorder?: (newOrder: string[]) => void;
initialColumnVisibility?: VisibilityState;
onColumnVisibilityChanged?: (visibility: VisibilityState) => void;
// pagination
pageSize?: number;
// actions
@ -173,6 +174,7 @@ export function DataTable<TData, TValue>({
enableColumnReordering = false,
onColumnReorder,
initialColumnVisibility,
onColumnVisibilityChanged,
pageSize: defaultPageSize = 100,
onRefresh,
onAdd,
@ -345,7 +347,11 @@ export function DataTable<TData, TValue>({
onSortingChange: setSorting,
onGlobalFilterChange: setGlobalFilter,
onColumnFiltersChange: setColumnFilters,
onColumnVisibilityChange: setColumnVisibility,
onColumnVisibilityChange: (updater) => {
const next = typeof updater === "function" ? updater(columnVisibility) : updater;
setColumnVisibility(next);
onColumnVisibilityChanged?.(next);
},
onRowSelectionChange: (updater) => {
if (onRowSelectionChange) {
onRowSelectionChange(updater);

View File

@ -716,8 +716,27 @@ function flattenVisible(tree: TreeNode[], expanded: Set<string>): TreeNode[] {
// --- Main Exported Component ---
const STORAGE_KEY = "denchclaw-tree-expanded";
function loadExpandedPaths(): Set<string> {
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
if (raw) {
const arr = JSON.parse(raw) as string[];
if (Array.isArray(arr)) return new Set(arr);
}
} catch { /* ignore corrupt data */ }
return new Set();
}
function saveExpandedPaths(paths: Set<string>) {
try {
window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...paths]));
} catch { /* storage full or unavailable */ }
}
export function FileManagerTree({ tree, activePath, onSelect, onRefresh, compact, parentDir, onNavigateUp, browseDir: _browseDir, workspaceRoot, onExternalDrop }: FileManagerTreeProps) {
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => new Set());
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(() => loadExpandedPaths());
const [selectedPath, setSelectedPath] = useState<string | null>(null);
const [renamingPath, setRenamingPath] = useState<string | null>(null);
const [dragOverPath, setDragOverPath] = useState<string | null>(null);
@ -771,21 +790,10 @@ export function FileManagerTree({ tree, activePath, onSelect, onRefresh, compact
const containerRef = useRef<HTMLDivElement>(null);
// Auto-expand first level on mount.
// Keep ~skills and ~memories collapsed by default; always expand ~chats.
const collapsedByDefault = new Set(["~skills", "~memories"]);
// Persist expanded paths to localStorage whenever they change
useEffect(() => {
if (tree.length > 0 && expandedPaths.size === 0) {
const initial = new Set<string>();
for (const node of tree) {
if (collapsedByDefault.has(node.path)) {continue;}
if (node.children && node.children.length > 0) {
initial.add(node.path);
}
}
setExpandedPaths(initial);
}
}, [tree]);
saveExpandedPaths(expandedPaths);
}, [expandedPaths]);
const handleToggleExpand = useCallback((path: string) => {
setExpandedPaths((prev) => {

View File

@ -50,6 +50,7 @@ type ObjectTableProps = {
onRefresh?: () => void;
/** Column visibility state keyed by field ID. */
columnVisibility?: Record<string, boolean>;
onColumnVisibilityChanged?: (visibility: Record<string, boolean>) => void;
/** Server-side pagination props. */
serverPagination?: ServerPaginationProps;
/** Server-side search callback. */
@ -440,6 +441,7 @@ export function ObjectTable({
onEntryClick,
onRefresh,
columnVisibility,
onColumnVisibilityChanged,
serverPagination,
onServerSearch,
}: ObjectTableProps) {
@ -711,6 +713,7 @@ export function ObjectTable({
rowActions={getRowActions}
stickyFirstColumn
initialColumnVisibility={columnVisibility}
onColumnVisibilityChanged={onColumnVisibilityChanged}
serverPagination={serverPagination}
onServerSearch={onServerSearch}
/>