({
graph,
// templates is reserved for future node palette feature
// templates 保留用于未来的节点面板功能
templates: _templates = [],
selectedNodeIds = new Set(),
selectedConnectionIds = new Set(),
executionStates,
animateExecConnections = false,
readOnly = false,
renderIcon,
onGraphChange,
onSelectionChange,
// onNodeDoubleClick is reserved for future double-click handling
// onNodeDoubleClick 保留用于未来的双击处理
onNodeDoubleClick: _onNodeDoubleClick,
onCanvasContextMenu,
onNodeContextMenu,
onConnectionContextMenu
})
| 85 | * NodeEditor - 完整的节点图编辑器组件 |
| 86 | */ |
| 87 | export const NodeEditor: React.FC<NodeEditorProps> = ({ |
| 88 | graph, |
| 89 | // templates is reserved for future node palette feature |
| 90 | // templates 保留用于未来的节点面板功能 |
| 91 | templates: _templates = [], |
| 92 | selectedNodeIds = new Set(), |
| 93 | selectedConnectionIds = new Set(), |
| 94 | executionStates, |
| 95 | animateExecConnections = false, |
| 96 | readOnly = false, |
| 97 | renderIcon, |
| 98 | onGraphChange, |
| 99 | onSelectionChange, |
| 100 | // onNodeDoubleClick is reserved for future double-click handling |
| 101 | // onNodeDoubleClick 保留用于未来的双击处理 |
| 102 | onNodeDoubleClick: _onNodeDoubleClick, |
| 103 | onCanvasContextMenu, |
| 104 | onNodeContextMenu, |
| 105 | onConnectionContextMenu |
| 106 | }) => { |
| 107 | // Silence unused variable warnings (消除未使用变量警告) |
| 108 | void _templates; |
| 109 | void _onNodeDoubleClick; |
| 110 | const containerRef = useRef<HTMLDivElement>(null); |
| 111 | |
| 112 | // Canvas transform state - use ref to always have latest values |
| 113 | // 画布变换状态 - 使用 ref 保证总是能获取最新值 |
| 114 | const transformRef = useRef({ pan: Position.ZERO, zoom: 1 }); |
| 115 | |
| 116 | // Callbacks for GraphCanvas to sync transform state |
| 117 | const handlePanChange = useCallback((newPan: Position) => { |
| 118 | transformRef.current.pan = newPan; |
| 119 | }, []); |
| 120 | |
| 121 | const handleZoomChange = useCallback((newZoom: number) => { |
| 122 | transformRef.current.zoom = newZoom; |
| 123 | }, []); |
| 124 | |
| 125 | // Local state for dragging (拖拽的本地状态) |
| 126 | const [dragState, setDragState] = useState<DragState | null>(null); |
| 127 | const [connectionDrag, setConnectionDrag] = useState<ConnectionDragState | null>(null); |
| 128 | const [hoveredPin, setHoveredPin] = useState<Pin | null>(null); |
| 129 | |
| 130 | /** |
| 131 | * Converts screen coordinates to canvas coordinates |
| 132 | * 将屏幕坐标转换为画布坐标 |
| 133 | * 使用 ref 中的最新值,避免闭包捕获旧状态 |
| 134 | */ |
| 135 | const screenToCanvas = useCallback((screenX: number, screenY: number): Position => { |
| 136 | if (!containerRef.current) return new Position(screenX, screenY); |
| 137 | const rect = containerRef.current.getBoundingClientRect(); |
| 138 | const { pan, zoom } = transformRef.current; |
| 139 | const x = (screenX - rect.left - pan.x) / zoom; |
| 140 | const y = (screenY - rect.top - pan.y) / zoom; |
| 141 | return new Position(x, y); |
| 142 | }, []); |
| 143 | |
| 144 | /** |
nothing calls this directly
no test coverage detected