Skip to content

Commit 0a9ea74

Browse files
authored
feat: CTA UI preview (#194)
* feat: CTA-UI preview * fix: post install updates now work * cleaner * feat: CTA-UI preview
1 parent 0d1aa71 commit 0a9ea74

File tree

15 files changed

+969
-30
lines changed

15 files changed

+969
-30
lines changed

packages/cta-ui-base/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@tanstack/react-query": "^5.66.5",
3333
"@uiw/codemirror-theme-github": "^4.23.10",
3434
"@uiw/react-codemirror": "^4.23.10",
35+
"@webcontainer/api": "^1.3.5",
3536
"chalk": "^5.4.1",
3637
"class-variance-authority": "^0.7.1",
3738
"clsx": "^2.1.1",

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

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import { getFileClass, twClasses } from '../file-classes'
1414

1515
import FileViewer from './file-viewer'
1616
import FileTree from './file-tree'
17+
import WebContainerProvider from './web-container-provider'
18+
import { WebContainerPreview } from './webcontainer-preview'
1719

1820
import { Label } from './ui/label'
1921
import { Switch } from './ui/switch'
22+
import { Tabs, TabsList, TabsTrigger, TabsContent } from './ui/tabs'
2023

2124
import type { FileTreeItem } from '../types'
2225

@@ -181,27 +184,69 @@ export default function FileNavigator() {
181184

182185
const ready = useReady()
183186

187+
// Prepare project files for WebContainer
188+
const webContainerFiles = useMemo(() => {
189+
console.log('Preparing WebContainer files, tree:', tree)
190+
if (!tree) {
191+
console.log('Tree is empty, returning empty array')
192+
return []
193+
}
194+
const files = Object.entries(tree).map(([path, content]) => ({
195+
path,
196+
content,
197+
}))
198+
console.log('WebContainer files prepared:', files.length, 'files')
199+
return files
200+
}, [tree])
201+
184202
if (!ready) {
185203
return null
186204
}
187205

188206
return (
189-
<div className="bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4">
190-
{mode === 'add' && <Filters />}
191-
<div className="flex flex-row @container">
192-
<div className="w-1/3 @6xl:w-1/4 bg-gray-500/10 rounded-l-lg">
193-
<FileTree selectedFile={selectedFile} tree={fileTree} />
194-
</div>
195-
<div className="w-2/3 @6xl:w-3/4">
196-
{selectedFile && modifiedFileContents ? (
197-
<FileViewer
198-
filePath={selectedFile}
199-
originalFile={originalFileContents}
200-
modifiedFile={modifiedFileContents}
201-
/>
202-
) : null}
203-
</div>
207+
<WebContainerProvider projectFiles={webContainerFiles}>
208+
<div className="bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4">
209+
{mode === 'add' && <Filters />}
210+
<Tabs defaultValue="files" className="w-full">
211+
<TabsList className="mb-1 h-7 p-0.5 bg-transparent border border-gray-300 dark:border-gray-700">
212+
<TabsTrigger
213+
value="files"
214+
className="text-xs h-6 px-3 py-0 data-[state=active]:bg-gray-200 dark:data-[state=active]:bg-gray-800"
215+
>
216+
Files
217+
</TabsTrigger>
218+
<TabsTrigger
219+
value="preview"
220+
className="text-xs h-6 px-3 py-0 data-[state=active]:bg-gray-200 dark:data-[state=active]:bg-gray-800"
221+
>
222+
Preview
223+
</TabsTrigger>
224+
</TabsList>
225+
226+
<TabsContent value="files" className="mt-0">
227+
<div className="flex flex-row @container">
228+
<div className="w-1/3 @6xl:w-1/4 bg-gray-500/10 rounded-l-lg">
229+
<FileTree selectedFile={selectedFile} tree={fileTree} />
230+
</div>
231+
<div className="w-2/3 @6xl:w-3/4">
232+
{selectedFile && modifiedFileContents ? (
233+
<FileViewer
234+
filePath={selectedFile}
235+
originalFile={originalFileContents}
236+
modifiedFile={modifiedFileContents}
237+
/>
238+
) : null}
239+
</div>
240+
</div>
241+
</TabsContent>
242+
243+
<TabsContent value="preview" className="mt-0">
244+
<div className="h-[800px]">
245+
<WebContainerPreview />
246+
</div>
247+
</TabsContent>
248+
</Tabs>
204249
</div>
205-
</div>
250+
</WebContainerProvider>
206251
)
207252
}

packages/cta-ui-base/src/components/file-viewer.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,18 @@ export default function FileViewer({
4545
}
4646
const language = getLanguage(filePath)
4747

48-
if (!originalFile || originalFile === modifiedFile) {
48+
// Display placeholder for binary files
49+
const displayModified = modifiedFile.startsWith('base64::')
50+
? '<binary file>'
51+
: modifiedFile
52+
const displayOriginal = originalFile?.startsWith('base64::')
53+
? '<binary file>'
54+
: originalFile
55+
56+
if (!displayOriginal || displayOriginal === displayModified) {
4957
return (
5058
<CodeMirror
51-
value={modifiedFile}
59+
value={displayModified}
5260
theme={theme}
5361
height="100vh"
5462
width="100%"
@@ -60,8 +68,14 @@ export default function FileViewer({
6068
}
6169
return (
6270
<CodeMirrorMerge orientation="a-b" theme={theme} className="text-lg">
63-
<CodeMirrorMerge.Original value={originalFile} extensions={[language]} />
64-
<CodeMirrorMerge.Modified value={modifiedFile} extensions={[language]} />
71+
<CodeMirrorMerge.Original
72+
value={displayOriginal}
73+
extensions={[language]}
74+
/>
75+
<CodeMirrorMerge.Modified
76+
value={displayModified}
77+
extensions={[language]}
78+
/>
6579
</CodeMirrorMerge>
6680
)
6781
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createContext, useEffect, useState } from 'react'
2+
import { useStore } from 'zustand'
3+
import createWebContainerStore from '../hooks/use-webcontainer-store'
4+
5+
export const WebContainerContext = createContext<ReturnType<
6+
typeof createWebContainerStore
7+
> | null>(null)
8+
9+
export default function WebContainerProvider({
10+
children,
11+
projectFiles,
12+
}: {
13+
children: React.ReactNode
14+
projectFiles: Array<{ path: string; content: string }>
15+
}) {
16+
console.log(
17+
'WebContainerProvider rendering with',
18+
projectFiles.length,
19+
'files',
20+
)
21+
const [containerStore] = useState(() => createWebContainerStore(true))
22+
23+
const updateProjectFiles = useStore(
24+
containerStore,
25+
(state) => state.updateProjectFiles,
26+
)
27+
28+
useEffect(() => {
29+
console.log(
30+
'WebContainerProvider useEffect triggered with',
31+
projectFiles.length,
32+
'files',
33+
)
34+
updateProjectFiles(projectFiles)
35+
}, [updateProjectFiles, projectFiles])
36+
37+
return (
38+
<WebContainerContext.Provider value={containerStore}>
39+
{children}
40+
</WebContainerContext.Provider>
41+
)
42+
}

0 commit comments

Comments
 (0)