feat(modeling): update associations on connection change

This commit is contained in:
Martin Stamm 2022-05-18 16:04:32 +02:00
parent 7acddf6b3d
commit 5814b72e95
5 changed files with 293 additions and 0 deletions

View File

@ -0,0 +1,117 @@
import {
assign
} from 'min-dash';
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getConnectionAdjustment as getConnectionAnchorPoint } from './util/ConnectionLayoutUtil';
/**
* A component that makes sure that Associations connected to Connections
* are updated together with the Connection.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function LayoutConnectionBehavior(
eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function getnewAnchorPoint(event, point) {
var context = event.context,
connection = context.connection,
hints = assign({}, context.hints),
newWaypoints = context.newWaypoints || connection.waypoints,
oldWaypoints = context.oldWaypoints;
if (typeof hints.startChanged === 'undefined') {
hints.startChanged = !!hints.connectionStart;
}
if (typeof hints.endChanged === 'undefined') {
hints.endChanged = !!hints.connectionEnd;
}
return getConnectionAnchorPoint(point, newWaypoints, oldWaypoints, hints);
}
this.postExecute([
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context;
var connection = context.connection,
outgoing = connection.outgoing,
incoming = connection.incoming;
incoming.forEach(function(connection) {
var endPoint = connection.waypoints[connection.waypoints.length - 1];
var newEndpoint = getnewAnchorPoint(event, endPoint);
var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);
modeling.updateWaypoints(connection, newWaypoints);
});
outgoing.forEach(function(connection) {
var startpoint = connection.waypoints[0];
var newStartpoint = getnewAnchorPoint(event, startpoint);
var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(0, -1));
modeling.updateWaypoints(connection, newWaypoints);
});
});
this.postExecute([
'connection.move'
], function(event) {
var context = event.context;
var connection = context.connection,
outgoing = connection.outgoing,
incoming = connection.incoming,
delta = context.delta;
incoming.forEach(function(connection) {
var endPoint = connection.waypoints[connection.waypoints.length - 1];
var newEndpoint = {
x: endPoint.x + delta.x,
y: endPoint.y + delta.y
};
var newWaypoints = [].concat(connection.waypoints.slice(0, -1), [ newEndpoint ]);
modeling.updateWaypoints(connection, newWaypoints);
});
outgoing.forEach(function(connection) {
var startpoint = connection.waypoints[0];
var newStartpoint = {
x: startpoint.x + delta.x,
y: startpoint.y + delta.y
};
var newWaypoints = [].concat([ newStartpoint ], connection.waypoints.slice(0, -1));
modeling.updateWaypoints(connection, newWaypoints);
});
});
}
inherits(LayoutConnectionBehavior, CommandInterceptor);
LayoutConnectionBehavior.$inject = [
'eventBus',
'modeling'
];

View File

@ -17,6 +17,7 @@ import GroupBehavior from './GroupBehavior';
import ImportDockingFix from './ImportDockingFix';
import IsHorizontalFix from './IsHorizontalFix';
import LabelBehavior from './LabelBehavior';
import LayoutConnectionBehavior from './LayoutConnectionBehavior';
import MessageFlowBehavior from './MessageFlowBehavior';
import ModelingFeedback from './ModelingFeedback';
import RemoveEmbeddedLabelBoundsBehavior from './RemoveEmbeddedLabelBoundsBehavior';
@ -57,6 +58,7 @@ export default {
'importDockingFix',
'isHorizontalFix',
'labelBehavior',
'layoutConnectionBehavior',
'messageFlowBehavior',
'modelingFeedback',
'removeElementBehavior',
@ -95,6 +97,7 @@ export default {
importDockingFix: [ 'type', ImportDockingFix ],
isHorizontalFix: [ 'type', IsHorizontalFix ],
labelBehavior: [ 'type', LabelBehavior ],
layoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ],
messageFlowBehavior: [ 'type', MessageFlowBehavior ],
modelingFeedback: [ 'type', ModelingFeedback ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],

View File

@ -0,0 +1,16 @@
import { getAnchorPointAdjustment } from './LayoutUtil';
/**
* Calculate the new point after the connection waypoints got updated.
*
* @param {djs.model.Label} label
* @param {Array<Point>} newWaypoints
* @param {Array<Point>} oldWaypoints
* @param {Object} hints
*
* @return {Point} point
*/
export function getConnectionAdjustment(position, newWaypoints, oldWaypoints, hints) {
return getAnchorPointAdjustment(position, newWaypoints, oldWaypoints, hints).point;
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="simple" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.0.0-rc.0" xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:process id="Process_1" isExecutable="false">
<bpmn2:endEvent id="EndEvent_1" name="End Event">
<bpmn2:incoming>SequenceFlow_1</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:startEvent id="StartEvent_1" name="Start">
<bpmn2:outgoing>SequenceFlow_1</bpmn2:outgoing>
</bpmn2:startEvent>
<bpmn2:sequenceFlow id="SequenceFlow_1" name="Flow" sourceRef="StartEvent_1" targetRef="EndEvent_1" />
<bpmn2:textAnnotation id="TextAnnotation_1" />
<bpmn2:association id="Association_1" sourceRef="TextAnnotation_1" targetRef="SequenceFlow_1" />
</bpmn2:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNEdge id="BPMNEdge_SequenceFlow_3" bpmnElement="SequenceFlow_1" sourceElement="_BPMNShape_StartEvent_11" targetElement="_BPMNShape_EndEvent_2">
<di:waypoint x="194" y="150" />
<di:waypoint x="700" y="150" />
<bpmndi:BPMNLabel>
<dc:Bounds x="259" y="124" width="24" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="_BPMNShape_EndEvent_2" bpmnElement="EndEvent_1">
<dc:Bounds x="700" y="132" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="693" y="168" width="51" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_11" bpmnElement="StartEvent_1">
<dc:Bounds x="158" y="132" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="164" y="173" width="24" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_1yj4jk8_di" bpmnElement="TextAnnotation_1">
<dc:Bounds x="500" y="80" width="100" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_1_di" bpmnElement="Association_1">
<di:waypoint x="525" y="110" />
<di:waypoint x="460" y="150" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>

View File

@ -0,0 +1,113 @@
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import {
assign,
map,
} from 'min-dash';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
describe('behavior - LayoutConnectionBehavior', function() {
var diagramXML = require('./LayoutConnectionBehavior.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: [
modelingModule,
coreModule
]
}));
describe('association', function() {
it('should reconnect on bendpoint move', inject(function(elementRegistry, modeling) {
// given
var sequenceFlow = elementRegistry.get('SequenceFlow_1');
var association = elementRegistry.get('Association_1');
// when
var newWaypoints = copyWaypoints(sequenceFlow);
newWaypoints.splice(1, 0,
{ x: 500, y: 300 }
);
var hints = {
bendpointMove: {
bendpointIndex: 1,
insert: true
}
};
modeling.updateWaypoints(sequenceFlow, newWaypoints, hints);
// then
expectWaypoints(association, [
{ x: 525, y: 110 },
{ x: 355, y: 229 },
]);
}));
it('should reconnect on connection move', inject(function(elementRegistry, modeling) {
// given
var startEvent = elementRegistry.get('StartEvent_1');
var endEvent = elementRegistry.get('EndEvent_1');
var association = elementRegistry.get('Association_1');
// when
modeling.moveElements([ startEvent, endEvent ], { x: 0, y: 200 });
// then
expectWaypoints(association, [
{ x: 525, y: 110 },
{ x: 460, y: 350 },
]);
}));
});
});
// helpers //////////
function copyWaypoint(waypoint) {
return assign({}, waypoint);
}
function copyWaypoints(connection) {
return map(connection.waypoints, function(waypoint) {
waypoint = copyWaypoint(waypoint);
if (waypoint.original) {
waypoint.original = copyWaypoint(waypoint.original);
}
return waypoint;
});
}
function expectWaypoints(connection, expectedWaypoints) {
var actualWaypoints = connection.waypoints;
expect(actualWaypoints).to.exist;
expect(expectedWaypoints).to.exist;
expect(connection.waypoints.length).to.eql(expectedWaypoints.length);
for (var i in actualWaypoints) {
expect(actualWaypoints[i].x).to.eql(expectedWaypoints[i].x);
expect(actualWaypoints[i].y).to.eql(expectedWaypoints[i].y);
}
}