Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/features/modeling/BpmnUpdater.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ export default function BpmnUpdater(
}
});


// attach / detach connection
function updateConnection(e) {
self.updateConnection(e.context);
Expand Down
211 changes: 211 additions & 0 deletions lib/features/modeling/behavior/ArtifactBehavior.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import inherits from 'inherits-browser';

import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';

import {
is
} from '../../../util/ModelUtil';
import { forEach } from 'min-dash';

/**
* @typedef {import('../BpmnFactory').default} BpmnFactory
* @typedef {import('../../../Modeler').default} Modeler
* @typedef {import('diagram-js/lib/core/ElementRegistry').default} ElementRegistry
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
* @typedef {import('didi').Injector} Injector
* @typedef {import('../../copy-paste/ModdleCopy').default} ModdleCopy
*
* @typedef {import('../../../model/Types').Element} Element
* @typedef {import('../../../model/Types').Shape} Shape
*
* @typedef {import('diagram-js/lib/util/Types').DirectionTRBL} DirectionTRBL
* @typedef {import('../Modeling').default} Modeling
*/

var HIGH_PRIORITY = 1500;

/**
* BPMN specific artifact behavior.
*
* @param {BpmnFactory} bpmnFactory
* @param {Modeler} bpmnjs
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Injector} injector
* @param {Modeling} modeling
*/
export default function ArtifactBehavior(
bpmnFactory,
bpmnjs,
elementRegistry,
eventBus,
injector,
modeling
) {
injector.invoke(CommandInterceptor, this);

this.preExecute('shape.delete', HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape;

if (!is(shape, 'bpmn:Participant') && !is(shape, 'bpmn:SubProcess')) {
return;
}

modeling.removeElements(getRelevantArtifacts(shape));
});

// Handles artifacts on participants
eventBus.on('shape.move.start', function(event) {
let shape = event.shape;

if (!is(shape, 'bpmn:Participant') && !is(shape, 'bpmn:SubProcess')) {
return;
}

event.context.shapes.push(...getRelevantArtifacts(shape));
});

function calculateOverlappingArea(child, possibleParent) {
const leftA = child.x;
const rightA = child.x + child.width;
const topA = child.y;
const bottomA = child.y + child.height;

const leftB = possibleParent.x;
const rightB = possibleParent.x + possibleParent.width;
const topB = possibleParent.y;
const bottomB = possibleParent.y + possibleParent.height;

const overlapWidth = Math.max(0, Math.min(rightA, rightB) - Math.max(leftA, leftB));
const overlapHeight = Math.max(0, Math.min(bottomA, bottomB) - Math.max(topA, topB));

return overlapWidth * overlapHeight;
}

function isAssociatedTextAnnotation(shape, possibleParticipants) {
if (shape.type !== 'bpmn:TextAnnotation') {
return false;
}
let possibleAssociatedElements = possibleParticipants.flatMap((participant) => participant.children).flatMap((element) => element.id),
incomingElements = shape.incoming.flatMap((shape) => shape.businessObject.sourceRef.id),
isAssociated = false;


incomingElements.forEach((associatedElement) => {
possibleAssociatedElements.forEach(possibleElement => {
if (isAssociated) {
return;
}
isAssociated = associatedElement === possibleElement;
});
});
return isAssociated;
}

function getRelevantArtifacts(shape) {

// when swimminglanes / participants were placed shape.businessObject.processRef has no mor artifacts because the bpmn:Collaboration has it

let relevantArtifacts = [],
parent = shape.parent,
allPossibleElements = getAllPossibleElements(parent),
possibleParticipants = getPossibleContainer(parent, allPossibleElements),
possibleArtifacts = getPossibleArtifacts(parent, allPossibleElements);

forEach(possibleArtifacts, function(currentShape) {

let participantCounter = 0,
curOverlap = calculateOverlappingArea(shape, currentShape.bounds ?? shape);

if (isAssociatedTextAnnotation (currentShape, possibleParticipants)) {
relevantArtifacts.push(currentShape);
return;
}

// should not be moved
if (isAssociatedAssociation(currentShape)) {
return;
}

if (curOverlap <= 0) {
return;
}
possibleParticipants.forEach(function(participant) {

if (participantCounter >= 2) {
return;
}
participantCounter = calculateOverlappingArea(currentShape.bounds, participant) === (currentShape.bounds?.height * currentShape.bounds?.width) ? participantCounter + 1 : participantCounter;
});

if (participantCounter === 1) {
relevantArtifacts.push(getElementFromChildren(parent, currentShape.id));
}
});

return relevantArtifacts;
}

function isAssociatedAssociation(shape) {
return shape.type === 'bpmn:Association' || shape.$type === 'bpmndi:BPMNEdge';
}

function getPossibleContainer(parent) {
return parent.children.filter(isContainer);
}

function getPossibleArtifacts(parent, possibleElements) {

let artifacts = parent.businessObject?.artifacts ?? parent.di.$parent?.bpmnElement?.artifacts ?? [];
artifacts = artifacts.flatMap((artifact) => `${artifact.id}_di`);
return possibleElements.filter((element) => artifacts.includes(element.id));
}

function getAllPossibleElements(parent) {
let diagram = getDiagram(parent);

// return parent.businessObject.$parent.diagrams[0].plane;
return diagram.diagrams[0].plane.planeElement;
}

function isContainer(shape) {
return is(shape, 'bpmn:Participant') || is(shape, 'bpmn:SubProcess');
}

function getDiagram(parent) {
let next = parent?.businessObject?.$parent ?? parent.$parent;
if (next.$type === 'bpmn:Definitions' || (next.$type === 'bpmn:Collaboration' && !next.$parent)) {
return next;
} else {
return getDiagram(next);
}
}

function getElementFromChildren(parent, shapeId) {

let filteredChildren =
parent.children.filter(child => child.di.id === shapeId).length
? parent.children.filter(child => child.di.id === shapeId)
: getRootImpl(parent).children.filter(child => child.di.id === shapeId);
return filteredChildren.at(0);
}

function getRootImpl(parent) {
while (parent.parent && parent.type !== 'bpmn:Collaboration') {
parent = parent.parent;
}
return parent;
}
}

