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