1
1
import { useMemo , useState } from 'react'
2
2
import { useStore } from '@tanstack/react-store'
3
- import { FilterIcon } from 'lucide-react'
3
+ import { FileText , Folder } from 'lucide-react'
4
4
5
5
import FileViewer from './file-viewer'
6
6
import FileTree from './file-tree'
7
7
8
- import { Button } from '@/components/ui/button'
8
+ import type { FileTreeItem } from '@/types'
9
+
9
10
import { Label } from '@/components/ui/label'
10
11
import { Checkbox } from '@/components/ui/checkbox'
12
+ import { Separator } from '@/components/ui/separator'
11
13
import {
12
- Popover ,
13
- PopoverContent ,
14
- PopoverTrigger ,
15
- } from '@/components/ui/popover'
16
-
17
- import {
14
+ applicationMode ,
15
+ includeFiles ,
18
16
projectFiles ,
19
17
projectLocalFiles ,
20
- applicationMode ,
18
+ isInitialized ,
21
19
} from '@/store/project'
22
20
23
- // TODO: Add file filters
24
- export function DropdownMenuDemo ( ) {
21
+ import { getFileClass , twClasses } from '@/file-classes'
22
+
23
+ export function Filters ( ) {
24
+ const includedFiles = useStore ( includeFiles )
25
+
26
+ function toggleFilter (
27
+ filter : 'unchanged' | 'added' | 'modified' | 'deleted' | 'overwritten' ,
28
+ ) {
29
+ includeFiles . setState ( ( state ) => {
30
+ if ( state . includes ( filter ) ) {
31
+ return state . filter ( ( file ) => file !== filter )
32
+ }
33
+ return [ ...state , filter ]
34
+ } )
35
+ }
36
+
25
37
return (
26
- < Popover >
27
- < PopoverTrigger asChild >
28
- < Button variant = "outline" >
29
- < FilterIcon />
30
- Filter
31
- </ Button >
32
- </ PopoverTrigger >
33
- < PopoverContent className = "w-80 backdrop-blur-lg bg-opacity-50" >
34
- < div className = "grid gap-4" >
35
- < div className = "space-y-2" >
36
- < h4 className = "font-medium leading-none" > File Filters</ h4 >
37
- </ div >
38
- < div className = "flex flex-col gap-2" >
39
- < div className = "flex flex-row items-center gap-2" >
40
- < Checkbox id = "width" checked = { true } className = "w-6 h-6" />
41
- < Label htmlFor = "width" className = "text-lg" >
42
- All Files
43
- </ Label >
44
- </ div >
45
- </ div >
38
+ < >
39
+ < div className = "text-center text-sm border-b-1 mb-4" > File Filters</ div >
40
+ < div className = "flex flex-row flex-wrap gap-y-2" >
41
+ < div className = "flex flex-row items-center gap-2 w-1/3" >
42
+ < Checkbox
43
+ id = "unchanged"
44
+ checked = { includedFiles . includes ( 'unchanged' ) }
45
+ className = "w-4 h-4"
46
+ onCheckedChange = { ( ) => toggleFilter ( 'unchanged' ) }
47
+ />
48
+ < Label htmlFor = "unchanged" className = { twClasses . unchanged } >
49
+ Unchanged
50
+ </ Label >
51
+ </ div >
52
+ < div className = "flex flex-row items-center gap-2 w-1/3" >
53
+ < Checkbox
54
+ id = "added"
55
+ checked = { includedFiles . includes ( 'added' ) }
56
+ className = "w-4 h-4"
57
+ onCheckedChange = { ( ) => toggleFilter ( 'added' ) }
58
+ />
59
+ < Label htmlFor = "added" className = { twClasses . added } >
60
+ Added
61
+ </ Label >
62
+ </ div >
63
+ < div className = "flex flex-row items-center gap-2 w-1/3" >
64
+ < Checkbox
65
+ id = "modified"
66
+ checked = { includedFiles . includes ( 'modified' ) }
67
+ className = "w-4 h-4"
68
+ onCheckedChange = { ( ) => toggleFilter ( 'modified' ) }
69
+ />
70
+ < Label htmlFor = "modified" className = { twClasses . modified } >
71
+ Modified
72
+ </ Label >
73
+ </ div >
74
+ < div className = "flex flex-row items-center gap-2 w-1/3" >
75
+ < Checkbox
76
+ id = "deleted"
77
+ checked = { includedFiles . includes ( 'deleted' ) }
78
+ className = "w-4 h-4"
79
+ onCheckedChange = { ( ) => toggleFilter ( 'deleted' ) }
80
+ />
81
+ < Label htmlFor = "deleted" className = { twClasses . deleted } >
82
+ Deleted
83
+ </ Label >
84
+ </ div >
85
+ < div className = "flex flex-row items-center gap-2 w-1/3" >
86
+ < Checkbox
87
+ id = "overwritten"
88
+ checked = { includedFiles . includes ( 'overwritten' ) }
89
+ className = "w-4 h-4"
90
+ onCheckedChange = { ( ) => toggleFilter ( 'overwritten' ) }
91
+ />
92
+ < Label htmlFor = "overwritten" className = { twClasses . overwritten } >
93
+ Overwritten
94
+ </ Label >
46
95
</ div >
47
- </ PopoverContent >
48
- </ Popover >
96
+ </ div >
97
+ < Separator className = "my-4" />
98
+ </ >
49
99
)
50
100
}
51
101
@@ -55,57 +105,97 @@ export default function FileNavigator() {
55
105
)
56
106
57
107
const { output, originalOutput } = useStore ( projectFiles )
58
- const localFiles = useStore ( projectLocalFiles )
108
+ const localTree = useStore ( projectLocalFiles )
59
109
60
110
const mode = useStore ( applicationMode )
111
+ const tree = output . files
112
+ const originalTree = mode === 'setup' ? output . files : originalOutput . files
113
+ const deletedFiles = output . deletedFiles
61
114
62
- const { originalFileContents, modifiedFileContents } = useMemo ( ( ) => {
63
- if ( ! selectedFile ) {
64
- return {
65
- originalFileContents : undefined ,
66
- modifiedFileContents : undefined ,
67
- }
68
- }
69
- if ( mode === 'add' ) {
70
- if ( localFiles [ selectedFile ] ) {
71
- if ( ! output . files [ selectedFile ] ) {
72
- return {
73
- originalFileContents : undefined ,
74
- modifiedFileContents : localFiles [ selectedFile ] ,
75
- }
115
+ const [ originalFileContents , setOriginalFileContents ] = useState < string > ( )
116
+ const [ modifiedFileContents , setModifiedFileContents ] = useState < string > ( )
117
+
118
+ const includedFiles = useStore ( includeFiles )
119
+
120
+ const fileTree = useMemo ( ( ) => {
121
+ const treeData : Array < FileTreeItem > = [ ]
122
+
123
+ const allFileSet = Array . from (
124
+ new Set ( [
125
+ ...Object . keys ( tree ) ,
126
+ ...Object . keys ( localTree ) ,
127
+ ...Object . keys ( originalTree ) ,
128
+ ] ) ,
129
+ )
130
+
131
+ allFileSet . sort ( ) . forEach ( ( file ) => {
132
+ const strippedFile = file . replace ( './' , '' )
133
+ const parts = strippedFile . split ( '/' )
134
+
135
+ let currentLevel = treeData
136
+ parts . forEach ( ( part , index ) => {
137
+ const existingNode = currentLevel . find ( ( node ) => node . name === part )
138
+ if ( existingNode ) {
139
+ currentLevel = existingNode . children || [ ]
76
140
} else {
77
- return {
78
- originalFileContents : localFiles [ selectedFile ] ,
79
- modifiedFileContents : output . files [ selectedFile ] ,
141
+ const fileInfo = getFileClass (
142
+ file ,
143
+ tree ,
144
+ originalTree ,
145
+ localTree ,
146
+ deletedFiles ,
147
+ )
148
+
149
+ if (
150
+ index === parts . length - 1 &&
151
+ ! includedFiles . includes ( fileInfo . fileClass )
152
+ ) {
153
+ return
80
154
}
155
+ if ( index === parts . length - 1 && file === selectedFile ) {
156
+ setModifiedFileContents ( fileInfo . modifiedFile )
157
+ setOriginalFileContents ( fileInfo . originalFile )
158
+ }
159
+
160
+ const newNode : FileTreeItem = {
161
+ id : parts . slice ( 0 , index + 1 ) . join ( '/' ) ,
162
+ name : part ,
163
+ fullPath : strippedFile ,
164
+ children : index < parts . length - 1 ? [ ] : undefined ,
165
+ icon :
166
+ index < parts . length - 1
167
+ ? ( ) => < Folder className = "w-4 h-4 mr-2" />
168
+ : ( ) => < FileText className = "w-4 h-4 mr-2" /> ,
169
+ onClick :
170
+ index === parts . length - 1
171
+ ? ( ) => {
172
+ setSelectedFile ( file )
173
+ setModifiedFileContents ( fileInfo . modifiedFile )
174
+ setOriginalFileContents ( fileInfo . originalFile )
175
+ }
176
+ : undefined ,
177
+ className : twClasses [ fileInfo . fileClass ] ,
178
+ ...fileInfo ,
179
+ contents : tree [ file ] || localTree [ file ] || originalTree [ file ] ,
180
+ }
181
+ currentLevel . push ( newNode )
182
+ currentLevel = newNode . children !
81
183
}
82
- } else {
83
- return {
84
- originalFileContents : originalOutput . files [ selectedFile ] ,
85
- modifiedFileContents : output . files [ selectedFile ] ,
86
- }
87
- }
88
- } else {
89
- return {
90
- modifiedFileContents : output . files [ selectedFile ] ,
91
- }
92
- }
93
- } , [ mode , selectedFile , output . files , originalOutput . files , localFiles ] )
184
+ } )
185
+ } )
186
+ return treeData
187
+ } , [ tree , originalTree , localTree , includedFiles ] )
188
+
189
+ const ready = useStore ( isInitialized )
190
+ if ( ! ready ) {
191
+ return null
192
+ }
94
193
95
194
return (
96
195
< div className = "flex flex-row w-[calc(100vw-450px)]" >
97
196
< div className = "w-1/4 max-w-1/4 pr-2" >
98
- < DropdownMenuDemo />
99
- < FileTree
100
- selectedFile = { selectedFile }
101
- prefix = "./"
102
- tree = { output . files }
103
- originalTree = { mode === 'setup' ? output . files : originalOutput . files }
104
- localTree = { localFiles }
105
- onFileSelected = { ( file ) => {
106
- setSelectedFile ( file )
107
- } }
108
- />
197
+ { mode === 'add' && < Filters /> }
198
+ < FileTree selectedFile = { selectedFile } tree = { fileTree } />
109
199
</ div >
110
200
< div className = "max-w-3/4 w-3/4 pl-2" >
111
201
{ selectedFile && modifiedFileContents ? (
0 commit comments