144 lines
4.3 KiB
TypeScript
144 lines
4.3 KiB
TypeScript
"use client"
|
|
|
|
import { useRef, useState } from "react"
|
|
import { useEditorStore } from "@/lib/store"
|
|
import { ComponentRenderer } from "./component-renderer"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export function Canvas() {
|
|
const canvasRef = useRef<HTMLDivElement>(null)
|
|
const [isDragging, setIsDragging] = useState(false)
|
|
const [dragStart, setDragStart] = useState<{ x: number; y: number } | null>(null)
|
|
const [selectedComponents, setSelectedComponents] = useState<Set<string>>(new Set())
|
|
|
|
const {
|
|
components,
|
|
selectedComponent,
|
|
selectComponent,
|
|
moveComponent,
|
|
updateComponent
|
|
} = useEditorStore()
|
|
|
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
if (e.target === canvasRef.current) {
|
|
// Clicked on empty canvas, deselect all
|
|
selectComponent(null)
|
|
setSelectedComponents(new Set())
|
|
}
|
|
}
|
|
|
|
const handleComponentClick = (componentId: string, e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
|
|
const component = components.find(c => c.id === componentId)
|
|
if (component) {
|
|
selectComponent(component)
|
|
setSelectedComponents(new Set([componentId]))
|
|
}
|
|
}
|
|
|
|
const handleComponentMouseDown = (componentId: string, e: React.MouseEvent) => {
|
|
e.stopPropagation()
|
|
|
|
const component = components.find(c => c.id === componentId)
|
|
if (component) {
|
|
selectComponent(component)
|
|
setSelectedComponents(new Set([componentId]))
|
|
|
|
// Start dragging
|
|
setIsDragging(true)
|
|
setDragStart({ x: e.clientX, y: e.clientY })
|
|
}
|
|
}
|
|
|
|
const handleMouseMove = (e: React.MouseEvent) => {
|
|
if (isDragging && dragStart && selectedComponent) {
|
|
const deltaX = e.clientX - dragStart.x
|
|
const deltaY = e.clientY - dragStart.y
|
|
|
|
const newPosition = {
|
|
x: Math.max(0, selectedComponent.position.x + deltaX),
|
|
y: Math.max(0, selectedComponent.position.y + deltaY)
|
|
}
|
|
|
|
moveComponent(selectedComponent.id, newPosition)
|
|
setDragStart({ x: e.clientX, y: e.clientY })
|
|
}
|
|
}
|
|
|
|
const handleMouseUp = () => {
|
|
setIsDragging(false)
|
|
setDragStart(null)
|
|
}
|
|
|
|
return (
|
|
<div
|
|
ref={canvasRef}
|
|
className={cn(
|
|
"relative w-full h-full bg-white overflow-hidden",
|
|
"canvas-grid"
|
|
)}
|
|
onMouseDown={handleMouseDown}
|
|
onMouseMove={handleMouseMove}
|
|
onMouseUp={handleMouseUp}
|
|
onMouseLeave={handleMouseUp}
|
|
>
|
|
{/* Canvas Grid Background */}
|
|
<div className="absolute inset-0 opacity-20">
|
|
<div
|
|
className="w-full h-full"
|
|
style={{
|
|
backgroundImage: `
|
|
linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px)
|
|
`,
|
|
backgroundSize: '20px 20px'
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
{/* Components */}
|
|
{components.map((component) => (
|
|
<div
|
|
key={component.id}
|
|
className={cn(
|
|
"absolute cursor-move select-none",
|
|
selectedComponents.has(component.id) && "ring-2 ring-blue-500 ring-opacity-50"
|
|
)}
|
|
style={{
|
|
left: component.position.x,
|
|
top: component.position.y,
|
|
width: component.size?.width || 100,
|
|
height: component.size?.height || 100,
|
|
}}
|
|
onClick={(e) => handleComponentClick(component.id, e)}
|
|
onMouseDown={(e) => handleComponentMouseDown(component.id, e)}
|
|
>
|
|
<ComponentRenderer
|
|
component={component}
|
|
isSelected={selectedComponents.has(component.id)}
|
|
/>
|
|
</div>
|
|
))}
|
|
|
|
{/* Drop Zone Indicator */}
|
|
{isDragging && (
|
|
<div className="absolute inset-0 pointer-events-none">
|
|
<div className="w-full h-full border-2 border-dashed border-blue-400 bg-blue-50 bg-opacity-20" />
|
|
</div>
|
|
)}
|
|
|
|
{/* Empty State */}
|
|
{components.length === 0 && (
|
|
<div className="absolute inset-0 flex items-center justify-center">
|
|
<div className="text-center text-gray-500">
|
|
<div className="text-6xl mb-4">🎨</div>
|
|
<h3 className="text-lg font-medium mb-2">No components yet</h3>
|
|
<p className="text-sm">Drag components from the palette to get started</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|