Merge remote-tracking branch 'origin/v3' into v3
This commit is contained in:
commit
90c9ed40d3
|
|
@ -12,7 +12,7 @@
|
|||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7072;http://localhost:5072",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5072",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export namespace Components {
|
|||
}
|
||||
interface ElsaCanvas {
|
||||
"addActivity": (args: AddActivityArgs) => Promise<Activity>;
|
||||
"autoLayout": () => Promise<void>;
|
||||
"autoLayout": (direction: "TB" | "BT" | "LR" | "RL") => Promise<void>;
|
||||
"exportGraph": () => Promise<Activity>;
|
||||
"getRootComponent": () => Promise<ContainerActivityComponent>;
|
||||
"importGraph": (root: Activity) => Promise<void>;
|
||||
|
|
@ -121,7 +121,7 @@ export namespace Components {
|
|||
}
|
||||
interface ElsaFlowchart {
|
||||
"addActivity": (args: AddActivityArgs) => Promise<Activity>;
|
||||
"autoLayout": () => Promise<void>;
|
||||
"autoLayout": (direction: "TB" | "BT" | "LR" | "RL") => Promise<void>;
|
||||
"export": () => Promise<Activity>;
|
||||
"getCurrentLevel": () => Promise<Activity>;
|
||||
"getGraph": () => Promise<Graph>;
|
||||
|
|
@ -267,7 +267,7 @@ export namespace Components {
|
|||
"workflowDefinition"?: WorkflowDefinition;
|
||||
}
|
||||
interface ElsaWorkflowDefinitionEditorToolbar {
|
||||
"autoLayout": () => Promise<void>;
|
||||
"autoLayout": (direction: "TB" | "BT" | "LR" | "RL") => Promise<void>;
|
||||
"zoomToFit": () => Promise<void>;
|
||||
}
|
||||
interface ElsaWorkflowDefinitionEditorToolbox {
|
||||
|
|
@ -955,7 +955,7 @@ declare namespace LocalJSX {
|
|||
"workflowDefinition"?: WorkflowDefinition;
|
||||
}
|
||||
interface ElsaWorkflowDefinitionEditorToolbar {
|
||||
"autoLayout"?: () => Promise<void>;
|
||||
"autoLayout"?: (direction: "TB" | "BT" | "LR" | "RL") => Promise<void>;
|
||||
"zoomToFit"?: () => Promise<void>;
|
||||
}
|
||||
interface ElsaWorkflowDefinitionEditorToolbox {
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ export class Canvas {
|
|||
}
|
||||
|
||||
@Method()
|
||||
public async autoLayout(): Promise<void> {
|
||||
return await this.root.autoLayout();
|
||||
public async autoLayout(direction: "TB" | "BT" | "LR" | "RL"): Promise<void> {
|
||||
return await this.root.autoLayout(direction);
|
||||
}
|
||||
|
||||
@Method()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,6 @@ export class Checkbox {
|
|||
}
|
||||
|
||||
private onExpressionChanged = (e: CustomEvent<ExpressionChangedArs>) => {
|
||||
debugger;
|
||||
this.inputContext.inputChanged(e.detail.expression, e.detail.syntax);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {Container, Service} from "typedi"
|
|||
import {ActivityNodeHandler, CreateUINodeContext} from "./activity-node-handler";
|
||||
import {PortProviderContext, PortProviderRegistry} from "../../services";
|
||||
import {PortMode} from "../../models";
|
||||
import {v4 as uuid} from 'uuid';
|
||||
|
||||
@Service()
|
||||
export class DefaultNodeHandler implements ActivityNodeHandler {
|
||||
|
|
@ -27,27 +28,31 @@ export class DefaultNodeHandler implements ActivityNodeHandler {
|
|||
if (outPorts.length == 1)
|
||||
outPorts[0].displayName = null;
|
||||
|
||||
const inPortModels = inPorts.map(x => ({
|
||||
id: x.name,
|
||||
group: 'in',
|
||||
const leftPortModels = inPorts.map((x) => ({
|
||||
id: uuid() + '_' + x.name,
|
||||
group: 'left',
|
||||
attrs: !!x.displayName ? {
|
||||
text: {
|
||||
text: x.displayName
|
||||
}
|
||||
} : null
|
||||
},
|
||||
} : null,
|
||||
type:'in',
|
||||
position:'left'
|
||||
}));
|
||||
|
||||
const outPortModels = outPorts.map(x => ({
|
||||
id: x.name,
|
||||
group: 'out',
|
||||
const rightPortModels = outPorts.map((x) => ({
|
||||
id: uuid() + '_' + x.name,
|
||||
group: 'right',
|
||||
attrs: {
|
||||
text: {
|
||||
text: x.displayName
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
type: 'out',
|
||||
position: 'right'
|
||||
}));
|
||||
|
||||
const portModels = [...inPortModels, ...outPortModels];
|
||||
const portModels = [...leftPortModels, ...rightPortModels];
|
||||
|
||||
return {
|
||||
id: activity.id,
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ import PositionEventArgs = NodeView.PositionEventArgs;
|
|||
import FromJSONData = Model.FromJSONData;
|
||||
import PointLike = Point.PointLike;
|
||||
import {generateUniqueActivityName} from "../../utils/generate-activity-name";
|
||||
import {DagreLayout, OutNode} from '@antv/layout';
|
||||
import { DagreLayout, OutNode} from '@antv/layout';
|
||||
import { adjustPortMarkupByNode, rebuildGraph } from '../../utils/graph';
|
||||
import {WorkflowDefinition} from "../workflow-definitions/models/entities";
|
||||
import FlowchartTunnel, {FlowchartState} from "./state";
|
||||
import WorkflowDefinitionTunnel, {WorkflowDefinitionState} from "../../state/workflow-definition-state";
|
||||
|
|
@ -125,11 +126,10 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
}
|
||||
|
||||
@Method()
|
||||
async autoLayout() {
|
||||
|
||||
async autoLayout(direction: "TB" | "BT" | "LR" | "RL") {
|
||||
const dagreLayout = new DagreLayout({
|
||||
type: 'dagre',
|
||||
rankdir: 'TB',
|
||||
rankdir: direction,
|
||||
align: 'UL',
|
||||
ranksep: 30,
|
||||
nodesep: 15,
|
||||
|
|
@ -161,8 +161,7 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
this.updateActivity({id: activity.id, originalId: activity.id, activity: activity});
|
||||
});
|
||||
|
||||
await this.import(this.activity);
|
||||
await this.scrollToStart();
|
||||
this.import(this.activity);
|
||||
}
|
||||
|
||||
@Method()
|
||||
|
|
@ -191,6 +190,8 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
|
||||
const node = this.nodeFactory.createNode(descriptor, activity, sx, sy);
|
||||
graph.addNode(node, {merge: true});
|
||||
|
||||
adjustPortMarkupByNode(graph.getNodes().find(n => n.id == node.id));
|
||||
await this.updateModel();
|
||||
return activity;
|
||||
}
|
||||
|
|
@ -257,7 +258,11 @@ export class FlowchartComponent implements ContainerActivityComponent {
|
|||
|
||||
@Method()
|
||||
async import(root: Activity): Promise<void> {
|
||||
return await this.importInternal(root);
|
||||
await this.importInternal(root);
|
||||
|
||||
rebuildGraph(this.graph);
|
||||
|
||||
await this.scrollToStart();
|
||||
}
|
||||
|
||||
@Method()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import {CellView, Graph, Node, Shape} from '@antv/x6';
|
||||
import { autoOrientPortsAndEdges } from '../../utils/graph';
|
||||
import './ports';
|
||||
import {Activity} from "../../models";
|
||||
import {Connection} from "./models";
|
||||
import descriptorsStore from "../../data/descriptors-store";
|
||||
import {generateUniqueActivityName} from "../../utils/generate-activity-name";
|
||||
import {Hash} from "../../utils";
|
||||
import {createActivityLookup} from "../../services";
|
||||
|
||||
export function createGraph(
|
||||
container: HTMLElement,
|
||||
|
|
@ -69,8 +68,8 @@ export function createGraph(
|
|||
router: {
|
||||
name: 'manhattan',
|
||||
args: {
|
||||
startDirections: ['right'],
|
||||
endDirections: ['left'],
|
||||
startDirections: ['top','right','left','bottom'],
|
||||
endDirections: ['top','right','left','bottom'],
|
||||
},
|
||||
},
|
||||
// router: {
|
||||
|
|
@ -90,23 +89,34 @@ export function createGraph(
|
|||
snap: {
|
||||
radius: 20,
|
||||
},
|
||||
validateMagnet({magnet}) {
|
||||
return magnet.getAttribute('port-group') !== 'in'
|
||||
validateMagnet({view, magnet}) {
|
||||
const node = view.cell as Node;
|
||||
const sourcePort = node.getPort(magnet.getAttribute('port'));
|
||||
return sourcePort.type !== 'in'
|
||||
},
|
||||
validateConnection({sourceView, targetView, sourceMagnet, targetMagnet}) {
|
||||
if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') {
|
||||
if(!sourceMagnet || !targetMagnet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sourceNode = sourceView.cell as Node;
|
||||
const sourcePort = sourceNode.getPort(sourceMagnet.getAttribute('port'));
|
||||
|
||||
const targetNode = targetView.cell as Node;
|
||||
const targetPort = targetNode.getPort(targetMagnet.getAttribute('port'));
|
||||
|
||||
if (sourcePort.type === 'in') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!targetMagnet || targetMagnet.getAttribute('port-group') !== 'in') {
|
||||
if (targetPort.type !== 'in') {
|
||||
return false
|
||||
}
|
||||
|
||||
const portId = targetMagnet.getAttribute('port')!
|
||||
const node = targetView.cell as Node
|
||||
const port = node.getPort(portId)
|
||||
return !(port && port.connected);
|
||||
|
||||
return !(targetPort && targetPort.connected);
|
||||
},
|
||||
createEdge() {
|
||||
return graph.createEdge({
|
||||
|
|
@ -276,5 +286,9 @@ export function createGraph(
|
|||
}
|
||||
});
|
||||
|
||||
graph.on("node:moving", ({ node }) => {
|
||||
autoOrientPortsAndEdges(graph, node);
|
||||
});
|
||||
|
||||
return graph;
|
||||
};
|
||||
};
|
||||
|
|
@ -1,35 +1,4 @@
|
|||
import {Graph, Line, Path} from "@antv/x6";
|
||||
import {toResult} from "@antv/x6/lib/registry/port-layout/util";
|
||||
|
||||
Graph.registerPortLayout('dynamicOut', (portsPositionArgs, elemBBox) => {
|
||||
return portsPositionArgs.map((_, index) => {
|
||||
|
||||
const portCount = portsPositionArgs.length;
|
||||
const ratio = (index + 0.5) / portCount;
|
||||
const p1 = portCount <= 3 ? elemBBox.getTopRight() : elemBBox.getBottomLeft();
|
||||
const p2 = portCount <= 3 ? elemBBox.getBottomRight() : elemBBox.getBottomRight();
|
||||
const line = new Line(p1, p2)
|
||||
const p = line.pointAt(ratio);
|
||||
|
||||
return toResult(p.round(), 0, {});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Graph.registerPortLayout('dynamicIn', (portsPositionArgs, elemBBox) => {
|
||||
return portsPositionArgs.map((_, index) => {
|
||||
|
||||
const portCount = portsPositionArgs.length;
|
||||
const ratio = (index + 0.5) / portCount;
|
||||
const p1 = portCount <= 3 ? elemBBox.getTopLeft() : elemBBox.getTopLeft();
|
||||
const p2 = portCount <= 3 ? elemBBox.getBottomLeft() : elemBBox.getTopRight();
|
||||
const line = new Line(p1, p2)
|
||||
const p = line.pointAt(ratio);
|
||||
|
||||
return toResult(p.round(), 0, {});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
Graph.registerConnector(
|
||||
'elsa-connector',
|
||||
|
|
|
|||
|
|
@ -114,8 +114,8 @@ export class ActivityNodeShape extends Shape.HTML {
|
|||
ActivityNodeShape.config({
|
||||
ports: {
|
||||
groups: {
|
||||
in: {
|
||||
position: 'dynamicIn',
|
||||
left: {
|
||||
position: 'left',
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 6,
|
||||
|
|
@ -135,15 +135,57 @@ ActivityNodeShape.config({
|
|||
},
|
||||
},
|
||||
},
|
||||
out: {
|
||||
position: 'dynamicOut',
|
||||
right: {
|
||||
position: 'right',
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 6,
|
||||
magnet: true,
|
||||
stroke: '#fff',
|
||||
stroke: '#3c82f6',
|
||||
strokeWidth: 2,
|
||||
fill: '#3c82f6',
|
||||
fill: '#fff',
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
fill: '#888',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
position: {
|
||||
name: 'outside',
|
||||
},
|
||||
},
|
||||
},
|
||||
top: {
|
||||
position: 'top',
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 6,
|
||||
magnet: true,
|
||||
stroke: '#3c82f6',
|
||||
strokeWidth: 2,
|
||||
fill: '#fff',
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
fill: '#888',
|
||||
},
|
||||
},
|
||||
label: {
|
||||
position: {
|
||||
name: 'outside',
|
||||
},
|
||||
},
|
||||
},
|
||||
bottom: {
|
||||
position: 'bottom',
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 6,
|
||||
magnet: true,
|
||||
stroke: '#3c82f6',
|
||||
strokeWidth: 2,
|
||||
fill: '#fff',
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ export class WorkflowDefinitionEditor {
|
|||
await this.updateWorkflowDefinition(workflowDefinition);
|
||||
await this.canvas.importGraph(workflowDefinition.root);
|
||||
await this.eventBus.emit(WorkflowEditorEventTypes.WorkflowDefinition.Imported, this, {workflowDefinition});
|
||||
await this.canvas.scrollToStart();
|
||||
}
|
||||
|
||||
// Updates the workflow definition without importing it into the designer.
|
||||
|
|
@ -229,9 +228,9 @@ export class WorkflowDefinitionEditor {
|
|||
});
|
||||
};
|
||||
|
||||
private onZoomToFit = async () => await this.canvas.zoomToFit()
|
||||
private onZoomToFit = async () => await this.canvas.zoomToFit();
|
||||
|
||||
private onAutoLayout = async () => await this.canvas.autoLayout()
|
||||
private onAutoLayout = async (direction: "TB" | "BT" | "LR" | "RL") => await this.canvas.autoLayout(direction);
|
||||
|
||||
private onActivityUpdated = async (e: CustomEvent<ActivityUpdatedArgs>) => {
|
||||
await this.canvas.updateActivity({
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ export class Toolbar {
|
|||
public zoomToFit: () => Promise<void>;
|
||||
|
||||
@Prop()
|
||||
public autoLayout: () => Promise<void>;
|
||||
public autoLayout: (direction: "TB" | "BT" | "LR" | "RL") => Promise<void>;
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="elsa-panel-toolbar flex justify-center absolute border-b border-gray-200 top-0 px-1 pl-4 pb-2 text-sm bg-white z-10 space-x-2">
|
||||
<button class="btn btn-primary" onClick={this.autoLayout}>
|
||||
Auto-Layout
|
||||
<button class="btn btn-primary" onClick={() => this.autoLayout("LR")}>
|
||||
Auto-Layout (LR)
|
||||
</button>
|
||||
<button class="btn btn-primary" onClick={() => this.autoLayout("TB")}>
|
||||
Auto-Layout (TB)
|
||||
</button>
|
||||
<button onClick={this.zoomToFit}class="btn btn-primary">
|
||||
Zoom to fit
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ 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';
|
||||
|
||||
@Service()
|
||||
export class WorkflowDefinitionsApi {
|
||||
|
|
@ -44,6 +45,10 @@ 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.
|
||||
adjustConnectionsInRequestModel(request.root);
|
||||
|
||||
const httpClient = await this.getHttpClient();
|
||||
const response = await httpClient.post<WorkflowDefinition>('workflow-definitions', request);
|
||||
return response.data;
|
||||
|
|
@ -57,6 +62,11 @@ export class WorkflowDefinitionsApi {
|
|||
const queryStringText = serializeQueryString(queryString);
|
||||
const httpClient = await this.getHttpClient();
|
||||
const response = await httpClient.get<WorkflowDefinition>(`workflow-definitions/${request.definitionId}${queryStringText}`);
|
||||
|
||||
//TODO: Written as a workaround for different server and client models.
|
||||
//To be deleted after the connection model on backend is updated.
|
||||
adjustConnectionsInResponseModel(response.data.root);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
|
@ -162,7 +172,6 @@ export class WorkflowDefinitionsApi {
|
|||
private getHttpClient = async () => await this.provider.getHttpClient();
|
||||
}
|
||||
|
||||
|
||||
export interface SaveWorkflowDefinitionRequest {
|
||||
definitionId: string;
|
||||
version?: number;
|
||||
|
|
@ -261,4 +270,4 @@ export interface UnpublishManyWorkflowDefinitionResponse {
|
|||
retracted: string[];
|
||||
notPublished: string[];
|
||||
notFound: string[];
|
||||
}
|
||||
}
|
||||
|
|
@ -54,3 +54,4 @@ export class WorkflowDefinitionManager {
|
|||
};
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export interface ContainerActivityComponent {
|
|||
export(): Promise<Activity>
|
||||
import(root: Activity): Promise<void>;
|
||||
zoomToFit(): Promise<void>;
|
||||
autoLayout(): Promise<void>;
|
||||
autoLayout(direction: "TB" | "BT" | "LR" | "RL"): Promise<void>;
|
||||
scrollToStart(): Promise<void>;
|
||||
reset(): Promise<void>;
|
||||
getCurrentLevel(): Promise<Activity>;
|
||||
|
|
|
|||
196
src/designer/elsa-workflows-designer/src/utils/graph.ts
Normal file
196
src/designer/elsa-workflows-designer/src/utils/graph.ts
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
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 adjustPortMarkupByNode(node: Node) {
|
||||
node.getPorts().forEach(port => {
|
||||
if(port.type == 'out'){
|
||||
node.setPortProp(port.id, "attrs", {
|
||||
circle: {
|
||||
r: 6,
|
||||
magnet: true,
|
||||
stroke: '#fff',
|
||||
strokeWidth: 2,
|
||||
fill: '#3c82f6',
|
||||
},
|
||||
text: {
|
||||
fontSize: 12,
|
||||
fill: '#888',
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
node.setPortProp(port.id, "attrs", {
|
||||
circle: {
|
||||
r: 6,
|
||||
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<Node.Properties>, position: string, portType: string, portName: string) {
|
||||
return node.getPorts().find(p => p.position == position && p.type == portType && 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<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);
|
||||
|
||||
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");
|
||||
|
||||
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 updatePort(graph: Graph, node: Node<Node.Properties>, 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<Node.Properties>, 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");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function rebuildGraph(graph: Graph)
|
||||
{
|
||||
graph.getNodes().forEach((node: Node<Node.Properties>) => {
|
||||
autoOrientPortsAndEdges(graph, node);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue