From c9f1021a4bc50986e40887a3ba362256903970f3 Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Mon, 5 Dec 2022 12:21:07 +0100 Subject: [PATCH] Fix merge of auto rotating ports --- .../src/components.d.ts | 8 +-- .../src/components/designer/canvas/canvas.tsx | 4 +- .../flowchart/default-activity-template.tsx | 2 +- .../modules/flowchart/default-node-handler.ts | 27 ++++++---- .../src/modules/flowchart/flowchart.tsx | 23 +++----- .../src/modules/flowchart/graph-factory.ts | 31 +++++++---- .../src/modules/flowchart/ports.tsx | 31 ----------- .../src/modules/flowchart/shapes.ts | 54 ++++++++++++++++--- .../components/editor.tsx | 15 +++--- .../components/toolbar.tsx | 9 ++-- .../modules/workflow-definitions/plugin.tsx | 1 - .../services/container-activity-component.tsx | 2 +- 12 files changed, 114 insertions(+), 93 deletions(-) diff --git a/src/designer/elsa-workflows-designer/src/components.d.ts b/src/designer/elsa-workflows-designer/src/components.d.ts index 772ccf351..150dc8675 100644 --- a/src/designer/elsa-workflows-designer/src/components.d.ts +++ b/src/designer/elsa-workflows-designer/src/components.d.ts @@ -68,7 +68,7 @@ export namespace Components { } interface ElsaCanvas { "addActivity": (args: AddActivityArgs) => Promise; - "autoLayout": () => Promise; + "autoLayout": (direction: "TB" | "BT" | "LR" | "RL") => Promise; "exportGraph": () => Promise; "getRootComponent": () => Promise; "importGraph": (root: Activity) => Promise; @@ -121,7 +121,7 @@ export namespace Components { } interface ElsaFlowchart { "addActivity": (args: AddActivityArgs) => Promise; - "autoLayout": () => Promise; + "autoLayout": (direction: "TB" | "BT" | "LR" | "RL") => Promise; "export": () => Promise; "getCurrentLevel": () => Promise; "getGraph": () => Promise; @@ -267,7 +267,7 @@ export namespace Components { "workflowDefinition"?: WorkflowDefinition; } interface ElsaWorkflowDefinitionEditorToolbar { - "autoLayout": () => Promise; + "autoLayout": (direction: "TB" | "BT" | "LR" | "RL") => Promise; "zoomToFit": () => Promise; } interface ElsaWorkflowDefinitionEditorToolbox { @@ -955,7 +955,7 @@ declare namespace LocalJSX { "workflowDefinition"?: WorkflowDefinition; } interface ElsaWorkflowDefinitionEditorToolbar { - "autoLayout"?: () => Promise; + "autoLayout"?: (direction: "TB" | "BT" | "LR" | "RL") => Promise; "zoomToFit"?: () => Promise; } interface ElsaWorkflowDefinitionEditorToolbox { diff --git a/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx index 1179f0887..e16a17abd 100644 --- a/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx +++ b/src/designer/elsa-workflows-designer/src/components/designer/canvas/canvas.tsx @@ -67,8 +67,8 @@ export class Canvas { } @Method() - public async autoLayout(): Promise { - return await this.root.autoLayout(); + public async autoLayout(direction: "TB" | "BT" | "LR" | "RL"): Promise { + return await this.root.autoLayout(direction); } @Method() diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx b/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx index df201262d..8f6eac47e 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/default-activity-template.tsx @@ -120,7 +120,7 @@ export class DefaultActivityTemplate { const iconCssClass = this.displayTypeIsPicker ? 'px-2' : 'px-4'; if (!icon) - return
; + return undefined; return (
diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/default-node-handler.ts b/src/designer/elsa-workflows-designer/src/modules/flowchart/default-node-handler.ts index c17326130..487702ec2 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/default-node-handler.ts +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/default-node-handler.ts @@ -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, diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx index dd55cfba9..0ba570fd5 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx @@ -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; } @@ -511,13 +512,6 @@ export class FlowchartComponent implements ContainerActivityComponent { return this.activity; const activity = this.activityLookup[currentItem.activityId] as Flowchart; - - if (activity == null) { - alert("Critical"); - location.reload(); - return; - } - const activityDescriptor = descriptorsStore.activityDescriptors.find(x => x.typeName == activity.type); if (activityDescriptor.isContainer) @@ -718,9 +712,6 @@ export class FlowchartComponent implements ContainerActivityComponent { }; private onNavigateHierarchy = async (e: CustomEvent) => { - - debugger; - const item = e.detail; const activityId = item.activityId; let activity = this.activityLookup[activityId]; diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/graph-factory.ts b/src/designer/elsa-workflows-designer/src/modules/flowchart/graph-factory.ts index f30c1b6a0..7d9b6f884 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/graph-factory.ts +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/graph-factory.ts @@ -5,8 +5,6 @@ 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, @@ -70,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: { @@ -91,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({ @@ -282,4 +291,4 @@ export function createGraph( }); return graph; -}; +}; \ No newline at end of file diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/ports.tsx b/src/designer/elsa-workflows-designer/src/modules/flowchart/ports.tsx index 790265d21..01ee3379e 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/ports.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/ports.tsx @@ -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', diff --git a/src/designer/elsa-workflows-designer/src/modules/flowchart/shapes.ts b/src/designer/elsa-workflows-designer/src/modules/flowchart/shapes.ts index ca76e8778..1d8755393 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/shapes.ts +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/shapes.ts @@ -114,8 +114,8 @@ export class ActivityNodeShape extends Shape.HTML { ActivityNodeShape.config({ ports: { groups: { - in: { - position: 'dynamicIn', + left: { + position: 'left', attrs: { circle: { r: 5, @@ -135,15 +135,57 @@ ActivityNodeShape.config({ }, }, }, - out: { - position: 'dynamicOut', + right: { + position: 'right', attrs: { circle: { r: 5, 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: 5, + magnet: true, + stroke: '#3c82f6', + strokeWidth: 2, + fill: '#fff', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + label: { + position: { + name: 'outside', + }, + }, + }, + bottom: { + position: 'bottom', + attrs: { + circle: { + r: 5, + magnet: true, + stroke: '#3c82f6', + strokeWidth: 2, + fill: '#fff', }, text: { fontSize: 12, diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx index d98b0daf8..dcfca8978 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/editor.tsx @@ -32,6 +32,8 @@ export class WorkflowDefinitionEditor { private canvas: HTMLElsaCanvasElement; private container: HTMLDivElement; private toolbox: HTMLElsaWorkflowDefinitionEditorToolboxElement; + private readonly emitActivityChangedDebounced: (e: ActivityPropertyChangedEventArgs) => void; + private readonly updateModelDebounced: () => void; private readonly saveChangesDebounced: () => void; private readonly workflowDefinitionApi: WorkflowDefinitionsApi; @@ -40,6 +42,8 @@ export class WorkflowDefinitionEditor { this.pluginRegistry = Container.get(PluginRegistry); this.activityNameFormatter = Container.get(ActivityNameFormatter); this.portProviderRegistry = Container.get(PortProviderRegistry); + this.emitActivityChangedDebounced = debounce(this.emitActivityChanged, 100); + this.updateModelDebounced = debounce(this.updateModel, 10); this.saveChangesDebounced = debounce(this.saveChanges, 1000); this.workflowDefinitionApi = Container.get(WorkflowDefinitionsApi); } @@ -110,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. @@ -225,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) => { await this.canvas.updateActivity({ @@ -237,12 +240,12 @@ export class WorkflowDefinitionEditor { }); await this.updateModel(); - await this.emitActivityChanged(e.detail.activity, e.detail.propertyName); + this.emitActivityChangedDebounced({...e.detail, workflowEditor: this.el}); this.saveChangesDebounced(); } - private onWorkflowPropsUpdated = async (e: CustomEvent) => { - await this.updateModel(); + private onWorkflowPropsUpdated = (e: CustomEvent) => { + this.updateModelDebounced(); this.saveChangesDebounced(); } diff --git a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbar.tsx b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbar.tsx index c94128da4..50a2346ba 100644 --- a/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbar.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/workflow-definitions/components/toolbar.tsx @@ -8,13 +8,16 @@ export class Toolbar { public zoomToFit: () => Promise; @Prop() - public autoLayout: () => Promise; + public autoLayout: (direction: "TB" | "BT" | "LR" | "RL") => Promise; render() { return (
- +