feat(modeling): reconnect flows on collapse

This commit is contained in:
Martin Stamm 2021-12-15 18:28:01 +01:00 committed by Nico Rehwaldt
parent b8ed73b7f8
commit 12fe06bfa6
4 changed files with 275 additions and 0 deletions

View File

@ -0,0 +1,74 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
forEach
} from 'min-dash';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import { selfAndAllChildren } from 'diagram-js/lib/util/Elements';
import { isExpanded } from '../../../util/DiUtil';
export default function ToggleCollapseConnectionBehaviour(
eventBus, modeling
) {
CommandInterceptor.call(this, eventBus);
this.postExecuted('shape.toggleCollapse', 1500, function(context) {
// var shape = context.shape;
var shape = context.shape;
// only change connections when collapsing
if (isExpanded(shape)) {
return;
}
var allChildren = selfAndAllChildren(shape);
allChildren.forEach(function(child) {
// Ensure that the connection array is not modified during iteration
var incomingConnections = child.incoming.slice(),
outgoingConnections = child.outgoing.slice();
forEach(incomingConnections, function(c) {
handleConnection(c, true);
});
forEach(outgoingConnections, function(c) {
handleConnection(c, false);
});
});
function handleConnection(c, incoming) {
if (allChildren.includes(c.source) && allChildren.includes(c.target)) {
return;
}
if (incoming) {
modeling.reconnectEnd(c, shape, getMid(shape));
} else {
modeling.reconnectStart(c, shape, getMid(shape));
}
}
}, true);
}
inherits(ToggleCollapseConnectionBehaviour, CommandInterceptor);
ToggleCollapseConnectionBehaviour.$inject = [
'eventBus',
'modeling',
];

View File

@ -29,6 +29,7 @@ import RemoveElementBehavior from './RemoveElementBehavior';
import SpaceToolBehavior from './SpaceToolBehavior';
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
import SubProcessPlaneBehavior from './SubProcessPlaneBehavior';
import ToggleCollapseConnectionBehaviour from './ToggleCollapseConnectionBehaviour';
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
import UnclaimIdBehavior from './UnclaimIdBehavior';
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
@ -64,6 +65,7 @@ export default {
'replaceElementBehaviour',
'resizeBehavior',
'resizeLaneBehavior',
'toggleCollapseConnectionBehaviour',
'toggleElementCollapseBehaviour',
'spaceToolBehavior',
'subProcessStartEventBehavior',
@ -100,6 +102,7 @@ export default {
resizeBehavior: [ 'type', ResizeBehavior ],
resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],
toggleCollapseConnectionBehaviour: [ 'type', ToggleCollapseConnectionBehaviour ],
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],

View File

