feat: update/remove embedded label bounds on shape moved/resized

* update embedded label bounds on shape move
* remove embedded label bounds on shape resize

Related to https: //github.com/camunda/camunda-modeler/issues/2591

Co-Authored-By: Martin Stamm <martin.stamm@camunda.com>
Co-Authored-By: Philipp Fromme <philippfromme@outlook.com>
This commit is contained in:
Valentin Serra 2022-01-26 14:27:54 +01:00 committed by Philipp Fromme
parent e0a2b4164d
commit 390031a7c3
7 changed files with 310 additions and 61 deletions

View File

@ -24,6 +24,10 @@ import {
isAny
} from './util/ModelingUtil';
import {
delta
} from 'diagram-js/lib/util/PositionUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
@ -344,7 +348,18 @@ BpmnUpdater.prototype.updateParent = function(element, oldParent) {
BpmnUpdater.prototype.updateBounds = function(shape) {
var di = getDi(shape);
var di = getDi(shape),
embeddedLabelBounds = getEmbeddedLabelBounds(shape);
// update embedded label bounds if possible
if (embeddedLabelBounds) {
var embeddedLabelBoundsDelta = delta(embeddedLabelBounds, di.get('bounds'));
assign(embeddedLabelBounds, {
x: shape.x + embeddedLabelBoundsDelta.x,
y: shape.y + embeddedLabelBoundsDelta.y
});
}
var target = (shape instanceof Label) ? this._getLabel(di) : di;
@ -718,3 +733,30 @@ function ifBpmn(fn) {
}
};
}
/**
* Return dc:Bounds of bpmndi:BPMNLabel if exists.
*
* @param {djs.model.shape} shape
*
* @returns {Object|undefined}
*/
function getEmbeddedLabelBounds(shape) {
if (!is(shape, 'bpmn:Activity')) {
return;
}
var di = getDi(shape);
if (!di) {
return;
}
var label = di.get('label');
if (!label) {
return;
}
return label.get('bounds');
}

View File

@ -0,0 +1,34 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { getDi } from '../../../util/ModelUtil';
/**
* BPMN specific behavior ensuring that bpmndi:Label's dc:Bounds are removed
* when shape is resized.
*/
export default function RemoveEmbeddedLabelBoundsBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute(['shape.resize'], function(context) {
var shape = context.shape;
var di = getDi(shape),
label = di && di.get('label'),
bounds = label && label.get('bounds');
if (bounds) {
modeling.updateModdleProperties(shape, label, {
bounds: undefined
});
}
}, true);
}
inherits(RemoveEmbeddedLabelBoundsBehavior, CommandInterceptor);
RemoveEmbeddedLabelBoundsBehavior.$inject = [
'eventBus',
'modeling'
];

View File

