feat(edges): add data and classname props, unify node/edge handling closes #357
This commit is contained in:
@@ -1,16 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default function CustomEdge({
|
export default function CustomEdge({ id, sourceX, sourceY, targetX, targetY, style = {}, data }) {
|
||||||
id, sourceX, sourceY, targetX, targetY, label, style = {}
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<path id={id} style={style} className="react-flow__edge-path" d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`} />
|
<path
|
||||||
|
id={id}
|
||||||
|
style={style}
|
||||||
|
className="react-flow__edge-path"
|
||||||
|
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
|
||||||
|
/>
|
||||||
<text>
|
<text>
|
||||||
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
|
<textPath href={`#${id}`} style={{ fontSize: '12px' }} startOffset="50%" textAnchor="middle">
|
||||||
{label}
|
{data.text}
|
||||||
</textPath>
|
</textPath>
|
||||||
</text>
|
</text>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface EdgeWrapperProps {
|
|||||||
selected: boolean;
|
selected: boolean;
|
||||||
elementsSelectable: boolean;
|
elementsSelectable: boolean;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
|
export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
|
||||||
@@ -38,6 +39,7 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
|
|||||||
labelBgStyle,
|
labelBgStyle,
|
||||||
className,
|
className,
|
||||||
isHidden,
|
isHidden,
|
||||||
|
data,
|
||||||
...rest
|
...rest
|
||||||
}: EdgeWrapperProps) => {
|
}: EdgeWrapperProps) => {
|
||||||
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
|
const setSelectedElements = useStoreActions((a) => a.setSelectedElements);
|
||||||
@@ -76,6 +78,7 @@ export default (EdgeComponent: ComponentType<EdgeCompProps>) => {
|
|||||||
labelStyle={labelStyle}
|
labelStyle={labelStyle}
|
||||||
labelShowBg={labelShowBg}
|
labelShowBg={labelShowBg}
|
||||||
labelBgStyle={labelBgStyle}
|
labelBgStyle={labelBgStyle}
|
||||||
|
data={data}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
@@ -166,6 +166,7 @@ function renderEdge(
|
|||||||
key={edge.id}
|
key={edge.id}
|
||||||
id={edge.id}
|
id={edge.id}
|
||||||
type={edge.type}
|
type={edge.type}
|
||||||
|
data={edge.data}
|
||||||
onClick={props.onElementClick}
|
onClick={props.onElementClick}
|
||||||
selected={isSelected}
|
selected={isSelected}
|
||||||
animated={edge.animated}
|
animated={edge.animated}
|
||||||
@@ -216,7 +217,7 @@ const EdgeRenderer = memo((props: EdgeRendererProps) => {
|
|||||||
<svg width={width} height={height} className="react-flow__edges">
|
<svg width={width} height={height} className="react-flow__edges">
|
||||||
<MarkerDefinitions color={arrowHeadColor} />
|
<MarkerDefinitions color={arrowHeadColor} />
|
||||||
<g transform={transformStyle}>
|
<g transform={transformStyle}>
|
||||||
{edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, elementsSelectable))}
|
{edges.map((edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))}
|
||||||
{renderConnectionLine && (
|
{renderConnectionLine && (
|
||||||
<ConnectionLine
|
<ConnectionLine
|
||||||
nodes={nodes}
|
nodes={nodes}
|
||||||
|
|||||||
@@ -3,94 +3,122 @@ import isEqual from 'fast-deep-equal';
|
|||||||
|
|
||||||
import { useStoreState, useStoreActions } from '../store/hooks';
|
import { useStoreState, useStoreActions } from '../store/hooks';
|
||||||
import { parseElement, isNode, isEdge } from '../utils/graph';
|
import { parseElement, isNode, isEdge } from '../utils/graph';
|
||||||
import { Elements, Node, Edge } from '../types';
|
import { Elements, Node, Edge, FlowElement } from '../types';
|
||||||
|
|
||||||
const useElementUpdater = (elements: Elements): void => {
|
const useElementUpdater = (propElements: Elements): void => {
|
||||||
const stateNodes = useStoreState((s) => s.nodes);
|
const stateElements = useStoreState((s) => s.elements);
|
||||||
const stateEdges = useStoreState((s) => s.edges);
|
const setElements = useStoreActions((a) => a.setElements);
|
||||||
|
|
||||||
const setNodes = useStoreActions((a) => a.setNodes);
|
|
||||||
const setEdges = useStoreActions((a) => a.setEdges);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const nextEdges: Edge[] = elements.filter(isEdge).map((e) => parseElement(e) as Edge);
|
const nextElements: Elements = propElements.map((propElement) => {
|
||||||
const nextNodes: Node[] = elements.filter(isNode).map((propNode) => {
|
const existingElement = stateElements.find((el) => el.id === propElement.id);
|
||||||
const existingNode = stateNodes.find((n) => n.id === propNode.id);
|
|
||||||
|
|
||||||
if (existingNode) {
|
if (existingElement) {
|
||||||
const data = !isEqual(existingNode.data, propNode.data)
|
const data = !isEqual(existingElement.data, propElement.data)
|
||||||
? { ...existingNode.data, ...propNode.data }
|
? { ...existingElement.data, ...propElement.data }
|
||||||
: existingNode.data;
|
: existingElement.data;
|
||||||
|
|
||||||
const style = !isEqual(existingNode.style, propNode.style)
|
const style = !isEqual(existingElement.style, propElement.style)
|
||||||
? { ...existingNode.style, ...propNode.style }
|
? { ...existingElement.style, ...propElement.style }
|
||||||
: existingNode.style;
|
: existingElement.style;
|
||||||
|
|
||||||
const className = existingNode.className === propNode.className ? existingNode.className : propNode.className;
|
const elementProps = {
|
||||||
const isHidden = existingNode.isHidden === propNode.isHidden ? existingNode.isHidden : propNode.isHidden;
|
...existingElement,
|
||||||
const draggable = existingNode.draggable === propNode.draggable ? existingNode.draggable : propNode.draggable;
|
|
||||||
const selectable =
|
|
||||||
existingNode.selectable === propNode.selectable ? existingNode.selectable : propNode.selectable;
|
|
||||||
const connectable =
|
|
||||||
existingNode.connectable === propNode.connectable ? existingNode.connectable : propNode.connectable;
|
|
||||||
|
|
||||||
const positionChanged =
|
|
||||||
existingNode.position.x !== propNode.position.x || existingNode.position.y !== propNode.position.y;
|
|
||||||
|
|
||||||
const nodeProps = {
|
|
||||||
...existingNode,
|
|
||||||
data,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (positionChanged) {
|
if (typeof data !== 'undefined') {
|
||||||
nodeProps.__rf = {
|
elementProps.data = data;
|
||||||
...existingNode.__rf,
|
|
||||||
position: propNode.position,
|
|
||||||
};
|
|
||||||
nodeProps.position = propNode.position;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof style !== 'undefined') {
|
if (typeof style !== 'undefined') {
|
||||||
nodeProps.style = style;
|
elementProps.style = style;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof className !== 'undefined') {
|
if (typeof propElement.className !== 'undefined') {
|
||||||
nodeProps.className = className;
|
elementProps.className = propElement.className;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof isHidden !== 'undefined') {
|
if (typeof propElement.isHidden !== 'undefined') {
|
||||||
nodeProps.isHidden = isHidden;
|
elementProps.isHidden = propElement.isHidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof draggable !== 'undefined') {
|
if (isNode(existingElement)) {
|
||||||
nodeProps.draggable = draggable;
|
const propNode = propElement as Node;
|
||||||
}
|
const nodeProps = elementProps as Node;
|
||||||
|
|
||||||
if (typeof selectable !== 'undefined') {
|
const positionChanged =
|
||||||
nodeProps.selectable = selectable;
|
existingElement.position.x !== propNode.position.x || existingElement.position.y !== propNode.position.y;
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof connectable !== 'undefined') {
|
if (positionChanged) {
|
||||||
nodeProps.connectable = connectable;
|
nodeProps.__rf = {
|
||||||
}
|
...existingElement.__rf,
|
||||||
|
position: propNode.position,
|
||||||
|
};
|
||||||
|
nodeProps.position = propNode.position;
|
||||||
|
}
|
||||||
|
|
||||||
return nodeProps;
|
if (typeof propNode.draggable !== 'undefined') {
|
||||||
|
nodeProps.draggable = propNode.draggable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof propNode.selectable !== 'undefined') {
|
||||||
|
nodeProps.selectable = propNode.selectable;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof propNode.connectable !== 'undefined') {
|
||||||
|
nodeProps.connectable = propNode.connectable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeProps;
|
||||||
|
} else if (isEdge(existingElement)) {
|
||||||
|
const propEdge = propElement as Edge;
|
||||||
|
const edgeProps = elementProps as Edge;
|
||||||
|
|
||||||
|
const labelStyle = !isEqual(existingElement.labelStyle, propEdge.labelStyle)
|
||||||
|
? { ...existingElement.labelStyle, ...propEdge.labelStyle }
|
||||||
|
: existingElement.labelStyle;
|
||||||
|
|
||||||
|
const labelBgStyle = !isEqual(existingElement.labelBgStyle, propEdge.labelBgStyle)
|
||||||
|
? { ...existingElement.labelBgStyle, ...propEdge.labelBgStyle }
|
||||||
|
: existingElement.labelBgStyle;
|
||||||
|
|
||||||
|
if (typeof propEdge.label !== 'undefined') {
|
||||||
|
edgeProps.label = propEdge.label;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof labelStyle !== 'undefined') {
|
||||||
|
edgeProps.labelStyle = labelStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof propEdge.labelShowBg !== 'undefined') {
|
||||||
|
edgeProps.labelShowBg = propEdge.labelShowBg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof labelBgStyle !== 'undefined') {
|
||||||
|
edgeProps.labelBgStyle = labelBgStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof propEdge.animated !== 'undefined') {
|
||||||
|
edgeProps.animated = propEdge.animated;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof propEdge.arrowHeadType !== 'undefined') {
|
||||||
|
edgeProps.arrowHeadType = propEdge.arrowHeadType;
|
||||||
|
}
|
||||||
|
|
||||||
|
return edgeProps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return parseElement(propNode) as Node;
|
return parseElement(propElement) as FlowElement;
|
||||||
});
|
});
|
||||||
|
|
||||||
const nodesChanged: boolean = !isEqual(stateNodes, nextNodes);
|
const elementsChanged: boolean = !isEqual(stateElements, nextElements);
|
||||||
const edgesChanged: boolean = !isEqual(stateEdges, nextEdges);
|
|
||||||
|
|
||||||
if (nodesChanged) {
|
if (elementsChanged) {
|
||||||
setNodes(nextNodes);
|
setElements(nextElements);
|
||||||
}
|
}
|
||||||
|
}, [propElements, stateElements]);
|
||||||
if (edgesChanged) {
|
|
||||||
setEdges(nextEdges);
|
|
||||||
}
|
|
||||||
}, [elements, stateNodes, stateEdges]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useElementUpdater;
|
export default useElementUpdater;
|
||||||
|
|||||||
+15
-19
@@ -1,4 +1,4 @@
|
|||||||
import { createStore, Action, action, Thunk, thunk } from 'easy-peasy';
|
import { createStore, Action, action, Thunk, thunk, computed, Computed } from 'easy-peasy';
|
||||||
import isEqual from 'fast-deep-equal';
|
import isEqual from 'fast-deep-equal';
|
||||||
import { Selection as D3Selection, ZoomBehavior } from 'd3';
|
import { Selection as D3Selection, ZoomBehavior } from 'd3';
|
||||||
import { zoom, zoomIdentity } from 'd3-zoom';
|
import { zoom, zoomIdentity } from 'd3-zoom';
|
||||||
@@ -7,7 +7,7 @@ import { select } from 'd3-selection';
|
|||||||
import { getDimensions, clamp } from '../utils';
|
import { getDimensions, clamp } from '../utils';
|
||||||
import { getHandleBounds } from '../components/Nodes/utils';
|
import { getHandleBounds } from '../components/Nodes/utils';
|
||||||
|
|
||||||
import { getNodesInside, getConnectedEdges, getRectOfNodes } from '../utils/graph';
|
import { getNodesInside, getConnectedEdges, getRectOfNodes, isNode, isEdge } from '../utils/graph';
|
||||||
import {
|
import {
|
||||||
ElementId,
|
ElementId,
|
||||||
Elements,
|
Elements,
|
||||||
@@ -55,8 +55,9 @@ export interface StoreModel {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
transform: Transform;
|
transform: Transform;
|
||||||
nodes: Node[];
|
elements: Elements;
|
||||||
edges: Edge[];
|
nodes: Computed<StoreModel, Node[]>;
|
||||||
|
edges: Computed<StoreModel, Edge[]>;
|
||||||
selectedElements: Elements | null;
|
selectedElements: Elements | null;
|
||||||
selectedNodesBbox: Rect;
|
selectedNodesBbox: Rect;
|
||||||
|
|
||||||
@@ -89,9 +90,7 @@ export interface StoreModel {
|
|||||||
|
|
||||||
setOnConnect: Action<StoreModel, OnConnectFunc>;
|
setOnConnect: Action<StoreModel, OnConnectFunc>;
|
||||||
|
|
||||||
setNodes: Action<StoreModel, Node[]>;
|
setElements: Action<StoreModel, Elements>;
|
||||||
|
|
||||||
setEdges: Action<StoreModel, Edge[]>;
|
|
||||||
|
|
||||||
updateNodeDimensions: Action<StoreModel, NodeDimensionUpdate>;
|
updateNodeDimensions: Action<StoreModel, NodeDimensionUpdate>;
|
||||||
|
|
||||||
@@ -138,8 +137,9 @@ export const storeModel: StoreModel = {
|
|||||||
width: 0,
|
width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
transform: [0, 0, 1],
|
transform: [0, 0, 1],
|
||||||
nodes: [],
|
elements: [],
|
||||||
edges: [],
|
nodes: computed([(state) => state.elements], (elements) => elements.filter((el) => isNode(el)) as Node[]),
|
||||||
|
edges: computed([(state) => state.elements], (elements) => elements.filter((el) => isEdge(el)) as Edge[]),
|
||||||
selectedElements: null,
|
selectedElements: null,
|
||||||
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
selectedNodesBbox: { x: 0, y: 0, width: 0, height: 0 },
|
||||||
|
|
||||||
@@ -181,12 +181,8 @@ export const storeModel: StoreModel = {
|
|||||||
state.onConnect = onConnect;
|
state.onConnect = onConnect;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
setNodes: action((state, nodes) => {
|
setElements: action((state, elements) => {
|
||||||
state.nodes = nodes;
|
state.elements = elements;
|
||||||
}),
|
|
||||||
|
|
||||||
setEdges: action((state, edges) => {
|
|
||||||
state.edges = edges;
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateNodeDimensions: action((state, { id, nodeElement }) => {
|
updateNodeDimensions: action((state, { id, nodeElement }) => {
|
||||||
@@ -207,8 +203,8 @@ export const storeModel: StoreModel = {
|
|||||||
target: getHandleBounds('.target', nodeElement, bounds, state.transform[2]),
|
target: getHandleBounds('.target', nodeElement, bounds, state.transform[2]),
|
||||||
};
|
};
|
||||||
|
|
||||||
state.nodes.forEach((n) => {
|
state.elements.forEach((n) => {
|
||||||
if (n.id === id) {
|
if (n.id === id && isNode(n)) {
|
||||||
n.__rf = {
|
n.__rf = {
|
||||||
...n.__rf,
|
...n.__rf,
|
||||||
...dimensions,
|
...dimensions,
|
||||||
@@ -230,8 +226,8 @@ export const storeModel: StoreModel = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
state.nodes.forEach((n) => {
|
state.elements.forEach((n) => {
|
||||||
if (n.id === id) {
|
if (n.id === id && isNode(n)) {
|
||||||
n.__rf = {
|
n.__rf = {
|
||||||
...n.__rf,
|
...n.__rf,
|
||||||
position,
|
position,
|
||||||
|
|||||||
+7
-1
@@ -2,7 +2,9 @@ import { CSSProperties, MouseEvent } from 'react';
|
|||||||
|
|
||||||
export type ElementId = string;
|
export type ElementId = string;
|
||||||
|
|
||||||
export type Elements = Array<Node | Edge>;
|
export type FlowElement = Node | Edge;
|
||||||
|
|
||||||
|
export type Elements = Array<FlowElement>;
|
||||||
|
|
||||||
export type Transform = [number, number, number];
|
export type Transform = [number, number, number];
|
||||||
|
|
||||||
@@ -64,6 +66,8 @@ export interface Edge {
|
|||||||
animated?: boolean;
|
animated?: boolean;
|
||||||
arrowHeadType?: ArrowHeadType;
|
arrowHeadType?: ArrowHeadType;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
|
data?: any;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum BackgroundVariant {
|
export enum BackgroundVariant {
|
||||||
@@ -95,6 +99,7 @@ export interface EdgeProps {
|
|||||||
style?: CSSProperties;
|
style?: CSSProperties;
|
||||||
arrowHeadType?: ArrowHeadType;
|
arrowHeadType?: ArrowHeadType;
|
||||||
markerEndId?: string;
|
markerEndId?: string;
|
||||||
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EdgeBezierProps extends EdgeProps {
|
export interface EdgeBezierProps extends EdgeProps {
|
||||||
@@ -227,6 +232,7 @@ export interface EdgeCompProps {
|
|||||||
onClick?: (edge: Edge) => void;
|
onClick?: (edge: Edge) => void;
|
||||||
animated?: boolean;
|
animated?: boolean;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
|
data?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EdgeTextProps {
|
export interface EdgeTextProps {
|
||||||
|
|||||||
+1
-1
@@ -85,7 +85,7 @@ export const project = (position: XYPosition): XYPosition => {
|
|||||||
|
|
||||||
export const parseElement = (element: Node | Edge): Node | Edge => {
|
export const parseElement = (element: Node | Edge): Node | Edge => {
|
||||||
if (!element.id) {
|
if (!element.id) {
|
||||||
throw new Error('All elements (nodes and edges) need to have an id.');
|
throw new Error('All nodes and edges need to have an id.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEdge(element)) {
|
if (isEdge(element)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user