@@ -28,30 +28,59 @@ type ContextMenu = {
28
28
clientY : number ;
29
29
} ;
30
30
31
- interface FileTreeViewProps {
31
+ interface TemplateFilesTreeProps {
32
32
onSelect : ( path : string ) => void ;
33
- onDelete : ( path : string ) => void ;
34
- onRename : ( path : string ) => void ;
33
+ onDelete ? : ( path : string ) => void ;
34
+ onRename ? : ( path : string ) => void ;
35
35
fileTree : FileTree ;
36
36
activePath ?: string ;
37
+ Label ?: FC < {
38
+ path : string ;
39
+ filename : string ;
40
+ label : string ;
41
+ isFolder : boolean ;
42
+ } > ;
37
43
}
38
44
39
- export const FileTreeView : FC < FileTreeViewProps > = ( {
45
+ export const TemplateFileTree : FC < TemplateFilesTreeProps > = ( {
40
46
fileTree,
41
47
activePath,
42
48
onDelete,
43
49
onRename,
44
50
onSelect,
51
+ Label,
45
52
} ) => {
46
53
const [ contextMenu , setContextMenu ] = useState < ContextMenu | undefined > ( ) ;
54
+
55
+ const isFolder = ( content ?: FileTree | string ) : content is FileTree =>
56
+ typeof content === "object" ;
57
+
47
58
const buildTreeItems = (
59
+ label : string ,
48
60
filename : string ,
49
61
content ?: FileTree | string ,
50
62
parentPath ?: string ,
51
63
) : JSX . Element => {
52
64
const currentPath = parentPath ? `${ parentPath } /${ filename } ` : filename ;
53
- const isFolder = typeof content === "object" ;
54
- let icon : JSX . Element | null = isFolder ? null : (
65
+ // Used to group empty folders in one single label like VSCode does
66
+ const shouldGroupFolder =
67
+ isFolder ( content ) &&
68
+ Object . keys ( content ) . length === 1 &&
69
+ isFolder ( Object . values ( content ) [ 0 ] ) ;
70
+ const isHiddenFile = currentPath . startsWith ( "." ) ;
71
+
72
+ if ( shouldGroupFolder ) {
73
+ const firstChildFileName = Object . keys ( content ) [ 0 ] ;
74
+ const child = content [ firstChildFileName ] ;
75
+ return buildTreeItems (
76
+ `${ label } / ${ firstChildFileName } ` ,
77
+ firstChildFileName ,
78
+ child ,
79
+ currentPath ,
80
+ ) ;
81
+ }
82
+
83
+ let icon : JSX . Element | null = isFolder ( content ) ? null : (
55
84
< FormatAlignLeftOutlined />
56
85
) ;
57
86
@@ -69,26 +98,40 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
69
98
< TreeItem
70
99
nodeId = { currentPath }
71
100
key = { currentPath }
72
- label = { filename }
101
+ label = {
102
+ Label ? (
103
+ < Label
104
+ path = { currentPath }
105
+ label = { label }
106
+ filename = { filename }
107
+ isFolder = { isFolder ( content ) }
108
+ />
109
+ ) : (
110
+ label
111
+ )
112
+ }
73
113
css = { ( theme ) => css `
74
114
overflow : hidden;
75
115
user-select : none;
76
116
77
117
& > .MuiTreeItem-content {
78
118
padding : 2px 16px ;
79
- color : ${ theme . palette . text . secondary } ;
119
+ color : ${ isHiddenFile
120
+ ? theme . palette . text . disabled
121
+ : theme . palette . text . secondary } ;
80
122
height : 32px ;
81
123
82
124
& svg {
83
125
width : 12px ;
84
126
height : 12px ;
85
- color : ${ theme . palette . text . secondary } ;
127
+ color : currentColor ;
86
128
}
87
129
88
130
& > .MuiTreeItem-label {
89
131
margin-left : 4px ;
90
132
font-size : 13px ;
91
133
color : inherit;
134
+ white-space : nowrap;
92
135
}
93
136
94
137
& .Mui-selected {
@@ -103,17 +146,22 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
103
146
104
147
& .MuiTreeItem-group {
105
148
margin-left : 0 ;
149
+ position : relative;
106
150
107
151
// We need to find a better way to recursive padding here
108
152
& .MuiTreeItem-content {
109
- padding-left : calc (var (--level ) * 40 px );
153
+ padding-left : calc (8 px + ( var (--level ) + 1 ) * 8 px );
110
154
}
111
155
}
112
156
` }
113
157
onClick = { ( ) => {
114
158
onSelect ( currentPath ) ;
115
159
} }
116
160
onContextMenu = { ( event ) => {
161
+ const hasContextActions = onRename || onDelete ;
162
+ if ( ! hasContextActions ) {
163
+ return ;
164
+ }
117
165
event . preventDefault ( ) ; // Avoid default browser behavior
118
166
event . stopPropagation ( ) ; // Avoid trigger parent context menu
119
167
setContextMenu (
@@ -133,12 +181,12 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
133
181
} as CSSProperties
134
182
}
135
183
>
136
- { isFolder &&
184
+ { isFolder ( content ) &&
137
185
Object . keys ( content )
138
186
. sort ( sortFileTree ( content ) )
139
187
. map ( ( filename ) => {
140
188
const child = content [ filename ] ;
141
- return buildTreeItems ( filename , child , currentPath ) ;
189
+ return buildTreeItems ( filename , filename , child , currentPath ) ;
142
190
} ) }
143
191
</ TreeItem >
144
192
) ;
@@ -149,13 +197,14 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
149
197
defaultCollapseIcon = { < ExpandMoreIcon /> }
150
198
defaultExpandIcon = { < ChevronRightIcon /> }
151
199
aria-label = "Files"
200
+ defaultExpanded = { activePath ? expandablePaths ( activePath ) : [ ] }
152
201
defaultSelected = { activePath }
153
202
>
154
203
{ Object . keys ( fileTree )
155
204
. sort ( sortFileTree ( fileTree ) )
156
205
. map ( ( filename ) => {
157
206
const child = fileTree [ filename ] ;
158
- return buildTreeItems ( filename , child ) ;
207
+ return buildTreeItems ( filename , filename , child ) ;
159
208
} ) }
160
209
161
210
< Menu
@@ -184,7 +233,7 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
184
233
if ( ! contextMenu ) {
185
234
return ;
186
235
}
187
- onRename ( contextMenu . path ) ;
236
+ onRename && onRename ( contextMenu . path ) ;
188
237
setContextMenu ( undefined ) ;
189
238
} }
190
239
>
@@ -195,7 +244,7 @@ export const FileTreeView: FC<FileTreeViewProps> = ({
195
244
if ( ! contextMenu ) {
196
245
return ;
197
246
}
198
- onDelete ( contextMenu . path ) ;
247
+ onDelete && onDelete ( contextMenu . path ) ;
199
248
setContextMenu ( undefined ) ;
200
249
} }
201
250
>
@@ -232,3 +281,12 @@ const FileTypeMarkdown: FC = () => (
232
281
< polygon points = "22.955 20.636 18.864 16.136 21.591 16.136 21.591 11.364 24.318 11.364 24.318 16.136 27.045 16.136 22.955 20.636" />
233
282
</ svg >
234
283
) ;
284
+
285
+ const expandablePaths = ( path : string ) => {
286
+ const paths = path . split ( "/" ) ;
287
+ const result = [ ] ;
288
+ for ( let i = 1 ; i < paths . length ; i ++ ) {
289
+ result . push ( paths . slice ( 0 , i ) . join ( "/" ) ) ;
290
+ }
291
+ return result ;
292
+ } ;
0 commit comments