@ -3,9 +3,7 @@ import AppendBehavior from './AppendBehavior';
import AssociationBehavior from './AssociationBehavior';
import AttachEventBehavior from './AttachEventBehavior';
import BoundaryEventBehavior from './BoundaryEventBehavior';
import RootElementReferenceBehavior from './RootElementReferenceBehavior';
import CreateBehavior from './CreateBehavior';
import FixHoverBehavior from './FixHoverBehavior';
import CreateDataObjectBehavior from './CreateDataObjectBehavior';
import CreateParticipantBehavior from './CreateParticipantBehavior';
import DataInputAssociationBehavior from './DataInputAssociationBehavior';
@ -14,26 +12,29 @@ import DeleteLaneBehavior from './DeleteLaneBehavior';
import DetachEventBehavior from './DetachEventBehavior';
import DropOnFlowBehavior from './DropOnFlowBehavior';
import EventBasedGatewayBehavior from './EventBasedGatewayBehavior';
import FixHoverBehavior from './FixHoverBehavior';
import GroupBehavior from './GroupBehavior';
import ImportDockingFix from './ImportDockingFix';
import IsHorizontalFix from './IsHorizontalFix';
import LabelBehavior from './LabelBehavior';
import MessageFlowBehavior from './MessageFlowBehavior';
import ModelingFeedback from './ModelingFeedback';
import ReplaceConnectionBehavior from './ReplaceConnectionBehavior';
import RemoveEmbeddedLabelBoundsBehavior from './RemoveEmbeddedLabelBoundsBehavior';
import RemoveElementBehavior from './RemoveElementBehavior';
import RemoveParticipantBehavior from './RemoveParticipantBehavior';
import ReplaceConnectionBehavior from './ReplaceConnectionBehavior';
import ReplaceElementBehaviour from './ReplaceElementBehaviour';
import ResizeBehavior from './ResizeBehavior';
import ResizeLaneBehavior from './ResizeLaneBehavior';
import RemoveElementBehavior from './RemoveElementBehavior';
import RootElementReferenceBehavior from './RootElementReferenceBehavior';
import SpaceToolBehavior from './SpaceToolBehavior';
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
import SubProcessPlaneBehavior from './SubProcessPlaneBehavior';
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
import ToggleCollapseConnectionBehaviour from './ToggleCollapseConnectionBehaviour';
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
import UnclaimIdBehavior from './UnclaimIdBehavior';
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
import UnsetDefaultFlowBehavior from './UnsetDefaultFlowBehavior';
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
export default {
__init__: [
@ -42,17 +43,16 @@ export default {
'associationBehavior',
'attachEventBehavior',
'boundaryEventBehavior',
'rootElementReferenceBehavior',
'createBehavior',
'fixHoverBehavior',
'createDataObjectBehavior',
'createParticipantBehavior',
'dataStoreBehavior',
'dataInputAssociationBehavior',
'dataStoreBehavior',
'deleteLaneBehavior',
'detachEventBehavior',
'dropOnFlowBehavior',
'eventBasedGatewayBehavior',
'fixHoverBehavior',
'groupBehavior',
'importDockingFix',
'isHorizontalFix',
@ -60,28 +60,28 @@ export default {
'messageFlowBehavior',
'modelingFeedback',
'removeElementBehavior',
'removeEmbeddedLabelBoundsBehavior',
'removeParticipantBehavior',
'replaceConnectionBehavior',
'replaceElementBehaviour',
'resizeBehavior',
'resizeLaneBehavior',
'rootElementReferenceBehavior',
'spaceToolBehavior',
'subProcessPlaneBehavior',
'subProcessStartEventBehavior',
'toggleCollapseConnectionBehaviour',
'toggleElementCollapseBehaviour',
'spaceToolBehavior',
'subProcessStartEventBehavior',
'subProcessPlaneBehavior',
'unclaimIdBehavior',
'unsetDefaultFlowBehavior',
'updateFlowNodeRefsBehavior'
'updateFlowNodeRefsBehavior',
'unsetDefaultFlowBehavior'
],
adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
appendBehavior: [ 'type', AppendBehavior ],
associationBehavior: [ 'type', AssociationBehavior ],
attachEventBehavior: [ 'type', AttachEventBehavior ],
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
createBehavior: [ 'type', CreateBehavior ],
fixHoverBehavior: [ 'type', FixHoverBehavior ],
createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
createParticipantBehavior: [ 'type', CreateParticipantBehavior ],
dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
@ -90,24 +90,27 @@ export default {
detachEventBehavior: [ 'type', DetachEventBehavior ],
dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
fixHoverBehavior: [ 'type', FixHoverBehavior ],
groupBehavior: [ 'type', GroupBehavior ],
importDockingFix: [ 'type', ImportDockingFix ],
isHorizontalFix: [ 'type', IsHorizontalFix ],
labelBehavior: [ 'type', LabelBehavior ],
messageFlowBehavior: [ 'type', MessageFlowBehavior ],
modelingFeedback: [ 'type', ModelingFeedback ],
replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],
removeEmbeddedLabelBoundsBehavior: ['type', RemoveEmbeddedLabelBoundsBehavior ],
removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
resizeBehavior: [ 'type', ResizeBehavior ],
resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],
rootElementReferenceBehavior: [ 'type', RootElementReferenceBehavior ],
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
toggleCollapseConnectionBehaviour: [ 'type', ToggleCollapseConnectionBehaviour ],
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
spaceToolBehavior: [ 'type', SpaceToolBehavior ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
subProcessPlaneBehavior: [ 'type', SubProcessPlaneBehavior ],
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ]
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ],
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ]
};

