Skip to content

Commit b89e6b1

Browse files
committed
chore: client side data loading
1 parent aaf22b6 commit b89e6b1

File tree

10 files changed

+222
-89
lines changed

10 files changed

+222
-89
lines changed

packages/cta-engine/src/add-to-app.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ function isDirectory(path: string) {
2121
return statSync(path).isDirectory()
2222
}
2323

24-
async function hasPendingGitChanges() {
25-
const status = await execaSync('git', ['status', '--porcelain'])
26-
return status.stdout.length > 0
27-
}
24+
// async function hasPendingGitChanges() {
25+
// const status = await execaSync('git', ['status', '--porcelain'])
26+
// return status.stdout.length > 0
27+
// }
2828

2929
async function createOptions(
3030
json: PersistedOptions,
@@ -75,13 +75,13 @@ export async function addToApp(
7575
environment.intro(`Adding ${addOns.join(', ')} to the project...`)
7676
}
7777

78-
if (await hasPendingGitChanges()) {
79-
environment.error(
80-
'You have pending git changes.',
81-
'Please commit or stash them before adding add-ons.',
82-
)
83-
return
84-
}
78+
// if (await hasPendingGitChanges()) {
79+
// environment.error(
80+
// 'You have pending git changes.',
81+
// 'Please commit or stash them before adding add-ons.',
82+
// )
83+
// return
84+
// }
8585

8686
const newOptions = await createOptions(persistedOptions, addOns)
8787

packages/cta-engine/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ export { formatCommand } from './utils.js'
3434

3535
export { initStarter, compileStarter } from './custom-add-ons/starter.js'
3636
export { initAddOn, compileAddOn } from './custom-add-ons/add-on.js'
37-
export { createAppOptionsFromPersisted } from './custom-add-ons/shared.js'
37+
export {
38+
createAppOptionsFromPersisted,
39+
recursivelyGatherFiles,
40+
} from './custom-add-ons/shared.js'
3841

3942
export type {
4043
AddOn,

packages/cta-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"devDependencies": {
5454
"@testing-library/dom": "^10.4.0",
5555
"@testing-library/react": "^16.2.0",
56-
"@types/node": "^22.13.14",
56+
"@types/node": "^22.14.1",
5757
"@types/react": "^19.0.8",
5858
"@types/react-dom": "^19.0.3",
5959
"@vitejs/plugin-react": "^4.3.4",

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

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,45 @@ import { useStore } from '@tanstack/react-store'
44
import FileViewer from './file-viewer'
55
import FileTree from './file-tree'
66

7-
import { projectFiles } from '@/store/project'
7+
import { projectFiles, projectLocalFiles } from '@/store/project'
88

99
export default function FileNavigator() {
1010
const [selectedFile, setSelectedFile] = useState<string | null>(null)
11+
const [originalFileContents, setOriginalFileContents] = useState<string>()
12+
const [modifiedFileContents, setModifiedFileContents] = useState<string>()
1113

1214
const { output, originalOutput } = useStore(projectFiles)
15+
const localFiles = useStore(projectLocalFiles)
1316

1417
return (
15-
<div className="flex flex-row w-[calc(100vw-300px)]">
18+
<div className="flex flex-row w-[calc(100vw-450px)]">
1619
<FileTree
1720
prefix="./"
1821
tree={output.files}
1922
originalTree={originalOutput.files}
23+
localTree={localFiles}
2024
onFileSelected={(file) => {
2125
setSelectedFile(file)
26+
if (localFiles[file]) {
27+
if (!output.files[file]) {
28+
setOriginalFileContents(undefined)
29+
setModifiedFileContents(localFiles[file])
30+
} else {
31+
setOriginalFileContents(localFiles[file])
32+
setModifiedFileContents(output.files[file])
33+
}
34+
} else {
35+
setOriginalFileContents(originalOutput.files[file])
36+
setModifiedFileContents(output.files[file])
37+
}
2238
}}
2339
/>
2440
<div className="max-w-3/4 w-3/4 pl-2">
25-
{selectedFile ? (
41+
{selectedFile && modifiedFileContents ? (
2642
<FileViewer
2743
filePath={selectedFile}
28-
originalFile={originalOutput.files[selectedFile]}
29-
modifiedFile={output.files[selectedFile]}
44+
originalFile={originalFileContents}
45+
modifiedFile={modifiedFileContents}
3046
/>
3147
) : null}
3248
</div>

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

Lines changed: 64 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,21 @@ import type { TreeDataItem } from '@/components/ui/tree-view'
55

66
import { TreeView } from '@/components/ui/tree-view'
77

8+
type FileTreeItem = TreeDataItem & {
9+
contents: string
10+
}
11+
812
export default function FileTree({
913
prefix,
1014
tree,
1115
originalTree,
16+
localTree,
1217
onFileSelected,
1318
}: {
1419
prefix: string
1520
tree: Record<string, string>
1621
originalTree: Record<string, string>
22+
localTree: Record<string, string>
1723
onFileSelected: (file: string) => void
1824
}) {
1925
const computedTree = useMemo(() => {
@@ -33,43 +39,69 @@ export default function FileTree({
3339
return tree[file] !== originalTree[file]
3440
}
3541

36-
Object.keys(tree)
37-
.sort()
38-
.forEach((file) => {
39-
const parts = file.split('/')
42+
const allFileSet = Array.from(
43+
new Set([
44+
...Object.keys(tree),
45+
...Object.keys(localTree),
46+
...Object.keys(originalTree),
47+
]),
48+
)
49+
50+
allFileSet.sort().forEach((file) => {
51+
const parts = file.split('/')
4052

41-
let currentLevel = treeData
42-
parts.forEach((part, index) => {
43-
const existingNode = currentLevel.find((node) => node.name === part)
44-
if (existingNode) {
45-
currentLevel = existingNode.children || []
46-
} else {
47-
const newNode: TreeDataItem = {
48-
id: index === parts.length - 1 ? file : `${file}-${index}`,
49-
name: part,
50-
children: index < parts.length - 1 ? [] : undefined,
51-
icon:
52-
index < parts.length - 1
53-
? () => <Folder className="w-4 h-4 mr-2" />
54-
: () => <FileText className="w-4 h-4 mr-2" />,
55-
onClick:
56-
index === parts.length - 1
57-
? () => {
58-
onFileSelected(file)
59-
}
60-
: undefined,
61-
className:
62-
index === parts.length - 1 && changed(file)
63-
? 'text-green-300'
64-
: '',
53+
let currentLevel = treeData
54+
parts.forEach((part, index) => {
55+
const existingNode = currentLevel.find((node) => node.name === part)
56+
if (existingNode) {
57+
currentLevel = existingNode.children || []
58+
} else {
59+
let color = ''
60+
if (parts.length - 1) {
61+
if (localTree[file]) {
62+
if (tree[file]) {
63+
if (localTree[file] !== tree[file]) {
64+
if (
65+
originalTree[file] &&
66+
localTree[file] !== originalTree[file]
67+
) {
68+
color = 'text-red-500 font-bold'
69+
} else {
70+
color = 'text-green-500 font-bold'
71+
}
72+
}
73+
} else {
74+
color = 'text-blue-500 font-bold'
75+
}
76+
} else {
77+
color = changed(file) ? 'text-green-500 font-bold' : ''
6578
}
66-
currentLevel.push(newNode)
67-
currentLevel = newNode.children!
6879
}
69-
})
80+
81+
const newNode: FileTreeItem = {
82+
id: index === parts.length - 1 ? file : `${file}-${index}`,
83+
name: part,
84+
children: index < parts.length - 1 ? [] : undefined,
85+
icon:
86+
index < parts.length - 1
87+
? () => <Folder className="w-4 h-4 mr-2" />
88+
: () => <FileText className="w-4 h-4 mr-2" />,
89+
onClick:
90+
index === parts.length - 1
91+
? () => {
92+
onFileSelected(file)
93+
}
94+
: undefined,
95+
className: color,
96+
contents: tree[file] || localTree[file] || originalTree[file],
97+
}
98+
currentLevel.push(newNode)
99+
currentLevel = newNode.children!
100+
}
70101
})
102+
})
71103
return treeData
72-
}, [prefix, tree, originalTree])
104+
}, [prefix, tree, originalTree, localTree])
73105

74106
return (
75107
<TreeView
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Button } from '@/components/ui/button'
2+
// import { closeApp, executeAddToApp } from '@/lib/server-fns'
3+
import { selectedAddOns } from '@/store/project'
4+
5+
export default function RunAddOns() {
6+
return (
7+
<div>
8+
<Button
9+
variant="outline"
10+
onClick={async () => {
11+
// await executeAddToApp({
12+
// data: {
13+
// addOns: selectedAddOns.state.map((addOn) => addOn.id),
14+
// },
15+
// })
16+
// await closeApp()
17+
// window.close()
18+
}}
19+
>
20+
Run Add-Ons
21+
</Button>
22+
</div>
23+
)
24+
}

packages/cta-ui/src/lib/server-fns.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@ import { register as registerReactCra } from '@tanstack/cta-framework-react-cra'
66
import { register as registerSolid } from '@tanstack/cta-framework-solid'
77

88
import {
9+
addToApp,
910
createApp,
1011
createAppOptionsFromPersisted,
12+
createDefaultEnvironment,
1113
createMemoryEnvironment,
1214
getAllAddOns,
1315
getFrameworkById,
16+
recursivelyGatherFiles,
1417
} from '@tanstack/cta-engine'
1518

1619
import type { Mode, PersistedOptions } from '@tanstack/cta-engine'
@@ -24,6 +27,28 @@ function register() {
2427
}
2528
}
2629

30+
function cleanUpFiles(files: Record<string, string>, targetDir?: string) {
31+
return Object.keys(files).reduce<Record<string, string>>((acc, file) => {
32+
let content = files[file]
33+
if (content.startsWith('base64::')) {
34+
content = '<binary file>'
35+
}
36+
if (basename(file) !== '.cta.json') {
37+
acc[targetDir ? file.replace(targetDir, '.') : file] = content
38+
}
39+
return acc
40+
}, {})
41+
}
42+
43+
export const getLocalFiles = createServerFn({
44+
method: 'GET',
45+
}).handler(async () => {
46+
register()
47+
return cleanUpFiles(
48+
await recursivelyGatherFiles(process.env.CTA_PROJECT_PATH!),
49+
)
50+
})
51+
2752
export const getAddons = createServerFn({
2853
method: 'GET',
2954
})
@@ -108,3 +133,23 @@ export const runCreateApp = createServerFn({
108133
}
109134
},
110135
)
136+
137+
export const executeAddToApp = createServerFn({
138+
method: 'POST',
139+
})
140+
.validator((data: unknown) => {
141+
return data as { addOns: Array<string> }
142+
})
143+
.handler(async ({ data: { addOns } }) => {
144+
register()
145+
const environment = createDefaultEnvironment()
146+
// await addToApp(addOns, { silent: true }, environment)
147+
return true
148+
})
149+
150+
export const closeApp = createServerFn({
151+
method: 'POST',
152+
}).handler(async () => {
153+
process.exit(0)
154+
return true
155+
})

0 commit comments

Comments
 (0)