@@ -2,99 +2,189 @@ import { useMemo, useState } from 'react'
22import { createFileRoute } from '@tanstack/react-router'
33import { FileText , Folder } from 'lucide-react'
44import { createServerFn } from '@tanstack/react-start'
5+ import CodeMirror from '@uiw/react-codemirror'
56
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'
715
816import {
917 getAllAddOns ,
1018 createApp ,
1119 createMemoryEnvironment ,
20+ createAppOptionsFromPersisted ,
1221} from '@tanstack/cta-engine'
1322
1423import { TreeView } from '@/components/ui/tree-view'
1524
25+ import type { TreeDataItem } from '@/components/ui/tree-view'
26+ import type { AddOn , PersistedOptions } from '@tanstack/cta-engine'
27+
1628const getAddons = createServerFn ( {
1729 method : 'GET' ,
1830} ) . handler ( ( ) => {
1931 return getAllAddOns ( 'react' , 'file-router' )
2032} )
2133
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+
2250const runCreateApp = createServerFn ( {
2351 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
4489 } ,
4590 )
46- return output
47- } )
4891
4992export const Route = createFileRoute ( '/' ) ( {
5093 component : App ,
5194 loader : async ( ) => {
95+ const originalOptions = await getOriginalOptions ( )
5296 return {
5397 addOns : await getAddons ( ) ,
5498 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,
56107 }
57108 } ,
58109} )
59110
60111function App ( ) {
61- const { projectPath, output } = Route . useLoaderData ( )
112+ const {
113+ projectPath,
114+ output,
115+ addOnInfo,
116+ outputWithoutAddon,
117+ originalOptions,
118+ } = Route . useLoaderData ( )
62119 const [ selectedFile , setSelectedFile ] = useState < string | null > ( null )
63120
64121 const tree = useMemo ( ( ) => {
65122 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 !
89163 }
90- currentLevel . push ( newNode )
91- currentLevel = newNode . children !
92- }
164+ } )
93165 } )
94- } )
95166 return treeData
96167 } , [ projectPath , output ] )
97168
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+
98188 return (
99189 < div className = "p-5 flex flex-row" >
100190 < TreeView
@@ -105,9 +195,17 @@ function App() {
105195 />
106196 < div className = "max-w-3/4 w-3/4 pl-2" >
107197 < 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 }
111209 </ pre >
112210 </ div >
113211 </ div >
0 commit comments