View File

@ -1,5 +1,5 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.4.0-nightly">
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.12.0">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_1</bpmn:outgoing>
@ -32,19 +32,48 @@
<bpmn:outgoing>SequenceFlow_5</bpmn:outgoing>
</bpmn:task>
<bpmn:sequenceFlow id="SequenceFlow_5" sourceRef="Task_2" targetRef="Task_2" />
<bpmn:task id="Task_3" name="Foo" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
<dc:Bounds x="173" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0giidzv_di" bpmnElement="SequenceFlow_5">
<di:waypoint x="308" y="530" />
<di:waypoint x="308" y="550" />
<di:waypoint x="238" y="550" />
<di:waypoint x="238" y="490" />
<di:waypoint x="258" y="490" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_1o287si_di" bpmnElement="SequenceFlow_4">
<di:waypoint x="209" y="389" />
<di:waypoint x="418" y="389" />
<bpmndi:BPMNLabel>
<dc:Bounds x="314" y="364" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_3_di" bpmnElement="SequenceFlow_3" sourceElement="StartEvent_2_di" targetElement="EndEvent_2_di">
<di:waypoint x="209" y="260" />
<di:waypoint x="418" y="260" />
<bpmndi:BPMNLabel>
<dc:Bounds x="314" y="245" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_2_di" bpmnElement="SequenceFlow_2" sourceElement="Task_1_di" targetElement="EndEvent_1_di">
<di:waypoint x="358" y="120" />
<di:waypoint x="418" y="120" />
<bpmndi:BPMNLabel>
<dc:Bounds x="388" y="95" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_1_di" bpmnElement="SequenceFlow_1" sourceElement="StartEvent_1_di" targetElement="Task_1_di">
<di:waypoint xsi:type="dc:Point" x="209" y="120" />
<di:waypoint xsi:type="dc:Point" x="258" y="120" />
<di:waypoint x="209" y="120" />
<di:waypoint x="258" y="120" />
<bpmndi:BPMNLabel>
<dc:Bounds x="234" y="105" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_1_di" bpmnElement="StartEvent_1">
<dc:Bounds x="173" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
<dc:Bounds x="418" y="102" width="36" height="36" />
<bpmndi:BPMNLabel>
@ -54,13 +83,6 @@
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="258" y="80" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_2_di" bpmnElement="SequenceFlow_2" sourceElement="Task_1_di" targetElement="EndEvent_1_di">
<di:waypoint xsi:type="dc:Point" x="358" y="120" />
<di:waypoint xsi:type="dc:Point" x="418" y="120" />
<bpmndi:BPMNLabel>
<dc:Bounds x="388" y="95" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_2_di" bpmnElement="StartEvent_2">
<dc:Bounds x="173" y="242" width="36" height="36" />
<bpmndi:BPMNLabel>
@ -73,13 +95,6 @@
<dc:Bounds x="436" y="278" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_3_di" bpmnElement="SequenceFlow_3" sourceElement="StartEvent_2_di" targetElement="EndEvent_2_di">
<di:waypoint xsi:type="dc:Point" x="209" y="260" />
<di:waypoint xsi:type="dc:Point" x="418" y="260" />
<bpmndi:BPMNLabel>
<dc:Bounds x="314" y="245" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="StartEvent_1ua609k_di" bpmnElement="StartEvent_3">
<dc:Bounds x="173" y="371" width="36" height="36" />
<bpmndi:BPMNLabel>
@ -92,23 +107,15 @@
<dc:Bounds x="436" y="407" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1o287si_di" bpmnElement="SequenceFlow_4">
<di:waypoint xsi:type="dc:Point" x="209" y="389" />
<di:waypoint xsi:type="dc:Point" x="418" y="389" />
<bpmndi:BPMNLabel>
<dc:Bounds x="314" y="364" width="0" height="0" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Task_15aeig4_di" bpmnElement="Task_2">
<dc:Bounds x="258" y="450" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0giidzv_di" bpmnElement="SequenceFlow_5">
<di:waypoint x="308" y="530" />
<di:waypoint x="308" y="550" />
<di:waypoint x="238" y="550" />
<di:waypoint x="238" y="490" />
<di:waypoint x="258" y="490" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Activity_1hgfykz_di" bpmnElement="Task_3">
<dc:Bounds x="258" y="600" width="100" height="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="268" y="610" width="80" height="60" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -5,10 +5,12 @@ import {
import { getDi } from 'lib/util/ModelUtil';
import modelingModule from 'lib/features/modeling';
import coreModule from 'lib/core';
import { pick } from 'min-dash';
var testModules = [ modelingModule, coreModule ];
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
var testModules = [ coreModule, modelingModule ];
describe('features - bpmn-updater', function() {
@ -207,4 +209,77 @@ describe('features - bpmn-updater', function() {
});
describe('update embedded label bounds', function() {
var diagramXML = require('./BpmnUpdater.bpmn');
beforeEach(bootstrapModeler(diagramXML, {
modules: testModules
}));
var bounds,
di;
beforeEach(inject(function(elementRegistry, modeling) {
// given
var task = elementRegistry.get('Task_3');
di = getDi(task);
bounds = pick(di.get('label').get('bounds'), [ 'x', 'y', 'width', 'height' ]);
// when
modeling.moveShape(task, {
x: 100,
y: 100
});
}));
it('<do>', function() {
// then
expect(di.get('label').get('bounds')).to.include({
x: bounds.x + 100,
y: bounds.y + 100,
width: bounds.width,
height: bounds.height
});
});
it('<undo>', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(di.get('label').get('bounds')).to.include({
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
});
}));
it('<redo>', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(di.get('label').get('bounds')).to.include({
x: bounds.x + 100,
y: bounds.y + 100,
width: bounds.width,
height: bounds.height
});
}));
});
});

