diff --git a/example/src/Edges/CustomEdge.js b/example/src/Edges/CustomEdge.js index 9cfd9619..0a26c7bd 100644 --- a/example/src/Edges/CustomEdge.js +++ b/example/src/Edges/CustomEdge.js @@ -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 ( <> - + - {label} + {data.text} ); -}; \ No newline at end of file +} diff --git a/src/components/Edges/wrapEdge.tsx b/src/components/Edges/wrapEdge.tsx index 83051afb..d3ea8e46 100644 --- a/src/components/Edges/wrapEdge.tsx +++ b/src/components/Edges/wrapEdge.tsx @@ -19,6 +19,7 @@ interface EdgeWrapperProps { selected: boolean; elementsSelectable: boolean; isHidden?: boolean; + data?: any; } export default (EdgeComponent: ComponentType) => { @@ -38,6 +39,7 @@ export default (EdgeComponent: ComponentType) => { labelBgStyle, className, isHidden, + data, ...rest }: EdgeWrapperProps) => { const setSelectedElements = useStoreActions((a) => a.setSelectedElements); @@ -76,6 +78,7 @@ export default (EdgeComponent: ComponentType) => { labelStyle={labelStyle} labelShowBg={labelShowBg} labelBgStyle={labelBgStyle} + data={data} {...rest} /> diff --git a/src/container/EdgeRenderer/index.tsx b/src/container/EdgeRenderer/index.tsx index e2e60fb5..ae4ce4ea 100644 --- a/src/container/EdgeRenderer/index.tsx +++ b/src/container/EdgeRenderer/index.tsx @@ -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) => { - {edges.map((e: Edge) => renderEdge(e, props, nodes, selectedElements, elementsSelectable))} + {edges.map((edge) => renderEdge(edge, props, nodes, selectedElements, elementsSelectable))} {renderConnectionLine && ( { - 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; diff --git a/src/store/index.ts b/src/store/index.ts index 7729f9cd..cfc9faf5 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -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; + edges: Computed; selectedElements: Elements | null; selectedNodesBbox: Rect; @@ -89,9 +90,7 @@ export interface StoreModel { setOnConnect: Action; - setNodes: Action; - - setEdges: Action; + setElements: Action; updateNodeDimensions: Action; @@ -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, diff --git a/src/types/index.ts b/src/types/index.ts index e4ce87ab..fc1124a0 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -2,7 +2,9 @@ import { CSSProperties, MouseEvent } from 'react'; export type ElementId = string; -export type Elements = Array; +export type FlowElement = Node | Edge; + +export type Elements = Array; 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 { diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 80ff56b3..d11527eb 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -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)) {