Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cta-ui-base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@tanstack/react-query": "^5.66.5",
"@uiw/codemirror-theme-github": "^4.23.10",
"@uiw/react-codemirror": "^4.23.10",
"@webcontainer/api": "^1.3.5",
"chalk": "^5.4.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand Down
77 changes: 61 additions & 16 deletions packages/cta-ui-base/src/components/file-navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import { getFileClass, twClasses } from '../file-classes'

import FileViewer from './file-viewer'
import FileTree from './file-tree'
import WebContainerProvider from './web-container-provider'
import { WebContainerPreview } from './webcontainer-preview'

import { Label } from './ui/label'
import { Switch } from './ui/switch'
import { Tabs, TabsList, TabsTrigger, TabsContent } from './ui/tabs'

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

Expand Down Expand Up @@ -181,27 +184,69 @@ export default function FileNavigator() {

const ready = useReady()

// Prepare project files for WebContainer
const webContainerFiles = useMemo(() => {
console.log('Preparing WebContainer files, tree:', tree)
if (!tree) {
console.log('Tree is empty, returning empty array')
return []
}
const files = Object.entries(tree).map(([path, content]) => ({
path,
content,
}))
console.log('WebContainer files prepared:', files.length, 'files')
return files
}, [tree])

if (!ready) {
return null
}

return (
<div className="bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4">
{mode === 'add' && <Filters />}
<div className="flex flex-row @container">
<div className="w-1/3 @6xl:w-1/4 bg-gray-500/10 rounded-l-lg">
<FileTree selectedFile={selectedFile} tree={fileTree} />
</div>
<div className="w-2/3 @6xl:w-3/4">
{selectedFile && modifiedFileContents ? (
<FileViewer
filePath={selectedFile}
originalFile={originalFileContents}
modifiedFile={modifiedFileContents}
/>
) : null}
</div>
<WebContainerProvider projectFiles={webContainerFiles}>
<div className="bg-white dark:bg-black/50 rounded-lg p-2 sm:p-4">
{mode === 'add' && <Filters />}
<Tabs defaultValue="files" className="w-full">
<TabsList className="mb-1 h-7 p-0.5 bg-transparent border border-gray-300 dark:border-gray-700">
<TabsTrigger
value="files"
className="text-xs h-6 px-3 py-0 data-[state=active]:bg-gray-200 dark:data-[state=active]:bg-gray-800"
>
Files
</TabsTrigger>
<TabsTrigger
value="preview"
className="text-xs h-6 px-3 py-0 data-[state=active]:bg-gray-200 dark:data-[state=active]:bg-gray-800"
>
Preview
</TabsTrigger>
</TabsList>

<TabsContent value="files" className="mt-0">
<div className="flex flex-row @container">
<div className="w-1/3 @6xl:w-1/4 bg-gray-500/10 rounded-l-lg">
<FileTree selectedFile={selectedFile} tree={fileTree} />
</div>
<div className="w-2/3 @6xl:w-3/4">
{selectedFile && modifiedFileContents ? (
<FileViewer
filePath={selectedFile}
originalFile={originalFileContents}
modifiedFile={modifiedFileContents}
/>
) : null}
</div>
</div>
</TabsContent>

<TabsContent value="preview" className="mt-0">
<div className="h-[800px]">
<WebContainerPreview />
</div>
</TabsContent>
</Tabs>
</div>
</div>
</WebContainerProvider>
)
}
22 changes: 18 additions & 4 deletions packages/cta-ui-base/src/components/file-viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,18 @@ export default function FileViewer({
}
const language = getLanguage(filePath)

if (!originalFile || originalFile === modifiedFile) {
// Display placeholder for binary files
const displayModified = modifiedFile.startsWith('base64::')
? '<binary file>'
: modifiedFile
const displayOriginal = originalFile?.startsWith('base64::')
? '<binary file>'
: originalFile

if (!displayOriginal || displayOriginal === displayModified) {
return (
<CodeMirror
value={modifiedFile}
value={displayModified}
theme={theme}
height="100vh"
width="100%"
Expand All @@ -60,8 +68,14 @@ export default function FileViewer({
}
return (
<CodeMirrorMerge orientation="a-b" theme={theme} className="text-lg">
<CodeMirrorMerge.Original value={originalFile} extensions={[language]} />
<CodeMirrorMerge.Modified value={modifiedFile} extensions={[language]} />
<CodeMirrorMerge.Original
value={displayOriginal}
extensions={[language]}
/>
<CodeMirrorMerge.Modified
value={displayModified}
extensions={[language]}
/>
</CodeMirrorMerge>
)
}
42 changes: 42 additions & 0 deletions packages/cta-ui-base/src/components/web-container-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createContext, useEffect, useState } from 'react'
import { useStore } from 'zustand'
import createWebContainerStore from '../hooks/use-webcontainer-store'

export const WebContainerContext = createContext<ReturnType<
typeof createWebContainerStore
> | null>(null)

export default function WebContainerProvider({
children,
projectFiles,
}: {
children: React.ReactNode
projectFiles: Array<{ path: string; content: string }>
}) {
console.log(
'WebContainerProvider rendering with',
projectFiles.length,
'files',
)
const [containerStore] = useState(() => createWebContainerStore(true))

const updateProjectFiles = useStore(
containerStore,
(state) => state.updateProjectFiles,
)

useEffect(() => {
console.log(
'WebContainerProvider useEffect triggered with',
projectFiles.length,
'files',
)
updateProjectFiles(projectFiles)
}, [updateProjectFiles, projectFiles])

return (
<WebContainerContext.Provider value={containerStore}>
{children}
</WebContainerContext.Provider>
)
}
Loading