11import { useMemo , useState } from 'react'
22import { useStore } from '@tanstack/react-store'
3- import { FilterIcon } from 'lucide-react'
3+ import { FileText , Folder } from 'lucide-react'
44
55import FileViewer from './file-viewer'
66import FileTree from './file-tree'
77
8- import { Button } from '@/components/ui/button'
8+ import type { FileTreeItem } from '@/types'
9+
910import { Label } from '@/components/ui/label'
1011import { Checkbox } from '@/components/ui/checkbox'
12+ import { Separator } from '@/components/ui/separator'
1113import {
12- Popover ,
13- PopoverContent ,
14- PopoverTrigger ,
15- } from '@/components/ui/popover'
16-
17- import {
14+ applicationMode ,
15+ includeFiles ,
1816 projectFiles ,
1917 projectLocalFiles ,
20- applicationMode ,
18+ isInitialized ,
2119} from '@/store/project'
2220
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+
2537 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 >
4695 </ div >
47- </ PopoverContent >
48- </ Popover >
96+ </ div >
97+ < Separator className = "my-4" />
98+ </ >
4999 )
50100}
51101
@@ -55,57 +105,97 @@ export default function FileNavigator() {
55105 )
56106
57107 const { output, originalOutput } = useStore ( projectFiles )
58- const localFiles = useStore ( projectLocalFiles )
108+ const localTree = useStore ( projectLocalFiles )
59109
60110 const mode = useStore ( applicationMode )
111+ const tree = output . files
112+ const originalTree = mode === 'setup' ? output . files : originalOutput . files
113+ const deletedFiles = output . deletedFiles
61114
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 || [ ]
76140 } 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
80154 }
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 !
81183 }
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+ }
94193
95194 return (
96195 < div className = "flex flex-row w-[calc(100vw-450px)]" >
97196 < 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 } />
109199 </ div >
110200 < div className = "max-w-3/4 w-3/4 pl-2" >
111201 { selectedFile && modifiedFileContents ? (
0 commit comments