Skip to content

Commit 97baf88

Browse files
committed
chore: more UI updates
1 parent 28ba2c9 commit 97baf88

File tree

12 files changed

+362
-268
lines changed

12 files changed

+362
-268
lines changed

packages/cta-engine/src/custom-add-ons/shared.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ export const IGNORE_FILES = [
2727
'dist',
2828
'node_modules',
2929
'package-lock.json',
30-
'package.json',
3130
'pnpm-lock.yaml',
3231
'starter.json',
3332
'starter-info.json',
3433
'yarn.lock',
3534
]
3635

37-
export function createIgnore(path: string) {
36+
const PROJECT_FILES = ['package.json']
37+
38+
export function createIgnore(path: string, includeProjectFiles = true) {
3839
const ignoreList = existsSync(resolve(path, '.gitignore'))
3940
? (
4041
parseGitignore(
@@ -45,7 +46,10 @@ export function createIgnore(path: string) {
4546
const ig = ignore().add(ignoreList)
4647
return (filePath: string) => {
4748
const fileName = basename(filePath)
48-
if (IGNORE_FILES.includes(fileName)) {
49+
if (
50+
IGNORE_FILES.includes(fileName) ||
51+
(includeProjectFiles && PROJECT_FILES.includes(fileName))
52+
) {
4953
return true
5054
}
5155
const nameWithoutDotSlash = fileName.replace(/^\.\//, '')
@@ -78,8 +82,11 @@ async function recursivelyGatherFilesHelper(
7882
}
7983
}
8084

81-
export async function recursivelyGatherFiles(path: string) {
82-
const ignore = createIgnore(path)
85+
export async function recursivelyGatherFiles(
86+
path: string,
87+
includeProjectFiles = true,
88+
) {
89+
const ignore = createIgnore(path, includeProjectFiles)
8390
const files: Record<string, string> = {}
8491
await recursivelyGatherFilesHelper(path, path, files, ignore)
8592
return files

packages/cta-ui/src/components/cta-sidebar.tsx

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useStore } from '@tanstack/react-store'
12
import {
23
Sidebar,
34
SidebarContent,
@@ -15,25 +16,33 @@ import ModeSelector from '@/components/sidebar-items/mode-selector'
1516
import TypescriptSwitch from '@/components/sidebar-items/typescript-switch'
1617
import StarterDialog from '@/components/sidebar-items/starter'
1718

19+
import { isInitialized } from '@/store/project'
20+
1821
export function AppSidebar() {
22+
const ready = useStore(isInitialized)
23+
1924
return (
2025
<Sidebar>
2126
<SidebarHeader className="flex justify-center items-center">
2227
<img src="/tanstack.png" className="w-3/5" />
2328
</SidebarHeader>
2429
<SidebarContent>
25-
<SidebarGroup>
26-
<ProjectName />
27-
<ModeSelector />
28-
<TypescriptSwitch />
29-
</SidebarGroup>
30-
<SidebarGroup>
31-
<SidebarGroupLabel>Add-ons</SidebarGroupLabel>
32-
<SelectedAddOns />
33-
</SidebarGroup>
34-
<SidebarGroup>
35-
<StarterDialog />
36-
</SidebarGroup>
30+
{ready && (
31+
<>
32+
<SidebarGroup>
33+
<ProjectName />
34+
<ModeSelector />
35+
<TypescriptSwitch />
36+
</SidebarGroup>
37+
<SidebarGroup>
38+
<SidebarGroupLabel>Add-ons</SidebarGroupLabel>
39+
<SelectedAddOns />
40+
</SidebarGroup>
41+
<SidebarGroup>
42+
<StarterDialog />
43+
</SidebarGroup>
44+
</>
45+
)}
3746
</SidebarContent>
3847
<SidebarFooter>
3948
<RunAddOns />

packages/cta-ui/src/components/file-navigator.tsx

Lines changed: 164 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,101 @@
11
import { useMemo, useState } from 'react'
22
import { useStore } from '@tanstack/react-store'
3-
import { FilterIcon } from 'lucide-react'
3+
import { FileText, Folder } from 'lucide-react'
44

55
import FileViewer from './file-viewer'
66
import FileTree from './file-tree'
77

8-
import { Button } from '@/components/ui/button'
8+
import type { FileTreeItem } from '@/types'
9+
910
import { Label } from '@/components/ui/label'
1011
import { Checkbox } from '@/components/ui/checkbox'
12+
import { Separator } from '@/components/ui/separator'
1113
import {
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

Comments
 (0)