@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="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" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0aznxr3" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.11.1" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.15.0">
<bpmn:collaboration id="Collaboration_01harm7">
<bpmn:participant id="Participant_11e0vmc" processRef="Process_1vdnuf7" />
<bpmn:participant id="Participant_0dgf5bz" processRef="Process_03jefgm" />
<bpmn:messageFlow id="MessageFlow_1" sourceRef="Activity_0y7prp6" targetRef="Task_1" />
<bpmn:messageFlow id="MessageFlow_2" sourceRef="Task_1" targetRef="Activity_0y7prp6" />
</bpmn:collaboration>
<bpmn:process id="Process_1vdnuf7" isExecutable="true">
<bpmn:dataStoreReference id="DataStoreReference_03oe1ud" />
<bpmn:startEvent id="Event_19nvrv6" />
<bpmn:subProcess id="Subprocess_1">
<bpmn:startEvent id="Event_02ek85q">
<bpmn:outgoing>Flow_01hgf3n</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:task id="Task_1">
<bpmn:incoming>Flow_01hgf3n</bpmn:incoming>
<bpmn:outgoing>Flow_0h4tfi2</bpmn:outgoing>
<bpmn:property id="Property_0kwu583" name="__targetRef_placeholder" />
<bpmn:dataInputAssociation id="DataAssociation_1">
<bpmn:sourceRef>DataStoreReference_03oe1ud</bpmn:sourceRef>
<bpmn:targetRef>Property_0kwu583</bpmn:targetRef>
</bpmn:dataInputAssociation>
<bpmn:dataOutputAssociation id="DataAssociation_2">
<bpmn:targetRef>DataStoreReference_03oe1ud</bpmn:targetRef>
</bpmn:dataOutputAssociation>
</bpmn:task>
<bpmn:sequenceFlow id="Flow_01hgf3n" sourceRef="Event_02ek85q" targetRef="Task_1" />
<bpmn:endEvent id="Event_1qrx5os">
<bpmn:incoming>Flow_0h4tfi2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0h4tfi2" sourceRef="Task_1" targetRef="Event_1qrx5os" />
<bpmn:association id="Association_1" sourceRef="Task_1" targetRef="TextAnnotation_06fcwe1" />
</bpmn:subProcess>
<bpmn:textAnnotation id="TextAnnotation_06fcwe1" />
</bpmn:process>
<bpmn:process id="Process_03jefgm" isExecutable="false">
<bpmn:task id="Activity_0y7prp6" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_01harm7">
<bpmndi:BPMNShape id="Participant_11e0vmc_di" bpmnElement="Participant_11e0vmc" isHorizontal="true">
<dc:Bounds x="160" y="230" width="600" height="317" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="TextAnnotation_06fcwe1_di" bpmnElement="TextAnnotation_06fcwe1">
<dc:Bounds x="520" y="250" width="100" height="30" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_19nvrv6_di" bpmnElement="Event_19nvrv6">
<dc:Bounds x="202" y="399" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Subprocess_1_di" bpmnElement="Subprocess_1" isExpanded="true">
<dc:Bounds x="290" y="310" width="350" height="207" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_01hgf3n_di" bpmnElement="Flow_01hgf3n">
<di:waypoint x="366" y="417" />
<di:waypoint x="420" y="417" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0h4tfi2_di" bpmnElement="Flow_0h4tfi2">
<di:waypoint x="520" y="417" />
<di:waypoint x="582" y="417" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Event_02ek85q_di" bpmnElement="Event_02ek85q">
<dc:Bounds x="330" y="399" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="420" y="377" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1qrx5os_di" bpmnElement="Event_1qrx5os">
<dc:Bounds x="582" y="399" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_0hx47wy_di" bpmnElement="Association_1">
<di:waypoint x="496" y="377" />
<di:waypoint x="560" y="280" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Participant_0dgf5bz_di" bpmnElement="Participant_0dgf5bz" isHorizontal="true">
<dc:Bounds x="160" y="590" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0y7prp6_di" bpmnElement="Activity_0y7prp6">
<dc:Bounds x="420" y="680" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataStoreReference_03oe1ud_di" bpmnElement="DataStoreReference_03oe1ud">
<dc:Bounds x="235" y="85" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1dx18am_di" bpmnElement="MessageFlow_1">
<di:waypoint x="460" y="680" />
<di:waypoint x="460" y="457" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0vd4mvq_di" bpmnElement="MessageFlow_2">
<di:waypoint x="490" y="457" />
<di:waypoint x="490" y="680" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataInputAssociation_1kczmjn_di" bpmnElement="DataAssociation_1">
<di:waypoint x="278" y="135" />
<di:waypoint x="453" y="377" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="DataOutputAssociation_0xkes2r_di" bpmnElement="DataAssociation_2">
<di:waypoint x="442" y="377" />
<di:waypoint x="269" y="135" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,96 @@
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
var testModules = [
coreModule,
modelingModule,
];
describe('features/modeling - Toggle Collapse Connection Behavior', function() {
var diagramXML = require('./ToggleCollapseConnectionBehaviourSpec.bpmn');
beforeEach(bootstrapModeler(diagramXML, { modules: testModules }));
describe('Subprocess', function() {
it('should reconnect flows on collapse', inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('Subprocess_1'),
commentConnection = elementRegistry.get('Association_1'),
incommingDataConnection = elementRegistry.get('DataAssociation_1'),
outgoingDataConnection = elementRegistry.get('DataAssociation_2'),
incommingMessageFlow = elementRegistry.get('MessageFlow_1'),
outgoingMessageFlow = elementRegistry.get('MessageFlow_2');
// when
modeling.toggleCollapse(subProcess);
// then
expect(commentConnection.source).to.equal(subProcess);
expect(incommingDataConnection.target).to.equal(subProcess);
expect(outgoingDataConnection.source).to.equal(subProcess);
expect(incommingMessageFlow.target).to.equal(subProcess);
expect(outgoingMessageFlow.source).to.equal(subProcess);
}));
it('should undo', inject(function(elementRegistry, modeling, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_1'),
task = elementRegistry.get('Task_1'),
commentConnection = elementRegistry.get('Association_1'),
incommingDataConnection = elementRegistry.get('DataAssociation_1'),
outgoingDataConnection = elementRegistry.get('DataAssociation_2'),
incommingMessageFlow = elementRegistry.get('MessageFlow_1'),
outgoingMessageFlow = elementRegistry.get('MessageFlow_2');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
// then
expect(commentConnection.source).to.equal(task);
expect(incommingDataConnection.target).to.equal(task);
expect(outgoingDataConnection.source).to.equal(task);
expect(incommingMessageFlow.target).to.equal(task);
expect(outgoingMessageFlow.source).to.equal(task);
}));
it('should redo', inject(function(elementRegistry, modeling, commandStack) {
// given
var subProcess = elementRegistry.get('Subprocess_1'),
commentConnection = elementRegistry.get('Association_1'),
incommingDataConnection = elementRegistry.get('DataAssociation_1'),
outgoingDataConnection = elementRegistry.get('DataAssociation_2'),
incommingMessageFlow = elementRegistry.get('MessageFlow_1'),
outgoingMessageFlow = elementRegistry.get('MessageFlow_2');
modeling.toggleCollapse(subProcess);
// when
commandStack.undo();
commandStack.redo();
// then
expect(commentConnection.source).to.equal(subProcess);
expect(incommingDataConnection.target).to.equal(subProcess);
expect(outgoingDataConnection.source).to.equal(subProcess);
expect(incommingMessageFlow.target).to.equal(subProcess);
expect(outgoingMessageFlow.source).to.equal(subProcess);
}));
});
});