From ad4a23a371b8247d96b71e2417aeb29bff5ee719 Mon Sep 17 00:00:00 2001 From: Allan Bogh Date: Wed, 5 Aug 2020 13:53:15 -0700 Subject: [PATCH] Updated types, function definitions, and optional properties. --- __tests__/components/graph-util.test.js | 4 +- __tests__/components/graph-view.test.js | 21 +++++++ src/components/edge.js | 2 +- src/components/graph-view-props.js | 30 ++++++---- src/components/graph-view.js | 75 +++++++++++++++++-------- src/examples/graph.js | 6 +- typings/index.d.ts | 28 +++++---- 7 files changed, 115 insertions(+), 51 deletions(-) diff --git a/__tests__/components/graph-util.test.js b/__tests__/components/graph-util.test.js index 3a7eb435..36d4b0fd 100644 --- a/__tests__/components/graph-util.test.js +++ b/__tests__/components/graph-util.test.js @@ -130,7 +130,7 @@ describe('GraphUtils class', () => { }, }; - jest.spyOn(document, 'getElementById').mockReturnValue(fakeElement); + jest.spyOn(document, 'querySelector').mockReturnValue(fakeElement); const result = GraphUtils.removeElementFromDom('fake'); expect(fakeElement.parentNode.removeChild).toHaveBeenCalledWith( @@ -140,7 +140,7 @@ describe('GraphUtils class', () => { }); it("does nothing when it can't find the element", () => { - jest.spyOn(document, 'getElementById').mockReturnValue(undefined); + jest.spyOn(document, 'querySelector').mockReturnValue(undefined); const result = GraphUtils.removeElementFromDom('fake'); expect(result).toEqual(false); diff --git a/__tests__/components/graph-view.test.js b/__tests__/components/graph-view.test.js index c0efba68..52813c94 100644 --- a/__tests__/components/graph-view.test.js +++ b/__tests__/components/graph-view.test.js @@ -838,6 +838,13 @@ describe('GraphView component', () => { it('drags an edge', () => { instance.canSwap.mockReturnValue(true); + instance.viewWrapper = { + current: { + querySelector: jest.fn().mockImplementation(selector => { + return {}; + }), + }, + }; const draggedEdge = { source: 'a', target: 'b', @@ -858,6 +865,13 @@ describe('GraphView component', () => { it('handles swapping the edge to a different node', () => { instance.canSwap.mockReturnValue(true); + instance.viewWrapper = { + current: { + querySelector: jest.fn().mockImplementation(selector => { + return {}; + }), + }, + }; const draggedEdge = { source: 'a', target: 'b', @@ -1040,6 +1054,13 @@ describe('GraphView component', () => { }); it('drags the edge', () => { + instance.viewWrapper = { + current: { + querySelector: jest.fn().mockImplementation(selector => { + return {}; + }), + }, + }; event.sourceEvent.buttons = 2; instance.handleZoomStart(event); expect(output.state().draggedEdge).toEqual(edge); diff --git a/src/components/edge.js b/src/components/edge.js index a6cbbeff..26bfa076 100644 --- a/src/components/edge.js +++ b/src/components/edge.js @@ -28,7 +28,7 @@ import { EdgeLabelText } from './edge-label-text'; export type IEdge = { source: string, target: string, - type?: string, + type?: null | string, handleText?: string, handleTooltipText?: string, label_from?: string, diff --git a/src/components/graph-view-props.js b/src/components/graph-view-props.js index b81e2066..ca43ee88 100644 --- a/src/components/graph-view-props.js +++ b/src/components/graph-view-props.js @@ -48,26 +48,34 @@ export type IGraphViewProps = { nodeSubtypes: any, nodeTypes: any, readOnly?: boolean, - selected: any, + selected?: null | any, showGraphControls?: boolean, zoomDelay?: number, zoomDur?: number, canCreateEdge?: (startNode?: INode, endNode?: INode) => boolean, canDeleteEdge?: (selected: any) => boolean, canDeleteNode?: (selected: any) => boolean, + canSwapEdge?: ( + sourceNode: INode, + hoveredNode: INode | null, + swapEdge: IEdge + ) => boolean, onBackgroundClick?: (x: number, y: number, event: any) => void, onCopySelected?: () => void, - onCreateEdge: (sourceNode: INode, targetNode: INode) => void, - onCreateNode: (x: number, y: number, event: any) => void, - onContextMenu: (x: number, y: number, event: any) => void, - onDeleteEdge: (selectedEdge: IEdge, edges: IEdge[]) => void, - onDeleteNode: (selected: any, nodeId: string, nodes: any[]) => void, - onPasteSelected?: () => void, - onSelectEdge: (selectedEdge: IEdge) => void, - onSelectNode: (node: INode | null, event: any) => void, - onSwapEdge: (sourceNode: INode, targetNode: INode, edge: IEdge) => void, + onCreateEdge?: (sourceNode: INode, targetNode: INode) => void, + onCreateNode?: (x: number, y: number, event: any) => void, + onContextMenu?: (x: number, y: number, event: any) => void, + onDeleteEdge?: (selectedEdge: IEdge, edges: IEdge[]) => void, + onDeleteNode?: (selected: any, nodeId: string, nodes: any[]) => void, + onPasteSelected?: ( + selectedNode: INode, + xyCoords?: { x: number, y: number } + ) => void, + onSelectEdge?: (selectedEdge: IEdge) => void, + onSelectNode?: (node: INode | null, event: any) => void, + onSwapEdge?: (sourceNode: INode, targetNode: INode, edge: IEdge) => void, onUndo?: () => void, - onUpdateNode: (node: INode) => void, + onUpdateNode?: (node: INode) => void, renderBackground?: (gridSize?: number) => any, renderDefs?: () => any, renderNode?: ( diff --git a/src/components/graph-view.js b/src/components/graph-view.js index a377fbdd..2c109857 100644 --- a/src/components/graph-view.js +++ b/src/components/graph-view.js @@ -383,15 +383,17 @@ class GraphView extends React.Component { const prevNodeMapNode = this.getNodeById(nodeId, prevNodesMap); - // remove all outgoing edges - prevNodeMapNode.outgoingEdges.forEach(edge => { - this.removeEdgeElement(edge.source, edge.target); - }); + if (prevNodeMapNode) { + // remove all outgoing edges + prevNodeMapNode.outgoingEdges.forEach(edge => { + this.removeEdgeElement(edge.source, edge.target); + }); - // remove all incoming edges - prevNodeMapNode.incomingEdges.forEach(edge => { - this.removeEdgeElement(edge.source, edge.target); - }); + // remove all incoming edges + prevNodeMapNode.incomingEdges.forEach(edge => { + this.removeEdgeElement(edge.source, edge.target); + }); + } // remove node const timeoutId = `nodes-${nodeId}`; @@ -476,12 +478,13 @@ class GraphView extends React.Component { sourceNode !== hoveredNode && (swapEdge.source !== sourceNode[this.props.nodeKey] || swapEdge.target !== hoveredNode[this.props.nodeKey]) && + canSwapEdge && canSwapEdge(sourceNode, hoveredNode, swapEdge) ); } deleteNode(selectedNode: INode) { - const { nodeKey } = this.props; + const { nodeKey, onSelectNode, onDeleteNode } = this.props; const { nodes } = this.state; const nodeId = selectedNode[nodeKey]; @@ -500,12 +503,18 @@ class GraphView extends React.Component { ); // inform consumer - this.props.onSelectNode(null); - this.props.onDeleteNode(selectedNode, nodeId, newNodesArr); + if (onSelectNode) { + onSelectNode(null); + } + + if (onDeleteNode) { + onDeleteNode(selectedNode, nodeId, newNodesArr); + } } deleteEdge(selectedEdge: IEdge) { const { edges } = this.state; + const { onDeleteEdge } = this.props; if (selectedEdge.source == null || selectedEdge.target == null) { return; @@ -541,7 +550,9 @@ class GraphView extends React.Component { } // inform consumer - this.props.onDeleteEdge(selectedEdge, newEdgesArr); + if (onDeleteEdge) { + onDeleteEdge(selectedEdge, newEdgesArr); + } } handleDelete = (selected: IEdge | INode) => { @@ -603,7 +614,7 @@ class GraphView extends React.Component { selectedNodeObj.node && onPasteSelected ) { - onPasteSelected(); + onPasteSelected(selectedNodeObj.node); } break; @@ -614,6 +625,7 @@ class GraphView extends React.Component { handleEdgeSelected = (e: any) => { const { source, target } = e.target.dataset; + const { onSelectEdge } = this.props; let newState = { svgClicked: true, focused: true, @@ -637,7 +649,10 @@ class GraphView extends React.Component { }, }; this.setState(newState); - this.props.onSelectEdge(this.state.edges[originalArrIndex]); + + if (onSelectEdge) { + onSelectEdge(this.state.edges[originalArrIndex]); + } } else { this.setState(newState); } @@ -698,13 +713,16 @@ class GraphView extends React.Component { focused: true, svgClicked: true, }); - onSelectNode(null); + + if (onSelectNode) { + onSelectNode(null); + } if (previousSelection) { this.syncRenderNode(previousSelection); } - if (!readOnly && d3.event.shiftKey) { + if (!readOnly && d3.event.shiftKey && onCreateNode) { const xycoords = d3.mouse(d3.event.target); onCreateNode(xycoords[0], xycoords[1], d3.event); @@ -767,7 +785,7 @@ class GraphView extends React.Component { this.renderConnectedEdgesFromNode(nodeMapNode, true); this.asyncRenderNode(node); } else if ( - (canCreateEdge && canCreateEdge(nodeId)) || + (canCreateEdge && canCreateEdge(node)) || this.state.draggingEdge ) { // render new edge @@ -808,7 +826,9 @@ class GraphView extends React.Component { }); // we expect the parent website to set the selected property to the new edge when it's created - onCreateEdge(hoveredNodeData, edgeEndNode); + if (onCreateEdge) { + onCreateEdge(hoveredNodeData, edgeEndNode); + } } else { // make the system understand that the edge creation process is done even though it didn't work. this.setState({ @@ -835,7 +855,10 @@ class GraphView extends React.Component { if (nodeMap) { Object.assign(nodeMap.node, position); - onUpdateNode(nodeMap.node); + + if (onUpdateNode) { + onUpdateNode(nodeMap.node); + } } } @@ -899,6 +922,7 @@ class GraphView extends React.Component { creatingEdge: boolean, event?: any ) => { + const { onSelectNode } = this.props; const newState = { componentUpToDate: false, selectedNodeObj: { @@ -909,8 +933,8 @@ class GraphView extends React.Component { this.setState(newState); - if (!creatingEdge) { - this.props.onSelectNode(node, event); + if (!creatingEdge && onSelectNode) { + onSelectNode(node, event); } }; @@ -1076,7 +1100,7 @@ class GraphView extends React.Component { handleZoomEnd = () => { const { draggingEdge, draggedEdge, edgeEndNode } = this.state; - const { nodeKey } = this.props; + const { nodeKey, onSwapEdge } = this.props; if (!draggingEdge || !draggedEdge) { if (draggingEdge && !draggedEdge) { @@ -1127,7 +1151,10 @@ class GraphView extends React.Component { // determine the target node and update the edge draggedEdgeCopy.target = edgeEndNode[nodeKey]; this.syncRenderEdge(draggedEdgeCopy); - this.props.onSwapEdge(sourceNodeById.node, edgeEndNode, draggedEdge); + + if (onSwapEdge) { + onSwapEdge(sourceNodeById.node, edgeEndNode, draggedEdge); + } } else { // this resets the dragged edge back to its original position. this.syncRenderEdge(draggedEdge); @@ -1601,7 +1628,7 @@ class GraphView extends React.Component { const height = parent.clientHeight; const next = { - k: viewTransform.k, + k: viewTransform ? viewTransform.k : 0, x: 0, y: 0, }; diff --git a/src/examples/graph.js b/src/examples/graph.js index f79a7af0..2fcb9ae0 100644 --- a/src/examples/graph.js +++ b/src/examples/graph.js @@ -465,14 +465,14 @@ class Graph extends React.Component { }; // Pastes the selected node to mouse position - onPasteSelected = (node: INode, mousePosition: [number, number]) => { + onPasteSelected = (node: INode, mousePosition?: [number, number]) => { const graph = this.state.graph; const newNode = { ...node, id: Date.now(), - x: mousePosition[0], - y: mousePosition[1], + x: mousePosition ? mousePosition[0] : node.x, + y: mousePosition ? mousePosition[1] : node.y, }; graph.nodes = [...graph.nodes, newNode]; diff --git a/typings/index.d.ts b/typings/index.d.ts index ae566762..b7ff27b0 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -118,25 +118,33 @@ declare module 'react-digraph' { nodeSubtypes: any; nodeTypes: any; readOnly?: boolean; - selected: any; + selected?: null | any; showGraphControls?: boolean; zoomDelay?: number; zoomDur?: number; canCreateEdge?: (startNode?: INode, endNode?: INode) => boolean; canDeleteEdge?: (selected: any) => boolean; canDeleteNode?: (selected: any) => boolean; + canSwapEdge?: ( + sourceNode: INode, + hoveredNode: INode | null, + swapEdge: IEdge + ) => boolean, onBackgroundClick?: (x: number, y: number, event: any) => void, onCopySelected?: () => void; - onCreateEdge: (sourceNode: INode, targetNode: INode) => void; - onCreateNode: (x: number, y: number, event: any) => void; - onDeleteEdge: (selectedEdge: IEdge, edges: IEdge[]) => void; - onDeleteNode: (selected: any, nodeId: string, nodes: any[]) => void; - onPasteSelected?: () => void; - onSelectEdge: (selectedEdge: IEdge) => void; - onSelectNode: (node: INode | null, event: any) => void; - onSwapEdge: (sourceNode: INode, targetNode: INode, edge: IEdge) => void; + onCreateEdge?: (sourceNode: INode, targetNode: INode) => void; + onCreateNode?: (x: number, y: number, event: any) => void; + onDeleteEdge?: (selectedEdge: IEdge, edges: IEdge[]) => void; + onDeleteNode?: (selected: any, nodeId: string, nodes: any[]) => void; + onPasteSelected?: ( + selectedNode: INode, + xyCoords?: { x: number, y: number } + ) => void, + onSelectEdge?: (selectedEdge: IEdge) => void; + onSelectNode?: (node: INode | null, event: any) => void; + onSwapEdge?: (sourceNode: INode, targetNode: INode, edge: IEdge) => void; onUndo?: () => void; - onUpdateNode: (node: INode) => void; + onUpdateNode?: (node: INode) => void; renderBackground?: (gridSize?: number) => any; renderDefs?: () => any; renderNode?: (