173 lines
4.6 KiB
TypeScript
173 lines
4.6 KiB
TypeScript
import {useDraggable} from '@neodrag/react';
|
|
import {
|
|
useReactFlow,
|
|
type XYPosition
|
|
} from '@xyflow/react';
|
|
import {
|
|
type ReactNode,
|
|
useCallback,
|
|
useRef,
|
|
useState
|
|
} from 'react';
|
|
|
|
// Is used to make sure each subsequent node gets a unique id, so the ReactFlow implementation
|
|
// can distinguish between different nodes.
|
|
let id = 0;
|
|
const getId = () => `dndnode_${id++}`;
|
|
|
|
/**
|
|
* DraggableNodeProps dictates the type properties of a DraggableNode
|
|
*/
|
|
interface DraggableNodeProps {
|
|
className?: string;
|
|
children: ReactNode;
|
|
nodeType: string;
|
|
onDrop: (nodeType: string, position: XYPosition) => void;
|
|
}
|
|
|
|
/**
|
|
* Definition of a node inside the drag and drop toolbar,
|
|
* these nodes require an onDrop function to be supplied
|
|
* that dictates how the node is created in the graph.
|
|
*
|
|
* @param className
|
|
* @param children
|
|
* @param nodeType
|
|
* @param onDrop
|
|
* @constructor
|
|
*/
|
|
function DraggableNode({className, children, nodeType, onDrop}: DraggableNodeProps) {
|
|
const draggableRef = useRef<HTMLDivElement>(null);
|
|
const [position, setPosition] = useState<XYPosition>({x: 0, y: 0});
|
|
|
|
// @ts-ignore
|
|
useDraggable(draggableRef, {
|
|
position: position,
|
|
onDrag: ({offsetX, offsetY}) => {
|
|
// Calculate position relative to the viewport
|
|
setPosition({
|
|
x: offsetX,
|
|
y: offsetY,
|
|
});
|
|
},
|
|
onDragEnd: ({event}) => {
|
|
setPosition({x: 0, y: 0});
|
|
onDrop(nodeType, {
|
|
x: event.clientX,
|
|
y: event.clientY,
|
|
});
|
|
},
|
|
});
|
|
|
|
return (
|
|
<div className={className === "default" ? "draggable-node" : "draggable-node" + "__" + className}
|
|
ref={draggableRef}>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* the DndToolbar defines how the drag and drop toolbar component works
|
|
* and includes the default onDrop behavior through handleNodeDrop
|
|
* @constructor
|
|
*/
|
|
export function DndToolbar() {
|
|
const {setNodes, screenToFlowPosition} = useReactFlow();
|
|
|
|
/**
|
|
* handleNodeDrop implements the default onDrop behavior
|
|
*/
|
|
const handleNodeDrop = useCallback(
|
|
(nodeType: string, 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;
|
|
|
|
// Create a new node and add it to the flow
|
|
if (isInFlow) {
|
|
const position = screenToFlowPosition(screenPosition);
|
|
|
|
const newNode = () => {
|
|
switch (nodeType) {
|
|
case "phase":
|
|
return {
|
|
id: getId(),
|
|
type: nodeType,
|
|
position,
|
|
data: {label: `"new"`, number: (-1)},
|
|
};
|
|
case "start":
|
|
return {
|
|
id: getId(),
|
|
type: nodeType,
|
|
position,
|
|
data: {label: `new start node`},
|
|
};
|
|
case "end":
|
|
return {
|
|
id: getId(),
|
|
type: nodeType,
|
|
position,
|
|
data: {label: `new end node`},
|
|
};
|
|
case "norm":
|
|
return {
|
|
id: getId(),
|
|
type: nodeType,
|
|
position,
|
|
data: {label: `new norm node`},
|
|
};
|
|
default: {
|
|
return {
|
|
id: getId(),
|
|
type: nodeType,
|
|
position,
|
|
data: {label: `new default node`},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
setNodes((nds) => nds.concat(newNode()));
|
|
}
|
|
},
|
|
[setNodes, screenToFlowPosition],
|
|
);
|
|
|
|
return (
|
|
<div style={{
|
|
padding: '10px 10px',
|
|
outline: '2.5pt solid black',
|
|
borderRadius: '0pt 0pt 5pt 5pt',
|
|
borderColor: 'dimgrey',
|
|
backgroundColor: 'canvas',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
gap: '1rem'
|
|
}}>
|
|
<div className="description">
|
|
You can drag these nodes to the pane to create new nodes.
|
|
</div>
|
|
<div className="DraggableNodeContainer" style={{
|
|
backgroundColor: 'canvas',
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
gap: '1rem',
|
|
justifyContent: 'center'
|
|
}}>
|
|
<DraggableNode className="phase" nodeType="phase" onDrop={handleNodeDrop}>
|
|
phase Node
|
|
</DraggableNode>
|
|
<DraggableNode className="norm" nodeType="norm" onDrop={handleNodeDrop}>
|
|
norm Node
|
|
</DraggableNode>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |