mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-01 17:24:57 +00:00
164 lines
5.0 KiB
TypeScript
164 lines
5.0 KiB
TypeScript
import { useState } from 'react'
|
|
import { useKV } from '@github/spark/hooks'
|
|
import { Button } from '@/components/ui/button'
|
|
import { ComponentCatalog } from './ComponentCatalog'
|
|
import { Canvas } from './Canvas'
|
|
import { PropertyInspector } from './PropertyInspector'
|
|
import { CodeEditor } from './CodeEditor'
|
|
import type { ComponentInstance, ComponentDefinition, BuilderState } from '@/lib/builder-types'
|
|
import { FloppyDisk, SignOut, Eye } from '@phosphor-icons/react'
|
|
import { toast } from 'sonner'
|
|
|
|
interface BuilderProps {
|
|
onLogout: () => void
|
|
}
|
|
|
|
export function Builder({ onLogout }: BuilderProps) {
|
|
const [builderState, setBuilderState] = useKV<BuilderState>('gui_builder_state', {
|
|
components: [],
|
|
selectedId: null,
|
|
})
|
|
|
|
const [draggingComponent, setDraggingComponent] = useState<ComponentDefinition | null>(null)
|
|
const [codeEditorOpen, setCodeEditorOpen] = useState(false)
|
|
|
|
if (!builderState) return null
|
|
|
|
const selectedComponent = builderState.components.find(c => c.id === builderState.selectedId) || null
|
|
|
|
const handleDragStart = (component: ComponentDefinition) => {
|
|
setDraggingComponent(component)
|
|
}
|
|
|
|
const handleDrop = (component: ComponentDefinition) => {
|
|
const newComponent: ComponentInstance = {
|
|
id: `comp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
type: component.type,
|
|
props: { ...component.defaultProps },
|
|
children: [],
|
|
}
|
|
|
|
setBuilderState(current => {
|
|
if (!current) return { components: [newComponent], selectedId: newComponent.id }
|
|
return {
|
|
...current,
|
|
components: [...current.components, newComponent],
|
|
selectedId: newComponent.id,
|
|
}
|
|
})
|
|
|
|
toast.success(`${component.label} added to canvas`)
|
|
}
|
|
|
|
const handleSelect = (id: string) => {
|
|
setBuilderState(current => {
|
|
if (!current) return { components: [], selectedId: id }
|
|
return {
|
|
...current,
|
|
selectedId: id,
|
|
}
|
|
})
|
|
}
|
|
|
|
const handleUpdateProps = (id: string, props: any) => {
|
|
setBuilderState(current => {
|
|
if (!current) return { components: [], selectedId: null }
|
|
return {
|
|
...current,
|
|
components: current.components.map(comp =>
|
|
comp.id === id ? { ...comp, props } : comp
|
|
),
|
|
}
|
|
})
|
|
}
|
|
|
|
const handleDelete = (id: string) => {
|
|
setBuilderState(current => {
|
|
if (!current) return { components: [], selectedId: null }
|
|
return {
|
|
...current,
|
|
components: current.components.filter(comp => comp.id !== id),
|
|
selectedId: current.selectedId === id ? null : current.selectedId,
|
|
}
|
|
})
|
|
toast.success('Component deleted')
|
|
}
|
|
|
|
const handleCodeSave = (code: string) => {
|
|
if (!selectedComponent) return
|
|
|
|
setBuilderState(current => {
|
|
if (!current) return { components: [], selectedId: null }
|
|
return {
|
|
...current,
|
|
components: current.components.map(comp =>
|
|
comp.id === selectedComponent.id ? { ...comp, customCode: code } : comp
|
|
),
|
|
}
|
|
})
|
|
toast.success('Code saved')
|
|
}
|
|
|
|
const handleSaveProject = () => {
|
|
toast.success('Project saved successfully')
|
|
}
|
|
|
|
const handlePreview = () => {
|
|
toast.info('Preview mode coming soon')
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-screen overflow-hidden bg-background">
|
|
<ComponentCatalog onDragStart={handleDragStart} />
|
|
|
|
<div className="flex-1 flex flex-col">
|
|
<header className="border-b border-border bg-card px-6 py-3 flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-xl font-bold">GUI Builder</h1>
|
|
<p className="text-xs text-muted-foreground">Visual Component Editor</p>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<Button variant="outline" size="sm" onClick={handlePreview}>
|
|
<Eye className="mr-2" size={16} />
|
|
Preview
|
|
</Button>
|
|
<Button variant="outline" size="sm" onClick={handleSaveProject}>
|
|
<FloppyDisk className="mr-2" size={16} />
|
|
Save
|
|
</Button>
|
|
<Button variant="outline" size="sm" onClick={onLogout}>
|
|
<SignOut className="mr-2" size={16} />
|
|
Logout
|
|
</Button>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="flex-1 flex overflow-hidden">
|
|
<Canvas
|
|
components={builderState.components}
|
|
selectedId={builderState.selectedId}
|
|
onSelect={handleSelect}
|
|
onDrop={handleDrop}
|
|
isDragging={draggingComponent !== null}
|
|
/>
|
|
|
|
<PropertyInspector
|
|
component={selectedComponent}
|
|
onUpdate={handleUpdateProps}
|
|
onDelete={handleDelete}
|
|
onCodeEdit={() => setCodeEditorOpen(true)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<CodeEditor
|
|
open={codeEditorOpen}
|
|
onClose={() => setCodeEditorOpen(false)}
|
|
code={selectedComponent?.customCode || ''}
|
|
onSave={handleCodeSave}
|
|
componentName={selectedComponent?.type || 'Component'}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|