feat: Refactor folder tree management to use FoldersManager and remove onToggle functionality

This commit is contained in:
2025-12-07 01:12:28 +01:00
parent bd1cdc0817
commit 47599aba4a
13 changed files with 165 additions and 488 deletions
+1 -23
View File
@@ -1,6 +1,5 @@
import React, { useRef, useSyncExternalStore, useCallback } from 'react';
import React, { useRef } from 'react';
import { useAppShell } from '../lib/context/AppShellContext';
import FoldersManager from '../documents/FoldersManager';
import { usePanelResizeBindings } from '../app/PanelManagerContext';
import type { Identifier } from '../types/identifiers';
import SidebarFolderList from './components/SidebarFolderList';
@@ -11,10 +10,7 @@ import { TenantOption } from './components/SidebarMenu';
import SidebarHeader from './components/SidebarHeader';
import SidebarSearch from './components/SidebarSearch';
interface SidebarProps {
onToggle: (folderId: FolderIdentifier) => void;
onSelect: (folderId: FolderIdentifier) => void;
onDrop: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
onDragOver: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
@@ -30,7 +26,6 @@ interface SidebarProps {
}
const Sidebar: React.FC<SidebarProps> = ({
onToggle,
onSelect,
onDrop,
onDragOver,
@@ -51,7 +46,6 @@ const Sidebar: React.FC<SidebarProps> = ({
handleLogout,
tags = [],
correspondents = [],
foldersManager,
tenant,
tenants,
tenantOptions,
@@ -60,19 +54,6 @@ const Sidebar: React.FC<SidebarProps> = ({
handleFileSelection, // Used for upload
} = useAppShell();
// Subscribe to folder tree updates
const subscribeToTree = useCallback((callback: () => void) => {
if (!foldersManager) return () => { };
return (foldersManager as FoldersManager).subscribe(callback);
}, [foldersManager]);
const getTreeSnapshot = useCallback(() => {
if (!foldersManager) return [];
return (foldersManager as FoldersManager).getTreeSnapshot();
}, [foldersManager]);
const roots = useSyncExternalStore(subscribeToTree, getTreeSnapshot);
const onManageTags = openTagsModal;
const onManageCorrespondents = openCorrespondentsModal;
@@ -81,7 +62,6 @@ const Sidebar: React.FC<SidebarProps> = ({
const effectiveTenants = (tenants || tenantOptions || []);
const activeTenantId = (tenant as TenantOption)?.id;
// Upload handler wrapper (if needed, or pass handleFileSelection directly if signature matches)
const onUploadFiles = handleFileSelection;
const sidebarRef = useRef<HTMLDivElement | null>(null);
@@ -125,9 +105,7 @@ const Sidebar: React.FC<SidebarProps> = ({
<SidebarSearch />
<SidebarFolderList
roots={roots}
selectedFolder={selectedFolder}
onToggle={onToggle}
onSelect={onSelect}
onDrop={onDrop}
onDragOver={onDragOver}
@@ -4,11 +4,12 @@ import type { Identifier } from '../../types/identifiers';
import { FolderPlusIcon } from '../../components/icons';
import FolderNode, { FolderIdentifier } from './SidebarFolderNode';
import type { FolderTreeNode } from '../../lib/api/apiTypes';
import { useAppShell } from '../../lib/context/AppShellContext';
import FoldersManager from '../../documents/FoldersManager';
import { useSyncExternalStore } from 'react';
interface SidebarFolderListProps {
roots: FolderTreeNode[];
selectedFolder: FolderIdentifier | null;
onToggle: (folderId: FolderIdentifier) => void;
onSelect: (folderId: FolderIdentifier) => void;
onDrop: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
onDragOver: (event: React.DragEvent<HTMLDivElement>, folderId: FolderIdentifier) => void;
@@ -23,9 +24,7 @@ interface SidebarFolderListProps {
}
const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
roots,
selectedFolder,
onToggle,
onSelect,
onDrop,
onDragOver,
@@ -38,17 +37,56 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
onCreateFolder,
creatingFolder,
}) => {
// Local expansion state for the UI
const { foldersManager } = useAppShell();
const getTreeSnapshot = useCallback(() => {
if (!foldersManager) return [];
return (foldersManager as FoldersManager).getTreeSnapshot();
}, [foldersManager]);
const getFolderMap = useCallback(() => {
if (!foldersManager) return new Map();
return (foldersManager as FoldersManager).getSnapshot();
}, [foldersManager]);
const subscribeToTree = useCallback((callback: () => void) => {
if (!foldersManager) return () => { };
return (foldersManager as FoldersManager).subscribe(callback);
}, [foldersManager]);
const roots = useSyncExternalStore(subscribeToTree, getTreeSnapshot);
const folderMap = useSyncExternalStore(subscribeToTree, getFolderMap);
const [expandedIds, setExpandedIds] = React.useState<Set<string>>(new Set(['root']));
// Override validation or external toggle if needed
// Auto-expand ancestors when selected folder changes
React.useEffect(() => {
if (selectedFolder) {
// ensure parent paths are expanded?
// Without a parent map, we can't easily auto-expand up.
// Rely on user or initial state for now, or add parent pointers.
if (!selectedFolder || !folderMap) return;
const ancestors = new Set<string>();
let current = folderMap.get(String(selectedFolder));
while (current) {
const parentId = current.parentId || current.parent_id;
if (!parentId || parentId === 'root') break;
ancestors.add(String(parentId));
current = folderMap.get(String(parentId));
}
}, [selectedFolder]);
if (ancestors.size > 0) {
setExpandedIds((prev) => {
const next = new Set(prev);
let changed = false;
ancestors.forEach(id => {
if (!next.has(id)) {
next.add(id);
changed = true;
}
});
return changed ? next : prev;
});
}
}, [selectedFolder, folderMap]);
const handleToggle = useCallback((folderId: FolderIdentifier) => {
setExpandedIds((prev) => {
@@ -61,8 +99,7 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
}
return next;
});
onToggle(folderId); // propagate event just in case parent needs it
}, [onToggle]);
}, []);
const renderNodes = useCallback(
(nodes: FolderTreeNode[], depth: number): ReactNode =>
@@ -126,7 +163,7 @@ const SidebarFolderList: React.FC<SidebarFolderListProps> = ({
) : null}
</div>
<ul className="folder-tree">
{renderNodes(roots, 0)}
{renderNodes(roots as FolderTreeNode[], 0)}
</ul>
</div>
);
+14 -38
View File
@@ -2,14 +2,12 @@ import { useMemo } from 'react';
import type { DragEvent } from 'react';
import type { Identifier } from '../types/identifiers';
type FolderToggleHandler = (folderId: Identifier) => void | Promise<void>;
type FolderDropHandler = (
event: DragEvent<HTMLElement>,
folderId: Identifier,
) => void | Promise<void>;
interface FolderClickHandlers {
onToggle: FolderToggleHandler;
onSelect: (folderId: Identifier | null) => void;
onDrop: FolderDropHandler;
onDragOver: (event: DragEvent<HTMLElement>, folderId: Identifier) => void;
@@ -30,7 +28,6 @@ interface UseSidebarPropsArgs {
}
interface SidebarHookResult {
onToggle: FolderClickHandlers['onToggle'];
onSelect: FolderClickHandlers['onSelect'];
onDrop: FolderClickHandlers['onDrop'];
onDragOver: FolderClickHandlers['onDragOver'];
@@ -45,44 +42,23 @@ interface SidebarHookResult {
creatingFolder: boolean;
}
const useSidebarProps = ({
folderClickHandlers,
handleFolderDelete,
handleFolderRename,
selectedFolder,
handleFolderDragStart,
handleFolderDragEnd,
draggedFolderId,
handlePromptCreateFolder,
creatingFolder,
}: UseSidebarPropsArgs): SidebarHookResult =>
const useSidebarProps = (args: UseSidebarPropsArgs): SidebarHookResult =>
useMemo(
() => ({
onToggle: folderClickHandlers.onToggle,
onSelect: folderClickHandlers.onSelect,
onDrop: folderClickHandlers.onDrop,
onDragOver: folderClickHandlers.onDragOver,
onDragLeave: folderClickHandlers.onDragLeave,
onDeleteFolder: handleFolderDelete,
onRenameFolder: handleFolderRename,
selectedFolder,
onFolderDragStart: handleFolderDragStart,
onFolderDragEnd: handleFolderDragEnd,
draggedFolderId,
onCreateFolder: (parentId) => handlePromptCreateFolder?.(parentId),
creatingFolder,
onSelect: args.folderClickHandlers.onSelect,
onDrop: args.folderClickHandlers.onDrop,
onDragOver: args.folderClickHandlers.onDragOver,
onDragLeave: args.folderClickHandlers.onDragLeave,
onDeleteFolder: args.handleFolderDelete,
onRenameFolder: args.handleFolderRename,
selectedFolder: args.selectedFolder,
onFolderDragStart: args.handleFolderDragStart,
onFolderDragEnd: args.handleFolderDragEnd,
draggedFolderId: args.draggedFolderId,
onCreateFolder: (parentId) => args.handlePromptCreateFolder?.(parentId),
creatingFolder: args.creatingFolder,
}),
[
creatingFolder,
folderClickHandlers,
handleFolderDragEnd,
handleFolderDragStart,
handleFolderDelete,
handleFolderRename,
handlePromptCreateFolder,
draggedFolderId,
selectedFolder,
],
[args],
);
export default useSidebarProps;