22 * FileTree - Displays file hierarchy with diff statistics
33 */
44
5- import React , { useState } from "react" ;
5+ import React from "react" ;
66import styled from "@emotion/styled" ;
77import type { FileTreeNode } from "@/utils/git/numstatParser" ;
8+ import { usePersistedState } from "@/hooks/usePersistedState" ;
9+ import { getFileTreeExpandStateKey } from "@/constants/storage" ;
810
911const TreeContainer = styled . div `
1012 flex: 1;
@@ -194,8 +196,30 @@ const TreeNodeContent: React.FC<{
194196 onSelectFile : ( path : string | null ) => void ;
195197 commonPrefix : string | null ;
196198 getFileReadStatus ?: ( filePath : string ) => { total : number ; read : number } | null ;
197- } > = ( { node, depth, selectedPath, onSelectFile, commonPrefix, getFileReadStatus } ) => {
198- const [ isOpen , setIsOpen ] = useState ( depth < 2 ) ; // Auto-expand first 2 levels
199+ expandStateMap : Record < string , boolean > ;
200+ setExpandStateMap : (
201+ value : Record < string , boolean > | ( ( prev : Record < string , boolean > ) => Record < string , boolean > )
202+ ) => void ;
203+ } > = ( {
204+ node,
205+ depth,
206+ selectedPath,
207+ onSelectFile,
208+ commonPrefix,
209+ getFileReadStatus,
210+ expandStateMap,
211+ setExpandStateMap,
212+ } ) => {
213+ // Check if user has manually set expand state for this directory
214+ const hasManualState = node . path in expandStateMap ;
215+ const isOpen = hasManualState ? expandStateMap [ node . path ] : depth < 2 ; // Default: auto-expand first 2 levels
216+
217+ const setIsOpen = ( open : boolean ) => {
218+ setExpandStateMap ( ( prev ) => ( {
219+ ...prev ,
220+ [ node . path ] : open ,
221+ } ) ) ;
222+ } ;
199223
200224 const handleClick = ( e : React . MouseEvent ) => {
201225 if ( node . isDirectory ) {
@@ -295,6 +319,8 @@ const TreeNodeContent: React.FC<{
295319 onSelectFile = { onSelectFile }
296320 commonPrefix = { commonPrefix }
297321 getFileReadStatus = { getFileReadStatus }
322+ expandStateMap = { expandStateMap }
323+ setExpandStateMap = { setExpandStateMap }
298324 />
299325 ) ) }
300326 </ >
@@ -308,6 +334,7 @@ interface FileTreeExternalProps {
308334 isLoading ?: boolean ;
309335 commonPrefix ?: string | null ;
310336 getFileReadStatus ?: ( filePath : string ) => { total : number ; read : number } | null ;
337+ workspaceId : string ;
311338}
312339
313340export const FileTree : React . FC < FileTreeExternalProps > = ( {
@@ -317,7 +344,15 @@ export const FileTree: React.FC<FileTreeExternalProps> = ({
317344 isLoading = false ,
318345 commonPrefix = null ,
319346 getFileReadStatus,
347+ workspaceId,
320348} ) => {
349+ // Use persisted state for expand/collapse per workspace (lifted to parent to avoid O(n) re-renders)
350+ const [ expandStateMap , setExpandStateMap ] = usePersistedState < Record < string , boolean > > (
351+ getFileTreeExpandStateKey ( workspaceId ) ,
352+ { } ,
353+ { listener : true }
354+ ) ;
355+
321356 // Find the node at the common prefix path to start rendering from
322357 const startNode = React . useMemo ( ( ) => {
323358 if ( ! commonPrefix || ! root ) return root ;
@@ -355,6 +390,8 @@ export const FileTree: React.FC<FileTreeExternalProps> = ({
355390 onSelectFile = { onSelectFile }
356391 commonPrefix = { commonPrefix }
357392 getFileReadStatus = { getFileReadStatus }
393+ expandStateMap = { expandStateMap }
394+ setExpandStateMap = { setExpandStateMap }
358395 />
359396 ) )
360397 ) : (
0 commit comments