View File

@ -0,0 +1,16 @@
<?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:color="http://www.omg.org/spec/BPMN/non-normative/color/1.0" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_1784xmh" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.7.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.16.0">
<bpmn:process id="Process_0" isExecutable="true">
<bpmn:subProcess id="Activity_1" name="SUBPROCESS EXAMPLE"/>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_0">
<bpmndi:BPMNShape id="Activity_1_di" bpmnElement="Activity_1" isExpanded="true">
<dc:Bounds x="0" y="50" width="400" height="200" />
<bpmndi:BPMNLabel>
<dc:Bounds x="10" y="60" width="380" height="180" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

View File

@ -0,0 +1,72 @@
import {
bootstrapModeler,
inject
} from 'test/TestHelper';
import coreModule from 'lib/core';
import modelingModule from 'lib/features/modeling';
import { getDi } from 'lib/util/ModelUtil';
describe('features/modeling - RemoveEmbeddedLabelBoundsBehavior', function() {
var processDiagramXML = require('./RemoveEmbeddedLabelBoundsBehavior.bpmn');
beforeEach(bootstrapModeler(processDiagramXML, {
modules: [
coreModule,
modelingModule
]
}));
var bounds,
di;
beforeEach(inject(function(elementRegistry, modeling) {
// given
var subProcess = elementRegistry.get('Activity_1');
di = getDi(subProcess);
bounds = di.get('label').get('bounds');
// when
modeling.resizeShape(subProcess, {
x: 0,
y: 0,
width: 100,
height: 100
});
}));
it('<do>', function() {
// then
expect(di.get('label').get('bounds')).not.to.exist;
});
it('<undo>', inject(function(commandStack) {
// when
commandStack.undo();
// then
expect(di.get('label').get('bounds')).to.equal(bounds);
}));
it('<redo>', inject(function(commandStack) {
// when
commandStack.undo();
commandStack.redo();
// then
expect(di.get('label').get('bounds')).not.to.exist;
}));
});