reaflow
reaflow copied to clipboard
[Feature Request] Implement `onPortLink`
My case is very simple - I need to link two ports between two nodes. How one should do it? This is what I tried to do:
<rf.Canvas
nodes={nodes}
edges={edges}
onNodeLink={(event, from, to) => {
// no `fromPort` and `toPort` here; does not run on port links
console.log({ from, to })
}}
node={nodeProps => (
<rf.Node
dragType="port"
port={
<Port
onEnter={(event, port) =>
setTargetPortId(port.id)
}}
onLeave={(event, port) => {
setTargetPortId(null)
}}
onDragStart={(event, pos, port) => {
setSrcPortId(port.id)
}}
onDragEnd={(event, pos, port) => {
setSrcPortId(null)
setTargetPortId(null)
}}
/>
}
/>
First I thought I can do it with onDragEnd but there is no node there
I think I figured it out
// const defaultNodes = ...
// const defaultEdges = ...
function PortDraggingExample(props) {
// dragging state
const [srcNodeId, setSrcNodeId] = useState(null);
const [srcPortId, setSrcPortId] = useState(null);
const [targetNodeId, setTargetNodeId] = useState(null);
const [targetPortId, setTargetPortId] = useState(null);
// graph state
const [nodes, setNodes] = useState(defaultNodes);
const [edges, setEdges] = useState(defaultEdges);
return (
<Canvas
nodes={nodes}
edges={edges}
node={(nodeProps) => (
<Node
dragType="port"
onEnter={(_event, node) => setTargetNodeId(node.id)}
port={
<Port
onEnter={(_event, port) => setTargetPortId(port.id)}
onLeave={(_event, _port) => setTargetPortId(null)}
onDragStart={(_event, _pos, port) => {
setSrcNodeId(nodeProps.id);
setSrcPortId(port.id);
}}
onDragEnd={(_event, _pos, _port) => {
setSrcPortId(null);
setTargetPortId(null);
setEdges([
...edges,
{
id: `${srcNodeId}.${srcPortId}-${targetNodeId}.${targetPortId}`,
from: srcNodeId,
fromPort: srcPortId,
to: targetNodeId,
toPort: targetPortId,
},
]);
}}
/>
}
/>
)}
/>
);
}
- It would be great to add this example to docs
- Would be great to have simpler API for that :)
@emil14 - Glad to hear you were able to figure it out.
- Absolutely - Could you make a PR to add what you think is missing?
- The API design is so tricky - the goal of what we implemented was to make it declarative and interchangeable. The alternative would be a bunch of top level props which would get messy too. I agree its not the most clear - I'm open to suggestions!
- As soon as I'll be sure this approach does the trick, I need a bit more testing for now
- I see 2 ways to implement this:
first way is to add onPortLink prop and second is to extend onNodeLink and pass fromPort/toPort properties
- I tested code and found a few bugs, now I have a working solution. Will add example to docs later
@emil14 Can you share your working solution snippet for our reference. I am also trying to implement a similar functionality.
@rishab-sharma here's a piece from my project
function NetworkEditor(props: NetworkProps) {
const [selections, setSelections] = useState<string[]>([])
// dragging state
const [srcNodeId, setSrcNodeId] = useState<string | null>(null)
const [srcPortId, setSrcPortId] = useState<string | null>(null)
const [dstNodeId, setDstNodeId] = useState<string | null>(null)
const [dstPortId, setDstPortId] = useState<string | null>(null)
const renderContextMenu = (e: MouseEvent) => {
e.preventDefault()
ContextMenu.show(
<Menu>
<MenuItem text="Add node" onClick={props.onAddNode} />
</Menu>,
{ left: e.clientX, top: e.clientY },
() => {}
)
}
const { nodes, edges } = rfGraph(props.module)
return (
<div
style={{
position: "relative",
width: "100%",
height: "100%",
}}
onContextMenu={event => renderContextMenu(event.nativeEvent as any)}
>
<div
style={{
position: "absolute",
left: 0,
right: 0,
top: 0,
bottom: 0,
background: "black",
}}
>
<rf.Canvas
nodes={nodes}
edges={edges}
selections={selections}
onCanvasClick={() => setSelections([])}
node={nodeProps => (
<rf.Node
dragType="port"
onClick={(_event, node) => {
if (selections.includes(node.id)) {
props.onNodeClick(node.id)
setSelections([])
return
}
setSelections([node.id])
}}
onRemove={(_event, node) => props.onRemoveNode(node.id)}
port={
<Port
onEnter={(_event, port) => {
setDstNodeId(nodeProps.id)
setDstPortId(port.id)
}}
onLeave={(_event, _port) => {}}
onDragStart={(_event, _pos, port) => {
setSrcNodeId(nodeProps.id)
setSrcPortId(port.id)
}}
onDragEnd={(_event, _pos, _port) => {
props.onAddConnection({
from: {
node: srcNodeId,
port: removePortPrefix(srcPortId),
},
to: {
node: dstNodeId,
port: removePortPrefix(dstPortId),
},
})
setSrcPortId(null)
setDstPortId(null)
}}
/>
}
onEnter={(_event, node) => {}}
/>
)}
edge={edge => (
<Edge
onClick={(_event, edge) => setSelections([edge.id])}
onRemove={(_event, edge) => {
props.onRemoveConnection({
from: {
node: edge.from,
port: removePortPrefix(edge.fromPort),
},
to: {
node: edge.to,
port: removePortPrefix(edge.toPort),
},
})
}}
// onAdd={}
/>
)}
/>
</div>
</div>
)
}