feat(nodes): allow multiple handles

This commit is contained in:
moklick
2019-09-11 17:58:18 +02:00
parent 46bf04f88d
commit 7958022ded
6 changed files with 85 additions and 32 deletions
+7 -2
View File
@@ -3,8 +3,13 @@ import cx from 'classnames';
export default (props) => {
const [sourceNode, setSourceNode] = useState(null);
const hasHandleId = props.connectionSourceId.includes('__');
const sourceIdSplitted = props.connectionSourceId.split('__');
const nodeId = sourceIdSplitted[0];
const handleId = hasHandleId ? sourceIdSplitted[1] : null;
useEffect(() => {
setSourceNode(props.nodes.find(n => n.id === props.connectionSourceId));
setSourceNode(props.nodes.find(n => n.id === nodeId));
}, []);
if (!sourceNode) {
@@ -14,7 +19,7 @@ export default (props) => {
const style = props.connectionLineStyle || {};
const className = cx('react-graph__edge', 'connection', props.className);
const sourceHandle = sourceNode.__rg.handleBounds.source;
const sourceHandle = handleId ? sourceNode.__rg.handleBounds.source.find(d => d.id === handleId) : sourceNode.__rg.handleBounds.source[0];
const sourceHandleX = sourceHandle ? sourceHandle.x + (sourceHandle.width / 2) : sourceNode.__rg.width / 2;
const sourceHandleY = sourceHandle ? sourceHandle.y + (sourceHandle.height / 2) : sourceNode.__rg.height;
const sourceX = sourceNode.__rg.position.x + sourceHandleX;
+46 -13
View File
@@ -4,27 +4,48 @@ import { useStoreState } from 'easy-peasy';
import ConnectionLine from '../ConnectionLine';
import { isEdge } from '../graph-utils';
function getEdgePositions(sourceNode, targetNode) {
function getEdgePositions({ sourceNode, targetNode, sourceHandleId, targetHandleId }) {
const hasSourceHandle = !!sourceNode.__rg.handleBounds.source;
const hasTargetHandle = !!targetNode.__rg.handleBounds.target;
let sourceHandle = null;
let targetHandle = null;
if (hasSourceHandle) {
// there is no sourceHandleId when there are no multiple handles/ handles with ids
// so we just pick the first one
if (sourceNode.__rg.handleBounds.source.length === 1 || !sourceHandleId) {
sourceHandle = sourceNode.__rg.handleBounds.source[0];
} else if (sourceHandleId) {
sourceHandle = sourceNode.__rg.handleBounds.source.find(d => d.id === sourceHandleId);
}
}
if (hasTargetHandle) {
if (targetNode.__rg.handleBounds.target.length === 1 || !targetHandleId) {
targetHandle = targetNode.__rg.handleBounds.target[0];
} else if (targetHandleId) {
targetHandle = targetNode.__rg.handleBounds.target.find(d => d.id === targetHandleId);
}
}
const sourceHandleX = hasSourceHandle ?
sourceNode.__rg.handleBounds.source.x + (sourceNode.__rg.handleBounds.source.width / 2) :
sourceHandle.x + (sourceHandle.width / 2) :
sourceNode.__rg.width / 2;
const sourceHandleY = hasSourceHandle ?
sourceNode.__rg.handleBounds.source.y + (sourceNode.__rg.handleBounds.source.height / 2) :
sourceHandle.y + (sourceHandle.height / 2) :
sourceNode.__rg.height;
const sourceX = sourceNode.__rg.position.x + sourceHandleX;
const sourceY = sourceNode.__rg.position.y + sourceHandleY;
const targetHandleX = hasTargetHandle ?
targetNode.__rg.handleBounds.target.x + (targetNode.__rg.handleBounds.target.width / 2) :
targetHandle.x + (targetHandle.width / 2) :
targetNode.__rg.width / 2;
const targetHandleY = hasTargetHandle ?
targetNode.__rg.handleBounds.target.y + (targetNode.__rg.handleBounds.target.height / 2) :
targetHandle.y + (targetHandle.height / 2) :
0;
const targetX = targetNode.__rg.position.x + targetHandleX;
@@ -37,22 +58,32 @@ function getEdgePositions(sourceNode, targetNode) {
function renderEdge(e, props, state) {
const edgeType = e.type || 'default';
const sourceNode = state.nodes.find(n => n.id === e.source);
const targetNode = state.nodes.find(n => n.id === e.target);
const hasSourceHandleId = e.source.includes('__');
const hasTargetHandleId = e.target.includes('__');
const sourceId = hasSourceHandleId ? e.source.split('__')[0] : e.source;
const targetId = hasTargetHandleId ? e.target.split('__')[0] : e.target;
const sourceHandleId = hasSourceHandleId ? e.source.split('__')[1] : null;
const targetHandleId = hasTargetHandleId ? e.target.split('__')[1] : null;
const sourceNode = state.nodes.find(n => n.id === sourceId);
const targetNode = state.nodes.find(n => n.id === targetId);
if (!sourceNode) {
throw new Error(`couldn't create edge for source id: ${e.source}`);
throw new Error(`couldn't create edge for source id: ${sourceId}`);
}
if (!targetNode) {
throw new Error(`couldn't create edge for target id: ${e.target}`);
throw new Error(`couldn't create edge for target id: ${targetId}`);
}
const EdgeComponent = props.edgeTypes[edgeType] || props.edgeTypes.default;
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(sourceNode, targetNode);
const { sourceX, sourceY, targetX, targetY } = getEdgePositions({ sourceNode, targetNode, sourceHandleId, targetHandleId });
const selected = state.selectedElements
.filter(isEdge)
.find(elm => elm.source === e.source && elm.target === e.target);
.find(elm => elm.source === sourceId && elm.target === targetId);
return (
<EdgeComponent
@@ -63,8 +94,10 @@ function renderEdge(e, props, state) {
selected={selected}
animated={e.animated}
style={e.style}
source={e.source}
target={e.target}
source={sourceId}
target={targetId}
sourceHandleId={sourceHandleId}
targetHandleId={targetHandleId}
sourceX={sourceX}
sourceY={sourceY}
targetX={targetX}
+6 -3
View File
@@ -41,7 +41,8 @@ function onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarg
const BaseHandle = memo(({
source, target, nodeId, onConnect,
setSourceId, setPosition, className, ...rest
setSourceId, setPosition, className,
id = false, ...rest
}) => {
const handleClasses = cx(
'react-graph__handle',
@@ -49,11 +50,13 @@ const BaseHandle = memo(({
{ source, target }
);
const handleId = id ? `${nodeId}__${id}` : nodeId;
return (
<div
data-nodeid={nodeId}
data-nodeid={handleId}
className={handleClasses}
onMouseDown={evt => onMouseDown(evt, { nodeId, setSourceId, setPosition, onConnect, isTarget: target })}
onMouseDown={evt => onMouseDown(evt, { nodeId: handleId, setSourceId, setPosition, onConnect, isTarget: target })}
{...rest}
/>
);
+20 -10
View File
@@ -13,20 +13,31 @@ const isHandle = e => (
);
const getHandleBounds = (sel, nodeElement, parentBounds, k) => {
const handle = nodeElement.querySelector(sel);
const handles = nodeElement.querySelectorAll(sel);
if (!handle) {
if (!handles || !handles.length) {
return null;
}
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
return [].map.call(handles, (handle) => {
const bounds = handle.getBoundingClientRect();
const dimensions = getDimensions(handle);
const nodeIdAttr = handle.getAttribute('data-nodeid');
const nodeIdSplitted = nodeIdAttr.split('__');
return {
x: (bounds.x - parentBounds.x) * (1 / k),
y: (bounds.y - parentBounds.y) * (1 / k),
...dimensions
};
let handleId = null;
if (nodeIdSplitted) {
handleId = nodeIdSplitted.length ? nodeIdSplitted[1] : nodeIdSplitted;
}
return {
id: handleId,
x: (bounds.x - parentBounds.x) * (1 / k),
y: (bounds.y - parentBounds.y) * (1 / k),
...dimensions
};
});
};
const onStart = (evt, { setOffset, onClick, id, type, data, position, transform }) => {
@@ -92,7 +103,6 @@ export default NodeComponent => {
source: getHandleBounds('.source', nodeElement.current, bounds, transform[2]),
target: getHandleBounds('.target', nodeElement.current, bounds, transform[2])
};
store.dispatch.updateNodeData({ id, ...dimensions, handleBounds });
}, []);