inherits(ArtifactBehavior, CommandInterceptor);

ArtifactBehavior.$inject = [
'bpmnFactory',
'bpmnjs',
'elementRegistry',
'eventBus',
'injector',
'modeling'
];
3 changes: 3 additions & 0 deletions lib/features/modeling/behavior/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import AdaptiveLabelPositioningBehavior from './AdaptiveLabelPositioningBehavior
import AppendBehavior from './AppendBehavior';
import AssociationBehavior from './AssociationBehavior';
import AttachEventBehavior from './AttachEventBehavior';
import ArtifactBehavior from './ArtifactBehavior';
import BoundaryEventBehavior from './BoundaryEventBehavior';
import CompensateBoundaryEventBehavior from './CompensateBoundaryEventBehavior';
import CreateBehavior from './CreateBehavior';
Expand Down Expand Up @@ -49,6 +50,7 @@ export default {
'appendBehavior',
'associationBehavior',
'attachEventBehavior',
'artifactBehavior',
'boundaryEventBehavior',
'compensateBoundaryEventBehaviour',
'createBehavior',
Expand Down Expand Up @@ -91,6 +93,7 @@ export default {
appendBehavior: [ 'type', AppendBehavior ],
associationBehavior: [ 'type', AssociationBehavior ],
attachEventBehavior: [ 'type', AttachEventBehavior ],
artifactBehavior: [ 'type', ArtifactBehavior ],
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
compensateBoundaryEventBehaviour: [ 'type', CompensateBoundaryEventBehavior ],
createBehavior: [ 'type', CreateBehavior ],
Expand Down
31 changes: 31 additions & 0 deletions test/fixtures/bpmn/participant-with-one-artifact.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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" id="Definitions_1jlhm18" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="18.6.1">
<bpmn:collaboration id="Collaboration_14pprp4">
<bpmn:participant id="Participant_1axg5jz" processRef="Process_18wyzo7" />
<bpmn:textAnnotation id="TextAnnotation_0hsrpnp">
<bpmn:text>test</bpmn:text>
</bpmn:textAnnotation>
<bpmn:association id="Association_1et62da" associationDirection="None" sourceRef="StartEvent_09y0l4d" targetRef="TextAnnotation_0hsrpnp" />
</bpmn:collaboration>
<bpmn:process id="Process_18wyzo7" isExecutable="false">
<bpmn:startEvent id="StartEvent_09y0l4d" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_14pprp4">
<bpmndi:BPMNShape id="Participant_1axg5jz_di" bpmnElement="Participant_1axg5jz" isHorizontal="true">
<dc:Bounds x="160" y="80" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_09y0l4d">
<dc:Bounds x="342" y="182" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Association_1et62da_di" bpmnElement="Association_1et62da">
<di:waypoint x="371" y="186" />
<di:waypoint x="418" y="130" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="TextAnnotation_0hsrpnp_di" bpmnElement="TextAnnotation_0hsrpnp">
<dc:Bounds x="380" y="100" width="100" height="30" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
42 changes: 42 additions & 0 deletions test/fixtures/bpmn/participant-with-one-group.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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" id="Definitions_1jlhm18" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="18.6.1">
<bpmn:collaboration id="Collaboration_14pprp4">
<bpmn:participant id="Participant_1axg5jz" processRef="Process_18wyzo7" />
<bpmn:group id="Group_0rfu791" categoryValueRef="CategoryValue_1nf9na9" />
</bpmn:collaboration>
<bpmn:process id="Process_18wyzo7" isExecutable="false">
<bpmn:startEvent id="StartEvent_09y0l4d">
<bpmn:outgoing>Flow_0vdmipo</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:endEvent id="Event_151nhg7">
<bpmn:incoming>Flow_0vdmipo</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_0vdmipo" sourceRef="StartEvent_09y0l4d" targetRef="Event_151nhg7" />
</bpmn:process>
<bpmn:category id="Category_0d3jxnq">
<bpmn:categoryValue id="CategoryValue_1nf9na9" value="important" />
</bpmn:category>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_14pprp4">
<bpmndi:BPMNShape id="Participant_1axg5jz_di" bpmnElement="Participant_1axg5jz" isHorizontal="true">
<dc:Bounds x="160" y="80" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_09y0l4d">
<dc:Bounds x="342" y="182" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_151nhg7_di" bpmnElement="Event_151nhg7">
<dc:Bounds x="562" y="182" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_0vdmipo_di" bpmnElement="Flow_0vdmipo">
<di:waypoint x="378" y="200" />
<di:waypoint x="562" y="200" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="Group_0rfu791_di" bpmnElement="Group_0rfu791">
<dc:Bounds x="310" y="130" width="310" height="140" />
<bpmndi:BPMNLabel>
<dc:Bounds x="442" y="137" width="47" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
33 changes: 33 additions & 0 deletions test/fixtures/bpmn/participants-with-artifact.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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" id="Definitions_0djjdgr" targetNamespace="http://bpmn.io/schema/bpmn" exporter="bpmn-js (https://demo.bpmn.io)" exporterVersion="18.6.1">
<bpmn:collaboration id="Collaboration_0yulavs">
<bpmn:participant id="Participant_09y906w" processRef="Process_10csbrj" />
<bpmn:participant id="Participant_1svjgx6" processRef="Process_024zi7r" />
<bpmn:group id="Group_0007vsj" />
</bpmn:collaboration>
<bpmn:process id="Process_10csbrj" isExecutable="false">
<bpmn:startEvent id="StartEvent_0f4eo5c" />
</bpmn:process>
<bpmn:process id="Process_024zi7r">
<bpmn:endEvent id="Event_0ofrct3" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0yulavs">
<bpmndi:BPMNShape id="Participant_09y906w_di" bpmnElement="Participant_09y906w" isHorizontal="true">
<dc:Bounds x="166" y="80" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_0f4eo5c">
<dc:Bounds x="422" y="172" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Participant_1svjgx6_di" bpmnElement="Participant_1svjgx6" isHorizontal="true">
<dc:Bounds x="160" y="540" width="600" height="250" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0ofrct3_di" bpmnElement="Event_0ofrct3">
<dc:Bounds x="422" y="632" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Group_0007vsj_di" bpmnElement="Group_0007vsj">
<dc:Bounds x="290" y="130" width="300" height="590" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Loading