From 1f7d88fd5ee673bd407ae49ceda39175311ffcaa Mon Sep 17 00:00:00 2001 From: moklick Date: Sat, 19 Sep 2020 13:36:45 +0200 Subject: [PATCH] feat(connectionline): custom component option #516 --- README.md | 1 + .../CustomConnectionLine/ConnectionLine.js | 25 +++++++++++ example/src/CustomConnectionLine/index.js | 25 +++++++++++ example/src/index.js | 5 +++ src/components/ConnectionLine/index.tsx | 45 ++++++++++++++----- src/container/EdgeRenderer/index.tsx | 36 ++++++++++----- src/container/GraphView/index.tsx | 4 ++ src/container/ReactFlow/index.tsx | 4 ++ src/style.css | 8 +++- src/types/index.ts | 13 ++++++ 10 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 example/src/CustomConnectionLine/ConnectionLine.js create mode 100644 example/src/CustomConnectionLine/index.js diff --git a/README.md b/README.md index 01f9e87e..d4521011 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ const BasicFlow = () => ; #### Connection Line Options - `connectionLineType`: connection line type = `default` (bezier), `straight`, `step`, `smoothstep` - `connectionLineStyle`: connection style as svg attributes +- `connectionLineComponent`: [custom connection line component](/example/src/CustomConnectionLin/index.js) #### Keys - `deleteKeyCode`: default: `8` (delete) diff --git a/example/src/CustomConnectionLine/ConnectionLine.js b/example/src/CustomConnectionLine/ConnectionLine.js new file mode 100644 index 00000000..e48fafb3 --- /dev/null +++ b/example/src/CustomConnectionLine/ConnectionLine.js @@ -0,0 +1,25 @@ +import React from 'react'; + +export default ({ + sourceX, + sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + connectionLineType, + connectionLineStyle, +}) => { + return ( + + + + + ); +}; diff --git a/example/src/CustomConnectionLine/index.js b/example/src/CustomConnectionLine/index.js new file mode 100644 index 00000000..d1663843 --- /dev/null +++ b/example/src/CustomConnectionLine/index.js @@ -0,0 +1,25 @@ +import React, { useState } from 'react'; +import ReactFlow, { removeElements, addEdge, Background } from 'react-flow-renderer'; + +import ConnectionLine from './ConnectionLine'; + +const initialElements = [{ id: '1', type: 'input', data: { label: 'Node 1' }, position: { x: 250, y: 5 } }]; + +const ConnectionLineFlow = () => { + const [elements, setElements] = useState(initialElements); + const onElementsRemove = (elementsToRemove) => setElements((els) => removeElements(elementsToRemove, els)); + const onConnect = (params) => setElements((els) => addEdge(params, els)); + + return ( + + + + ); +}; + +export default ConnectionLineFlow; diff --git a/example/src/index.js b/example/src/index.js index 51b9c00f..49e54ccf 100644 --- a/example/src/index.js +++ b/example/src/index.js @@ -14,6 +14,7 @@ import Horizontal from './Horizontal'; import Provider from './Provider'; import Hidden from './Hidden'; import EdgeTypes from './EdgeTypes'; +import CustomConnectionLine from './CustomConnectionLine'; import './index.css'; @@ -74,6 +75,10 @@ const routes = [ path: '/edge-types', component: EdgeTypes, }, + { + path: '/custom-connectionline', + component: CustomConnectionLine, + }, ]; const navLinks = routes.filter((route) => route.label); diff --git a/src/components/ConnectionLine/index.tsx b/src/components/ConnectionLine/index.tsx index 4d010e2f..05d942e3 100644 --- a/src/components/ConnectionLine/index.tsx +++ b/src/components/ConnectionLine/index.tsx @@ -1,9 +1,17 @@ import React, { useEffect, useState, CSSProperties } from 'react'; -import cc from 'classcat'; import { getBezierPath } from '../Edges/BezierEdge'; import { getSmoothStepPath } from '../Edges/SmoothStepEdge'; -import { ElementId, Node, Transform, HandleElement, Position, ConnectionLineType, HandleType } from '../../types'; +import { + ElementId, + Node, + Transform, + HandleElement, + Position, + ConnectionLineType, + ConnectionLineComponent, + HandleType, +} from '../../types'; interface ConnectionLineProps { connectionNodeId: ElementId; @@ -15,7 +23,7 @@ interface ConnectionLineProps { transform: Transform; isConnectable: boolean; connectionLineStyle?: CSSProperties; - className?: string; + CustomConnectionLineComponent?: ConnectionLineComponent; } export default ({ @@ -26,9 +34,9 @@ export default ({ connectionPositionY, connectionLineType = ConnectionLineType.Bezier, nodes = [], - className, transform, isConnectable, + CustomConnectionLineComponent, }: ConnectionLineProps) => { const [sourceNode, setSourceNode] = useState(null); const hasHandleId = connectionNodeId.includes('__'); @@ -45,8 +53,6 @@ export default ({ return null; } - const connectionLineClasses: string = cc(['react-flow__connection', className]); - const sourceHandle = handleId ? sourceNode.__rf.handleBounds[connectionHandleType].find((d: HandleElement) => d.id === handleId) : sourceNode.__rf.handleBounds[connectionHandleType][0]; @@ -55,14 +61,31 @@ export default ({ const sourceX = sourceNode.__rf.position.x + sourceHandleX; const sourceY = sourceNode.__rf.position.y + sourceHandleY; - const targetX = (connectionPositionX - transform[0]) * (1 / transform[2]); - const targetY = (connectionPositionY - transform[1]) * (1 / transform[2]); - - let dAttr: string = ''; + const targetX = (connectionPositionX - transform[0]) / transform[2]; + const targetY = (connectionPositionY - transform[1]) / transform[2]; const isRightOrLeft = sourceHandle?.position === Position.Left || sourceHandle?.position === Position.Right; const targetPosition = isRightOrLeft ? Position.Left : Position.Top; + if (CustomConnectionLineComponent) { + return ( + + + + ); + } + + let dAttr: string = ''; + if (connectionLineType === ConnectionLineType.Bezier) { dAttr = getBezierPath({ sourceX, @@ -96,7 +119,7 @@ export default ({ } return ( - + ); diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index b84161a3..e3ba07bd 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -4,7 +4,17 @@ import { useStoreState } from '../../store/hooks'; import ConnectionLine from '../../components/ConnectionLine/index'; import { isEdge } from '../../utils/graph'; import MarkerDefinitions from './MarkerDefinitions'; -import { XYPosition, Position, Edge, Node, ElementId, HandleElement, Elements, ConnectionLineType } from '../../types'; +import { + XYPosition, + Position, + Edge, + Node, + ElementId, + HandleElement, + Elements, + ConnectionLineType, + ConnectionLineComponent, +} from '../../types'; interface EdgeRendererProps { edgeTypes: any; @@ -13,6 +23,7 @@ interface EdgeRendererProps { onElementClick?: (event: React.MouseEvent, element: Node | Edge) => void; arrowHeadColor: string; markerEndId?: string; + connectionLineComponent?: ConnectionLineComponent; } interface EdgePositions { @@ -193,19 +204,19 @@ function renderEdge( } const EdgeRenderer = (props: EdgeRendererProps) => { - const [tX, tY, tScale] = useStoreState((s) => s.transform); - const edges = useStoreState((s) => s.edges); - const nodes = useStoreState((s) => s.nodes); - const connectionNodeId = useStoreState((s) => s.connectionNodeId); - const connectionHandleType = useStoreState((s) => s.connectionHandleType); - const connectionPosition = useStoreState((s) => s.connectionPosition); - const selectedElements = useStoreState((s) => s.selectedElements); - const nodesConnectable = useStoreState((s) => s.nodesConnectable); - const elementsSelectable = useStoreState((s) => s.elementsSelectable); + const [tX, tY, tScale] = useStoreState((state) => state.transform); + const edges = useStoreState((state) => state.edges); + const nodes = useStoreState((state) => state.nodes); + const connectionNodeId = useStoreState((state) => state.connectionNodeId); + const connectionHandleType = useStoreState((state) => state.connectionHandleType); + const connectionPosition = useStoreState((state) => state.connectionPosition); + const selectedElements = useStoreState((state) => state.selectedElements); + const nodesConnectable = useStoreState((state) => state.nodesConnectable); + const elementsSelectable = useStoreState((state) => state.elementsSelectable); const width = useStoreState((state) => state.width); const height = useStoreState((state) => state.height); - const { connectionLineStyle, connectionLineType, arrowHeadColor } = props; + const { connectionLineStyle, connectionLineType, arrowHeadColor, connectionLineComponent } = props; if (!width) { return null; @@ -218,7 +229,7 @@ const EdgeRenderer = (props: EdgeRendererProps) => { - {edges.map((edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))} + {edges.map((edge: Edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))} {renderConnectionLine && ( { connectionLineStyle={connectionLineStyle} connectionLineType={connectionLineType} isConnectable={nodesConnectable} + CustomConnectionLineComponent={connectionLineComponent} /> )} diff --git a/src/container/GraphView/index.tsx b/src/container/GraphView/index.tsx index b73a24ef..5f2010d3 100644 --- a/src/container/GraphView/index.tsx +++ b/src/container/GraphView/index.tsx @@ -20,6 +20,7 @@ import { Edge, Connection, ConnectionLineType, + ConnectionLineComponent, FlowTransform, OnConnectStartFunc, OnConnectStopFunc, @@ -57,6 +58,7 @@ export interface GraphViewProps { edgeTypes: EdgeTypesType; connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; + connectionLineComponent?: ConnectionLineComponent; deleteKeyCode: number; snapToGrid: boolean; snapGrid: [number, number]; @@ -97,6 +99,7 @@ const GraphView = ({ onSelectionContextMenu, connectionLineType, connectionLineStyle, + connectionLineComponent, selectionKeyCode, onElementsRemove, deleteKeyCode, @@ -289,6 +292,7 @@ const GraphView = ({ connectionLineStyle={connectionLineStyle} arrowHeadColor={arrowHeadColor} markerEndId={markerEndId} + connectionLineComponent={connectionLineComponent} /> {nodesSelectionActive && ( diff --git a/src/container/ReactFlow/index.tsx b/src/container/ReactFlow/index.tsx index 61f7ef43..aee9fa52 100644 --- a/src/container/ReactFlow/index.tsx +++ b/src/container/ReactFlow/index.tsx @@ -26,6 +26,7 @@ import { Edge, Connection, ConnectionLineType, + ConnectionLineComponent, FlowTransform, OnConnectStartFunc, OnConnectStopFunc, @@ -65,6 +66,7 @@ export interface ReactFlowProps extends Omit, 'on edgeTypes: EdgeTypesType; connectionLineType: ConnectionLineType; connectionLineStyle?: CSSProperties; + connectionLineComponent?: ConnectionLineComponent; deleteKeyCode: number; selectionKeyCode: number; snapToGrid: boolean; @@ -116,6 +118,7 @@ const ReactFlow = ({ onSelectionContextMenu, connectionLineType, connectionLineStyle, + connectionLineComponent, deleteKeyCode, selectionKeyCode, snapToGrid, @@ -162,6 +165,7 @@ const ReactFlow = ({ edgeTypes={edgeTypesParsed} connectionLineType={connectionLineType} connectionLineStyle={connectionLineStyle} + connectionLineComponent={connectionLineComponent} selectionKeyCode={selectionKeyCode} onElementsRemove={onElementsRemove} deleteKeyCode={deleteKeyCode} diff --git a/src/style.css b/src/style.css index 4ca6545d..47c9aa0c 100644 --- a/src/style.css +++ b/src/style.css @@ -78,6 +78,11 @@ .react-flow__connection { pointer-events: none; + + .animated { + stroke-dasharray: 5; + animation: dashdraw 0.5s linear infinite; + } } .react-flow__connection-path { @@ -117,6 +122,7 @@ .react-flow__node-default.selectable, .react-flow__node-input.selectable, .react-flow__node-output.selectable { + &.selected, &.selected:hover { box-shadow: 0 0 0 1px #333; @@ -193,4 +199,4 @@ right: 0; top: 50%; transform: translate(0, -50%); -} +} \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts index 04888665..4fd58470 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -248,6 +248,19 @@ export enum ConnectionLineType { SmoothStep = 'smoothstep', } +export type ConnectionLineComponentProps = { + sourceX: number; + sourceY: number; + sourcePosition?: Position; + targetX: number; + targetY: number; + targetPosition?: Position; + connectionLineStyle?: CSSProperties; + connectionLineType: ConnectionLineType; +}; + +export type ConnectionLineComponent = React.ComponentType; + export type OnConnectFunc = (connection: Connection) => void; export type OnConnectStartParams = { nodeId: ElementId | null;