Merge remote-tracking branch 'origin/v3' into v3-before-autorotate
This commit is contained in:
commit
cfc06cf058
|
|
@ -9,10 +9,18 @@
|
|||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100% !important;
|
||||
max-width: 100%;
|
||||
min-height: 130px;
|
||||
max-height: 200px !important;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
--tw-border-opacity: 1;
|
||||
border: 1px solid rgb(209 213 219 / var(--tw-border-opacity));
|
||||
-moz-appearance: textfield-multiline;
|
||||
-webkit-appearance: textarea;
|
||||
overflow: auto;
|
||||
resize: both;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.monaco-editor {
|
||||
|
|
|
|||
|
|
@ -213,9 +213,9 @@ export class MonacoEditor {
|
|||
const padding = this.padding || 'pt-1.5 pl-1';
|
||||
return (
|
||||
<Host
|
||||
class="monaco-editor-host border focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300 p-4"
|
||||
class="monaco-editor-host focus:ring-blue-500 focus:border-blue-500 block w-full min-w-0 sm:text-sm border-gray-300 p-4"
|
||||
style={{'min-height': this.editorHeight}}>
|
||||
<div ref={el => this.container = el} class={`monaco-editor-container ${padding}`}/>
|
||||
<div ref={el => this.container = el} class={`rounded-md monaco-editor-container ${padding}`}/>
|
||||
</Host>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -257,7 +257,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
|
||||
@Method()
|
||||
async import(root: Activity): Promise<void> {
|
||||
return await this.importInternal(root);
|
||||
await this.importInternal(root);
|
||||
}
|
||||
|
||||
@Method()
|
||||
|
|
@ -275,6 +275,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
const currentActivityId = this.currentPath[this.currentPath.length - 1].activityId;
|
||||
const currentActivity = this.activityLookup[currentActivityId];
|
||||
const parentActivity = this.activityLookup[parentActivityId] as Flowchart;
|
||||
|
||||
const parentActivityDescriptor = descriptorsStore.activityDescriptors.find(x => x.typeName == parentActivity.type);
|
||||
const indexInParent = currentActivity.activities?.findIndex(x => x == parentActivity);
|
||||
const portName = e.detail.port.name;
|
||||
|
|
@ -385,6 +386,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
const currentNavigationItem = currentPath[currentPath.length - 1];
|
||||
const currentPortName = currentNavigationItem?.portName;
|
||||
const currentScope = this.activityLookup[currentNavigationItem.activityId] as Activity;
|
||||
|
||||
const currentScopeDescriptor = this.getActivityDescriptor(currentScope.type);
|
||||
|
||||
if (!!currentPortName) {
|
||||
|
|
@ -445,6 +447,9 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
this.graph.freeze();
|
||||
this.graph.fromJSON(model, {silent: false});
|
||||
this.graph.unfreeze();
|
||||
|
||||
rebuildGraph(this.graph);
|
||||
await this.scrollToStart();
|
||||
};
|
||||
|
||||
private getFlowchartModel = (): FlowchartModel => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import {CellView, Graph, Node, Shape} from '@antv/x6';
|
||||
import { autoOrientConnections } from '../../utils/graph';
|
||||
import './ports';
|
||||
import {Activity} from "../../models";
|
||||
import {Connection} from "./models";
|
||||
|
|
@ -276,5 +277,9 @@ export function createGraph(
|
|||
}
|
||||
});
|
||||
|
||||
graph.on("node:moving", ({ node }) => {
|
||||
autoOrientConnections(graph, node);
|
||||
});
|
||||
|
||||
return graph;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import {Activity, PagedList, Variable, VersionedEntity, VersionOptions} from "..
|
|||
import {Service} from "typedi";
|
||||
import {ElsaApiClientProvider} from "../../../services";
|
||||
import {AxiosResponse} from "axios";
|
||||
import { adjustConnectionsInRequestModel, adjustConnectionsInResponseModel } from '../../../utils/graph';
|
||||
import { adjustConnectionsInRequestModel, adjustConnectionsInResponseModel } from '../../../utils/graph';
|
||||
import { cloneDeep } from '@antv/x6/lib/util/object/object';
|
||||
|
||||
@Service()
|
||||
export class WorkflowDefinitionsApi {
|
||||
|
|
@ -44,8 +47,15 @@ export class WorkflowDefinitionsApi {
|
|||
}
|
||||
|
||||
async post(request: SaveWorkflowDefinitionRequest): Promise<WorkflowDefinition> {
|
||||
//TODO: Written as a workaround for different server and client models.
|
||||
//To be deleted after the port model on backend is updated.
|
||||
const requestClone = cloneDeep(request);
|
||||
adjustConnectionsInRequestModel(requestClone.root);
|
||||
|
||||
const httpClient = await this.getHttpClient();
|
||||
const response = await httpClient.post<WorkflowDefinition>('workflow-definitions', request);
|
||||
const response = await httpClient.post<WorkflowDefinition>('workflow-definitions', requestClone);
|
||||
|
||||
adjustConnectionsInResponseModel(response.data.root);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
|
@ -132,6 +142,11 @@ export class WorkflowDefinitionsApi {
|
|||
}
|
||||
|
||||
const workflowDefinition = response.data;
|
||||
|
||||
//TODO: Written as a workaround for different server and client models.
|
||||
//To be deleted after the connection model on backend is updated.
|
||||
adjustConnectionsInResponseModel(workflowDefinition.root);
|
||||
|
||||
return {workflowDefinition: workflowDefinition};
|
||||
}
|
||||
|
||||
|
|
|
|||
261
src/designer/elsa-workflows-designer/src/utils/graph.ts
Normal file
261
src/designer/elsa-workflows-designer/src/utils/graph.ts
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
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 { Activity } from "../models";
|
||||
|
||||
export function rebuildGraph(graph: Graph) {
|
||||
graph.getNodes().forEach((node: Node<Node.Properties>) => {
|
||||
autoOrientConnections(graph, node);
|
||||
adjustPortMarkupByNode(node);
|
||||
});
|
||||
}
|
||||
|
||||
export function autoOrientConnections(graph: Graph, selectedNode: Node) {
|
||||
const neighbors = graph.getNeighbors(selectedNode);
|
||||
const selectedCenter = selectedNode.getBBox().center;
|
||||
const nodeCouplesWithPositions = neighbors.map((neighbourNode) => {
|
||||
return {
|
||||
...calculatePortPositionsOfNodeCouple(selectedCenter.x, selectedCenter.y, neighbourNode.getBBox().center.x, neighbourNode.getBBox().center.y),
|
||||
selectedNode: selectedNode,
|
||||
neighbourNode: neighbourNode
|
||||
}
|
||||
});
|
||||
nodeCouplesWithPositions.forEach(couple => updatePortsWithNewPositions(graph, couple.selectedNode, couple.portPositionOfSelectedNode, couple.neighbourNode as Node, couple.portPositionOfNeighbourNode));
|
||||
}
|
||||
|
||||
function updatePortsWithNewPositions(
|
||||
graph: Graph,
|
||||
selectedNode: Node,
|
||||
portPositionOfSelectedNode: "left" | "right" | "top" | "bottom",
|
||||
neighbourNode: Node,
|
||||
portPositionOfNeighbourNode: "left" | "right" | "top" | "bottom"
|
||||
) {
|
||||
updatePortsAndEdgeOfNodeCouple(graph, selectedNode, neighbourNode, portPositionOfSelectedNode, portPositionOfNeighbourNode);
|
||||
updatePortsAndEdgeOfNodeCouple(graph, neighbourNode, selectedNode, portPositionOfNeighbourNode, portPositionOfSelectedNode);
|
||||
|
||||
adjustPortMarkupByNode(selectedNode);
|
||||
adjustPortMarkupByNode(neighbourNode);
|
||||
}
|
||||
|
||||
function calculatePositionsForInflexibleNode(sourceNode: Node<Node.Properties>, targetNodes: Node<Node.Properties>[]):
|
||||
{ sourceNode: Node<Node.Properties>, sourceNodePosition: "left" | "right" | "top" | "bottom"; targetNode: Node<Node.Properties>, 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) / targetNodes.length;
|
||||
const dyAverageForTargetNodes = targetNodes.map(node => node.getBBox().center.y).reduce((a, b) => a + b, 0) / targetNodes.length;
|
||||
|
||||
const sourcePortWithNewPosition = { node: sourceNode, position: calculatePortPositionsOfNodeCouple(sourceNodeCenter.x, sourceNodeCenter.y, dxAverageForTargetNodes, dyAverageForTargetNodes).portPositionOfSelectedNode };
|
||||
return targetNodes.map((targetNode) => {
|
||||
return {
|
||||
sourceNode: sourcePortWithNewPosition.node,
|
||||
sourceNodePosition: sourcePortWithNewPosition.position,
|
||||
targetNode: targetNode,
|
||||
targetNodePosition: calculatePortPositionsOfNodeCouple(sourceNode.getBBox().center.x, sourceNode.getBBox().center.y, targetNode.getBBox().center.x, targetNode.getBBox().center.y).portPositionOfNeighbourNode
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function calculatePortPositionsOfNodeCouple(selectedNodeX: number, selectedNodeY: number, neighbourNodeX: number, neighbourNodeY: number): { portPositionOfSelectedNode: "left" | "right" | "top" | "bottom"; portPositionOfNeighbourNode: "left" | "right" | "top" | "bottom"; } {
|
||||
const dx = selectedNodeX - neighbourNodeX;
|
||||
const dy = selectedNodeY - neighbourNodeY;
|
||||
if (dx >= 0 && dy >= 0) {
|
||||
if (dx > dy) {
|
||||
return {portPositionOfSelectedNode: "left", portPositionOfNeighbourNode: "right"};
|
||||
} else {
|
||||
return {portPositionOfSelectedNode: "top", portPositionOfNeighbourNode: "bottom"};
|
||||
}
|
||||
} else if (dx >= 0 && dy <= 0) {
|
||||
if (dx > -dy) {
|
||||
return {portPositionOfSelectedNode: "left", portPositionOfNeighbourNode: "right"};
|
||||
} else {
|
||||
return {portPositionOfSelectedNode: "bottom", portPositionOfNeighbourNode: "top"};
|
||||
}
|
||||
} else if (dx <= 0 && dy >= 0) {
|
||||
if (-dx > dy) {
|
||||
return {portPositionOfSelectedNode: "right", portPositionOfNeighbourNode: "left"};
|
||||
} else {
|
||||
return {portPositionOfSelectedNode: "top", portPositionOfNeighbourNode: "bottom"};
|
||||
}
|
||||
} else if (dx <= 0 && dy <= 0) {
|
||||
if (dx > dy) {
|
||||
return {portPositionOfSelectedNode: "right", portPositionOfNeighbourNode: "left"};
|
||||
} else {
|
||||
return {portPositionOfSelectedNode: "bottom", portPositionOfNeighbourNode: "left"};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updatePortsAndEdgeOfNodeCouple(graph: Graph, sourceNode: Node<Node.Properties>, targetNode: Node<Node.Properties>, portPositionOfSourceNode: string, portPositionOfTargetNode: 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, sourceNode, sourcePortOfConnection)){
|
||||
const outgoingEdges = findOutgoingEdges(graph, sourceNode, sourcePortOfConnection);
|
||||
const targetNodes = graph.getNodes().filter(node => outgoingEdges.map(edge => edge.data.target).includes(node.id));
|
||||
const nodeCouplesWithPositions = calculatePositionsForInflexibleNode(sourceNode, targetNodes);
|
||||
|
||||
nodeCouplesWithPositions.forEach(couple => {
|
||||
updatePortsAndEdge(graph, couple.sourceNode, couple.targetNode, couple.sourceNodePosition, couple.targetNodePosition);
|
||||
});
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
updatePortsAndEdge(graph, sourceNode, targetNode, portPositionOfSourceNode, portPositionOfTargetNode);
|
||||
}
|
||||
}
|
||||
|
||||
function isNewCalculationNeededForInflexiblePort(graph: Graph, sourceNode: Node<Node.Properties>, inflexiblePort: any) {
|
||||
const portName = getPortNameByPortId(inflexiblePort);
|
||||
if (portName != null && portName != "Done") {
|
||||
const outgoingEdges = findOutgoingEdges(graph, sourceNode, inflexiblePort);
|
||||
if (outgoingEdges.length > 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function updatePortsAndEdge(graph: Graph, sourceNode: Node<Node.Properties>, targetNode: Node<Node.Properties>, 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 (sourceNode.getPort(edge.data.sourcePort) == null || targetNode.getPort(edge.data.targetPort) == null || 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 hasPortAnEdge(graph: Graph, port: PortManager.PortMetadata) {
|
||||
return graph.getEdges().some(({ data }) => data.sourcePort == port.id || data.targetPort == port.id);
|
||||
}
|
||||
|
||||
function findMatchingPortForEdge(node: Node<Node.Properties>, 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 findOutgoingEdges(graph: Graph, node: Node<Node.Properties>, portId: string): Edge<Edge.Properties>[] {
|
||||
return graph.model.getEdges().filter(({ data }) => data.source == node.id && getPortNameByPortId(data.sourcePort) == getPortNameByPortId(portId));
|
||||
}
|
||||
|
||||
function updatePort(graph: Graph, node: Node<Node.Properties>, nodePort: PortManager.PortMetadata, newPortPosition: string) {
|
||||
let newNodePortId = null;
|
||||
if (nodePort?.position != newPortPosition) {
|
||||
|
||||
if (!hasPortAnEdge(graph, nodePort)) {
|
||||
node.removePort(nodePort);
|
||||
}
|
||||
|
||||
const matchingPort = findMatchingPortForEdge(node, newPortPosition, nodePort.type, getPortNameByPortId(nodePort.id));
|
||||
|
||||
if (matchingPort == null) {
|
||||
newNodePortId = createNewPort(nodePort, node, newPortPosition);
|
||||
}
|
||||
else {
|
||||
newNodePortId = matchingPort.id;
|
||||
}
|
||||
}
|
||||
return newNodePortId;
|
||||
}
|
||||
|
||||
function createNewPort(nodePort: PortManager.PortMetadata, node: Node<Node.Properties>, newPortPosition: string) {
|
||||
const newNodePortId = uuid() + '_' + getPortNameByPortId(nodePort.id);
|
||||
|
||||
node.addPort({
|
||||
...nodePort,
|
||||
group: newPortPosition,
|
||||
id: newNodePortId,
|
||||
position: newPortPosition,
|
||||
type: nodePort.type
|
||||
});
|
||||
return newNodePortId;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
export function adjustConnectionsInRequestModel(root: Activity) {
|
||||
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) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in a new issue