1+ import { useEffect , useMemo , useState } from 'react' ;
12import { View } from 'react-native' ;
23
3- import { type CodeBrowserTreeDirectory } from '~/types' ;
4+ import { type CodeBrowserTreeDirectory , type CodeBrowserTreeFile } from '~/types' ;
45
56import CodeBrowserFileRow from './CodeBrowserFileRow' ;
67
@@ -10,6 +11,7 @@ type Props = {
1011 onSelectFile : ( filePath : string ) => void ;
1112 depth ?: number ;
1213 isNested ?: boolean ;
14+ isSearchActive ?: boolean ;
1315} ;
1416
1517export default function CodeBrowserFileTree ( {
@@ -18,27 +20,26 @@ export default function CodeBrowserFileTree({
1820 onSelectFile,
1921 depth = 0 ,
2022 isNested = false ,
23+ isSearchActive = false ,
2124} : Props ) {
22- const directories = Object . values ( tree . directories ) . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
25+ const directories = useMemo (
26+ ( ) => Object . values ( tree . directories ) . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ,
27+ [ tree . directories ]
28+ ) ;
2329 const files = [ ...tree . files ] . sort ( ( a , b ) => a . name . localeCompare ( b . name ) ) ;
2430
2531 return (
2632 < >
27- { directories . map ( directory => {
28- const collapsedDirectory = collapseDirectoryPath ( directory ) ;
29-
30- return (
31- < View key = { directory . path } >
32- < CodeBrowserFileRow label = { collapsedDirectory . label } depth = { depth } isDirectory />
33- < CodeBrowserFileTree
34- tree = { collapsedDirectory . directory }
35- activeFile = { activeFile }
36- onSelectFile = { onSelectFile }
37- depth = { depth + 1 }
38- />
39- </ View >
40- ) ;
41- } ) }
33+ { directories . map ( directory => (
34+ < CodeBrowserDirectoryRow
35+ key = { directory . path }
36+ directory = { directory }
37+ activeFile = { activeFile }
38+ onSelectFile = { onSelectFile }
39+ depth = { depth }
40+ isSearchActive = { isSearchActive }
41+ />
42+ ) ) }
4243 { files . map ( file => (
4344 < View key = { file . path } >
4445 < CodeBrowserFileRow
@@ -60,6 +61,7 @@ export default function CodeBrowserFileTree({
6061 onSelectFile = { onSelectFile }
6162 depth = { depth + 1 }
6263 isNested
64+ isSearchActive = { isSearchActive }
6365 />
6466 ) }
6567 </ View >
@@ -68,22 +70,93 @@ export default function CodeBrowserFileTree({
6870 ) ;
6971}
7072
71- function collapseDirectoryPath ( directory : CodeBrowserTreeDirectory ) {
72- const pathSegments = [ directory . name ] ;
73- let collapsedDirectory = directory ;
73+ type CodeBrowserDirectoryRowProps = {
74+ directory : CodeBrowserTreeDirectory ;
75+ activeFile : string | null ;
76+ onSelectFile : ( filePath : string ) => void ;
77+ depth : number ;
78+ isSearchActive : boolean ;
79+ } ;
80+
81+ function CodeBrowserDirectoryRow ( {
82+ directory,
83+ activeFile,
84+ onSelectFile,
85+ depth,
86+ isSearchActive,
87+ } : CodeBrowserDirectoryRowProps ) {
88+ const [ collapsed , setCollapsed ] = useState ( false ) ;
89+
90+ const collapsedDirectory = useMemo ( ( ) => {
91+ const pathSegments = [ directory . name ] ;
92+ let collapsedDirectory = directory ;
7493
75- while (
76- collapsedDirectory . files . length === 0 &&
77- Object . keys ( collapsedDirectory . directories ) . length === 1
78- ) {
79- const [ nextDirectory ] = Object . values ( collapsedDirectory . directories ) ;
94+ while (
95+ collapsedDirectory . files . length === 0 &&
96+ Object . keys ( collapsedDirectory . directories ) . length === 1
97+ ) {
98+ const [ nextDirectory ] = Object . values ( collapsedDirectory . directories ) ;
8099
81- pathSegments . push ( nextDirectory . name ) ;
82- collapsedDirectory = nextDirectory ;
100+ pathSegments . push ( nextDirectory . name ) ;
101+ collapsedDirectory = nextDirectory ;
102+ }
103+
104+ return {
105+ directory : collapsedDirectory ,
106+ label : pathSegments . join ( '/' ) ,
107+ } ;
108+ } , [ directory ] ) ;
109+
110+ const shouldForceExpand =
111+ isSearchActive || directoryContainsFile ( collapsedDirectory . directory , activeFile ) ;
112+
113+ useEffect ( ( ) => {
114+ if ( shouldForceExpand ) {
115+ setCollapsed ( false ) ;
116+ }
117+ } , [ shouldForceExpand ] ) ;
118+
119+ return (
120+ < View >
121+ < CodeBrowserFileRow
122+ label = { collapsedDirectory . label }
123+ depth = { depth }
124+ isDirectory
125+ isCollapsed = { collapsed }
126+ onPress = { ( ) => setCollapsed ( currentCollapsed => ! currentCollapsed ) }
127+ />
128+ { ! collapsed && (
129+ < CodeBrowserFileTree
130+ tree = { collapsedDirectory . directory }
131+ activeFile = { activeFile }
132+ onSelectFile = { onSelectFile }
133+ depth = { depth + 1 }
134+ isSearchActive = { isSearchActive }
135+ />
136+ ) }
137+ </ View >
138+ ) ;
139+ }
140+
141+ function directoryContainsFile (
142+ directory : CodeBrowserTreeDirectory ,
143+ activeFile : string | null
144+ ) : boolean {
145+ if ( ! activeFile ) {
146+ return false ;
83147 }
84148
85- return {
86- directory : collapsedDirectory ,
87- label : pathSegments . join ( '/' ) ,
88- } ;
149+ return (
150+ directory . files . some ( file => fileContainsPath ( file , activeFile ) ) ||
151+ Object . values ( directory . directories ) . some ( childDirectory =>
152+ directoryContainsFile ( childDirectory , activeFile )
153+ )
154+ ) ;
155+ }
156+
157+ function fileContainsPath ( file : CodeBrowserTreeFile , activeFile : string ) : boolean {
158+ return (
159+ file . path === activeFile ||
160+ file . nestedFiles ?. some ( nestedFile => fileContainsPath ( nestedFile , activeFile ) ) === true
161+ ) ;
89162}
0 commit comments