From 6797c3c3e151073baecbe7619a36e5b616e5bfbc Mon Sep 17 00:00:00 2001 From: Sipke Schoorstra Date: Sun, 12 Mar 2023 20:07:56 +0100 Subject: [PATCH] Fix copy-paste issues --- .../modules/flowchart/default-node-handler.ts | 1 - .../src/modules/flowchart/flowchart.tsx | 64 +++++++++++-------- .../src/modules/flowchart/graph-factory.ts | 56 +++++++++++----- .../src/services/activity-walker.ts | 24 +++---- 4 files changed, 88 insertions(+), 57 deletions(-) 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 dc07e6699..b3689d095 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 @@ -19,7 +19,6 @@ export class DefaultNodeHandler implements ActivityNodeHandler { const portModels = this.createPorts(context); return { - id: activity.id, shape: 'activity', activity: activity, activityDescriptor: activityDescriptor, 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 5e3e827b4..2205a96e5 100644 --- a/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx +++ b/src/designer/elsa-workflows-designer/src/modules/flowchart/flowchart.tsx @@ -5,9 +5,9 @@ import {Edge, Graph, Model, Node, NodeView, Point} from '@antv/x6'; import './shapes'; import './ports'; import {ActivityNodeShape} from './shapes'; -import {Activity, ActivityDeletedArgs, ActivityDescriptor, ActivitySelectedArgs, ChildActivitySelectedArgs, ContainerSelectedArgs, EditChildActivityArgs, GraphUpdatedArgs, Port, WorkflowUpdatedArgs} from '../../models'; +import {Activity, ActivityDeletedArgs, ActivityDescriptor, ActivitySelectedArgs, ChildActivitySelectedArgs, ContainerSelectedArgs, EditChildActivityArgs, GraphUpdatedArgs, WorkflowUpdatedArgs} from '../../models'; import {createGraph} from './graph-factory'; -import {AddActivityArgs, Connection, Flowchart, FlowchartModel, FlowchartNavigationItem, FlowchartPathItem, LayoutDirection, RenameActivityArgs, UpdateActivityArgs} from './models'; +import {AddActivityArgs, Connection, Flowchart, FlowchartModel, FlowchartPathItem, LayoutDirection, RenameActivityArgs, UpdateActivityArgs} from './models'; import {NodeFactory} from "./node-factory"; import {Container} from "typedi"; import {createActivityLookup, EventBus, flatten, PortProviderRegistry, walkActivities} from "../../services"; @@ -182,10 +182,10 @@ export class FlowchartComponent { }, }; - const node = this.nodeFactory.createNode(descriptor, activity, sx, sy); - graph.addNode(node, {merge: true}); - - adjustPortMarkupByNode(graph.getNodes().find(n => n.id == node.id)); + const nodeMetadata = this.nodeFactory.createNode(descriptor, activity, sx, sy); + graph.addNode(nodeMetadata, {merge: true}); + const node = graph.getNodes().find(n => n.data.id == nodeMetadata.activity.id); + adjustPortMarkupByNode(node); await this.updateModel(); return activity; } @@ -194,9 +194,8 @@ export class FlowchartComponent { async updateActivity(args: UpdateActivityArgs) { const activityId = args.id; const originalId = args.originalId; - const nodeId = originalId; const activity = args.activity; - const node = this.graph.getNodes().find(x => x.id == nodeId); + const node = this.graph.getNodes().find(x => x.data.id == originalId); const nodeShape = node as ActivityNodeShape; if (!!node) { @@ -209,7 +208,7 @@ export class FlowchartComponent { // If the ID of the activity changed, we need to update connection references (X6 stores deep copies of data). if (activityId !== originalId) - this.syncEdgeData(nodeId, activity); + this.syncEdgeData(originalId, activity); // Update ports. if (args.updatePorts) { @@ -227,9 +226,9 @@ export class FlowchartComponent { @Method() public async renameActivity(args: RenameActivityArgs) { - const nodeId = args.originalId; + const originalId = args.originalId; const activity = args.activity; - const node = this.graph.getNodes().find(x => x.id == nodeId) as ActivityNodeShape; + const node = this.graph.getNodes().find(x => x.data.id == originalId) as ActivityNodeShape; if (!node) return; @@ -237,12 +236,12 @@ export class FlowchartComponent { // Update the node's data with the activity. node.setData(activity, {overwrite: true}); - // Updating the node's activity property to trigger a rerender. + // Update the node's activity property to trigger a rerender. node.activity = activity; // If the ID of the activity changed, we need to update connection references (X6 stores deep copies of data). - if (activity.id !== nodeId) - this.syncEdgeData(nodeId, activity); + if (activity.id !== originalId) + this.syncEdgeData(originalId, activity); } @Method() @@ -337,7 +336,7 @@ export class FlowchartComponent { this.getAllActivities); graph.on('blank:click', this.onGraphClick); - graph.on('node:click', this.onNodeClick); + graph.on('node:selected', this.onNodeSelected); graph.on('node:contextmenu', this.onNodeContextMenu); graph.on('edge:connected', this.onEdgeConnected); graph.on('node:moved', this.onNodeMoved); @@ -346,7 +345,6 @@ export class FlowchartComponent { //graph.on('node:added', this.onGraphChanged); graph.on('node:added', this.onNodeAdded); graph.on('node:removed', this.onNodeRemoved); - graph.on('node:removed', this.onGraphChanged); graph.on('edge:added', this.onGraphChanged); graph.on('edge:removed', this.onGraphChanged); graph.on('edge:connected', this.onGraphChanged); @@ -395,7 +393,6 @@ export class FlowchartComponent { private updateModel = async () => { const model = this.getFlowchartModel(); const currentFlowchart = await this.getCurrentFlowchartActivity(); - currentFlowchart.activities = model.activities; currentFlowchart.connections = model.connections; currentFlowchart.start = model.start; @@ -495,7 +492,8 @@ export class FlowchartComponent { }; private updateLookups = () => { - const activityNodes = flatten(walkActivities(this.rootActivity)); + const graph = walkActivities(this.rootActivity); + const activityNodes = flatten(graph); this.activities = activityNodes.map(x => x.activity); this.activityLookup = createActivityLookup(activityNodes); } @@ -516,7 +514,7 @@ export class FlowchartComponent { return this.containerSelected.emit(args); }; - private onNodeClick = async (e: PositionEventArgs) => { + private onNodeSelected = async (e: PositionEventArgs) => { const node = e.node as ActivityNodeShape; const activityCopy = node.activity; @@ -614,22 +612,32 @@ export class FlowchartComponent { await this.updateModel(); this.graphUpdated.emit(); } - private onNodeRemoved = (e: any) => { + + private onNodeRemoved = async (e: any) => { const activity = e.node.data as Activity; this.activityDeleted.emit({activity}); - }; - private onNodeAdded = async (e: any) => { - const activity = e.node.data as Activity; - const activityDescriptor = this.getActivityDescriptor(activity.type); - activity.id = await this.generateUniqueActivityName(activityDescriptor) - e.node.activity = {...activity}; await this.onGraphChanged(e); }; + + private onNodeAdded = async (e: any) => { + const node = e.node as any; + + if (!node.isClone) { + const activity = {...node.getData()} as Activity; + const activityDescriptor = this.getActivityDescriptor(activity.type); + activity.id = await this.generateUniqueActivityName(activityDescriptor) + node.activity = {...activity}; + } + + await this.onGraphChanged(e); + }; + private onToggleCanStartWorkflowClicked = (node: ActivityNodeShape) => { const activity = node.data as Activity; activity.canStartWorkflow = !activity.canStartWorkflow; node.activity = {...activity}; }; + private onDeleteActivityClicked = (node: ActivityNodeShape) => { let cells = this.graph.getSelectedCells(); @@ -685,10 +693,10 @@ export class FlowchartComponent { const addedPorts = desiredPorts.filter(x => !actualPorts.some(y => getPortNameByPortId(y.id) == getPortNameByPortId(x.id))); const removedPorts = actualPorts.filter(x => !desiredPorts.some(y => getPortNameByPortId(y.id) == getPortNameByPortId(x.id))); - if(addedPorts.length > 0 ) + if (addedPorts.length > 0) node.addPorts(addedPorts); - if(removedPorts.length > 0) + if (removedPorts.length > 0) node.removePorts(removedPorts); }; 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 ce5b9b766..95420bc41 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 @@ -1,5 +1,5 @@ import {CellView, Graph, Node, Shape} from '@antv/x6'; -import { autoOrientConnections } from '../../utils/graph'; +import {autoOrientConnections} from '../../utils/graph'; import './ports'; import {Activity} from "../../models"; import {Connection} from "./models"; @@ -66,8 +66,8 @@ export function createGraph( router: { name: 'manhattan', args: { - startDirections: ['top','right','left','bottom'], - endDirections: ['top','right','left','bottom'], + startDirections: ['top', 'right', 'left', 'bottom'], + endDirections: ['top', 'right', 'left', 'bottom'], }, }, // router: { @@ -93,7 +93,7 @@ export function createGraph( return sourcePort.type !== 'in' }, validateConnection({sourceView, targetView, sourceMagnet, targetMagnet}) { - if(!sourceMagnet || !targetMagnet) { + if (!sourceMagnet || !targetMagnet) { return false; } @@ -113,7 +113,6 @@ export function createGraph( const portId = targetMagnet.getAttribute('port')! const node = targetView.cell as Node - const port = node.getPort(portId) return !(targetPort && targetPort.connected); }, createEdge() { @@ -199,40 +198,62 @@ export function createGraph( graph.bindKey(['meta+v', 'ctrl+v'], async () => { if (!graph.isClipboardEmpty()) { - const cells = graph.paste({offset: 32}); + const allActivities = [...getAllActivities()]; + const cells = graph.getCellsInClipboard(); const activityCells = cells.filter(x => x.shape == 'activity'); const connectionCells = cells.filter(x => x.shape == 'elsa-edge'); - const allActivities = [...getAllActivities()]; const idMap = {}; + const newCells = []; for (const cell of activityCells) { - const activity = {...cell.data} as Activity; + const clonedCell = cell.clone(); + clonedCell.isClone = true; + const activity = {...clonedCell.getData()} as Activity; const activityTypeName = activity.type; const activityDescriptor = descriptorsStore.activityDescriptors.find(x => x.typeName == activityTypeName); const currentId = activity.id; - const idExists = allActivities.find(x => x.id == currentId); + const idExists = !!allActivities.find(x => x.id == currentId); - if(idExists) { + if (idExists) { const newId = await generateUniqueActivityName(allActivities, activityDescriptor); idMap[currentId] = newId; activity.id = newId; } - cell.setData(activity) + clonedCell.replaceData(activity, {}); + clonedCell.activity = activity; + + const clonedNode = clonedCell as Node; + const position = clonedNode.getPosition(); + position.x += 64; + position.y += 64; + clonedNode.setPosition(position); + allActivities.push(activity); + newCells.push(clonedCell); } for (const cell of connectionCells) { - const connection = {...cell.data} as Connection; + const clonedCell = cell.clone(); + const connection = {...clonedCell.getData()} as Connection; connection.source = idMap[connection.source]; connection.target = idMap[connection.target]; - cell.setData(connection); + clonedCell.replaceData(connection); + newCells.push(clonedCell); } + + graph.addCell(newCells, {}); + + // Wait for the new cells to be rendered. + requestAnimationFrame(() => { + graph.cleanSelection(); + graph.select(newCells); + }); } return false }); - //undo redo + // undo graph.bindKey(['meta+z', 'ctrl+z'], () => { if (graph.history.canUndo()) { graph.history.undo() @@ -240,6 +261,7 @@ export function createGraph( return false }); + // redo graph.bindKey(['meta+y', 'ctrl+y'], () => { if (graph.history.canRedo()) { graph.history.redo() @@ -247,7 +269,7 @@ export function createGraph( return false }); - // select all; + // select all graph.bindKey(['meta+a', 'ctrl+a'], () => { const nodes = graph.getNodes() if (nodes) { @@ -255,7 +277,7 @@ export function createGraph( } }); - //delete + // delete graph.bindKey('del', () => { const cells = graph.getSelectedCells() if (cells.length) { @@ -278,7 +300,7 @@ export function createGraph( } }); - graph.on("node:moving", ({ node }) => { + graph.on("node:moving", ({node}) => { autoOrientConnections(graph, node); }); diff --git a/src/designer/elsa-workflows-designer/src/services/activity-walker.ts b/src/designer/elsa-workflows-designer/src/services/activity-walker.ts index c027147c0..f9edf0b96 100644 --- a/src/designer/elsa-workflows-designer/src/services/activity-walker.ts +++ b/src/designer/elsa-workflows-designer/src/services/activity-walker.ts @@ -62,12 +62,6 @@ export function flattenList(activities: Array): Array boolean): Activity { - const graph = walkActivities(root); - const nodes = this.nodes; - return nodes.find(node => predicate(node.activity))?.activity; -} - function walkRecursive(node: ActivityNode, activity: Activity, collectedActivities: Set, collectedNodes: Set, descriptors: Array) { const ports = getPorts(node, activity, descriptors); @@ -77,14 +71,22 @@ function walkRecursive(node: ActivityNode, activity: Activity, collectedActiviti if (!childNode) { childNode = {activity: port.activity, children: [], parents: [], port: port.port}; - collectedNodes.add(childNode); + + if (!collectedNodes.has(childNode)) + collectedNodes.add(childNode); } if (childNode !== node) { - if(!!childNode.activity) { - childNode.parents.push(node); - node.children.push(childNode); - collectedActivities.add(port.activity) + if (!!childNode.activity) { + if (!childNode.parents.includes(node)) + childNode.parents.push(node); + + if (!node.children.includes(childNode)) + node.children.push(childNode); + + if (!collectedActivities.has(port.activity)) + collectedActivities.add(port.activity); + walkRecursive(childNode, port.activity, collectedActivities, collectedNodes, descriptors); } }