import { useDraggable } from '@neodrag/react'; import { useReactFlow, type XYPosition } from '@xyflow/react'; import { type ReactNode, useCallback, useRef, useState } from 'react'; import styles from '../../VisProg.module.css'; import { NodeDefaults, type NodeTypes } from '../NodeRegistry' import addNode from '../utils/AddNode'; /** * DraggableNodeProps dictates the type properties of a DraggableNode */ interface DraggableNodeProps { className?: string; children: ReactNode; nodeType: keyof typeof NodeTypes; onDrop: (nodeType: keyof typeof NodeTypes, position: XYPosition) => void; } /** * Definition of a node inside the drag and drop toolbar. * These nodes require an onDrop function that dictates * how the node is created in the graph. */ function DraggableNode({ className, children, nodeType, onDrop }: DraggableNodeProps) { const draggableRef = useRef(null); const [position, setPosition] = useState({ x: 0, y: 0 }); // @ts-expect-error from the neodrag package — safe to ignore useDraggable(draggableRef, { position, onDrag: ({ offsetX, offsetY }) => { setPosition({ x: offsetX, y: offsetY }); }, onDragEnd: ({ event }) => { setPosition({ x: 0, y: 0 }); onDrop(nodeType, { x: event.clientX, y: event.clientY }); }, }); return (
{children}
); } /** * DndToolbar defines how the drag and drop toolbar component works * and includes the default onDrop behavior. */ export function DndToolbar() { const { screenToFlowPosition } = useReactFlow(); const handleNodeDrop = useCallback( (nodeType: keyof typeof NodeTypes, screenPosition: XYPosition) => { const flow = document.querySelector('.react-flow'); const flowRect = flow?.getBoundingClientRect(); const isInFlow = flowRect && screenPosition.x >= flowRect.left && screenPosition.x <= flowRect.right && screenPosition.y >= flowRect.top && screenPosition.y <= flowRect.bottom; if (isInFlow) { const position = screenToFlowPosition(screenPosition); addNode(nodeType, position); } }, [screenToFlowPosition], ); // Map over our default settings to see which of them have their droppable data set to true const droppableNodes = Object.entries(NodeDefaults) .filter(([, data]) => data.droppable) .map(([type, data]) => ({ type: type as DraggableNodeProps['nodeType'], data })); return (
You can drag these nodes to the pane to create new nodes.
{/* Maps over all the nodes that are droppable, and puts them in the panel */} {droppableNodes.map(({type, data}) => ( {data.label} ))}
); }