From 9f5461e278aa45da8b9107edd2a2c67e547fae73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=BCrkan=20G=C3=BCran?= Date: Fri, 2 Dec 2022 12:34:04 +0100 Subject: [PATCH] Partially implemented inflexible ports(cases) --- .../src/utils/graph.ts | 401 ++++++++++-------- 1 file changed, 234 insertions(+), 167 deletions(-) diff --git a/src/designer/elsa-workflows-designer/src/utils/graph.ts b/src/designer/elsa-workflows-designer/src/utils/graph.ts index 0eb008c17..dd2f61d3e 100644 --- a/src/designer/elsa-workflows-designer/src/utils/graph.ts +++ b/src/designer/elsa-workflows-designer/src/utils/graph.ts @@ -1,198 +1,265 @@ import { Edge, Graph, Node } from "@antv/x6"; import { PortManager } from "@antv/x6/lib/model/port"; import { Connection } from "../modules/flowchart/models"; -import {v4 as uuid} from 'uuid'; +import { v4 as uuid } from 'uuid'; import { Activity } from "../models"; +import {ActivityNode} from "../services"; export function adjustPortMarkupByNode(node: Node) { - node.getPorts().forEach(port => { - if(port.type == 'out'){ - node.setPortProp(port.id, "attrs", { - circle: { - r: 5, - magnet: true, - stroke: '#fff', - strokeWidth: 2, - fill: '#3c82f6', - }, - text: { - fontSize: 12, - fill: '#888', - }, - }); - } - else { - node.setPortProp(port.id, "attrs", { - circle: { - r: 5, - magnet: true, - stroke: '#3c82f6', - strokeWidth: 2, - fill: '#fff', - }, - text: { - fontSize: 12, - fill: '#888', - }, - }); - } - }); -} - -function createEdge(connection: Connection): Edge.Metadata { - return { - shape: 'elsa-edge', - zIndex: -1, - data: connection, - source: connection.source, - target: connection.target, - sourcePort: connection.sourcePort, - targetPort: connection.targetPort - }; -} - -function hasPortAnEdge(graph: Graph, port: PortManager.PortMetadata) { - return graph.getEdges().some(({data}) => data.sourcePort == port.id || data.targetPort == port.id); -} - -function findMatchingPortForEdge(node: Node, position: string, portType: string, portName: string) { - return node.getPorts().find(p => p.position == position && p.type == portType && (portName == "In" || p.attrs?.text?.text == portName)); -} - -function getPortNameByPortId(portId: string) { - return portId.split('_')[1] == 'null' ? null : portId.split('_')[1]; -} - -function updatePortsAndEdges( - graph: Graph, - selectedNode: Node, - newSelectedNodePosition: "left" | "right" | "top" | "bottom", - neighbourNode: Node, - newNeighbourNodePosition: "left" | "right" | "top" | "bottom" -) -{ - updatePortsAndEdgesOfNodeCouple(graph, selectedNode, neighbourNode, newSelectedNodePosition, newNeighbourNodePosition); - updatePortsAndEdgesOfNodeCouple(graph, neighbourNode, selectedNode, newNeighbourNodePosition, newSelectedNodePosition); - - adjustPortMarkupByNode(selectedNode); - adjustPortMarkupByNode(neighbourNode); -} - -function updatePortsAndEdgesOfNodeCouple(graph: Graph, sourceNode: Node, targetNode: Node, newSourceNodePosition: string, newTargetNodePosition: string) -{ - const edge = graph.model.getEdges().find(({ data }) => data.source == sourceNode.id && data.target == targetNode.id); - - if (edge != null) { - const sourceNodePort = sourceNode.getPort(edge.data.sourcePort) ?? sourceNode.getPorts().find(p => p.type == "out" && getPortNameByPortId(p.id) == getPortNameByPortId(edge.data.sourcePort)); - const targetNodePort = targetNode.getPort(edge.data.targetPort) ?? targetNode.getPorts().find(p => p.type == "in"); - - if(sourceNodePort.position != newSourceNodePosition || targetNodePort.position != newTargetNodePosition) { - graph.removeEdge(edge); - - const newSourceNodePortId = updatePort(graph, sourceNode, sourceNodePort, newSourceNodePosition); - const newTargetNodePortId = updatePort(graph, targetNode, targetNodePort, newTargetNodePosition); - - graph.addEdge(createEdge({ - source: sourceNode.id, - target: targetNode.id, - sourcePort: newSourceNodePortId ?? sourceNodePort.id, - targetPort: newTargetNodePortId ?? targetNodePort.id - })); - } + node.getPorts().forEach(port => { + if (port.type == 'out') { + node.setPortProp(port.id, "attrs", { + circle: { + r: 5, + magnet: true, + stroke: '#fff', + strokeWidth: 2, + fill: '#3c82f6', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }); } -} - -function updatePort(graph: Graph, node: Node, nodePort: PortManager.PortMetadata, newNodePosition: string) -{ - let newNodePortId = null; - if (nodePort.position != newNodePosition) { - - if (!hasPortAnEdge(graph, nodePort)) { - node.removePort(nodePort); - } - - const matchingPort = findMatchingPortForEdge(node, newNodePosition, nodePort.type, getPortNameByPortId(nodePort.id)); - - if(matchingPort == null){ - newNodePortId = createNewPort(nodePort, node, newNodePosition); - } - else{ - newNodePortId = matchingPort.id; - } - } - return newNodePortId; -} - -function createNewPort(nodePort: PortManager.PortMetadata, node: Node, newNodePosition: string) { - const newNodePortId = uuid() + '_' + getPortNameByPortId(nodePort.id); - - node.addPort({ - ...nodePort, - group: newNodePosition, - id: newNodePortId, - position: newNodePosition, - type: nodePort.type - }); - return newNodePortId; -} - -export function autoOrientPortsAndEdges(graph: Graph, selectedNode: Node) -{ - const selectedCenter = selectedNode.getBBox().center; - const neighbors = graph.getNeighbors(selectedNode); - neighbors.forEach((neighbourNode) => { - const neighborCenter = neighbourNode.getBBox().center; - const dx = selectedCenter.x - neighborCenter.x; - const dy = selectedCenter.y - neighborCenter.y; - if (dx >= 0 && dy >= 0) { - if (dx > dy) { - updatePortsAndEdges(graph, selectedNode, "left", neighbourNode as Node, "right"); - } else { - updatePortsAndEdges(graph, selectedNode, "top", neighbourNode as Node, "bottom"); - } - } else if (dx >= 0 && dy <= 0) { - if (dx > -dy) { - updatePortsAndEdges(graph, selectedNode, "left", neighbourNode as Node, "right"); - } else { - updatePortsAndEdges(graph, selectedNode, "bottom", neighbourNode as Node, "top"); - } - } else if (dx <= 0 && dy >= 0) { - if (-dx > dy) { - updatePortsAndEdges(graph, selectedNode, "right", neighbourNode as Node, "left"); - } else { - updatePortsAndEdges(graph, selectedNode, "top", neighbourNode as Node, "bottom"); - } - } else if (dx <= 0 && dy <= 0) { - if (dx > dy) { - updatePortsAndEdges(graph, selectedNode, "right", neighbourNode as Node, "left"); - } else { - updatePortsAndEdges(graph, selectedNode, "bottom", neighbourNode as Node, "left"); - } + else { + node.setPortProp(port.id, "attrs", { + circle: { + r: 5, + magnet: true, + stroke: '#3c82f6', + strokeWidth: 2, + fill: '#fff', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }); } }); } -export function rebuildGraph(graph: Graph) -{ +function createEdge(connection: Connection): Edge.Metadata { + return { + shape: 'elsa-edge', + zIndex: -1, + data: connection, + source: connection.source, + target: connection.target, + sourcePort: connection.sourcePort, + targetPort: connection.targetPort + }; +} + +function hasPortAnEdge(graph: Graph, port: PortManager.PortMetadata) { + return graph.getEdges().some(({ data }) => data.sourcePort == port.id || data.targetPort == port.id); +} + +function findMatchingPortForEdge(node: Node, position: string, portType: string, portName: string) { + return node.getPorts().find(p => p.position == position && p.type == portType && getPortNameByPortId(p.id) == portName); +} + +function getPortNameByPortId(portId: string) { + return portId.includes('_') ? (portId.split('_')[1] == 'null' ? null : portId.split('_')[1]) : portId; +} + +function findOutgoingEdgesByPortId(graph: Graph, portId: string): Edge[] { + return graph.model.getEdges().filter(({ data }) => data.sourcePort == portId); +} + +function updatePortsAndEdges( + graph: Graph, + selectedNode: Node, + newSelectedNodePosition: "left" | "right" | "top" | "bottom", + neighbourNode: Node, + newNeighbourNodePosition: "left" | "right" | "top" | "bottom" +) { + updatePortsAndEdgeOfNodeCouple(graph, selectedNode, neighbourNode, newSelectedNodePosition, newNeighbourNodePosition); + updatePortsAndEdgeOfNodeCouple(graph, neighbourNode, selectedNode, newNeighbourNodePosition, newSelectedNodePosition); + + adjustPortMarkupByNode(selectedNode); + adjustPortMarkupByNode(neighbourNode); +} + +function updatePortsAndEdgeOfNodeCouple(graph: Graph, sourceNode: Node, targetNode: Node, newSourceNodePosition: string, newTargetNodePosition: string) { + const edge = graph.model.getEdges().find(({ data }) => data.source == sourceNode.id && data.target == targetNode.id); + + if (edge != null) { + const sourcePortOfConnection = edge.data.sourcePort; + + if(isNewCalculationNeededForInflexiblePort(graph, sourcePortOfConnection)){ + // const outgoingEdges = findOutgoingEdgesByPortId(graph, sourcePortOfConnection); + // const targetNodes = graph.getNodes().filter(node => outgoingEdges.map(edge => edge.data.target).includes(node.id)); + // const nodeCouplesWithPositions = calculatePositionsForInflexibleNode(sourceNode, targetNodes); + // console.log(nodeCouplesWithPositions); + + // nodeCouplesWithPositions.forEach(couple => { + // update(graph, couple.sourceNode, couple.targetNode, couple.sourceNodePosition, couple.targetNodePosition); + // }); + + return; + }; + + update(graph, sourceNode, targetNode, newSourceNodePosition, newTargetNodePosition); + } +} + +function update(graph: Graph, sourceNode: Node, targetNode: Node, newSourceNodePosition: string, newTargetNodePosition: string) { + const edge = graph.model.getEdges().find(({ data }) => data.source == sourceNode.id && data.target == targetNode.id); + + const sourceNodePort = sourceNode.getPort(edge.data.sourcePort) ?? sourceNode.getPorts().find(p => p.type == "out" && getPortNameByPortId(p.id) == getPortNameByPortId(edge.data.sourcePort)); + const targetNodePort = targetNode.getPort(edge.data.targetPort) ?? targetNode.getPorts().find(p => p.type == "in" && getPortNameByPortId(p.id) == getPortNameByPortId(edge.data.targetPort)); + + if (sourceNodePort?.position != newSourceNodePosition || targetNodePort?.position != newTargetNodePosition) { + graph.removeEdge(edge); + + const newSourceNodePortId = updatePort(graph, sourceNode, sourceNodePort, newSourceNodePosition); + const newTargetNodePortId = updatePort(graph, targetNode, targetNodePort, newTargetNodePosition); + + graph.addEdge(createEdge({ + source: sourceNode.id, + target: targetNode.id, + sourcePort: newSourceNodePortId ?? sourceNodePort.id, + targetPort: newTargetNodePortId ?? targetNodePort.id + })); + } +} + +function calculatePositionsForInflexibleNode(sourceNode: Node, targetNodes: Node[]): +{ sourceNode: Node, sourceNodePosition: "left" | "right" | "top" | "bottom"; targetNode: Node, targetNodePosition: "left" | "right" | "top" | "bottom"; }[] { + const sourceNodeCenter = sourceNode.getBBox().center; + const dxAverageForTargetNodes = targetNodes.map(node => node.getBBox().center.x).reduce((a, b) => a + b, 0); + const dyAverageForTargetNodes = targetNodes.map(node => node.getBBox().center.y).reduce((a, b) => a + b, 0); + + const sourceNodeWithNewPosition = { node: sourceNode, position: calculatePositionsOfNodeCouple(sourceNodeCenter.x, sourceNodeCenter.y, dxAverageForTargetNodes, dyAverageForTargetNodes).selectedNodePosition }; + return targetNodes.map((targetNode) => { + return { + sourceNode: sourceNodeWithNewPosition.node, + sourceNodePosition: sourceNodeWithNewPosition.position, + targetNode: targetNode, + targetNodePosition: calculatePositionsOfNodeCouple(sourceNode.getBBox().center.x, sourceNode.getBBox().center.y, targetNode.getBBox().center.x, targetNode.getBBox().center.y).neighbourNodePosition + } + }); +} + +function isNewCalculationNeededForInflexiblePort(graph: Graph, inflexiblePort: any) { + const portName = getPortNameByPortId(inflexiblePort); + if (portName != null && portName != "Done") { + const outgoingEdges = findOutgoingEdgesByPortId(graph, inflexiblePort); + if (outgoingEdges.length > 1) { + return true; + } + } + return false; +} + +function updatePort(graph: Graph, node: Node, nodePort: PortManager.PortMetadata, newNodePosition: string) { + let newNodePortId = null; + if (nodePort?.position != newNodePosition) { + + if (!hasPortAnEdge(graph, nodePort)) { + node.removePort(nodePort); + } + + const matchingPort = findMatchingPortForEdge(node, newNodePosition, nodePort.type, getPortNameByPortId(nodePort.id)); + + if (matchingPort == null) { + newNodePortId = createNewPort(nodePort, node, newNodePosition); + } + else { + newNodePortId = matchingPort.id; + } + } + return newNodePortId; +} + +function createNewPort(nodePort: PortManager.PortMetadata, node: Node, newNodePosition: string) { + const newNodePortId = uuid() + '_' + getPortNameByPortId(nodePort.id); + + node.addPort({ + ...nodePort, + group: newNodePosition, + id: newNodePortId, + position: newNodePosition, + type: nodePort.type + }); + return newNodePortId; +} + +export function autoOrientPortsAndEdges(graph: Graph, selectedNode: Node) { + const neighbors = graph.getNeighbors(selectedNode); + const selectedCenter = selectedNode.getBBox().center; + const nodeGroupWithPositions = neighbors.map((neighbourNode) => { + return { + ...calculatePositionsOfNodeCouple(selectedCenter.x, selectedCenter.y, neighbourNode.getBBox().center.x, neighbourNode.getBBox().center.y), + selectedNode: selectedNode, + neighbourNode: neighbourNode + } + }); + nodeGroupWithPositions.forEach(group => updatePortsAndEdges(graph, group.selectedNode, group.selectedNodePosition, group.neighbourNode as Node, group.neighbourNodePosition)); +} + +export function rebuildGraph(graph: Graph) { graph.getNodes().forEach((node: Node) => { autoOrientPortsAndEdges(graph, node); + adjustPortMarkupByNode(node); }); } export function adjustConnectionsInRequestModel(root: Activity) { - if(root.connections.length > 0){ + if (root.connections?.length > 0) { root.connections.forEach((connection: { sourcePort: string; targetPort: string; }) => { connection.sourcePort = getPortNameByPortId(connection.sourcePort); connection.targetPort = getPortNameByPortId(connection.targetPort); }); } + let activitiesWithConnections = root.activities?.filter(act => act.body?.connections?.length > 0); + activitiesWithConnections.forEach(activity => { + adjustConnectionsInRequestModel(activity.body); + }); } export function adjustConnectionsInResponseModel(root: Activity) { - if(root.connections.length > 0){ + if (root.connections.length > 0) { root.connections.forEach((connection: { sourcePort: string; targetPort: string; }) => { connection.sourcePort = uuid() + '_' + connection.sourcePort; connection.targetPort = uuid() + '_' + connection.targetPort; }); } + let activitiesWithConnections = root.activities?.filter(act => act.body?.connections?.length > 0); + activitiesWithConnections.forEach(activity => { + adjustConnectionsInResponseModel(activity.body); + }); } + +function calculatePositionsOfNodeCouple(selectedNodeX: number, selectedNodeY: number, neighbourNodeX: number, neighbourNodeY: number): { selectedNodePosition: "left" | "right" | "top" | "bottom"; neighbourNodePosition: "left" | "right" | "top" | "bottom"; } { + const dx = selectedNodeX - neighbourNodeX; + const dy = selectedNodeY - neighbourNodeY; + if (dx >= 0 && dy >= 0) { + if (dx > dy) { + return {selectedNodePosition: "left", neighbourNodePosition: "right"}; + } else { + return {selectedNodePosition: "top", neighbourNodePosition: "bottom"}; + } + } else if (dx >= 0 && dy <= 0) { + if (dx > -dy) { + return {selectedNodePosition: "left", neighbourNodePosition: "right"}; + } else { + return {selectedNodePosition: "bottom", neighbourNodePosition: "top"}; + } + } else if (dx <= 0 && dy >= 0) { + if (-dx > dy) { + return {selectedNodePosition: "right", neighbourNodePosition: "left"}; + } else { + return {selectedNodePosition: "top", neighbourNodePosition: "bottom"}; + } + } else if (dx <= 0 && dy <= 0) { + if (dx > dy) { + return {selectedNodePosition: "right", neighbourNodePosition: "left"}; + } else { + return {selectedNodePosition: "bottom", neighbourNodePosition: "left"}; + } + } +} +