merge(connectionLine): wbkd/react-flow:main

This commit is contained in:
Clément Loridan
2021-10-21 14:05:34 +03:00
27 changed files with 1494 additions and 1083 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ const onConnect = (params: Connection | Edge) => console.log('handle onConnect',
const ColorSelectorNode: FC<NodeProps> = ({ data, isConnectable }) => {
return (
<>
<Handle type="source" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
<Handle type="target" position={Position.Left} style={targetHandleStyle} onConnect={onConnect} />
<div>
Custom Color Picker Node: <strong>{data.color}</strong>
</div>
+2 -10
View File
@@ -7,13 +7,11 @@ import ReactFlow, {
addEdge,
MiniMap,
Controls,
updateEdge,
Node,
FlowElement,
OnLoadParams,
Elements,
Position,
ConnectionMode,
SnapGrid,
Connection,
Edge,
@@ -81,7 +79,6 @@ const CustomNodeFlow = () => {
data: { label: 'Output A' },
position: { x: 550, y: 25 },
targetPosition: Position.Left,
},
{
id: '4',
@@ -89,7 +86,6 @@ const CustomNodeFlow = () => {
data: { label: 'Output B' },
position: { x: 550, y: 100 },
targetPosition: Position.Left,
},
{ id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
@@ -101,8 +97,7 @@ const CustomNodeFlow = () => {
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
const onConnect = (params: Connection | Edge) =>
setElements((els) => addEdge({ ...params, animated: true, style: { stroke: '#fff' } }, els));
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
setElements((els) => updateEdge(oldEdge, newConnection, els));
return (
<ReactFlow
elements={elements}
@@ -117,9 +112,6 @@ const CustomNodeFlow = () => {
snapToGrid={true}
snapGrid={snapGrid}
defaultZoom={1.5}
onEdgeUpdate={onEdgeUpdate}
connectionMode={ConnectionMode.Loose}
>
<MiniMap
nodeStrokeColor={(n: Node): string => {
@@ -140,4 +132,4 @@ const CustomNodeFlow = () => {
);
};
export default CustomNodeFlow;
export default CustomNodeFlow;
+33
View File
@@ -0,0 +1,33 @@
import { memo, FC } from 'react';
import { Handle, Position, NodeProps, Connection, Edge } from 'react-flow-renderer';
const onConnect = (params: Connection | Edge) => console.log('handle onConnect', params);
const labelStyle = {
display: 'flex',
alignItems: 'center',
};
const dragHandleStyle = {
display: 'inline-block',
width: 25,
height: 25,
backgroundColor: 'teal',
marginLeft: 5,
borderRadius: '50%',
};
const ColorSelectorNode: FC<NodeProps> = () => {
return (
<>
<Handle type="target" position={Position.Left} onConnect={onConnect} />
<div style={labelStyle}>
Only draggable here <span className="custom-drag-handle" style={dragHandleStyle} />
</div>
<Handle type="source" position={Position.Right} />
</>
);
};
export default memo(ColorSelectorNode);
+21
View File
@@ -0,0 +1,21 @@
import ReactFlow from 'react-flow-renderer';
import DragHandleNode from './DragHandleNode';
const nodeTypes = {
dragHandleNode: DragHandleNode,
};
const elements = [
{
id: '2',
type: 'dragHandleNode',
dragHandle: '.custom-drag-handle',
style: { border: '1px solid #ddd', padding: '20px 40px' },
position: { x: 200, y: 200 },
},
];
const DragHandleFlow = () => <ReactFlow elements={elements} nodeTypes={nodeTypes} />;
export default DragHandleFlow;
@@ -0,0 +1,40 @@
import { FC } from 'react';
import { getBezierPath, ConnectionLineComponentProps, Node } from 'react-flow-renderer';
import { getEdgeParams } from './utils';
const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({
targetX,
targetY,
sourcePosition,
targetPosition,
sourceNode,
}) => {
if (!sourceNode) {
return null;
}
const targetNode = {
id: 'connection-target',
__rf: { width: 1, height: 1, position: { x: targetX, y: targetY } },
} as Node;
const { sx, sy } = getEdgeParams(sourceNode, targetNode);
const d = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition,
targetPosition,
targetX,
targetY,
});
return (
<g>
<path fill="none" stroke="#222" strokeWidth={1.5} className="animated" d={d} />
<circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
</g>
);
};
export default FloatingConnectionLine;
@@ -0,0 +1,35 @@
import { FC, useMemo, CSSProperties } from 'react';
import { EdgeProps, getMarkerEnd, useStoreState, getBezierPath } from 'react-flow-renderer';
import { getEdgeParams } from './utils';
const FloatingEdge: FC<EdgeProps> = ({ id, source, target, arrowHeadType, markerEndId, style }) => {
const nodes = useStoreState((state) => state.nodes);
const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]);
const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]);
if (!sourceNode || !targetNode) {
return null;
}
const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
const d = getBezierPath({
sourceX: sx,
sourceY: sy,
sourcePosition: sourcePos,
targetPosition: targetPos,
targetX: tx,
targetY: ty,
});
return (
<g className="react-flow__connection">
<path id={id} className="react-flow__edge-path" d={d} markerEnd={markerEnd} style={style as CSSProperties} />
</g>
);
};
export default FloatingEdge;
+53
View File
@@ -0,0 +1,53 @@
import React, { useState } from 'react';
import ReactFlow, {
removeElements,
addEdge,
Background,
OnLoadParams,
EdgeTypesType,
Elements,
Connection,
Edge,
ArrowHeadType,
} from 'react-flow-renderer';
import './style.css';
import FloatingEdge from './FloatingEdge';
import FloatingConnectionLine from './FloatingConnectionLine';
import { createElements } from './utils';
const onLoad = (reactFlowInstance: OnLoadParams) => reactFlowInstance.fitView();
const initialElements: Elements = createElements();
const edgeTypes: EdgeTypesType = {
floating: FloatingEdge,
};
const NodeAsHandleFlow = () => {
const [elements, setElements] = useState<Elements>(initialElements);
const onElementsRemove = (elementsToRemove: Elements) => setElements((els) => removeElements(elementsToRemove, els));
const onConnect = (params: Connection | Edge) =>
setElements((els) => addEdge({ ...params, type: 'floating', arrowHeadType: ArrowHeadType.Arrow }, els));
return (
<div className="floatingedges">
<ReactFlow
elements={elements}
onElementsRemove={onElementsRemove}
onConnect={onConnect}
onLoad={onLoad}
edgeTypes={edgeTypes}
connectionLineComponent={FloatingConnectionLine}
>
<Background />
</ReactFlow>
</div>
);
};
export default NodeAsHandleFlow;
+9
View File
@@ -0,0 +1,9 @@
.floatingedges {
flex-direction: column;
display: flex;
height: 100%;
}
.floatingedges .react-flow__handle {
opacity: 0;
}
+99
View File
@@ -0,0 +1,99 @@
import { Position, ArrowHeadType, Node, XYPosition } from 'react-flow-renderer';
// this helper function returns the intersection point
// of the line between the center of the intersectionNode and the target node
function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPosition {
// https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a
const {
width: intersectionNodeWidth,
height: intersectionNodeHeight,
position: intersectionNodePosition,
} = intersectionNode.__rf;
const targetPosition = targetNode.__rf.position;
const w = intersectionNodeWidth / 2;
const h = intersectionNodeHeight / 2;
const x2 = intersectionNodePosition.x + w;
const y2 = intersectionNodePosition.y + h;
const x1 = targetPosition.x + w;
const y1 = targetPosition.y + h;
const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h);
const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h);
const a = 1 / (Math.abs(xx1) + Math.abs(yy1));
const xx3 = a * xx1;
const yy3 = a * yy1;
const x = w * (xx3 + yy3) + x2;
const y = h * (-xx3 + yy3) + y2;
return { x, y };
}
// returns the position (top,right,bottom or right) passed node compared to the intersection point
function getEdgePosition(node: Node, intersectionPoint: XYPosition) {
const n = { ...node.__rf.position, ...node.__rf };
const nx = Math.round(n.x);
const ny = Math.round(n.y);
const px = Math.round(intersectionPoint.x);
const py = Math.round(intersectionPoint.y);
if (px <= nx + 1) {
return Position.Left;
}
if (px >= nx + n.width - 1) {
return Position.Right;
}
if (py <= ny + 1) {
return Position.Top;
}
if (py >= n.y + n.height - 1) {
return Position.Bottom;
}
return Position.Top;
}
// returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge
export function getEdgeParams(source: Node, target: Node) {
const sourceIntersectionPoint = getNodeIntersection(source, target);
const targetIntersectionPoint = getNodeIntersection(target, source);
const sourcePos = getEdgePosition(source, sourceIntersectionPoint);
const targetPos = getEdgePosition(target, targetIntersectionPoint);
return {
sx: sourceIntersectionPoint.x,
sy: sourceIntersectionPoint.y,
tx: targetIntersectionPoint.x,
ty: targetIntersectionPoint.y,
sourcePos,
targetPos,
};
}
export function createElements() {
const elements = [];
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
elements.push({ id: 'target', data: { label: 'Target' }, position: center });
for (let i = 0; i < 8; i++) {
const degrees = i * (360 / 8);
const radians = degrees * (Math.PI / 180);
const x = 250 * Math.cos(radians) + center.x;
const y = 250 * Math.sin(radians) + center.y;
elements.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } });
elements.push({
id: `edge-${i}`,
target: 'target',
source: `${i}`,
type: 'floating',
arrowHeadType: ArrowHeadType.Arrow,
});
}
return elements;
}
+1 -1
View File
@@ -49,7 +49,7 @@ const LayoutFlow = () => {
const nodeWithPosition = dagreGraph.node(el.id);
el.targetPosition = isHorizontal ? Position.Left : Position.Top;
el.sourcePosition = isHorizontal ? Position.Right : Position.Bottom;
// we need to pass a slighltiy different position in order to notify react flow about the change
// we need to pass a slightly different position in order to notify react flow about the change
// @TODO how can we change the position handling so that we dont need this hack?
el.position = { x: nodeWithPosition.x + Math.random() / 1000, y: nodeWithPosition.y };
}
+13 -14
View File
@@ -68,7 +68,7 @@ const initialElements: Elements = [
target: '01',
sourceHandle: 'left',
targetHandle: 'bottom',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -77,7 +77,7 @@ const initialElements: Elements = [
target: '01',
sourceHandle: 'top',
targetHandle: 'right',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -86,7 +86,7 @@ const initialElements: Elements = [
target: '02',
sourceHandle: 'top',
targetHandle: 'left',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -95,7 +95,7 @@ const initialElements: Elements = [
target: '02',
sourceHandle: 'right',
targetHandle: 'bottom',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -104,7 +104,7 @@ const initialElements: Elements = [
target: '03',
sourceHandle: 'right',
targetHandle: 'top',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -113,7 +113,7 @@ const initialElements: Elements = [
target: '03',
sourceHandle: 'bottom',
targetHandle: 'left',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -122,7 +122,7 @@ const initialElements: Elements = [
target: '04',
sourceHandle: 'bottom',
targetHandle: 'right',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -131,7 +131,7 @@ const initialElements: Elements = [
target: '04',
sourceHandle: 'left',
targetHandle: 'top',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -140,7 +140,7 @@ const initialElements: Elements = [
target: '10',
sourceHandle: 'top',
targetHandle: 'bottom',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -149,7 +149,7 @@ const initialElements: Elements = [
target: '20',
sourceHandle: 'right',
targetHandle: 'left',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -158,7 +158,7 @@ const initialElements: Elements = [
target: '30',
sourceHandle: 'bottom',
targetHandle: 'top',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
{
@@ -167,7 +167,7 @@ const initialElements: Elements = [
target: '40',
sourceHandle: 'left',
targetHandle: 'right',
type: 'smoothstep',
type: 'default',
arrowHeadType: ArrowHeadType.Arrow,
},
];
@@ -181,8 +181,7 @@ const getId = (): ElementId => `${id++}`;
const UpdateNodeInternalsFlow = () => {
const [elements, setElements] = useState<Elements>(initialElements);
const onConnect = (params: Connection | Edge) =>
setElements((els) => addEdge({ ...params, type: 'smoothstep' }, els));
const onConnect = (params: Connection | Edge) => setElements((els) => addEdge({ ...params, type: 'default' }, els));
const { project } = useZoomPanHelper();
const onEdgeUpdate = (oldEdge: Edge, newConnection: Connection) =>
setElements((els) => updateEdge(oldEdge, newConnection, els));
+10
View File
@@ -14,6 +14,7 @@ import Provider from './Provider';
import Hidden from './Hidden';
import EdgeTypes from './EdgeTypes';
import CustomConnectionLine from './CustomConnectionLine';
import FloatingEdges from './FloatingEdges';
import NodeTypeChange from './NodeTypeChange';
import NodeTypesObjectChange from './NodeTypesObjectChange';
import UpdatableEdge from './UpdatableEdge';
@@ -26,6 +27,7 @@ import UseZoomPanHelper from './UseZoomPanHelper';
import UseUpdateNodeInternals from './UseUpdateNodeInternals';
import Undirectional from './Undirectional';
import MultiFlows from './MultiFlows';
import DragHandle from './DragHandle';
import './index.css';
@@ -78,6 +80,10 @@ const routes = [
path: '/custom-connectionline',
component: CustomConnectionLine,
},
{
path: '/floating-edges',
component: FloatingEdges,
},
{
path: '/nodetype-change',
component: NodeTypeChange,
@@ -126,6 +132,10 @@ const routes = [
path: '/multiflows',
component: MultiFlows,
},
{
path: '/draghandle',
component: DragHandle,
},
];
const Header = withRouter(({ history, location }) => {