feat(edges): add data and classname props, unify node/edge handling closes #357

This commit is contained in:
moklick
2020-07-27 12:40:39 +02:00
parent cbc41f244e
commit b3391860da
7 changed files with 127 additions and 90 deletions

View File

@@ -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>
</>
);
};
}

View File

@@ -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>

View File

@@ -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}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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)) {