update
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
getOrientation,
|
||||
getMid,
|
||||
asTRBL
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
substract
|
||||
} from 'diagram-js/lib/util/Math';
|
||||
|
||||
import {
|
||||
hasExternalLabel
|
||||
} from '../../../util/LabelUtil';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
var ALIGNMENTS = [
|
||||
'top',
|
||||
'bottom',
|
||||
'left',
|
||||
'right'
|
||||
];
|
||||
|
||||
var ELEMENT_LABEL_DISTANCE = 10;
|
||||
|
||||
/**
|
||||
* A component that makes sure that external labels are added
|
||||
* together with respective elements and properly updated (DI wise)
|
||||
* during move.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this.postExecuted([
|
||||
'connection.create',
|
||||
'connection.layout',
|
||||
'connection.updateWaypoints'
|
||||
], function(event) {
|
||||
|
||||
var context = event.context,
|
||||
connection = context.connection;
|
||||
|
||||
var source = connection.source,
|
||||
target = connection.target;
|
||||
|
||||
checkLabelAdjustment(source);
|
||||
checkLabelAdjustment(target);
|
||||
});
|
||||
|
||||
|
||||
this.postExecuted([
|
||||
'label.create'
|
||||
], function(event) {
|
||||
checkLabelAdjustment(event.context.shape.labelTarget);
|
||||
});
|
||||
|
||||
|
||||
function checkLabelAdjustment(element) {
|
||||
|
||||
// skip non-existing labels
|
||||
if (!hasExternalLabel(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var optimalPosition = getOptimalPosition(element);
|
||||
|
||||
// no optimal position found
|
||||
if (!optimalPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustLabelPosition(element, optimalPosition);
|
||||
}
|
||||
|
||||
function adjustLabelPosition(element, orientation) {
|
||||
|
||||
var elementMid = getMid(element),
|
||||
label = element.label,
|
||||
labelMid = getMid(label);
|
||||
|
||||
var elementTrbl = asTRBL(element);
|
||||
|
||||
var newLabelMid;
|
||||
|
||||
switch (orientation) {
|
||||
case 'top':
|
||||
newLabelMid = {
|
||||
x: elementMid.x,
|
||||
y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case 'left':
|
||||
|
||||
newLabelMid = {
|
||||
x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
|
||||
y: elementMid.y
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case 'bottom':
|
||||
|
||||
newLabelMid = {
|
||||
x: elementMid.x,
|
||||
y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
|
||||
};
|
||||
|
||||
break;
|
||||
|
||||
case 'right':
|
||||
|
||||
newLabelMid = {
|
||||
x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
|
||||
y: elementMid.y
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
var delta = substract(newLabelMid, labelMid);
|
||||
|
||||
modeling.moveShape(label, delta);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inherits(AdaptiveLabelPositioningBehavior, CommandInterceptor);
|
||||
|
||||
AdaptiveLabelPositioningBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
/**
|
||||
* Return alignments which are taken by a boundary's host element
|
||||
*
|
||||
* @param {Shape} element
|
||||
*
|
||||
* @return {Array<String>}
|
||||
*/
|
||||
function getTakenHostAlignments(element) {
|
||||
|
||||
var hostElement = element.host,
|
||||
elementMid = getMid(element),
|
||||
hostOrientation = getOrientation(elementMid, hostElement);
|
||||
|
||||
var freeAlignments;
|
||||
|
||||
// check whether there is a multi-orientation, e.g. 'top-left'
|
||||
if (hostOrientation.indexOf('-') >= 0) {
|
||||
freeAlignments = hostOrientation.split('-');
|
||||
} else {
|
||||
freeAlignments = [ hostOrientation ];
|
||||
}
|
||||
|
||||
var takenAlignments = ALIGNMENTS.filter(function(alignment) {
|
||||
|
||||
return freeAlignments.indexOf(alignment) === -1;
|
||||
});
|
||||
|
||||
return takenAlignments;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Return alignments which are taken by related connections
|
||||
*
|
||||
* @param {Shape} element
|
||||
*
|
||||
* @return {Array<String>}
|
||||
*/
|
||||
function getTakenConnectionAlignments(element) {
|
||||
|
||||
var elementMid = getMid(element);
|
||||
|
||||
var takenAlignments = [].concat(
|
||||
element.incoming.map(function(c) {
|
||||
return c.waypoints[c.waypoints.length - 2 ];
|
||||
}),
|
||||
element.outgoing.map(function(c) {
|
||||
return c.waypoints[1];
|
||||
})
|
||||
).map(function(point) {
|
||||
return getApproximateOrientation(elementMid, point);
|
||||
});
|
||||
|
||||
return takenAlignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the optimal label position around an element
|
||||
* or _undefined_, if none was found.
|
||||
*
|
||||
* @param {Shape} element
|
||||
*
|
||||
* @return {String} positioning identifier
|
||||
*/
|
||||
function getOptimalPosition(element) {
|
||||
|
||||
var labelMid = getMid(element.label);
|
||||
|
||||
var elementMid = getMid(element);
|
||||
|
||||
var labelOrientation = getApproximateOrientation(elementMid, labelMid);
|
||||
|
||||
if (!isAligned(labelOrientation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var takenAlignments = getTakenConnectionAlignments(element);
|
||||
|
||||
if (element.host) {
|
||||
var takenHostAlignments = getTakenHostAlignments(element);
|
||||
|
||||
takenAlignments = takenAlignments.concat(takenHostAlignments);
|
||||
}
|
||||
|
||||
var freeAlignments = ALIGNMENTS.filter(function(alignment) {
|
||||
|
||||
return takenAlignments.indexOf(alignment) === -1;
|
||||
});
|
||||
|
||||
// NOTHING TO DO; label already aligned a.O.K.
|
||||
if (freeAlignments.indexOf(labelOrientation) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
return freeAlignments[0];
|
||||
}
|
||||
|
||||
function getApproximateOrientation(p0, p1) {
|
||||
return getOrientation(p1, p0, 5);
|
||||
}
|
||||
|
||||
function isAligned(orientation) {
|
||||
return ALIGNMENTS.indexOf(orientation) !== -1;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
|
||||
export default function AppendBehavior(eventBus, elementFactory, bpmnRules) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
// assign correct shape position unless already set
|
||||
|
||||
this.preExecute('shape.append', function(context) {
|
||||
|
||||
var source = context.source,
|
||||
shape = context.shape;
|
||||
|
||||
if (!context.position) {
|
||||
|
||||
if (is(shape, 'bpmn:TextAnnotation')) {
|
||||
context.position = {
|
||||
x: source.x + source.width / 2 + 75,
|
||||
y: source.y - (50) - shape.height / 2
|
||||
};
|
||||
} else {
|
||||
context.position = {
|
||||
x: source.x + source.width + 80 + shape.width / 2,
|
||||
y: source.y + source.height / 2
|
||||
};
|
||||
}
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
inherits(AppendBehavior, CommandInterceptor);
|
||||
|
||||
AppendBehavior.$inject = [
|
||||
'eventBus',
|
||||
'elementFactory',
|
||||
'bpmnRules'
|
||||
];
|
||||
@@ -0,0 +1,69 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { isAny } from '../util/ModelingUtil';
|
||||
import { getBusinessObject } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific attach event behavior
|
||||
*/
|
||||
export default function AttachEventBehavior(eventBus, bpmnReplace) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* replace intermediate event with boundary event when
|
||||
* attaching it to a shape
|
||||
*/
|
||||
|
||||
this.preExecute('elements.move', function(context) {
|
||||
var shapes = context.shapes,
|
||||
host = context.newHost,
|
||||
shape,
|
||||
eventDefinition,
|
||||
boundaryEvent,
|
||||
newShape;
|
||||
|
||||
if (shapes.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
shape = shapes[0];
|
||||
|
||||
if (host && isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ])) {
|
||||
|
||||
eventDefinition = getEventDefinition(shape);
|
||||
|
||||
boundaryEvent = {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
host: host
|
||||
};
|
||||
|
||||
if (eventDefinition) {
|
||||
boundaryEvent.eventDefinitionType = eventDefinition.$type;
|
||||
}
|
||||
|
||||
newShape = bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false });
|
||||
|
||||
context.shapes = [ newShape ];
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
AttachEventBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnReplace'
|
||||
];
|
||||
|
||||
inherits(AttachEventBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
|
||||
// helper /////
|
||||
function getEventDefinition(element) {
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return bo && bo.eventDefinitions && bo.eventDefinitions[0];
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
filter,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific boundary event behavior
|
||||
*/
|
||||
export default function BoundaryEventBehavior(eventBus, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
function getBoundaryEvents(element) {
|
||||
return filter(element.attachers, function(attacher) {
|
||||
return is(attacher, 'bpmn:BoundaryEvent');
|
||||
});
|
||||
}
|
||||
|
||||
// remove after connecting to event-based gateway
|
||||
this.postExecute('connection.create', function(event) {
|
||||
var source = event.context.source,
|
||||
target = event.context.target,
|
||||
boundaryEvents = getBoundaryEvents(target);
|
||||
|
||||
if (
|
||||
is(source, 'bpmn:EventBasedGateway') &&
|
||||
is(target, 'bpmn:ReceiveTask') &&
|
||||
boundaryEvents.length > 0
|
||||
) {
|
||||
modeling.removeElements(boundaryEvents);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// remove after replacing connected gateway with event-based gateway
|
||||
this.postExecute('connection.reconnectStart', function(event) {
|
||||
var oldSource = event.context.oldSource,
|
||||
newSource = event.context.newSource;
|
||||
|
||||
if (is(oldSource, 'bpmn:Gateway') &&
|
||||
is(newSource, 'bpmn:EventBasedGateway')) {
|
||||
forEach(newSource.outgoing, function(connection) {
|
||||
var target = connection.target,
|
||||
attachedboundaryEvents = getBoundaryEvents(target);
|
||||
|
||||
if (is(target, 'bpmn:ReceiveTask') &&
|
||||
attachedboundaryEvents.length > 0) {
|
||||
modeling.removeElements(attachedboundaryEvents);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BoundaryEventBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
inherits(BoundaryEventBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,71 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
|
||||
export default function CopyPasteBehavior(eventBus, modeling, canvas) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this.preExecute('elements.paste', 1500, function(context) {
|
||||
var topParent = context.topParent;
|
||||
|
||||
// always grab the latest root
|
||||
if (!topParent.parent) {
|
||||
context.topParent = canvas.getRootElement();
|
||||
}
|
||||
|
||||
if (is(topParent, 'bpmn:Lane')) {
|
||||
do {
|
||||
// unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
|
||||
topParent = context.topParent = topParent.parent;
|
||||
|
||||
} while (is(topParent, 'bpmn:Lane') || !is(topParent, 'bpmn:Participant'));
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.postExecute('elements.paste', function(context) {
|
||||
|
||||
var tree = context.tree,
|
||||
createdElements = tree.createdElements;
|
||||
|
||||
forEach(createdElements, function(data) {
|
||||
var element = data.element,
|
||||
businessObject = element.businessObject,
|
||||
descriptor = data.descriptor,
|
||||
defaultFlow;
|
||||
|
||||
if ((is(businessObject, 'bpmn:ExclusiveGateway') || is(businessObject, 'bpmn:InclusiveGateway') ||
|
||||
is(businessObject, 'bpmn:Activity')) && descriptor.default) {
|
||||
|
||||
defaultFlow = createdElements[descriptor.default];
|
||||
|
||||
// if the default flow wasn't created, means that it wasn't copied
|
||||
if (defaultFlow) {
|
||||
defaultFlow = defaultFlow.element;
|
||||
} else {
|
||||
defaultFlow = undefined;
|
||||
}
|
||||
|
||||
delete element.default;
|
||||
|
||||
modeling.updateProperties(element, { default: defaultFlow });
|
||||
}
|
||||
});
|
||||
}, true);
|
||||
}
|
||||
|
||||
|
||||
CopyPasteBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'canvas'
|
||||
];
|
||||
|
||||
inherits(CopyPasteBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,54 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific create boundary event behavior
|
||||
*/
|
||||
export default function CreateBoundaryEventBehavior(
|
||||
eventBus, modeling, elementFactory,
|
||||
bpmnFactory) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* replace intermediate event with boundary event when
|
||||
* attaching it to a shape
|
||||
*/
|
||||
|
||||
this.preExecute('shape.create', function(context) {
|
||||
var shape = context.shape,
|
||||
host = context.host,
|
||||
businessObject,
|
||||
boundaryEvent;
|
||||
|
||||
var attrs = {
|
||||
cancelActivity: true
|
||||
};
|
||||
|
||||
if (host && is(shape, 'bpmn:IntermediateThrowEvent')) {
|
||||
attrs.attachedToRef = host.businessObject;
|
||||
|
||||
businessObject = bpmnFactory.create('bpmn:BoundaryEvent', attrs);
|
||||
|
||||
boundaryEvent = {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
businessObject: businessObject
|
||||
};
|
||||
|
||||
context.shape = elementFactory.createShape(boundaryEvent);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
CreateBoundaryEventBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'elementFactory',
|
||||
'bpmnFactory'
|
||||
];
|
||||
|
||||
inherits(CreateBoundaryEventBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,38 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific create data object behavior
|
||||
*/
|
||||
export default function CreateDataObjectBehavior(eventBus, bpmnFactory, moddle) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this.preExecute('shape.create', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
|
||||
|
||||
// create a DataObject every time a DataObjectReference is created
|
||||
var dataObject = bpmnFactory.create('bpmn:DataObject');
|
||||
|
||||
// set the reference to the DataObject
|
||||
shape.businessObject.dataObjectRef = dataObject;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
CreateDataObjectBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnFactory',
|
||||
'moddle'
|
||||
];
|
||||
|
||||
inherits(CreateDataObjectBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,190 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import { isLabel } from '../../../util/LabelUtil';
|
||||
|
||||
import { getBBox } from 'diagram-js/lib/util/Elements';
|
||||
|
||||
import { assign } from 'min-dash';
|
||||
|
||||
import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
var HORIZONTAL_PARTICIPANT_PADDING = 20,
|
||||
VERTICAL_PARTICIPANT_PADDING = 20;
|
||||
|
||||
export var PARTICIPANT_BORDER_WIDTH = 30;
|
||||
|
||||
var HIGH_PRIORITY = 2000;
|
||||
|
||||
|
||||
/**
|
||||
* BPMN-specific behavior for creating participants.
|
||||
*/
|
||||
export default function CreateParticipantBehavior(canvas, eventBus, modeling) {
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
// fit participant
|
||||
eventBus.on([
|
||||
'create.start',
|
||||
'shape.move.start'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
rootElement = canvas.getRootElement();
|
||||
|
||||
if (!is(shape, 'bpmn:Participant') ||
|
||||
!is(rootElement, 'bpmn:Process') ||
|
||||
!rootElement.children.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore connections, groups and labels
|
||||
var children = rootElement.children.filter(function(element) {
|
||||
return !is(element, 'bpmn:Group') &&
|
||||
!isLabel(element) &&
|
||||
!isConnection(element);
|
||||
});
|
||||
|
||||
// ensure for available children to calculate bounds
|
||||
if (!children.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var childrenBBox = getBBox(children);
|
||||
|
||||
var participantBounds = getParticipantBounds(shape, childrenBBox);
|
||||
|
||||
// assign width and height
|
||||
assign(shape, participantBounds);
|
||||
|
||||
// assign create constraints
|
||||
context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
|
||||
});
|
||||
|
||||
// force hovering process when creating first participant
|
||||
eventBus.on('create.start', HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
rootElement = canvas.getRootElement(),
|
||||
rootElementGfx = canvas.getGraphics(rootElement);
|
||||
|
||||
function ensureHoveringProcess(event) {
|
||||
event.element = rootElement;
|
||||
event.gfx = rootElementGfx;
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:Participant') && is(rootElement, 'bpmn:Process')) {
|
||||
eventBus.on('element.hover', HIGH_PRIORITY, ensureHoveringProcess);
|
||||
|
||||
eventBus.once('create.cleanup', function() {
|
||||
eventBus.off('element.hover', ensureHoveringProcess);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// turn process into collaboration before adding participant
|
||||
this.preExecute('shape.create', function(context) {
|
||||
var parent = context.parent,
|
||||
shape = context.shape,
|
||||
position = context.position;
|
||||
|
||||
var rootElement = canvas.getRootElement();
|
||||
|
||||
if (
|
||||
is(parent, 'bpmn:Process') &&
|
||||
is(shape, 'bpmn:Participant') &&
|
||||
!is(rootElement, 'bpmn:Collaboration')
|
||||
) {
|
||||
|
||||
// this is going to detach the process root
|
||||
// and set the returned collaboration element
|
||||
// as the new root element
|
||||
var collaborationElement = modeling.makeCollaboration();
|
||||
|
||||
// monkey patch the create context
|
||||
// so that the participant is being dropped
|
||||
// onto the new collaboration root instead
|
||||
context.position = position;
|
||||
context.parent = collaborationElement;
|
||||
|
||||
context.processRoot = parent;
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.execute('shape.create', function(context) {
|
||||
var processRoot = context.processRoot,
|
||||
shape = context.shape;
|
||||
|
||||
if (processRoot) {
|
||||
context.oldProcessRef = shape.businessObject.processRef;
|
||||
|
||||
// assign the participant processRef
|
||||
shape.businessObject.processRef = processRoot.businessObject;
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.revert('shape.create', function(context) {
|
||||
var processRoot = context.processRoot,
|
||||
shape = context.shape;
|
||||
|
||||
if (processRoot) {
|
||||
|
||||
// assign the participant processRef
|
||||
shape.businessObject.processRef = context.oldProcessRef;
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.postExecute('shape.create', function(context) {
|
||||
var processRoot = context.processRoot,
|
||||
shape = context.shape;
|
||||
|
||||
if (processRoot) {
|
||||
|
||||
// process root is already detached at this point
|
||||
var processChildren = processRoot.children.slice();
|
||||
|
||||
modeling.moveElements(processChildren, { x: 0, y: 0 }, shape);
|
||||
}
|
||||
|
||||
}, true);
|
||||
}
|
||||
|
||||
CreateParticipantBehavior.$inject = [
|
||||
'canvas',
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
inherits(CreateParticipantBehavior, CommandInterceptor);
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function getParticipantBounds(shape, childrenBBox) {
|
||||
childrenBBox = {
|
||||
width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
|
||||
height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
|
||||
};
|
||||
|
||||
return {
|
||||
width: Math.max(shape.width, childrenBBox.width),
|
||||
height: Math.max(shape.height, childrenBBox.height)
|
||||
};
|
||||
}
|
||||
|
||||
function getParticipantCreateConstraints(shape, childrenBBox) {
|
||||
childrenBBox = asTRBL(childrenBBox);
|
||||
|
||||
return {
|
||||
bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
|
||||
left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
|
||||
top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
|
||||
right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
|
||||
};
|
||||
}
|
||||
|
||||
function isConnection(element) {
|
||||
return !!element.waypoints;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
add as collectionAdd,
|
||||
remove as collectionRemove
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import {
|
||||
find
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';
|
||||
|
||||
|
||||
/**
|
||||
* This behavior makes sure we always set a fake
|
||||
* DataInputAssociation#targetRef as demanded by the BPMN 2.0
|
||||
* XSD schema.
|
||||
*
|
||||
* The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
|
||||
* which is created on the fly and cleaned up afterwards if not needed
|
||||
* anymore.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {BpmnFactory} bpmnFactory
|
||||
*/
|
||||
export default function DataInputAssociationBehavior(eventBus, bpmnFactory) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
|
||||
this.executed([
|
||||
'connection.create',
|
||||
'connection.delete',
|
||||
'connection.move',
|
||||
'connection.reconnectEnd'
|
||||
], ifDataInputAssociation(fixTargetRef));
|
||||
|
||||
this.reverted([
|
||||
'connection.create',
|
||||
'connection.delete',
|
||||
'connection.move',
|
||||
'connection.reconnectEnd'
|
||||
], ifDataInputAssociation(fixTargetRef));
|
||||
|
||||
|
||||
function usesTargetRef(element, targetRef, removedConnection) {
|
||||
|
||||
var inputAssociations = element.get('dataInputAssociations');
|
||||
|
||||
return find(inputAssociations, function(association) {
|
||||
return association !== removedConnection &&
|
||||
association.targetRef === targetRef;
|
||||
});
|
||||
}
|
||||
|
||||
function getTargetRef(element, create) {
|
||||
|
||||
var properties = element.get('properties');
|
||||
|
||||
var targetRefProp = find(properties, function(p) {
|
||||
return p.name === TARGET_REF_PLACEHOLDER_NAME;
|
||||
});
|
||||
|
||||
if (!targetRefProp && create) {
|
||||
targetRefProp = bpmnFactory.create('bpmn:Property', {
|
||||
name: TARGET_REF_PLACEHOLDER_NAME
|
||||
});
|
||||
|
||||
collectionAdd(properties, targetRefProp);
|
||||
}
|
||||
|
||||
return targetRefProp;
|
||||
}
|
||||
|
||||
function cleanupTargetRef(element, connection) {
|
||||
|
||||
var targetRefProp = getTargetRef(element);
|
||||
|
||||
if (!targetRefProp) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!usesTargetRef(element, targetRefProp, connection)) {
|
||||
collectionRemove(element.get('properties'), targetRefProp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure targetRef is set to a valid property or
|
||||
* `null` if the connection is detached.
|
||||
*
|
||||
* @param {Event} event
|
||||
*/
|
||||
function fixTargetRef(event) {
|
||||
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
connectionBo = connection.businessObject,
|
||||
target = connection.target,
|
||||
targetBo = target && target.businessObject,
|
||||
newTarget = context.newTarget,
|
||||
newTargetBo = newTarget && newTarget.businessObject,
|
||||
oldTarget = context.oldTarget || context.target,
|
||||
oldTargetBo = oldTarget && oldTarget.businessObject;
|
||||
|
||||
var dataAssociation = connection.businessObject,
|
||||
targetRefProp;
|
||||
|
||||
if (oldTargetBo && oldTargetBo !== targetBo) {
|
||||
cleanupTargetRef(oldTargetBo, connectionBo);
|
||||
}
|
||||
|
||||
if (newTargetBo && newTargetBo !== targetBo) {
|
||||
cleanupTargetRef(newTargetBo, connectionBo);
|
||||
}
|
||||
|
||||
if (targetBo) {
|
||||
targetRefProp = getTargetRef(targetBo, true);
|
||||
dataAssociation.targetRef = targetRefProp;
|
||||
} else {
|
||||
dataAssociation.targetRef = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DataInputAssociationBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnFactory'
|
||||
];
|
||||
|
||||
inherits(DataInputAssociationBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
/**
|
||||
* Only call the given function when the event
|
||||
* touches a bpmn:DataInputAssociation.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function}
|
||||
*/
|
||||
function ifDataInputAssociation(fn) {
|
||||
|
||||
return function(event) {
|
||||
var context = event.context,
|
||||
connection = context.connection;
|
||||
|
||||
if (is(connection, 'bpmn:DataInputAssociation')) {
|
||||
return fn(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import { isAny } from '../util/ModelingUtil';
|
||||
|
||||
import UpdateSemanticParentHandler from '../cmd/UpdateSemanticParentHandler';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific data store behavior
|
||||
*/
|
||||
export default function DataStoreBehavior(
|
||||
canvas, commandStack, elementRegistry,
|
||||
eventBus) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);
|
||||
|
||||
function getFirstParticipant() {
|
||||
return elementRegistry.filter(function(element) {
|
||||
return is(element, 'bpmn:Participant');
|
||||
})[0];
|
||||
}
|
||||
|
||||
function getDataStores(element) {
|
||||
return element.children.filter(function(child) {
|
||||
return is(child, 'bpmn:DataStoreReference') && !child.labelTarget;
|
||||
});
|
||||
}
|
||||
|
||||
function updateDataStoreParent(dataStore, newDataStoreParent) {
|
||||
var dataStoreBo = dataStore.businessObject || dataStore;
|
||||
|
||||
newDataStoreParent = newDataStoreParent || getFirstParticipant();
|
||||
|
||||
if (newDataStoreParent) {
|
||||
var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent;
|
||||
|
||||
commandStack.execute('dataStore.updateContainment', {
|
||||
dataStoreBo: dataStoreBo,
|
||||
newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo,
|
||||
newDiParent: newDataStoreParentBo.di
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// disable auto-resize for data stores
|
||||
this.preExecute('shape.create', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:DataStoreReference') &&
|
||||
shape.type !== 'label') {
|
||||
|
||||
if (!context.hints) {
|
||||
context.hints = {};
|
||||
}
|
||||
|
||||
// prevent auto resizing
|
||||
context.hints.autoResize = false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// disable auto-resize for data stores
|
||||
this.preExecute('elements.move', function(event) {
|
||||
var context = event.context,
|
||||
shapes = context.shapes;
|
||||
|
||||
var dataStoreReferences = shapes.filter(function(shape) {
|
||||
return is(shape, 'bpmn:DataStoreReference');
|
||||
});
|
||||
|
||||
if (dataStoreReferences.length) {
|
||||
if (!context.hints) {
|
||||
context.hints = {};
|
||||
}
|
||||
|
||||
// prevent auto resizing for data store references
|
||||
context.hints.autoResize = shapes.filter(function(shape) {
|
||||
return !is(shape, 'bpmn:DataStoreReference');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// update parent on data store created
|
||||
this.postExecute('shape.create', function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
parent = shape.parent;
|
||||
|
||||
|
||||
if (is(shape, 'bpmn:DataStoreReference') &&
|
||||
shape.type !== 'label' &&
|
||||
is(parent, 'bpmn:Collaboration')) {
|
||||
|
||||
updateDataStoreParent(shape);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// update parent on data store moved
|
||||
this.postExecute('shape.move', function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
oldParent = context.oldParent,
|
||||
parent = shape.parent;
|
||||
|
||||
if (is(oldParent, 'bpmn:Collaboration')) {
|
||||
|
||||
// do nothing if not necessary
|
||||
return;
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:DataStoreReference') &&
|
||||
shape.type !== 'label' &&
|
||||
is(parent, 'bpmn:Collaboration')) {
|
||||
|
||||
var participant = is(oldParent, 'bpmn:Participant') ?
|
||||
oldParent :
|
||||
getAncestor(oldParent, 'bpmn:Participant');
|
||||
|
||||
updateDataStoreParent(shape, participant);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// update data store parents on participant or subprocess deleted
|
||||
this.postExecute('shape.delete', function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
rootElement = canvas.getRootElement();
|
||||
|
||||
if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ])
|
||||
&& is(rootElement, 'bpmn:Collaboration')) {
|
||||
getDataStores(rootElement)
|
||||
.filter(function(dataStore) {
|
||||
return isDescendant(dataStore, shape);
|
||||
})
|
||||
.forEach(function(dataStore) {
|
||||
updateDataStoreParent(dataStore);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// update data store parents on collaboration -> process
|
||||
this.postExecute('canvas.updateRoot', function(event) {
|
||||
var context = event.context,
|
||||
oldRoot = context.oldRoot,
|
||||
newRoot = context.newRoot;
|
||||
|
||||
var dataStores = getDataStores(oldRoot);
|
||||
|
||||
dataStores.forEach(function(dataStore) {
|
||||
|
||||
if (is(newRoot, 'bpmn:Process')) {
|
||||
updateDataStoreParent(dataStore, newRoot);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
DataStoreBehavior.$inject = [
|
||||
'canvas',
|
||||
'commandStack',
|
||||
'elementRegistry',
|
||||
'eventBus',
|
||||
];
|
||||
|
||||
inherits(DataStoreBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function isDescendant(descendant, ancestor) {
|
||||
var descendantBo = descendant.businessObject || descendant,
|
||||
ancestorBo = ancestor.businessObject || ancestor;
|
||||
|
||||
while (descendantBo.$parent) {
|
||||
if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) {
|
||||
return true;
|
||||
}
|
||||
|
||||
descendantBo = descendantBo.$parent;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAncestor(element, type) {
|
||||
|
||||
while (element.parent) {
|
||||
if (is(element.parent, type)) {
|
||||
return element.parent;
|
||||
}
|
||||
|
||||
element = element.parent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
getChildLanes
|
||||
} from '../util/LaneUtil';
|
||||
|
||||
import {
|
||||
eachElement
|
||||
} from 'diagram-js/lib/util/Elements';
|
||||
|
||||
|
||||
var LOW_PRIORITY = 500;
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific delete lane behavior
|
||||
*/
|
||||
export default function DeleteLaneBehavior(eventBus, modeling, spaceTool) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
|
||||
function compensateLaneDelete(shape, oldParent) {
|
||||
|
||||
var siblings = getChildLanes(oldParent);
|
||||
|
||||
var topAffected = [];
|
||||
var bottomAffected = [];
|
||||
|
||||
eachElement(siblings, function(element) {
|
||||
|
||||
if (element.y > shape.y) {
|
||||
bottomAffected.push(element);
|
||||
} else {
|
||||
topAffected.push(element);
|
||||
}
|
||||
|
||||
return element.children;
|
||||
});
|
||||
|
||||
if (!siblings.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var offset;
|
||||
|
||||
if (bottomAffected.length && topAffected.length) {
|
||||
offset = shape.height / 2;
|
||||
} else {
|
||||
offset = shape.height;
|
||||
}
|
||||
|
||||
var topAdjustments,
|
||||
bottomAdjustments;
|
||||
|
||||
if (topAffected.length) {
|
||||
topAdjustments = spaceTool.calculateAdjustments(
|
||||
topAffected, 'y', offset, shape.y - 10);
|
||||
|
||||
spaceTool.makeSpace(
|
||||
topAdjustments.movingShapes,
|
||||
topAdjustments.resizingShapes,
|
||||
{ x: 0, y: offset }, 's');
|
||||
}
|
||||
|
||||
if (bottomAffected.length) {
|
||||
bottomAdjustments = spaceTool.calculateAdjustments(
|
||||
bottomAffected, 'y', -offset, shape.y + shape.height + 10);
|
||||
|
||||
spaceTool.makeSpace(
|
||||
bottomAdjustments.movingShapes,
|
||||
bottomAdjustments.resizingShapes,
|
||||
{ x: 0, y: -offset }, 'n');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjust sizes of other lanes after lane deletion
|
||||
*/
|
||||
this.postExecuted('shape.delete', LOW_PRIORITY, function(event) {
|
||||
|
||||
var context = event.context,
|
||||
hints = context.hints,
|
||||
shape = context.shape,
|
||||
oldParent = context.oldParent;
|
||||
|
||||
// only compensate lane deletes
|
||||
if (!is(shape, 'bpmn:Lane')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// compensate root deletes only
|
||||
if (hints && hints.nested) {
|
||||
return;
|
||||
}
|
||||
|
||||
compensateLaneDelete(shape, oldParent);
|
||||
});
|
||||
}
|
||||
|
||||
DeleteLaneBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'spaceTool'
|
||||
];
|
||||
|
||||
inherits(DeleteLaneBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,75 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import { isLabel } from '../../../util/LabelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific detach event behavior
|
||||
*/
|
||||
export default function DetachEventBehavior(eventBus, bpmnReplace) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* replace boundary event with intermediate event when
|
||||
* detaching from a shape
|
||||
*/
|
||||
|
||||
this.preExecute('elements.move', function(context) {
|
||||
var shapes = context.shapes,
|
||||
host = context.newHost,
|
||||
shape,
|
||||
eventDefinition,
|
||||
intermediateEvent,
|
||||
newShape;
|
||||
|
||||
if (shapes.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
shape = shapes[0];
|
||||
|
||||
if (!isLabel(shape) && !host && is(shape, 'bpmn:BoundaryEvent')) {
|
||||
|
||||
eventDefinition = getEventDefinition(shape);
|
||||
|
||||
if (eventDefinition) {
|
||||
intermediateEvent = {
|
||||
type: 'bpmn:IntermediateCatchEvent',
|
||||
eventDefinitionType: eventDefinition.$type
|
||||
};
|
||||
} else {
|
||||
intermediateEvent = {
|
||||
type: 'bpmn:IntermediateThrowEvent'
|
||||
};
|
||||
}
|
||||
|
||||
newShape = bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false });
|
||||
|
||||
context.shapes = [ newShape ];
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
DetachEventBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnReplace'
|
||||
];
|
||||
|
||||
inherits(DetachEventBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
|
||||
// helper /////
|
||||
function getEventDefinition(element) {
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return bo && bo.eventDefinitions && bo.eventDefinitions[0];
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
assign,
|
||||
find,
|
||||
filter
|
||||
} from 'min-dash';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
getApproxIntersection
|
||||
} from 'diagram-js/lib/util/LineIntersection';
|
||||
|
||||
|
||||
export default function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Reconnect start / end of a connection after
|
||||
* dropping an element on a flow.
|
||||
*/
|
||||
|
||||
function insertShape(shape, targetFlow, position) {
|
||||
var waypoints = targetFlow.waypoints,
|
||||
waypointsBefore,
|
||||
waypointsAfter,
|
||||
dockingPoint,
|
||||
source,
|
||||
target,
|
||||
incomingConnection,
|
||||
outgoingConnection,
|
||||
oldOutgoing = shape.outgoing.slice(),
|
||||
oldIncoming = shape.incoming.slice();
|
||||
|
||||
var intersection = getApproxIntersection(waypoints, position);
|
||||
|
||||
if (intersection) {
|
||||
waypointsBefore = waypoints.slice(0, intersection.index);
|
||||
waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));
|
||||
|
||||
// due to inaccuracy intersection might have been found
|
||||
if (!waypointsBefore.length || !waypointsAfter.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : position;
|
||||
|
||||
// if last waypointBefore is inside shape's bounds, ignore docking point
|
||||
if (!isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length-1])) {
|
||||
waypointsBefore.push(copy(dockingPoint));
|
||||
}
|
||||
|
||||
// if first waypointAfter is inside shape's bounds, ignore docking point
|
||||
if (!isPointInsideBBox(shape, waypointsAfter[0])) {
|
||||
waypointsAfter.unshift(copy(dockingPoint));
|
||||
}
|
||||
}
|
||||
|
||||
source = targetFlow.source;
|
||||
target = targetFlow.target;
|
||||
|
||||
if (bpmnRules.canConnect(source, shape, targetFlow)) {
|
||||
// reconnect source -> inserted shape
|
||||
modeling.reconnectEnd(targetFlow, shape, waypointsBefore || position);
|
||||
|
||||
incomingConnection = targetFlow;
|
||||
}
|
||||
|
||||
if (bpmnRules.canConnect(shape, target, targetFlow)) {
|
||||
|
||||
if (!incomingConnection) {
|
||||
// reconnect inserted shape -> end
|
||||
modeling.reconnectStart(targetFlow, shape, waypointsAfter || position);
|
||||
|
||||
outgoingConnection = targetFlow;
|
||||
} else {
|
||||
outgoingConnection = modeling.connect(
|
||||
shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var duplicateConnections = [].concat(
|
||||
|
||||
incomingConnection && filter(oldIncoming, function(connection) {
|
||||
return connection.source === incomingConnection.source;
|
||||
}) || [],
|
||||
|
||||
outgoingConnection && filter(oldOutgoing, function(connection) {
|
||||
return connection.source === outgoingConnection.source;
|
||||
}) || []
|
||||
);
|
||||
|
||||
if (duplicateConnections.length) {
|
||||
modeling.removeElements(duplicateConnections);
|
||||
}
|
||||
}
|
||||
|
||||
this.preExecute('elements.move', function(context) {
|
||||
|
||||
var newParent = context.newParent,
|
||||
shapes = context.shapes,
|
||||
delta = context.delta,
|
||||
shape = shapes[0];
|
||||
|
||||
if (!shape || !newParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if the new parent is a connection,
|
||||
// change it to the new parent's parent
|
||||
if (newParent && newParent.waypoints) {
|
||||
context.newParent = newParent = newParent.parent;
|
||||
}
|
||||
|
||||
var shapeMid = getMid(shape);
|
||||
var newShapeMid = {
|
||||
x: shapeMid.x + delta.x,
|
||||
y: shapeMid.y + delta.y
|
||||
};
|
||||
|
||||
// find a connection which intersects with the
|
||||
// element's mid point
|
||||
var connection = find(newParent.children, function(element) {
|
||||
var canInsert = bpmnRules.canInsert(shapes, element);
|
||||
|
||||
return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
|
||||
});
|
||||
|
||||
if (connection) {
|
||||
context.targetFlow = connection;
|
||||
context.position = newShapeMid;
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
this.postExecuted('elements.move', function(context) {
|
||||
|
||||
var shapes = context.shapes,
|
||||
targetFlow = context.targetFlow,
|
||||
position = context.position;
|
||||
|
||||
if (targetFlow) {
|
||||
insertShape(shapes[0], targetFlow, position);
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
this.preExecute('shape.create', function(context) {
|
||||
|
||||
var parent = context.parent,
|
||||
shape = context.shape;
|
||||
|
||||
if (bpmnRules.canInsert(shape, parent)) {
|
||||
context.targetFlow = parent;
|
||||
context.parent = parent.parent;
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.postExecuted('shape.create', function(context) {
|
||||
|
||||
var shape = context.shape,
|
||||
targetFlow = context.targetFlow,
|
||||
position = context.position;
|
||||
|
||||
if (targetFlow) {
|
||||
insertShape(shape, targetFlow, position);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
inherits(DropOnFlowBehavior, CommandInterceptor);
|
||||
|
||||
DropOnFlowBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnRules',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
// helpers /////////////////////
|
||||
|
||||
function isPointInsideBBox(bbox, point) {
|
||||
var x = point.x,
|
||||
y = point.y;
|
||||
|
||||
return x >= bbox.x &&
|
||||
x <= bbox.x + bbox.width &&
|
||||
y >= bbox.y &&
|
||||
y <= bbox.y + bbox.height;
|
||||
}
|
||||
|
||||
function copy(obj) {
|
||||
return assign({}, obj);
|
||||
}
|
||||
|
||||
function getMid(bounds) {
|
||||
|
||||
return {
|
||||
x: Math.round(bounds.x + bounds.width / 2),
|
||||
y: Math.round(bounds.y + bounds.height / 2)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
export default function EventBasedGatewayBehavior(eventBus, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Remove existing sequence flows of event-based target before connecting
|
||||
* from event-based gateway.
|
||||
*/
|
||||
this.preExecuted('connection.create', function(event) {
|
||||
|
||||
var source = event.context.source,
|
||||
target = event.context.target,
|
||||
existingIncomingConnections = target.incoming.slice();
|
||||
|
||||
if (
|
||||
is(source, 'bpmn:EventBasedGateway') &&
|
||||
target.incoming.length
|
||||
) {
|
||||
|
||||
existingIncomingConnections.filter(isSequenceFlow)
|
||||
.forEach(function(sequenceFlow) {
|
||||
modeling.removeConnection(sequenceFlow);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* After replacing shape with event-based gateway, remove incoming sequence
|
||||
* flows of event-based targets which do not belong to event-based gateway
|
||||
* source.
|
||||
*/
|
||||
this.preExecuted('shape.replace', function(event) {
|
||||
|
||||
var newShape = event.context.newShape,
|
||||
newShapeTargets,
|
||||
newShapeTargetsIncomingSequenceFlows;
|
||||
|
||||
if (!is(newShape, 'bpmn:EventBasedGateway')) {
|
||||
return;
|
||||
}
|
||||
|
||||
newShapeTargets = newShape.outgoing.filter(isSequenceFlow)
|
||||
.map(function(sequenceFlow) {
|
||||
return sequenceFlow.target;
|
||||
});
|
||||
|
||||
newShapeTargetsIncomingSequenceFlows = newShapeTargets.reduce(function(sequenceFlows, target) {
|
||||
var incomingSequenceFlows = target.incoming.filter(isSequenceFlow);
|
||||
|
||||
return sequenceFlows.concat(incomingSequenceFlows);
|
||||
}, []);
|
||||
|
||||
newShapeTargetsIncomingSequenceFlows.forEach(function(sequenceFlow) {
|
||||
if (sequenceFlow.source !== newShape) {
|
||||
modeling.removeConnection(sequenceFlow);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
EventBasedGatewayBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
inherits(EventBasedGatewayBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function isSequenceFlow(connection) {
|
||||
return is(connection, 'bpmn:SequenceFlow');
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import { getLanesRoot } from '../util/LaneUtil';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import { isAny } from '../util/ModelingUtil';
|
||||
|
||||
var HIGH_PRIORITY = 1500;
|
||||
var HIGHEST_PRIORITY = 2000;
|
||||
|
||||
|
||||
/**
|
||||
* Correct hover targets in certain situations to improve diagram interaction.
|
||||
*
|
||||
* @param {ElementRegistry} elementRegistry
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Canvas} canvas
|
||||
*/
|
||||
export default function FixHoverBehavior(elementRegistry, eventBus, canvas) {
|
||||
|
||||
eventBus.on([
|
||||
'create.hover',
|
||||
'create.move',
|
||||
'create.end',
|
||||
'shape.move.hover',
|
||||
'shape.move.move',
|
||||
'shape.move.end'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape || event.shape,
|
||||
hover = event.hover;
|
||||
|
||||
// ensure elements are not dropped onto a bpmn:Lane but onto
|
||||
// the underlying bpmn:Participant
|
||||
if (is(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
|
||||
event.hover = getLanesRoot(hover);
|
||||
event.hoverGfx = elementRegistry.getGraphics(event.hover);
|
||||
}
|
||||
|
||||
var rootElement = canvas.getRootElement();
|
||||
|
||||
// ensure bpmn:Group and label elements are dropped
|
||||
// always onto the root
|
||||
if (hover !== rootElement && (shape.labelTarget || is(shape, 'bpmn:Group'))) {
|
||||
event.hover = rootElement;
|
||||
event.hoverGfx = elementRegistry.getGraphics(event.hover);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
eventBus.on([
|
||||
'connect.hover',
|
||||
'global-connect.hover'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var hover = event.hover;
|
||||
|
||||
// ensure connections start/end on bpmn:Participant,
|
||||
// not the underlying bpmn:Lane
|
||||
if (is(hover, 'bpmn:Lane')) {
|
||||
event.hover = getLanesRoot(hover) || hover;
|
||||
event.hoverGfx = elementRegistry.getGraphics(event.hover);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
eventBus.on([
|
||||
'bendpoint.move.hover'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
type = context.type,
|
||||
hover = event.hover;
|
||||
|
||||
// ensure reconnect start/end on bpmn:Participant,
|
||||
// not the underlying bpmn:Lane
|
||||
if (is(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
|
||||
event.hover = getLanesRoot(hover) || hover;
|
||||
event.hoverGfx = elementRegistry.getGraphics(event.hover);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
eventBus.on([
|
||||
'connect.start'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
|
||||
var context = event.context,
|
||||
source = context.source;
|
||||
|
||||
// ensure connect start on bpmn:Participant,
|
||||
// not the underlying bpmn:Lane
|
||||
if (is(source, 'bpmn:Lane')) {
|
||||
context.source = getLanesRoot(source) || source;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// allow movement of participants from lanes
|
||||
eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) {
|
||||
var shape = event.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Lane')) {
|
||||
event.shape = getLanesRoot(shape) || shape;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
FixHoverBehavior.$inject = [
|
||||
'elementRegistry',
|
||||
'eventBus',
|
||||
'canvas'
|
||||
];
|
||||
@@ -0,0 +1,192 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
add as collectionAdd,
|
||||
remove as collectionRemove
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
createCategoryValue
|
||||
} from './util/CategoryUtil';
|
||||
|
||||
/**
|
||||
* BPMN specific Group behavior
|
||||
*/
|
||||
export default function GroupBehavior(eventBus, bpmnFactory, canvas, elementRegistry) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Gets process definitions
|
||||
*
|
||||
* @return {ModdleElement} definitions
|
||||
*/
|
||||
function getDefinitions() {
|
||||
var rootElement = canvas.getRootElement(),
|
||||
businessObject = getBusinessObject(rootElement);
|
||||
|
||||
return businessObject.$parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a referenced category value for a given group shape
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
*/
|
||||
function removeReferencedCategoryValue(shape) {
|
||||
|
||||
var businessObject = getBusinessObject(shape),
|
||||
categoryValue = businessObject.categoryValueRef,
|
||||
category = categoryValue.$parent;
|
||||
|
||||
if (!categoryValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
collectionRemove(category.categoryValue, categoryValue);
|
||||
|
||||
// cleanup category if it is empty
|
||||
if (category && !category.categoryValue.length) {
|
||||
removeCategory(category);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a given category from the definitions
|
||||
*
|
||||
* @param {ModdleElement} category
|
||||
*/
|
||||
function removeCategory(category) {
|
||||
|
||||
var definitions = getDefinitions();
|
||||
|
||||
collectionRemove(definitions.get('rootElements'), category);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all group element in the current registry
|
||||
*
|
||||
* @return {Array<djs.model.shape>} a list of group shapes
|
||||
*/
|
||||
function getGroupElements() {
|
||||
return elementRegistry.filter(function(e) {
|
||||
return is(e, 'bpmn:Group');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given categoryValue is referenced in one of the given elements
|
||||
*
|
||||
* @param {Array<djs.model.shape>} elements
|
||||
* @param {ModdleElement} categoryValue
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function isReferenced(elements, categoryValue) {
|
||||
return elements.some(function(e) {
|
||||
|
||||
var businessObject = getBusinessObject(e);
|
||||
|
||||
return businessObject.categoryValueRef
|
||||
&& businessObject.categoryValueRef === categoryValue;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* remove referenced category + value when group was deleted
|
||||
*/
|
||||
this.executed('shape.delete', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Group')) {
|
||||
|
||||
var businessObject = getBusinessObject(shape),
|
||||
categoryValueRef = businessObject.categoryValueRef,
|
||||
groupElements = getGroupElements();
|
||||
|
||||
if (!isReferenced(groupElements, categoryValueRef)) {
|
||||
removeReferencedCategoryValue(shape);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* re-attach removed category
|
||||
*/
|
||||
this.reverted('shape.delete', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Group')) {
|
||||
|
||||
var businessObject = getBusinessObject(shape),
|
||||
categoryValueRef = businessObject.categoryValueRef,
|
||||
definitions = getDefinitions(),
|
||||
category = categoryValueRef ? categoryValueRef.$parent : null;
|
||||
|
||||
collectionAdd(category.get('categoryValue'), categoryValueRef);
|
||||
collectionAdd(definitions.get('rootElements'), category);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* create new category + value when group was created
|
||||
*/
|
||||
this.execute('shape.create', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
businessObject = getBusinessObject(shape),
|
||||
oldBusinessObject = shape.oldBusinessObject;
|
||||
|
||||
if (is(businessObject, 'bpmn:Group') && !businessObject.categoryValueRef) {
|
||||
|
||||
var definitions = getDefinitions(),
|
||||
categoryValue = createCategoryValue(definitions, bpmnFactory);
|
||||
|
||||
// set name from copied group if existing
|
||||
if (oldBusinessObject && oldBusinessObject.categoryValueRef) {
|
||||
categoryValue.value = oldBusinessObject.categoryValueRef.value;
|
||||
}
|
||||
|
||||
// link the reference to the Group
|
||||
businessObject.categoryValueRef = categoryValue;
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
this.revert('shape.create', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Group')) {
|
||||
|
||||
removeReferencedCategoryValue(shape);
|
||||
|
||||
delete getBusinessObject(shape).categoryValueRef;
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
GroupBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnFactory',
|
||||
'canvas',
|
||||
'elementRegistry'
|
||||
];
|
||||
|
||||
inherits(GroupBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
getMid
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import lineIntersect from './util/LineIntersect';
|
||||
|
||||
|
||||
/**
|
||||
* Fix broken dockings after DI imports.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
*/
|
||||
export default function ImportDockingFix(eventBus) {
|
||||
|
||||
function adjustDocking(startPoint, nextPoint, elementMid) {
|
||||
|
||||
var elementTop = {
|
||||
x: elementMid.x,
|
||||
y: elementMid.y - 50
|
||||
};
|
||||
|
||||
var elementLeft = {
|
||||
x: elementMid.x - 50,
|
||||
y: elementMid.y
|
||||
};
|
||||
|
||||
var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
|
||||
horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);
|
||||
|
||||
// original is horizontal or vertical center cross intersection
|
||||
var centerIntersect;
|
||||
|
||||
if (verticalIntersect && horizontalIntersect) {
|
||||
if (getDistance(verticalIntersect, elementMid) > getDistance(horizontalIntersect, elementMid)) {
|
||||
centerIntersect = horizontalIntersect;
|
||||
} else {
|
||||
centerIntersect = verticalIntersect;
|
||||
}
|
||||
} else {
|
||||
centerIntersect = verticalIntersect || horizontalIntersect;
|
||||
}
|
||||
|
||||
startPoint.original = centerIntersect;
|
||||
}
|
||||
|
||||
function fixDockings(connection) {
|
||||
var waypoints = connection.waypoints;
|
||||
|
||||
adjustDocking(
|
||||
waypoints[0],
|
||||
waypoints[1],
|
||||
getMid(connection.source)
|
||||
);
|
||||
|
||||
adjustDocking(
|
||||
waypoints[waypoints.length - 1],
|
||||
waypoints[waypoints.length - 2],
|
||||
getMid(connection.target)
|
||||
);
|
||||
}
|
||||
|
||||
eventBus.on('bpmnElement.added', function(e) {
|
||||
|
||||
var element = e.element;
|
||||
|
||||
if (element.waypoints) {
|
||||
fixDockings(element);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ImportDockingFix.$inject = [
|
||||
'eventBus'
|
||||
];
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function getDistance(p1, p2) {
|
||||
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
getBusinessObject
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from '../util/ModelingUtil';
|
||||
|
||||
/**
|
||||
* A component that makes sure that each created or updated
|
||||
* Pool and Lane is assigned an isHorizontal property set to true.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
*/
|
||||
export default function IsHorizontalFix(eventBus) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
var elementTypesToUpdate = [
|
||||
'bpmn:Participant',
|
||||
'bpmn:Lane'
|
||||
];
|
||||
|
||||
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) {
|
||||
var bo = getBusinessObject(event.context.shape);
|
||||
|
||||
if (isAny(bo, elementTypesToUpdate) && !bo.di.get('isHorizontal')) {
|
||||
// set attribute directly to avoid modeling#updateProperty side effects
|
||||
bo.di.set('isHorizontal', true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
IsHorizontalFix.$inject = [ 'eventBus' ];
|
||||
|
||||
inherits(IsHorizontalFix, CommandInterceptor);
|
||||
@@ -0,0 +1,381 @@
|
||||
import {
|
||||
assign
|
||||
} from 'min-dash';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
is,
|
||||
getBusinessObject
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isLabelExternal,
|
||||
getExternalLabelMid,
|
||||
hasExternalLabel
|
||||
} from '../../../util/LabelUtil';
|
||||
|
||||
import {
|
||||
getLabel
|
||||
} from '../../label-editing/LabelUtil';
|
||||
|
||||
import {
|
||||
getLabelAdjustment
|
||||
} from './util/LabelLayoutUtil';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
getNewAttachPoint
|
||||
} from 'diagram-js/lib/util/AttachUtil';
|
||||
|
||||
import {
|
||||
getMid,
|
||||
roundPoint
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
delta
|
||||
} from 'diagram-js/lib/util/PositionUtil';
|
||||
|
||||
import {
|
||||
sortBy
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
getDistancePointLine,
|
||||
perpendicularFoot
|
||||
} from './util/GeometricUtil';
|
||||
|
||||
var DEFAULT_LABEL_DIMENSIONS = {
|
||||
width: 90,
|
||||
height: 20
|
||||
};
|
||||
|
||||
var NAME_PROPERTY = 'name';
|
||||
var TEXT_PROPERTY = 'text';
|
||||
|
||||
/**
|
||||
* A component that makes sure that external labels are added
|
||||
* together with respective elements and properly updated (DI wise)
|
||||
* during move.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
* @param {BpmnFactory} bpmnFactory
|
||||
* @param {TextRenderer} textRenderer
|
||||
*/
|
||||
export default function LabelBehavior(
|
||||
eventBus, modeling, bpmnFactory,
|
||||
textRenderer) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
// update label if name property was updated
|
||||
this.postExecute('element.updateProperties', function(e) {
|
||||
var context = e.context,
|
||||
element = context.element,
|
||||
properties = context.properties;
|
||||
|
||||
if (NAME_PROPERTY in properties) {
|
||||
modeling.updateLabel(element, properties[NAME_PROPERTY]);
|
||||
}
|
||||
|
||||
if (TEXT_PROPERTY in properties
|
||||
&& is(element, 'bpmn:TextAnnotation')) {
|
||||
|
||||
var newBounds = textRenderer.getTextAnnotationBounds(
|
||||
{
|
||||
x: element.x,
|
||||
y: element.y,
|
||||
width: element.width,
|
||||
height: element.height
|
||||
},
|
||||
properties[TEXT_PROPERTY] || ''
|
||||
);
|
||||
|
||||
modeling.updateLabel(element, properties.text, newBounds);
|
||||
}
|
||||
});
|
||||
|
||||
// create label shape after shape/connection was created
|
||||
this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
|
||||
var context = e.context;
|
||||
|
||||
var element = context.shape || context.connection,
|
||||
businessObject = element.businessObject;
|
||||
|
||||
if (!isLabelExternal(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only create label if attribute available
|
||||
if (!getLabel(element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var labelCenter = getExternalLabelMid(element);
|
||||
// we don't care about x and y
|
||||
var labelDimensions = textRenderer.getExternalLabelBounds(
|
||||
DEFAULT_LABEL_DIMENSIONS,
|
||||
getLabel(element)
|
||||
);
|
||||
|
||||
modeling.createLabel(element, labelCenter, {
|
||||
id: businessObject.id + '_label',
|
||||
businessObject: businessObject,
|
||||
width: labelDimensions.width,
|
||||
height: labelDimensions.height
|
||||
});
|
||||
});
|
||||
|
||||
// update label after label shape was deleted
|
||||
this.postExecute('shape.delete', function(event) {
|
||||
var context = event.context,
|
||||
labelTarget = context.labelTarget,
|
||||
hints = context.hints || {};
|
||||
|
||||
// check if label
|
||||
if (labelTarget && hints.unsetLabel !== false) {
|
||||
modeling.updateLabel(labelTarget, null, null, { removeShape: false });
|
||||
}
|
||||
});
|
||||
|
||||
// update di information on label creation
|
||||
this.postExecute([ 'label.create' ], function(event) {
|
||||
var context = event.context,
|
||||
element = context.shape,
|
||||
businessObject,
|
||||
di;
|
||||
|
||||
// we want to trigger on real labels only
|
||||
if (!element.labelTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we want to trigger on BPMN elements only
|
||||
if (!is(element.labelTarget || element, 'bpmn:BaseElement')) {
|
||||
return;
|
||||
}
|
||||
|
||||
businessObject = element.businessObject,
|
||||
di = businessObject.di;
|
||||
|
||||
if (!di.label) {
|
||||
|
||||
di.label = bpmnFactory.create('bpmndi:BPMNLabel', {
|
||||
bounds: bpmnFactory.create('dc:Bounds')
|
||||
});
|
||||
}
|
||||
|
||||
assign(di.label.bounds, {
|
||||
x: element.x,
|
||||
y: element.y,
|
||||
width: element.width,
|
||||
height: element.height
|
||||
});
|
||||
});
|
||||
|
||||
function getVisibleLabelAdjustment(event) {
|
||||
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
label = connection.label,
|
||||
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 getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
|
||||
}
|
||||
|
||||
this.postExecute([
|
||||
'connection.layout',
|
||||
'connection.updateWaypoints'
|
||||
], function(event) {
|
||||
|
||||
var label = event.context.connection.label,
|
||||
labelAdjustment;
|
||||
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
|
||||
labelAdjustment = getVisibleLabelAdjustment(event);
|
||||
|
||||
modeling.moveShape(label, labelAdjustment);
|
||||
});
|
||||
|
||||
|
||||
// keep label position on shape replace
|
||||
this.postExecute([ 'shape.replace' ], function(event) {
|
||||
var context = event.context,
|
||||
newShape = context.newShape,
|
||||
oldShape = context.oldShape;
|
||||
|
||||
var businessObject = getBusinessObject(newShape);
|
||||
|
||||
if (businessObject
|
||||
&& isLabelExternal(businessObject)
|
||||
&& oldShape.label
|
||||
&& newShape.label) {
|
||||
newShape.label.x = oldShape.label.x;
|
||||
newShape.label.y = oldShape.label.y;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// move external label after resizing
|
||||
this.postExecute('shape.resize', function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
newBounds = context.newBounds,
|
||||
oldBounds = context.oldBounds;
|
||||
|
||||
if (hasExternalLabel(shape)) {
|
||||
|
||||
var label = shape.label,
|
||||
labelMid = getMid(label),
|
||||
edges = asEdges(oldBounds);
|
||||
|
||||
// get nearest border point to label as reference point
|
||||
var referencePoint = getReferencePoint(labelMid, edges);
|
||||
|
||||
var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
|
||||
|
||||
modeling.moveShape(label, delta);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
inherits(LabelBehavior, CommandInterceptor);
|
||||
|
||||
LabelBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'bpmnFactory',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
/**
|
||||
* Calculates a reference point delta relative to a new position
|
||||
* of a certain element's bounds
|
||||
*
|
||||
* @param {Point} point
|
||||
* @param {Bounds} oldBounds
|
||||
* @param {Bounds} newBounds
|
||||
*
|
||||
* @return {Delta} delta
|
||||
*/
|
||||
export function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
|
||||
|
||||
var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
|
||||
|
||||
return roundPoint(delta(newReferencePoint, referencePoint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the nearest point (reference point) for a given point
|
||||
* onto given set of lines
|
||||
*
|
||||
* @param {Array<Point, Point>} lines
|
||||
* @param {Point} point
|
||||
*
|
||||
* @param {Point}
|
||||
*/
|
||||
export function getReferencePoint(point, lines) {
|
||||
|
||||
if (!lines.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var nearestLine = getNearestLine(point, lines);
|
||||
|
||||
return perpendicularFoot(point, nearestLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given bounds to a lines array containing all edges
|
||||
*
|
||||
* @param {Bounds|Point} bounds
|
||||
*
|
||||
* @return Array<Point>
|
||||
*/
|
||||
export function asEdges(bounds) {
|
||||
return [
|
||||
[ // top
|
||||
{
|
||||
x: bounds.x,
|
||||
y: bounds.y
|
||||
},
|
||||
{
|
||||
x: bounds.x + (bounds.width || 0),
|
||||
y: bounds.y
|
||||
}
|
||||
],
|
||||
[ // right
|
||||
{
|
||||
x: bounds.x + (bounds.width || 0),
|
||||
y: bounds.y
|
||||
},
|
||||
{
|
||||
x: bounds.x + (bounds.width || 0),
|
||||
y: bounds.y + (bounds.height || 0)
|
||||
}
|
||||
],
|
||||
[ // bottom
|
||||
{
|
||||
x: bounds.x,
|
||||
y: bounds.y + (bounds.height || 0)
|
||||
},
|
||||
{
|
||||
x: bounds.x + (bounds.width || 0),
|
||||
y: bounds.y + (bounds.height || 0)
|
||||
}
|
||||
],
|
||||
[ // left
|
||||
{
|
||||
x: bounds.x,
|
||||
y: bounds.y
|
||||
},
|
||||
{
|
||||
x: bounds.x,
|
||||
y: bounds.y + (bounds.height || 0)
|
||||
}
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nearest line for a given point by distance
|
||||
* @param {Point} point
|
||||
* @param Array<Point> lines
|
||||
*
|
||||
* @return Array<Point>
|
||||
*/
|
||||
function getNearestLine(point, lines) {
|
||||
|
||||
var distances = lines.map(function(l) {
|
||||
return {
|
||||
line: l,
|
||||
distance: getDistancePointLine(point, l)
|
||||
};
|
||||
});
|
||||
|
||||
var sorted = sortBy(distances, 'distance');
|
||||
|
||||
return sorted[0].line;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants',
|
||||
PROCESS_ERR_MSG = 'participants cannot be pasted onto a non-empty process diagram';
|
||||
|
||||
|
||||
export default function ModelingFeedback(eventBus, tooltips, translate) {
|
||||
|
||||
function showError(position, message, timeout) {
|
||||
tooltips.add({
|
||||
position: {
|
||||
x: position.x + 5,
|
||||
y: position.y + 5
|
||||
},
|
||||
type: 'error',
|
||||
timeout: timeout || 2000,
|
||||
html: '<div>' + message + '</div>'
|
||||
});
|
||||
}
|
||||
|
||||
eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
target = context.target;
|
||||
|
||||
if (is(target, 'bpmn:Collaboration') && is(shape, 'bpmn:FlowNode')) {
|
||||
showError(event, translate(COLLAB_ERR_MSG));
|
||||
}
|
||||
});
|
||||
|
||||
eventBus.on([ 'elements.paste.rejected' ], function(event) {
|
||||
var context = event.context,
|
||||
position = context.position,
|
||||
target = context.target;
|
||||
|
||||
if (is(target, 'bpmn:Collaboration')) {
|
||||
showError(position, translate(COLLAB_ERR_MSG));
|
||||
}
|
||||
|
||||
if (is(target, 'bpmn:Process')) {
|
||||
showError(position, translate(PROCESS_ERR_MSG), 3000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ModelingFeedback.$inject = [
|
||||
'eventBus',
|
||||
'tooltips',
|
||||
'translate'
|
||||
];
|
||||
@@ -0,0 +1,82 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import lineIntersect from './util/LineIntersect';
|
||||
|
||||
|
||||
export default function RemoveElementBehavior(eventBus, bpmnRules, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Combine sequence flows when deleting an element
|
||||
* if there is one incoming and one outgoing
|
||||
* sequence flow
|
||||
*/
|
||||
this.preExecute('shape.delete', function(e) {
|
||||
|
||||
var shape = e.context.shape;
|
||||
|
||||
// only handle [a] -> [shape] -> [b] patterns
|
||||
if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
var inConnection = shape.incoming[0],
|
||||
outConnection = shape.outgoing[0];
|
||||
|
||||
// only handle sequence flows
|
||||
if (!is(inConnection, 'bpmn:SequenceFlow') || !is(outConnection, 'bpmn:SequenceFlow')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) {
|
||||
|
||||
// compute new, combined waypoints
|
||||
var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints);
|
||||
|
||||
modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
inherits(RemoveElementBehavior, CommandInterceptor);
|
||||
|
||||
RemoveElementBehavior.$inject = [
|
||||
'eventBus',
|
||||
'bpmnRules',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function getDocking(point) {
|
||||
return point.original || point;
|
||||
}
|
||||
|
||||
|
||||
function getNewWaypoints(inWaypoints, outWaypoints) {
|
||||
|
||||
var intersection = lineIntersect(
|
||||
getDocking(inWaypoints[inWaypoints.length - 2]),
|
||||
getDocking(inWaypoints[inWaypoints.length - 1]),
|
||||
getDocking(outWaypoints[1]),
|
||||
getDocking(outWaypoints[0]));
|
||||
|
||||
if (intersection) {
|
||||
return [].concat(
|
||||
inWaypoints.slice(0, inWaypoints.length - 1),
|
||||
[ intersection ],
|
||||
outWaypoints.slice(1));
|
||||
} else {
|
||||
return [
|
||||
getDocking(inWaypoints[0]),
|
||||
getDocking(outWaypoints[outWaypoints.length - 1])
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific remove behavior
|
||||
*/
|
||||
export default function RemoveParticipantBehavior(eventBus, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
|
||||
/**
|
||||
* morph collaboration diagram into process diagram
|
||||
* after the last participant has been removed
|
||||
*/
|
||||
|
||||
this.preExecute('shape.delete', function(context) {
|
||||
|
||||
var shape = context.shape,
|
||||
parent = shape.parent;
|
||||
|
||||
// activate the behavior if the shape to be removed
|
||||
// is a participant
|
||||
if (is(shape, 'bpmn:Participant')) {
|
||||
context.collaborationRoot = parent;
|
||||
}
|
||||
}, true);
|
||||
|
||||
this.postExecute('shape.delete', function(context) {
|
||||
|
||||
var collaborationRoot = context.collaborationRoot;
|
||||
|
||||
if (collaborationRoot && !collaborationRoot.businessObject.participants.length) {
|
||||
// replace empty collaboration with process diagram
|
||||
modeling.makeProcess();
|
||||
}
|
||||
}, true);
|
||||
|
||||
}
|
||||
|
||||
RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];
|
||||
|
||||
inherits(RemoveParticipantBehavior, CommandInterceptor);
|
||||
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
forEach,
|
||||
find,
|
||||
matchPattern
|
||||
} from 'min-dash';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
export default function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
var dragging = injector.get('dragging', false);
|
||||
|
||||
function fixConnection(connection) {
|
||||
|
||||
var source = connection.source,
|
||||
target = connection.target,
|
||||
parent = connection.parent;
|
||||
|
||||
// do not do anything if connection
|
||||
// is already deleted (may happen due to other
|
||||
// behaviors plugged-in before)
|
||||
if (!parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
var replacementType,
|
||||
remove;
|
||||
|
||||
/**
|
||||
* Check if incoming or outgoing connections
|
||||
* can stay or could be substituted with an
|
||||
* appropriate replacement.
|
||||
*
|
||||
* This holds true for SequenceFlow <> MessageFlow.
|
||||
*/
|
||||
|
||||
if (is(connection, 'bpmn:SequenceFlow')) {
|
||||
if (!bpmnRules.canConnectSequenceFlow(source, target)) {
|
||||
remove = true;
|
||||
}
|
||||
|
||||
if (bpmnRules.canConnectMessageFlow(source, target)) {
|
||||
replacementType = 'bpmn:MessageFlow';
|
||||
}
|
||||
}
|
||||
|
||||
// transform message flows into sequence flows, if possible
|
||||
|
||||
if (is(connection, 'bpmn:MessageFlow')) {
|
||||
|
||||
if (!bpmnRules.canConnectMessageFlow(source, target)) {
|
||||
remove = true;
|
||||
}
|
||||
|
||||
if (bpmnRules.canConnectSequenceFlow(source, target)) {
|
||||
replacementType = 'bpmn:SequenceFlow';
|
||||
}
|
||||
}
|
||||
|
||||
if (is(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) {
|
||||
remove = true;
|
||||
}
|
||||
|
||||
|
||||
// remove invalid connection,
|
||||
// unless it has been removed already
|
||||
if (remove) {
|
||||
modeling.removeConnection(connection);
|
||||
}
|
||||
|
||||
// replace SequenceFlow <> MessageFlow
|
||||
|
||||
if (replacementType) {
|
||||
modeling.connect(source, target, {
|
||||
type: replacementType,
|
||||
waypoints: connection.waypoints.slice()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function replaceReconnectedConnection(event) {
|
||||
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
source = context.newSource || connection.source,
|
||||
target = context.newTarget || connection.target,
|
||||
allowed,
|
||||
replacement;
|
||||
|
||||
allowed = bpmnRules.canConnect(source, target);
|
||||
|
||||
if (!allowed || allowed.type === connection.type) {
|
||||
return;
|
||||
}
|
||||
|
||||
replacement = modeling.connect(source, target, {
|
||||
type: allowed.type,
|
||||
waypoints: connection.waypoints.slice()
|
||||
});
|
||||
|
||||
// remove old connection
|
||||
modeling.removeConnection(connection);
|
||||
|
||||
// replace connection in context to reconnect end/start
|
||||
context.connection = replacement;
|
||||
|
||||
if (dragging) {
|
||||
cleanDraggingSelection(connection, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
// monkey-patch selection saved in dragging in order to not re-select non-existing connection
|
||||
function cleanDraggingSelection(oldConnection, newConnection) {
|
||||
var context = dragging.context(),
|
||||
previousSelection = context && context.payload.previousSelection,
|
||||
index;
|
||||
|
||||
// do nothing if not dragging or no selection was present
|
||||
if (!previousSelection || !previousSelection.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
index = previousSelection.indexOf(oldConnection);
|
||||
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
previousSelection.splice(index, 1, newConnection);
|
||||
}
|
||||
|
||||
// lifecycle hooks
|
||||
|
||||
this.postExecuted('elements.move', function(context) {
|
||||
|
||||
var closure = context.closure,
|
||||
allConnections = closure.allConnections;
|
||||
|
||||
forEach(allConnections, fixConnection);
|
||||
}, true);
|
||||
|
||||
this.preExecute([
|
||||
'connection.reconnectStart',
|
||||
'connection.reconnectEnd'
|
||||
], replaceReconnectedConnection);
|
||||
|
||||
this.postExecuted('element.updateProperties', function(event) {
|
||||
var context = event.context,
|
||||
properties = context.properties,
|
||||
element = context.element,
|
||||
businessObject = element.businessObject,
|
||||
connection;
|
||||
|
||||
// remove condition expression when morphing to default flow
|
||||
if (properties.default) {
|
||||
connection = find(
|
||||
element.outgoing,
|
||||
matchPattern({ id: element.businessObject.default.id })
|
||||
);
|
||||
|
||||
if (connection) {
|
||||
modeling.updateProperties(connection, { conditionExpression: undefined });
|
||||
}
|
||||
}
|
||||
|
||||
// remove default property from source when morphing to conditional flow
|
||||
if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) {
|
||||
modeling.updateProperties(element.source, { default: undefined });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inherits(ReplaceConnectionBehavior, CommandInterceptor);
|
||||
|
||||
ReplaceConnectionBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling',
|
||||
'bpmnRules',
|
||||
'injector'
|
||||
];
|
||||
@@ -0,0 +1,127 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
isEventSubProcess
|
||||
} from '../../../util/DiUtil';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Defines the behaviour of what happens to the elements inside a container
|
||||
* that morphs into another BPMN element
|
||||
*/
|
||||
export default function ReplaceElementBehaviour(
|
||||
eventBus, bpmnReplace, bpmnRules,
|
||||
elementRegistry, selection, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this._bpmnReplace = bpmnReplace;
|
||||
this._elementRegistry = elementRegistry;
|
||||
this._selection = selection;
|
||||
this._modeling = modeling;
|
||||
|
||||
this.postExecuted([ 'elements.move' ], 500, function(event) {
|
||||
|
||||
var context = event.context,
|
||||
target = context.newParent,
|
||||
newHost = context.newHost,
|
||||
elements = [];
|
||||
|
||||
forEach(context.closure.topLevel, function(topLevelElements) {
|
||||
if (isEventSubProcess(topLevelElements)) {
|
||||
elements = elements.concat(topLevelElements.children);
|
||||
} else {
|
||||
elements = elements.concat(topLevelElements);
|
||||
}
|
||||
});
|
||||
|
||||
// Change target to host when the moving element is a `bpmn:BoundaryEvent`
|
||||
if (elements.length === 1 && newHost) {
|
||||
target = newHost;
|
||||
}
|
||||
|
||||
var canReplace = bpmnRules.canReplace(elements, target);
|
||||
|
||||
if (canReplace) {
|
||||
this.replaceElements(elements, canReplace.replacements, newHost);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// update attachments if the host is replaced
|
||||
this.postExecute([ 'shape.replace' ], 1500, function(e) {
|
||||
|
||||
var context = e.context,
|
||||
oldShape = context.oldShape,
|
||||
newShape = context.newShape,
|
||||
attachers = oldShape.attachers,
|
||||
canReplace;
|
||||
|
||||
if (attachers && attachers.length) {
|
||||
canReplace = bpmnRules.canReplace(attachers, newShape);
|
||||
|
||||
this.replaceElements(attachers, canReplace.replacements);
|
||||
}
|
||||
|
||||
}, this);
|
||||
|
||||
this.postExecuted([ 'shape.replace' ], 1500, function(e) {
|
||||
var context = e.context,
|
||||
oldShape = context.oldShape,
|
||||
newShape = context.newShape;
|
||||
|
||||
modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
|
||||
modeling.updateProperties(newShape, { id: oldShape.id });
|
||||
});
|
||||
}
|
||||
|
||||
inherits(ReplaceElementBehaviour, CommandInterceptor);
|
||||
|
||||
|
||||
ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements, newHost) {
|
||||
var elementRegistry = this._elementRegistry,
|
||||
bpmnReplace = this._bpmnReplace,
|
||||
selection = this._selection,
|
||||
modeling = this._modeling;
|
||||
|
||||
forEach(newElements, function(replacement) {
|
||||
|
||||
var newElement = {
|
||||
type: replacement.newElementType
|
||||
};
|
||||
|
||||
var oldElement = elementRegistry.get(replacement.oldElementId);
|
||||
|
||||
if (newHost && is(oldElement, 'bpmn:BoundaryEvent')) {
|
||||
modeling.updateAttachment(oldElement, null);
|
||||
}
|
||||
|
||||
var idx = elements.indexOf(oldElement);
|
||||
|
||||
elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false });
|
||||
|
||||
if (newHost && is(elements[idx], 'bpmn:BoundaryEvent')) {
|
||||
modeling.updateAttachment(elements[idx], newHost);
|
||||
}
|
||||
});
|
||||
|
||||
if (newElements) {
|
||||
selection.select(elements);
|
||||
}
|
||||
};
|
||||
|
||||
ReplaceElementBehaviour.$inject = [
|
||||
'eventBus',
|
||||
'bpmnReplace',
|
||||
'bpmnRules',
|
||||
'elementRegistry',
|
||||
'selection',
|
||||
'modeling'
|
||||
];
|
||||
@@ -0,0 +1,44 @@
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import { isExpanded } from '../../../util/DiUtil';
|
||||
|
||||
import { getParticipantResizeConstraints } from './util/ResizeUtil';
|
||||
|
||||
var HIGH_PRIORITY = 1500;
|
||||
|
||||
var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 },
|
||||
SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 },
|
||||
TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };
|
||||
|
||||
|
||||
/**
|
||||
* Set minimum bounds/resize constraints on resize.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
*/
|
||||
export default function ResizeBehavior(eventBus) {
|
||||
eventBus.on('resize.start', HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
direction = context.direction,
|
||||
balanced = context.balanced;
|
||||
|
||||
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
|
||||
context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:Participant')) {
|
||||
context.minDimensions = PARTICIPANT_MIN_DIMENSIONS;
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
|
||||
context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:TextAnnotation')) {
|
||||
context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ResizeBehavior.$inject = [ 'eventBus' ];
|
||||
@@ -0,0 +1,62 @@
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
roundBounds
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
hasPrimaryModifier
|
||||
} from 'diagram-js/lib/util/Mouse';
|
||||
|
||||
var SLIGHTLY_HIGHER_PRIORITY = 1001;
|
||||
|
||||
|
||||
/**
|
||||
* Invoke {@link Modeling#resizeLane} instead of
|
||||
* {@link Modeling#resizeShape} when resizing a Lane
|
||||
* or Participant shape.
|
||||
*/
|
||||
export default function ResizeLaneBehavior(eventBus, modeling) {
|
||||
|
||||
eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
|
||||
|
||||
// should we resize the opposite lane(s) in
|
||||
// order to compensate for the resize operation?
|
||||
context.balanced = !hasPrimaryModifier(event);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Intercept resize end and call resize lane function instead.
|
||||
*/
|
||||
eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
shape = context.shape,
|
||||
canExecute = context.canExecute,
|
||||
newBounds = context.newBounds;
|
||||
|
||||
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
|
||||
|
||||
if (canExecute) {
|
||||
// ensure we have actual pixel values for new bounds
|
||||
// (important when zoom level was > 1 during move)
|
||||
newBounds = roundBounds(newBounds);
|
||||
|
||||
// perform the actual resize
|
||||
modeling.resizeLane(shape, newBounds, context.balanced);
|
||||
}
|
||||
|
||||
// stop propagation
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ResizeLaneBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
@@ -0,0 +1,69 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
import { isExpanded } from '../../../util/DiUtil.js';
|
||||
|
||||
/**
|
||||
* Add start event child by default when creating an expanded subprocess
|
||||
* with create.start or replacing a task with an expanded subprocess.
|
||||
*/
|
||||
export default function SubProcessStartEventBehavior(eventBus, modeling) {
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
eventBus.on('create.start', function(event) {
|
||||
var shape = event.context.shape,
|
||||
hints = event.context.hints;
|
||||
|
||||
hints.shouldAddStartEvent = is(shape, 'bpmn:SubProcess') && isExpanded(shape);
|
||||
});
|
||||
|
||||
this.postExecuted('shape.create', function(event) {
|
||||
var shape = event.context.shape,
|
||||
hints = event.context.hints,
|
||||
position;
|
||||
|
||||
if (!hints.shouldAddStartEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
position = calculatePositionRelativeToShape(shape);
|
||||
|
||||
modeling.createShape({ type: 'bpmn:StartEvent' }, position, shape);
|
||||
});
|
||||
|
||||
this.postExecuted('shape.replace', function(event) {
|
||||
var oldShape = event.context.oldShape,
|
||||
newShape = event.context.newShape,
|
||||
position;
|
||||
|
||||
if (
|
||||
!is(newShape, 'bpmn:SubProcess') ||
|
||||
!is(oldShape, 'bpmn:Task') ||
|
||||
!isExpanded(newShape)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
position = calculatePositionRelativeToShape(newShape);
|
||||
|
||||
modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
|
||||
});
|
||||
}
|
||||
|
||||
SubProcessStartEventBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
inherits(SubProcessStartEventBehavior, CommandInterceptor);
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function calculatePositionRelativeToShape(shape) {
|
||||
return {
|
||||
x: shape.x + shape.width / 6,
|
||||
y: shape.y + shape.height / 2
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
computeChildrenBBox
|
||||
} from 'diagram-js/lib/features/resize/ResizeUtil';
|
||||
|
||||
|
||||
var LOW_PRIORITY = 500;
|
||||
|
||||
|
||||
export default function ToggleElementCollapseBehaviour(
|
||||
eventBus, elementFactory, modeling,
|
||||
resize) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
|
||||
function hideEmptyLables(children) {
|
||||
if (children.length) {
|
||||
children.forEach(function(child) {
|
||||
if (child.type === 'label' && !child.businessObject.name) {
|
||||
child.hidden = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function expandedBounds(shape, defaultSize) {
|
||||
var children = shape.children,
|
||||
newBounds = defaultSize,
|
||||
visibleElements,
|
||||
visibleBBox;
|
||||
|
||||
visibleElements = filterVisible(children).concat([ shape ]);
|
||||
|
||||
visibleBBox = computeChildrenBBox(visibleElements);
|
||||
|
||||
if (visibleBBox) {
|
||||
// center to visibleBBox with max(defaultSize, childrenBounds)
|
||||
newBounds.width = Math.max(visibleBBox.width, newBounds.width);
|
||||
newBounds.height = Math.max(visibleBBox.height, newBounds.height);
|
||||
|
||||
newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2;
|
||||
newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2;
|
||||
} else {
|
||||
// center to collapsed shape with defaultSize
|
||||
newBounds.x = shape.x + (shape.width - newBounds.width) / 2;
|
||||
newBounds.y = shape.y + (shape.height - newBounds.height) / 2;
|
||||
}
|
||||
|
||||
return newBounds;
|
||||
}
|
||||
|
||||
function collapsedBounds(shape, defaultSize) {
|
||||
|
||||
return {
|
||||
x: shape.x + (shape.width - defaultSize.width) / 2,
|
||||
y: shape.y + (shape.height - defaultSize.height) / 2,
|
||||
width: defaultSize.width,
|
||||
height: defaultSize.height
|
||||
};
|
||||
}
|
||||
|
||||
this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
|
||||
|
||||
var context = e.context,
|
||||
shape = context.shape;
|
||||
|
||||
if (!is(shape, 'bpmn:SubProcess')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shape.collapsed) {
|
||||
// all children got made visible through djs, hide empty labels
|
||||
hideEmptyLables(shape.children);
|
||||
|
||||
// remove collapsed marker
|
||||
getBusinessObject(shape).di.isExpanded = true;
|
||||
} else {
|
||||
// place collapsed marker
|
||||
getBusinessObject(shape).di.isExpanded = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
|
||||
|
||||
var context = e.context;
|
||||
var shape = context.shape;
|
||||
|
||||
|
||||
// revert removing/placing collapsed marker
|
||||
if (!shape.collapsed) {
|
||||
getBusinessObject(shape).di.isExpanded = true;
|
||||
|
||||
} else {
|
||||
getBusinessObject(shape).di.isExpanded = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
|
||||
var shape = e.context.shape,
|
||||
defaultSize = elementFactory._getDefaultSize(shape),
|
||||
newBounds;
|
||||
|
||||
if (shape.collapsed) {
|
||||
|
||||
// resize to default size of collapsed shapes
|
||||
newBounds = collapsedBounds(shape, defaultSize);
|
||||
} else {
|
||||
|
||||
// resize to bounds of max(visible children, defaultSize)
|
||||
newBounds = expandedBounds(shape, defaultSize);
|
||||
}
|
||||
|
||||
modeling.resizeShape(shape, newBounds, null, {
|
||||
autoResize: shape.collapsed ? false : 'nwse'
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
inherits(ToggleElementCollapseBehaviour, CommandInterceptor);
|
||||
|
||||
ToggleElementCollapseBehaviour.$inject = [
|
||||
'eventBus',
|
||||
'elementFactory',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function filterVisible(elements) {
|
||||
return elements.filter(function(e) {
|
||||
return !e.hidden;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
|
||||
/**
|
||||
* Unclaims model IDs on element deletion.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function UnclaimIdBehavior(eventBus, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this.preExecute('elements.delete', function(event) {
|
||||
var context = event.context,
|
||||
elements = context.elements;
|
||||
|
||||
forEach(elements, function(element) {
|
||||
modeling.unclaimId(element.businessObject.id, element.businessObject);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
inherits(UnclaimIdBehavior, CommandInterceptor);
|
||||
|
||||
UnclaimIdBehavior.$inject = [ 'eventBus', 'modeling' ];
|
||||
@@ -0,0 +1,57 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* A behavior that unsets the Default property of
|
||||
* sequence flow source on element delete, if the
|
||||
* removed element is the Gateway or Task's default flow.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function DeleteSequenceFlowBehavior(eventBus, modeling) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
|
||||
this.preExecute('connection.delete', function(event) {
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
source = connection.source;
|
||||
|
||||
if (isDefaultFlow(connection, source)) {
|
||||
modeling.updateProperties(source, {
|
||||
'default': null
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inherits(DeleteSequenceFlowBehavior, CommandInterceptor);
|
||||
|
||||
DeleteSequenceFlowBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function isDefaultFlow(connection, source) {
|
||||
|
||||
if (!is(connection, 'bpmn:SequenceFlow')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var sourceBo = getBusinessObject(source),
|
||||
sequenceFlow = getBusinessObject(connection);
|
||||
|
||||
return sourceBo.get('default') === sequenceFlow;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import {
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
var LOW_PRIORITY = 500,
|
||||
HIGH_PRIORITY = 5000;
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific delete lane behavior
|
||||
*/
|
||||
export default function UpdateFlowNodeRefsBehavior(eventBus, modeling, translate) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Ok, this is it:
|
||||
*
|
||||
* We have to update the Lane#flowNodeRefs _and_
|
||||
* FlowNode#lanes with every FlowNode move/resize and
|
||||
* Lane move/resize.
|
||||
*
|
||||
* We want to group that stuff to recompute containments
|
||||
* as efficient as possible.
|
||||
*
|
||||
* Yea!
|
||||
*/
|
||||
|
||||
// the update context
|
||||
var context;
|
||||
|
||||
|
||||
function initContext() {
|
||||
context = context || new UpdateContext();
|
||||
context.enter();
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function getContext() {
|
||||
if (!context) {
|
||||
throw new Error(translate('out of bounds release'));
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function releaseContext() {
|
||||
|
||||
if (!context) {
|
||||
throw new Error(translate('out of bounds release'));
|
||||
}
|
||||
|
||||
var triggerUpdate = context.leave();
|
||||
|
||||
if (triggerUpdate) {
|
||||
modeling.updateLaneRefs(context.flowNodes, context.lanes);
|
||||
|
||||
context = null;
|
||||
}
|
||||
|
||||
return triggerUpdate;
|
||||
}
|
||||
|
||||
|
||||
var laneRefUpdateEvents = [
|
||||
'spaceTool',
|
||||
'lane.add',
|
||||
'lane.resize',
|
||||
'lane.split',
|
||||
'elements.move',
|
||||
'elements.delete',
|
||||
'shape.create',
|
||||
'shape.delete',
|
||||
'shape.move',
|
||||
'shape.resize'
|
||||
];
|
||||
|
||||
|
||||
// listen to a lot of stuff to group lane updates
|
||||
|
||||
this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY, function(event) {
|
||||
initContext();
|
||||
});
|
||||
|
||||
this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY, function(event) {
|
||||
releaseContext();
|
||||
});
|
||||
|
||||
|
||||
// Mark flow nodes + lanes that need an update
|
||||
|
||||
this.preExecute([
|
||||
'shape.create',
|
||||
'shape.move',
|
||||
'shape.delete',
|
||||
'shape.resize'
|
||||
], function(event) {
|
||||
|
||||
var context = event.context,
|
||||
shape = context.shape;
|
||||
|
||||
var updateContext = getContext();
|
||||
|
||||
// no need to update labels
|
||||
if (shape.labelTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:Lane')) {
|
||||
updateContext.addLane(shape);
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:FlowNode')) {
|
||||
updateContext.addFlowNode(shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UpdateFlowNodeRefsBehavior.$inject = [
|
||||
'eventBus',
|
||||
'modeling' ,
|
||||
'translate'
|
||||
];
|
||||
|
||||
inherits(UpdateFlowNodeRefsBehavior, CommandInterceptor);
|
||||
|
||||
|
||||
function UpdateContext() {
|
||||
|
||||
this.flowNodes = [];
|
||||
this.lanes = [];
|
||||
|
||||
this.counter = 0;
|
||||
|
||||
this.addLane = function(lane) {
|
||||
this.lanes.push(lane);
|
||||
};
|
||||
|
||||
this.addFlowNode = function(flowNode) {
|
||||
this.flowNodes.push(flowNode);
|
||||
};
|
||||
|
||||
this.enter = function() {
|
||||
this.counter++;
|
||||
};
|
||||
|
||||
this.leave = function() {
|
||||
this.counter--;
|
||||
|
||||
return !this.counter;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import AdaptiveLabelPositioningBehavior from './AdaptiveLabelPositioningBehavior';
|
||||
import AppendBehavior from './AppendBehavior';
|
||||
import AttachEventBehavior from './AttachEventBehavior';
|
||||
import BoundaryEventBehavior from './BoundaryEventBehavior';
|
||||
import CopyPasteBehavior from './CopyPasteBehavior';
|
||||
import FixHoverBehavior from './FixHoverBehavior';
|
||||
import CreateBoundaryEventBehavior from './CreateBoundaryEventBehavior';
|
||||
import CreateDataObjectBehavior from './CreateDataObjectBehavior';
|
||||
import CreateParticipantBehavior from './CreateParticipantBehavior';
|
||||
import DataInputAssociationBehavior from './DataInputAssociationBehavior';
|
||||
import DataStoreBehavior from './DataStoreBehavior';
|
||||
import DeleteLaneBehavior from './DeleteLaneBehavior';
|
||||
import DetachEventBehavior from './DetachEventBehavior';
|
||||
import DropOnFlowBehavior from './DropOnFlowBehavior';
|
||||
import EventBasedGatewayBehavior from './EventBasedGatewayBehavior';
|
||||
import GroupBehavior from './GroupBehavior';
|
||||
import ImportDockingFix from './ImportDockingFix';
|
||||
import IsHorizontalFix from './IsHorizontalFix';
|
||||
import LabelBehavior from './LabelBehavior';
|
||||
import ModelingFeedback from './ModelingFeedback';
|
||||
import ReplaceConnectionBehavior from './ReplaceConnectionBehavior';
|
||||
import RemoveParticipantBehavior from './RemoveParticipantBehavior';
|
||||
import ReplaceElementBehaviour from './ReplaceElementBehaviour';
|
||||
import ResizeBehavior from './ResizeBehavior';
|
||||
import ResizeLaneBehavior from './ResizeLaneBehavior';
|
||||
import RemoveElementBehavior from './RemoveElementBehavior';
|
||||
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
|
||||
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
|
||||
import UnclaimIdBehavior from './UnclaimIdBehavior';
|
||||
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
|
||||
import UnsetDefaultFlowBehavior from './UnsetDefaultFlowBehavior';
|
||||
|
||||
export default {
|
||||
__init__: [
|
||||
'adaptiveLabelPositioningBehavior',
|
||||
'appendBehavior',
|
||||
'attachEventBehavior',
|
||||
'boundaryEventBehavior',
|
||||
'copyPasteBehavior',
|
||||
'fixHoverBehavior',
|
||||
'createBoundaryEventBehavior',
|
||||
'createDataObjectBehavior',
|
||||
'createParticipantBehavior',
|
||||
'dataStoreBehavior',
|
||||
'dataInputAssociationBehavior',
|
||||
'deleteLaneBehavior',
|
||||
'detachEventBehavior',
|
||||
'dropOnFlowBehavior',
|
||||
'eventBasedGatewayBehavior',
|
||||
'groupBehavior',
|
||||
'importDockingFix',
|
||||
'isHorizontalFix',
|
||||
'labelBehavior',
|
||||
'modelingFeedback',
|
||||
'removeElementBehavior',
|
||||
'removeParticipantBehavior',
|
||||
'replaceConnectionBehavior',
|
||||
'replaceElementBehaviour',
|
||||
'resizeBehavior',
|
||||
'resizeLaneBehavior',
|
||||
'toggleElementCollapseBehaviour',
|
||||
'subProcessStartEventBehavior',
|
||||
'unclaimIdBehavior',
|
||||
'unsetDefaultFlowBehavior',
|
||||
'updateFlowNodeRefsBehavior'
|
||||
],
|
||||
adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
|
||||
appendBehavior: [ 'type', AppendBehavior ],
|
||||
attachEventBehavior: [ 'type', AttachEventBehavior ],
|
||||
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
|
||||
copyPasteBehavior: [ 'type', CopyPasteBehavior ],
|
||||
fixHoverBehavior: [ 'type', FixHoverBehavior ],
|
||||
createBoundaryEventBehavior: [ 'type', CreateBoundaryEventBehavior ],
|
||||
createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
|
||||
createParticipantBehavior: [ 'type', CreateParticipantBehavior ],
|
||||
dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
|
||||
dataStoreBehavior: [ 'type', DataStoreBehavior ],
|
||||
deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
|
||||
detachEventBehavior: [ 'type', DetachEventBehavior ],
|
||||
dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
|
||||
eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
|
||||
groupBehavior: [ 'type', GroupBehavior ],
|
||||
importDockingFix: [ 'type', ImportDockingFix ],
|
||||
isHorizontalFix: [ 'type', IsHorizontalFix ],
|
||||
labelBehavior: [ 'type', LabelBehavior ],
|
||||
modelingFeedback: [ 'type', ModelingFeedback ],
|
||||
replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
|
||||
removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
|
||||
replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
|
||||
resizeBehavior: [ 'type', ResizeBehavior ],
|
||||
resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
|
||||
removeElementBehavior: [ 'type', RemoveElementBehavior ],
|
||||
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
|
||||
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
|
||||
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
|
||||
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
|
||||
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ]
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import {
|
||||
add as collectionAdd
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import {
|
||||
getBusinessObject
|
||||
} from '../../../../util/ModelUtil';
|
||||
|
||||
/**
|
||||
* Creates a new bpmn:CategoryValue inside a new bpmn:Category
|
||||
*
|
||||
* @param {ModdleElement} definitions
|
||||
* @param {BpmnFactory} bpmnFactory
|
||||
*
|
||||
* @return {ModdleElement} categoryValue.
|
||||
*/
|
||||
export function createCategoryValue(definitions, bpmnFactory) {
|
||||
var categoryValue = bpmnFactory.create('bpmn:CategoryValue'),
|
||||
category = bpmnFactory.create('bpmn:Category', {
|
||||
categoryValue: [ categoryValue ]
|
||||
});
|
||||
|
||||
// add to correct place
|
||||
collectionAdd(definitions.get('rootElements'), category);
|
||||
getBusinessObject(category).$parent = definitions;
|
||||
getBusinessObject(categoryValue).$parent = category;
|
||||
|
||||
return categoryValue;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Returns the length of a vector
|
||||
*
|
||||
* @param {Vector}
|
||||
* @return {Float}
|
||||
*/
|
||||
export function vectorLength(v) {
|
||||
return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the angle between a line a the yAxis
|
||||
*
|
||||
* @param {Array}
|
||||
* @return {Float}
|
||||
*/
|
||||
export function getAngle(line) {
|
||||
// return value is between 0, 180 and -180, -0
|
||||
// @janstuemmel: maybe replace return a/b with b/a
|
||||
return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Rotates a vector by a given angle
|
||||
*
|
||||
* @param {Vector}
|
||||
* @param {Float} Angle in radians
|
||||
* @return {Vector}
|
||||
*/
|
||||
export function rotateVector(vector, angle) {
|
||||
return (!angle) ? vector : {
|
||||
x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
|
||||
y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Solves a 2D equation system
|
||||
* a + r*b = c, where a,b,c are 2D vectors
|
||||
*
|
||||
* @param {Vector}
|
||||
* @param {Vector}
|
||||
* @param {Vector}
|
||||
* @return {Float}
|
||||
*/
|
||||
function solveLambaSystem(a, b, c) {
|
||||
|
||||
// the 2d system
|
||||
var system = [
|
||||
{ n: a[0] - c[0], lambda: b[0] },
|
||||
{ n: a[1] - c[1], lambda: b[1] }
|
||||
];
|
||||
|
||||
// solve
|
||||
var n = system[0].n * b[0] + system[1].n * b[1],
|
||||
l = system[0].lambda * b[0] + system[1].lambda * b[1];
|
||||
|
||||
return -n/l;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Position of perpendicular foot
|
||||
*
|
||||
* @param {Point}
|
||||
* @param [ {Point}, {Point} ] line defined throug two points
|
||||
* @return {Point} the perpendicular foot position
|
||||
*/
|
||||
export function perpendicularFoot(point, line) {
|
||||
|
||||
var a = line[0], b = line[1];
|
||||
|
||||
// relative position of b from a
|
||||
var bd = { x: b.x - a.x, y: b.y - a.y };
|
||||
|
||||
// solve equation system to the parametrized vectors param real value
|
||||
var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);
|
||||
|
||||
return { x: a.x + r*bd.x, y: a.y + r*bd.y };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the distance between a point and a line
|
||||
*
|
||||
* @param {Point}
|
||||
* @param [ {Point}, {Point} ] line defined throug two points
|
||||
* @return {Float} distance
|
||||
*/
|
||||
export function getDistancePointLine(point, line) {
|
||||
|
||||
var pfPoint = perpendicularFoot(point, line);
|
||||
|
||||
// distance vector
|
||||
var connectionVector = {
|
||||
x: pfPoint.x - point.x,
|
||||
y: pfPoint.y - point.y
|
||||
};
|
||||
|
||||
return vectorLength(connectionVector);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points
|
||||
*
|
||||
* @param {Point}
|
||||
* @param {Point}
|
||||
* @return {Float} distance
|
||||
*/
|
||||
export function getDistancePointPoint(point1, point2) {
|
||||
|
||||
return vectorLength({
|
||||
x: point1.x - point2.x,
|
||||
y: point1.y - point2.y
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
import {
|
||||
getDistancePointPoint,
|
||||
rotateVector,
|
||||
getAngle
|
||||
} from './GeometricUtil';
|
||||
|
||||
import {
|
||||
getAttachment
|
||||
} from './LineAttachmentUtil';
|
||||
|
||||
import {
|
||||
roundPoint
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
|
||||
export function findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
|
||||
|
||||
var index = attachment.segmentIndex;
|
||||
|
||||
var offset = newWaypoints.length - oldWaypoints.length;
|
||||
|
||||
// segmentMove happend
|
||||
if (hints.segmentMove) {
|
||||
|
||||
var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
|
||||
newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
|
||||
|
||||
// if label was on moved segment return new segment index
|
||||
if (index === oldSegmentStartIndex) {
|
||||
return newSegmentStartIndex;
|
||||
}
|
||||
|
||||
// label is after new segment index
|
||||
if (index >= newSegmentStartIndex) {
|
||||
return (index+offset < newSegmentStartIndex) ? newSegmentStartIndex : index+offset;
|
||||
}
|
||||
|
||||
// if label is before new segment index
|
||||
return index;
|
||||
}
|
||||
|
||||
// bendpointMove happend
|
||||
if (hints.bendpointMove) {
|
||||
|
||||
var insert = hints.bendpointMove.insert,
|
||||
bendpointIndex = hints.bendpointMove.bendpointIndex,
|
||||
newIndex;
|
||||
|
||||
// waypoints length didnt change
|
||||
if (offset === 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// label behind new/removed bendpoint
|
||||
if (index >= bendpointIndex) {
|
||||
newIndex = insert ? index + 1 : index - 1;
|
||||
}
|
||||
|
||||
// label before new/removed bendpoint
|
||||
if (index < bendpointIndex) {
|
||||
|
||||
newIndex = index;
|
||||
|
||||
// decide label should take right or left segment
|
||||
if (insert && attachment.type !== 'bendpoint' && bendpointIndex-1 === index) {
|
||||
|
||||
var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
|
||||
|
||||
if (rel < attachment.relativeLocation) {
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
// start/end changed
|
||||
if (offset === 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (hints.connectionStart) {
|
||||
return (index === 0) ? 0 : null;
|
||||
}
|
||||
|
||||
if (hints.connectionEnd) {
|
||||
return (index === oldWaypoints.length - 2) ? newWaypoints.length - 2 : null;
|
||||
}
|
||||
|
||||
// if nothing fits, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the required adjustment (move delta) for the given label
|
||||
* after the connection waypoints got updated.
|
||||
*
|
||||
* @param {djs.model.Label} label
|
||||
* @param {Array<Point>} newWaypoints
|
||||
* @param {Array<Point>} oldWaypoints
|
||||
* @param {Object} hints
|
||||
*
|
||||
* @return {Point} delta
|
||||
*/
|
||||
export function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
|
||||
|
||||
var x = 0,
|
||||
y = 0;
|
||||
|
||||
var labelPosition = getLabelMid(label);
|
||||
|
||||
// get closest attachment
|
||||
var attachment = getAttachment(labelPosition, oldWaypoints),
|
||||
oldLabelLineIndex = attachment.segmentIndex,
|
||||
newLabelLineIndex = findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
|
||||
|
||||
if (newLabelLineIndex === null) {
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
// should never happen
|
||||
// TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
|
||||
if (newLabelLineIndex < 0 ||
|
||||
newLabelLineIndex > newWaypoints.length - 2) {
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
|
||||
newLabelLine = getLine(newWaypoints, newLabelLineIndex),
|
||||
oldFoot = attachment.position;
|
||||
|
||||
var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
|
||||
angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
|
||||
|
||||
// special rule if label on bendpoint
|
||||
if (attachment.type === 'bendpoint') {
|
||||
|
||||
var offset = newWaypoints.length - oldWaypoints.length,
|
||||
oldBendpointIndex = attachment.bendpointIndex,
|
||||
oldBendpoint = oldWaypoints[oldBendpointIndex];
|
||||
|
||||
// bendpoint position hasnt changed, return same position
|
||||
if (newWaypoints.indexOf(oldBendpoint) !== -1) {
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
// new bendpoint and old bendpoint have same index, then just return the offset
|
||||
if (offset === 0) {
|
||||
var newBendpoint = newWaypoints[oldBendpointIndex];
|
||||
|
||||
return {
|
||||
x: newBendpoint.x - attachment.position.x,
|
||||
y: newBendpoint.y - attachment.position.y
|
||||
};
|
||||
}
|
||||
|
||||
// if bendpoints get removed
|
||||
if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
|
||||
relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
|
||||
}
|
||||
}
|
||||
|
||||
var newFoot = {
|
||||
x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
|
||||
y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
|
||||
};
|
||||
|
||||
// the rotated vector to label
|
||||
var newLabelVector = rotateVector({
|
||||
x: labelPosition.x - oldFoot.x,
|
||||
y: labelPosition.y - oldFoot.y
|
||||
}, angleDelta);
|
||||
|
||||
// the new relative position
|
||||
x = newFoot.x + newLabelVector.x - labelPosition.x;
|
||||
y = newFoot.y + newLabelVector.y - labelPosition.y;
|
||||
|
||||
return roundPoint({
|
||||
x: x,
|
||||
y: y
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// HELPERS //////////////////////
|
||||
|
||||
function relativePositionMidWaypoint(waypoints, idx) {
|
||||
|
||||
var distanceSegment1 = getDistancePointPoint(waypoints[idx-1], waypoints[idx]),
|
||||
distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx+1]);
|
||||
|
||||
var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);
|
||||
|
||||
return relativePosition;
|
||||
}
|
||||
|
||||
function getLabelMid(label) {
|
||||
return {
|
||||
x: label.x + label.width / 2,
|
||||
y: label.y + label.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
function getAngleDelta(l1, l2) {
|
||||
var a1 = getAngle(l1),
|
||||
a2 = getAngle(l2);
|
||||
return a2 - a1;
|
||||
}
|
||||
|
||||
function getLine(waypoints, idx) {
|
||||
return [ waypoints[idx], waypoints[idx+1] ];
|
||||
}
|
||||
|
||||
function getRelativeFootPosition(line, foot) {
|
||||
|
||||
var length = getDistancePointPoint(line[0], line[1]),
|
||||
lengthToFoot = getDistancePointPoint(line[0], foot);
|
||||
|
||||
return length === 0 ? 0 : lengthToFoot / length;
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
var sqrt = Math.sqrt,
|
||||
min = Math.min,
|
||||
max = Math.max,
|
||||
abs = Math.abs;
|
||||
|
||||
/**
|
||||
* Calculate the square (power to two) of a number.
|
||||
*
|
||||
* @param {Number} n
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
function sq(n) {
|
||||
return Math.pow(n, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distance between two points.
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
function getDistance(p1, p2) {
|
||||
return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attachment of the given point on the specified line.
|
||||
*
|
||||
* The attachment is either a bendpoint (attached to the given point)
|
||||
* or segment (attached to a location on a line segment) attachment:
|
||||
*
|
||||
* ```javascript
|
||||
* var pointAttachment = {
|
||||
* type: 'bendpoint',
|
||||
* bendpointIndex: 3,
|
||||
* position: { x: 10, y: 10 } // the attach point on the line
|
||||
* };
|
||||
*
|
||||
* var segmentAttachment = {
|
||||
* type: 'segment',
|
||||
* segmentIndex: 2,
|
||||
* relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
|
||||
* position: { x: 10, y: 10 } // the attach point on the line
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {Point} point
|
||||
* @param {Array<Point>} line
|
||||
*
|
||||
* @return {Object} attachment
|
||||
*/
|
||||
export function getAttachment(point, line) {
|
||||
|
||||
var idx = 0,
|
||||
segmentStart,
|
||||
segmentEnd,
|
||||
segmentStartDistance,
|
||||
segmentEndDistance,
|
||||
attachmentPosition,
|
||||
minDistance,
|
||||
intersections,
|
||||
attachment,
|
||||
attachmentDistance,
|
||||
closestAttachmentDistance,
|
||||
closestAttachment;
|
||||
|
||||
for (idx = 0; idx < line.length - 1; idx++) {
|
||||
|
||||
segmentStart = line[idx];
|
||||
segmentEnd = line[idx + 1];
|
||||
|
||||
if (pointsEqual(segmentStart, segmentEnd)) {
|
||||
intersections = [ segmentStart ];
|
||||
} else {
|
||||
segmentStartDistance = getDistance(point, segmentStart);
|
||||
segmentEndDistance = getDistance(point, segmentEnd);
|
||||
|
||||
minDistance = min(segmentStartDistance, segmentEndDistance);
|
||||
|
||||
intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
|
||||
}
|
||||
|
||||
if (intersections.length < 1) {
|
||||
throw new Error('expected between [1, 2] circle -> line intersections');
|
||||
}
|
||||
|
||||
// one intersection -> bendpoint attachment
|
||||
if (intersections.length === 1) {
|
||||
attachment = {
|
||||
type: 'bendpoint',
|
||||
position: intersections[0],
|
||||
segmentIndex: idx,
|
||||
bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
|
||||
};
|
||||
}
|
||||
|
||||
// two intersections -> segment attachment
|
||||
if (intersections.length === 2) {
|
||||
|
||||
attachmentPosition = mid(intersections[0], intersections[1]);
|
||||
|
||||
attachment = {
|
||||
type: 'segment',
|
||||
position: attachmentPosition,
|
||||
segmentIndex: idx,
|
||||
relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
|
||||
};
|
||||
}
|
||||
|
||||
attachmentDistance = getDistance(attachment.position, point);
|
||||
|
||||
if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
|
||||
closestAttachment = attachment;
|
||||
closestAttachmentDistance = attachmentDistance;
|
||||
}
|
||||
}
|
||||
|
||||
return closestAttachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the intersection between a circle and a line segment.
|
||||
*
|
||||
* @param {Point} s1 segment start
|
||||
* @param {Point} s2 segment end
|
||||
* @param {Point} cc circle center
|
||||
* @param {Number} cr circle radius
|
||||
*
|
||||
* @return {Array<Point>} intersections
|
||||
*/
|
||||
function getCircleSegmentIntersections(s1, s2, cc, cr) {
|
||||
|
||||
var baX = s2.x - s1.x;
|
||||
var baY = s2.y - s1.y;
|
||||
var caX = cc.x - s1.x;
|
||||
var caY = cc.y - s1.y;
|
||||
|
||||
var a = baX * baX + baY * baY;
|
||||
var bBy2 = baX * caX + baY * caY;
|
||||
var c = caX * caX + caY * caY - cr * cr;
|
||||
|
||||
var pBy2 = bBy2 / a;
|
||||
var q = c / a;
|
||||
|
||||
var disc = pBy2 * pBy2 - q;
|
||||
|
||||
// check against negative value to work around
|
||||
// negative, very close to zero results (-4e-15)
|
||||
// being produced in some environments
|
||||
if (disc < 0 && disc > -0.000001) {
|
||||
disc = 0;
|
||||
}
|
||||
|
||||
if (disc < 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// if disc == 0 ... dealt with later
|
||||
var tmpSqrt = sqrt(disc);
|
||||
var abScalingFactor1 = -pBy2 + tmpSqrt;
|
||||
var abScalingFactor2 = -pBy2 - tmpSqrt;
|
||||
|
||||
var i1 = {
|
||||
x: s1.x - baX * abScalingFactor1,
|
||||
y: s1.y - baY * abScalingFactor1
|
||||
};
|
||||
|
||||
if (disc === 0) { // abScalingFactor1 == abScalingFactor2
|
||||
return [ i1 ];
|
||||
}
|
||||
|
||||
var i2 = {
|
||||
x: s1.x - baX * abScalingFactor2,
|
||||
y: s1.y - baY * abScalingFactor2
|
||||
};
|
||||
|
||||
// return only points on line segment
|
||||
return [ i1, i2 ].filter(function(p) {
|
||||
return isPointInSegment(p, s1, s2);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function isPointInSegment(p, segmentStart, segmentEnd) {
|
||||
return (
|
||||
fenced(p.x, segmentStart.x, segmentEnd.x) &&
|
||||
fenced(p.y, segmentStart.y, segmentEnd.y)
|
||||
);
|
||||
}
|
||||
|
||||
function fenced(n, rangeStart, rangeEnd) {
|
||||
|
||||
// use matching threshold to work around
|
||||
// precisison errors in intersection computation
|
||||
|
||||
return (
|
||||
n >= min(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
|
||||
n <= max(rangeStart, rangeEnd) + EQUAL_THRESHOLD
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate mid of two points.
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
*
|
||||
* @return {Point}
|
||||
*/
|
||||
function mid(p1, p2) {
|
||||
|
||||
return {
|
||||
x: (p1.x + p2.x) / 2,
|
||||
y: (p1.y + p2.y) / 2
|
||||
};
|
||||
}
|
||||
|
||||
var EQUAL_THRESHOLD = 0.1;
|
||||
|
||||
function pointsEqual(p1, p2) {
|
||||
|
||||
return (
|
||||
abs(p1.x - p2.x) <= EQUAL_THRESHOLD &&
|
||||
abs(p1.y - p2.y) <= EQUAL_THRESHOLD
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Returns the intersection between two line segments a and b.
|
||||
*
|
||||
* @param {Point} l1s
|
||||
* @param {Point} l1e
|
||||
* @param {Point} l2s
|
||||
* @param {Point} l2e
|
||||
*
|
||||
* @return {Point}
|
||||
*/
|
||||
export default function lineIntersect(l1s, l1e, l2s, l2e) {
|
||||
// if the lines intersect, the result contains the x and y of the
|
||||
// intersection (treating the lines as infinite) and booleans for
|
||||
// whether line segment 1 or line segment 2 contain the point
|
||||
var denominator, a, b, c, numerator;
|
||||
|
||||
denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));
|
||||
|
||||
if (denominator == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
a = l1s.y - l2s.y;
|
||||
b = l1s.x - l2s.x;
|
||||
numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);
|
||||
|
||||
c = numerator / denominator;
|
||||
|
||||
// if we cast these lines infinitely in
|
||||
// both directions, they intersect here
|
||||
return {
|
||||
x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
|
||||
y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
import { is } from '../../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
asTRBL
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
collectLanes,
|
||||
getLanesRoot
|
||||
} from '../../../modeling/util/LaneUtil';
|
||||
|
||||
var abs = Math.abs,
|
||||
min = Math.min,
|
||||
max = Math.max;
|
||||
|
||||
|
||||
function addToTrbl(trbl, attr, value, choice) {
|
||||
var current = trbl[attr];
|
||||
|
||||
// make sure to set the value if it does not exist
|
||||
// or apply the correct value by comparing against
|
||||
// choice(value, currentValue)
|
||||
trbl[attr] = current === undefined ? value : choice(value, current);
|
||||
}
|
||||
|
||||
function addMin(trbl, attr, value) {
|
||||
return addToTrbl(trbl, attr, value, min);
|
||||
}
|
||||
|
||||
function addMax(trbl, attr, value) {
|
||||
return addToTrbl(trbl, attr, value, max);
|
||||
}
|
||||
|
||||
var LANE_MIN_HEIGHT = 60,
|
||||
LANE_MIN_WIDTH = 300,
|
||||
LANE_RIGHT_PADDING = 20,
|
||||
LANE_LEFT_PADDING = 50,
|
||||
LANE_TOP_PADDING = 20,
|
||||
LANE_BOTTOM_PADDING = 20;
|
||||
|
||||
|
||||
export function getParticipantResizeConstraints(laneShape, resizeDirection, balanced) {
|
||||
var lanesRoot = getLanesRoot(laneShape);
|
||||
|
||||
var isFirst = true,
|
||||
isLast = true;
|
||||
|
||||
// max top/bottom size for lanes
|
||||
var allLanes = collectLanes(lanesRoot, [ lanesRoot ]);
|
||||
|
||||
var laneTrbl = asTRBL(laneShape);
|
||||
|
||||
var maxTrbl = {},
|
||||
minTrbl = {};
|
||||
|
||||
if (/e/.test(resizeDirection)) {
|
||||
minTrbl.right = laneTrbl.left + LANE_MIN_WIDTH;
|
||||
} else
|
||||
if (/w/.test(resizeDirection)) {
|
||||
minTrbl.left = laneTrbl.right - LANE_MIN_WIDTH;
|
||||
}
|
||||
|
||||
allLanes.forEach(function(other) {
|
||||
|
||||
var otherTrbl = asTRBL(other);
|
||||
|
||||
if (/n/.test(resizeDirection)) {
|
||||
|
||||
if (otherTrbl.top < (laneTrbl.top - 10)) {
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
// max top size (based on next element)
|
||||
if (balanced && abs(laneTrbl.top - otherTrbl.bottom) < 10) {
|
||||
addMax(maxTrbl, 'top', otherTrbl.top + LANE_MIN_HEIGHT);
|
||||
}
|
||||
|
||||
// min top size (based on self or nested element)
|
||||
if (abs(laneTrbl.top - otherTrbl.top) < 5) {
|
||||
addMin(minTrbl, 'top', otherTrbl.bottom - LANE_MIN_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
if (/s/.test(resizeDirection)) {
|
||||
|
||||
if (otherTrbl.bottom > (laneTrbl.bottom + 10)) {
|
||||
isLast = false;
|
||||
}
|
||||
|
||||
// max bottom size (based on previous element)
|
||||
if (balanced && abs(laneTrbl.bottom - otherTrbl.top) < 10) {
|
||||
addMin(maxTrbl, 'bottom', otherTrbl.bottom - LANE_MIN_HEIGHT);
|
||||
}
|
||||
|
||||
// min bottom size (based on self or nested element)
|
||||
if (abs(laneTrbl.bottom - otherTrbl.bottom) < 5) {
|
||||
addMax(minTrbl, 'bottom', otherTrbl.top + LANE_MIN_HEIGHT);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// max top/bottom/left/right size based on flow nodes
|
||||
var flowElements = lanesRoot.children.filter(function(s) {
|
||||
return !s.hidden && !s.waypoints && (is(s, 'bpmn:FlowElement') || is(s, 'bpmn:Artifact'));
|
||||
});
|
||||
|
||||
flowElements.forEach(function(flowElement) {
|
||||
|
||||
var flowElementTrbl = asTRBL(flowElement);
|
||||
|
||||
if (isFirst && /n/.test(resizeDirection)) {
|
||||
addMin(minTrbl, 'top', flowElementTrbl.top - LANE_TOP_PADDING);
|
||||
}
|
||||
|
||||
if (/e/.test(resizeDirection)) {
|
||||
addMax(minTrbl, 'right', flowElementTrbl.right + LANE_RIGHT_PADDING);
|
||||
}
|
||||
|
||||
if (isLast && /s/.test(resizeDirection)) {
|
||||
addMax(minTrbl, 'bottom', flowElementTrbl.bottom + LANE_BOTTOM_PADDING);
|
||||
}
|
||||
|
||||
if (/w/.test(resizeDirection)) {
|
||||
addMin(minTrbl, 'left', flowElementTrbl.left - LANE_LEFT_PADDING);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
min: minTrbl,
|
||||
max: maxTrbl
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user