@@ -2,99 +2,189 @@ import { useMemo, useState } from 'react'
2
2
import { createFileRoute } from '@tanstack/react-router'
3
3
import { FileText , Folder } from 'lucide-react'
4
4
import { createServerFn } from '@tanstack/react-start'
5
+ import CodeMirror from '@uiw/react-codemirror'
5
6
6
- import type { TreeDataItem } from '@/components/ui/tree-view'
7
+ import { javascript } from '@codemirror/lang-javascript'
8
+ import { json } from '@codemirror/lang-json'
9
+ import { css } from '@codemirror/lang-css'
10
+ import { html } from '@codemirror/lang-html'
11
+
12
+ import { okaidia } from '@uiw/codemirror-theme-okaidia'
13
+ import { readFileSync } from 'node:fs'
14
+ import { basename , resolve } from 'node:path'
7
15
8
16
import {
9
17
getAllAddOns ,
10
18
createApp ,
11
19
createMemoryEnvironment ,
20
+ createAppOptionsFromPersisted ,
12
21
} from '@tanstack/cta-engine'
13
22
14
23
import { TreeView } from '@/components/ui/tree-view'
15
24
25
+ import type { TreeDataItem } from '@/components/ui/tree-view'
26
+ import type { AddOn , PersistedOptions } from '@tanstack/cta-engine'
27
+
16
28
const getAddons = createServerFn ( {
17
29
method : 'GET' ,
18
30
} ) . handler ( ( ) => {
19
31
return getAllAddOns ( 'react' , 'file-router' )
20
32
} )
21
33
34
+ const getAddonInfo = createServerFn ( {
35
+ method : 'GET' ,
36
+ } ) . handler ( async ( ) => {
37
+ const addOnInfo = readFileSync (
38
+ resolve ( process . env . PROJECT_PATH , 'add-on.json' ) ,
39
+ )
40
+ return JSON . parse ( addOnInfo . toString ( ) )
41
+ } )
42
+
43
+ const getOriginalOptions = createServerFn ( {
44
+ method : 'GET' ,
45
+ } ) . handler ( async ( ) => {
46
+ const addOnInfo = readFileSync ( resolve ( process . env . PROJECT_PATH , '.cta.json' ) )
47
+ return JSON . parse ( addOnInfo . toString ( ) ) as PersistedOptions
48
+ } )
49
+
22
50
const runCreateApp = createServerFn ( {
23
51
method : 'POST' ,
24
- } ) . handler ( async ( ) => {
25
- const { output, environment } = createMemoryEnvironment ( )
26
- await createApp (
27
- {
28
- addOns : false ,
29
- framework : 'react' ,
30
- chosenAddOns : [ ] ,
31
- git : true ,
32
- mode : 'code-router' ,
33
- packageManager : 'npm' ,
34
- projectName : 'foo' ,
35
- tailwind : false ,
36
- toolchain : 'none' ,
37
- typescript : false ,
38
- variableValues : { } ,
39
- } ,
40
- {
41
- silent : true ,
42
- environment,
43
- cwd : process . env . PROJECT_PATH ,
52
+ } )
53
+ . validator ( ( data : unknown ) => {
54
+ return data as { withAddOn : boolean ; options : PersistedOptions }
55
+ } )
56
+ . handler (
57
+ async ( {
58
+ data : { withAddOn, options : persistedOptions } ,
59
+ } : {
60
+ data : { withAddOn : boolean ; options : PersistedOptions }
61
+ } ) => {
62
+ const { output, environment } = createMemoryEnvironment ( )
63
+ const options = await createAppOptionsFromPersisted ( persistedOptions )
64
+ options . chosenAddOns = withAddOn
65
+ ? [ ...options . chosenAddOns , ( await getAddonInfo ( ) ) as AddOn ]
66
+ : [ ]
67
+ await createApp (
68
+ {
69
+ ...options ,
70
+ } ,
71
+ {
72
+ silent : true ,
73
+ environment,
74
+ cwd : process . env . PROJECT_PATH ,
75
+ } ,
76
+ )
77
+
78
+ output . files = Object . keys ( output . files ) . reduce < Record < string , string > > (
79
+ ( acc , file ) => {
80
+ if ( basename ( file ) !== '.cta.json' ) {
81
+ acc [ file ] = output . files [ file ]
82
+ }
83
+ return acc
84
+ } ,
85
+ { } ,
86
+ )
87
+
88
+ return output
44
89
} ,
45
90
)
46
- return output
47
- } )
48
91
49
92
export const Route = createFileRoute ( '/' ) ( {
50
93
component : App ,
51
94
loader : async ( ) => {
95
+ const originalOptions = await getOriginalOptions ( )
52
96
return {
53
97
addOns : await getAddons ( ) ,
54
98
projectPath : process . env . PROJECT_PATH ! ,
55
- output : await runCreateApp ( ) ,
99
+ output : await runCreateApp ( {
100
+ data : { withAddOn : true , options : originalOptions } ,
101
+ } ) ,
102
+ outputWithoutAddon : await runCreateApp ( {
103
+ data : { withAddOn : false , options : originalOptions } ,
104
+ } ) ,
105
+ addOnInfo : await getAddonInfo ( ) ,
106
+ originalOptions,
56
107
}
57
108
} ,
58
109
} )
59
110
60
111
function App ( ) {
61
- const { projectPath, output } = Route . useLoaderData ( )
112
+ const {
113
+ projectPath,
114
+ output,
115
+ addOnInfo,
116
+ outputWithoutAddon,
117
+ originalOptions,
118
+ } = Route . useLoaderData ( )
62
119
const [ selectedFile , setSelectedFile ] = useState < string | null > ( null )
63
120
64
121
const tree = useMemo ( ( ) => {
65
122
const treeData : Array < TreeDataItem > = [ ]
66
- Object . keys ( output . files ) . forEach ( ( file ) => {
67
- const parts = file . replace ( `${ projectPath } /` , '' ) . split ( '/' )
68
-
69
- let currentLevel = treeData
70
- parts . forEach ( ( part , index ) => {
71
- const existingNode = currentLevel . find ( ( node ) => node . name === part )
72
- if ( existingNode ) {
73
- currentLevel = existingNode . children || [ ]
74
- } else {
75
- const newNode : TreeDataItem = {
76
- id : index === parts . length - 1 ? file : `${ file } -${ index } ` ,
77
- name : part ,
78
- children : index < parts . length - 1 ? [ ] : undefined ,
79
- icon :
80
- index < parts . length - 1
81
- ? ( ) => < Folder className = "w-4 h-4 mr-2" />
82
- : ( ) => < FileText className = "w-4 h-4 mr-2" /> ,
83
- onClick :
84
- index === parts . length - 1
85
- ? ( ) => {
86
- setSelectedFile ( file )
87
- }
88
- : undefined ,
123
+
124
+ function changed ( file : string ) {
125
+ if ( ! outputWithoutAddon . files [ file ] ) {
126
+ return true
127
+ }
128
+ return output . files [ file ] !== outputWithoutAddon . files [ file ]
129
+ }
130
+
131
+ Object . keys ( output . files )
132
+ . sort ( )
133
+ . forEach ( ( file ) => {
134
+ const parts = file . replace ( `${ projectPath } /` , '' ) . split ( '/' )
135
+
136
+ let currentLevel = treeData
137
+ parts . forEach ( ( part , index ) => {
138
+ const existingNode = currentLevel . find ( ( node ) => node . name === part )
139
+ if ( existingNode ) {
140
+ currentLevel = existingNode . children || [ ]
141
+ } else {
142
+ const newNode : TreeDataItem = {
143
+ id : index === parts . length - 1 ? file : `${ file } -${ index } ` ,
144
+ name : part ,
145
+ children : index < parts . length - 1 ? [ ] : undefined ,
146
+ icon :
147
+ index < parts . length - 1
148
+ ? ( ) => < Folder className = "w-4 h-4 mr-2" />
149
+ : ( ) => < FileText className = "w-4 h-4 mr-2" /> ,
150
+ onClick :
151
+ index === parts . length - 1
152
+ ? ( ) => {
153
+ setSelectedFile ( file )
154
+ }
155
+ : undefined ,
156
+ className :
157
+ index === parts . length - 1 && changed ( file )
158
+ ? 'text-green-300'
159
+ : '' ,
160
+ }
161
+ currentLevel . push ( newNode )
162
+ currentLevel = newNode . children !
89
163
}
90
- currentLevel . push ( newNode )
91
- currentLevel = newNode . children !
92
- }
164
+ } )
93
165
} )
94
- } )
95
166
return treeData
96
167
} , [ projectPath , output ] )
97
168
169
+ function getLanguage ( file : string ) {
170
+ if ( file . endsWith ( '.js' ) || file . endsWith ( '.jsx' ) ) {
171
+ return javascript ( { jsx : true } )
172
+ }
173
+ if ( file . endsWith ( '.ts' ) || file . endsWith ( '.tsx' ) ) {
174
+ return javascript ( { typescript : true , jsx : true } )
175
+ }
176
+ if ( file . endsWith ( '.json' ) ) {
177
+ return json ( )
178
+ }
179
+ if ( file . endsWith ( '.css' ) ) {
180
+ return css ( )
181
+ }
182
+ if ( file . endsWith ( '.html' ) ) {
183
+ return html ( )
184
+ }
185
+ return javascript ( )
186
+ }
187
+
98
188
return (
99
189
< div className = "p-5 flex flex-row" >
100
190
< TreeView
@@ -105,9 +195,17 @@ function App() {
105
195
/>
106
196
< div className = "max-w-3/4 w-3/4 pl-2" >
107
197
< pre >
108
- { selectedFile
109
- ? output . files [ selectedFile ] || 'Select a file to view its content'
110
- : null }
198
+ { selectedFile && output . files [ selectedFile ] ? (
199
+ < CodeMirror
200
+ value = { output . files [ selectedFile ] }
201
+ theme = { okaidia }
202
+ height = "100vh"
203
+ width = "100%"
204
+ readOnly
205
+ extensions = { [ getLanguage ( selectedFile ) ] }
206
+ className = "text-lg"
207
+ />
208
+ ) : null }
111
209
</ pre >
112
210
</ div >
113
211
</ div >
0 commit comments