bolt-new / components /NodeEditor.js
00Boobs00's picture
Upload components/NodeEditor.js with huggingface_hub
7dd2375 verified
import { useState, useRef, useEffect } from 'react'
export default function NodeEditor({ nodes, setNodes, connections, setConnections, selectedNode, setSelectedNode }) {
const canvasRef = useRef(null)
const [isConnecting, setIsConnecting] = useState(false)
const [connectionStart, setConnectionStart] = useState(null)
const [draggedNode, setDraggedNode] = useState(null)
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
useEffect(() => {
const handleDragOver = (e) => {
e.preventDefault()
setMousePos({ x: e.clientX, y: e.clientY })
}
const handleDrop = (e) => {
e.preventDefault()
const nodeType = e.dataTransfer.getData('node-type')
if (nodeType && canvasRef.current) {
const rect = canvasRef.current.getBoundingClientRect()
const newNode = {
id: `node-${Date.now()}`,
type: nodeType,
x: e.clientX - rect.left - 75,
y: e.clientY - rect.top - 30,
properties: {
name: nodeType,
units: 128,
activation: 'relu'
}
}
setNodes(prev => [...prev, newNode])
}
}
const canvas = canvasRef.current
if (canvas) {
canvas.addEventListener('dragover', handleDragOver)
canvas.addEventListener('drop', handleDrop)
}
return () => {
if (canvas) {
canvas.removeEventListener('dragover', handleDragOver)
canvas.removeEventListener('drop', handleDrop)
}
}
}, [setNodes])
const handleNodeMouseDown = (e, nodeId) => {
if (e.target.dataset.handle === 'output') {
setIsConnecting(true)
setConnectionStart(nodeId)
} else {
setDraggedNode(nodeId)
setSelectedNode(nodeId)
}
}
const handleNodeMouseUp = (e, nodeId) => {
if (isConnecting && connectionStart && connectionStart !== nodeId) {
const newConnection = {
id: `conn-${Date.now()}`,
from: connectionStart,
to: nodeId
}
setConnections(prev => [...prev, newConnection])
}
setIsConnecting(false)
setConnectionStart(null)
}
const handleMouseMove = (e) => {
if (draggedNode && canvasRef.current) {
const rect = canvasRef.current.getBoundingClientRect()
setNodes(prev => prev.map(node =>
node.id === draggedNode
? { ...node, x: e.clientX - rect.left - 75, y: e.clientY - rect.top - 30 }
: node
))
}
setMousePos({ x: e.clientX, y: e.clientY })
}
const handleMouseUp = () => {
setDraggedNode(null)
setIsConnecting(false)
setConnectionStart(null)
}
return (
<div
ref={canvasRef}
className="flex-1 bg-gray-100 relative overflow-hidden"
data-tutorial-target="canvas"
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
{/* Grid Background */}
<div className="absolute inset-0 opacity-10">
<div className="h-full w-full" style={{
backgroundImage: 'linear-gradient(#000 1px, transparent 1px), linear-gradient(90deg, #000 1px, transparent 1px)',
backgroundSize: '20px 20px'
}} />
</div>
{/* Connections */}
<svg className="absolute inset-0 pointer-events-none" style={{ width: '100%', height: '100%' }}>
{connections.map(conn => {
const fromNode = nodes.find(n => n.id === conn.from)
const toNode = nodes.find(n => n.id === conn.to)
if (!fromNode || !toNode) return null
return (
<g key={conn.id}>
<line
x1={fromNode.x + 150}
y1={fromNode.y + 30}
x2={toNode.x}
y2={toNode.y + 30}
stroke="#3b82f6"
strokeWidth="2"
markerEnd="url(#arrowhead)"
/>
</g>
)
})}
{isConnecting && connectionStart && (() => {
const fromNode = nodes.find(n => n.id === connectionStart)
if (!fromNode) return null
return (
<line
x1={fromNode.x + 150}
y1={fromNode.y + 30}
x2={mousePos.x - canvasRef.current?.getBoundingClientRect().left}
y2={mousePos.y - canvasRef.current?.getBoundingClientRect().top}
stroke="#3b82f6"
strokeWidth="2"
strokeDasharray="5,5"
/>
)
})()}
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#3b82f6" />
</marker>
</defs>
</svg>
{/* Nodes */}
{nodes.map(node => (
<div
key={node.id}
className={`absolute bg-white rounded-lg shadow-lg border-2 cursor-move select-none ${
selectedNode === node.id ? 'border-primary-500' : 'border-gray-300'
}`}
style={{ left: node.x, top: node.y, width: '150px' }}
onMouseDown={(e) => handleNodeMouseDown(e, node.id)}
onMouseUp={(e) => handleNodeMouseUp(e, node.id)}
>
<div className="px-3 py-2 bg-gray-50 border-b border-gray-200 rounded-t-lg">
<h3 className="text-sm font-semibold text-gray-900">{node.properties.name}</h3>
</div>
<div className="p-3">
<div className="text-xs text-gray-600 mb-2">{node.type}</div>
<div className="text-xs text-gray-500">Units: {node.properties.units}</div>
</div>
<div className="absolute left-0 top-1/2 -translate-y-1/2 -translate-x-2">
<div
data-handle="input"
className="w-4 h-4 bg-gray-400 rounded-full cursor-crosshair hover:bg-primary-500"
/>
</div>
<div className="absolute right-0 top-1/2 -translate-y-1/2 translate-x-2">
<div
data-handle="output"
className="w-4 h-4 bg-gray-400 rounded-full cursor-crosshair hover:bg-primary-500"
/>
</div>
</div>
))}
{/* Empty State */}
{nodes.length === 0 && (
<div className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center">
<div className="w-24 h-24 bg-gray-200 rounded-full flex items-center justify-center mx-auto mb-4">
<Layers className="w-12 h-12 text-gray-400" />
</div>
<h3 className="text-xl font-semibold text-gray-700 mb-2">Start Building Your Architecture</h3>
<p className="text-gray-500 mb-6">Drag layers from the toolbar onto the canvas</p>
<button
onClick={() => window.dispatchEvent(new CustomEvent('start-tutorial'))}
className="px-6 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
>
Start Tutorial
</button>
</div>
)}
</div>
)
}