codenuk_frontend_mine/src/components/canvas.tsx

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>
)
}