update
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { getNewShapePosition } from './AutoPlaceUtil';
|
||||
|
||||
|
||||
/**
|
||||
* A service that places elements connected to existing ones
|
||||
* to an appropriate position in an _automated_ fashion.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function AutoPlace(eventBus, modeling) {
|
||||
|
||||
function emit(event, payload) {
|
||||
return eventBus.fire(event, payload);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Append shape to source at appropriate position.
|
||||
*
|
||||
* @param {djs.model.Shape} source
|
||||
* @param {djs.model.Shape} shape
|
||||
*
|
||||
* @return {djs.model.Shape} appended shape
|
||||
*/
|
||||
this.append = function(source, shape) {
|
||||
|
||||
// allow others to provide the position
|
||||
var position = emit('autoPlace', {
|
||||
source: source,
|
||||
shape: shape
|
||||
});
|
||||
|
||||
if (!position) {
|
||||
position = getNewShapePosition(source, shape);
|
||||
}
|
||||
|
||||
var newShape = modeling.appendShape(source, shape, position, source.parent);
|
||||
|
||||
// notify interested parties on new shape placed
|
||||
emit('autoPlace.end', {
|
||||
shape: newShape
|
||||
});
|
||||
|
||||
return newShape;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
AutoPlace.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Select element after auto placement.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Selection} selection
|
||||
*/
|
||||
export default function AutoPlaceSelectionBehavior(eventBus, selection) {
|
||||
|
||||
eventBus.on('autoPlace.end', 500, function(e) {
|
||||
selection.select(e.shape);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
AutoPlaceSelectionBehavior.$inject = [
|
||||
'eventBus',
|
||||
'selection'
|
||||
];
|
||||
@@ -0,0 +1,429 @@
|
||||
import { is } from '../../util/ModelUtil';
|
||||
import { isAny } from '../modeling/util/ModelingUtil';
|
||||
|
||||
import {
|
||||
getMid,
|
||||
asTRBL,
|
||||
getOrientation
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
find,
|
||||
reduce
|
||||
} from 'min-dash';
|
||||
|
||||
var DEFAULT_HORIZONTAL_DISTANCE = 50;
|
||||
|
||||
var MAX_HORIZONTAL_DISTANCE = 250;
|
||||
|
||||
// padding to detect element placement
|
||||
var PLACEMENT_DETECTION_PAD = 10;
|
||||
|
||||
/**
|
||||
* Find the new position for the target element to
|
||||
* connect to source.
|
||||
*
|
||||
* @param {djs.model.Shape} source
|
||||
* @param {djs.model.Shape} element
|
||||
*
|
||||
* @return {Point}
|
||||
*/
|
||||
export function getNewShapePosition(source, element) {
|
||||
|
||||
if (is(element, 'bpmn:TextAnnotation')) {
|
||||
return getTextAnnotationPosition(source, element);
|
||||
}
|
||||
|
||||
if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
|
||||
return getDataElementPosition(source, element);
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:FlowNode')) {
|
||||
return getFlowNodePosition(source, element);
|
||||
}
|
||||
|
||||
return getDefaultPosition(source, element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Always try to place element right of source;
|
||||
* compute actual distance from previous nodes in flow.
|
||||
*/
|
||||
export function getFlowNodePosition(source, element) {
|
||||
|
||||
var sourceTrbl = asTRBL(source);
|
||||
var sourceMid = getMid(source);
|
||||
|
||||
var horizontalDistance = getFlowNodeDistance(source, element);
|
||||
|
||||
var orientation = 'left',
|
||||
rowSize = 80,
|
||||
margin = 30;
|
||||
|
||||
if (is(source, 'bpmn:BoundaryEvent')) {
|
||||
orientation = getOrientation(source, source.host, -25);
|
||||
|
||||
if (orientation.indexOf('top') !== -1) {
|
||||
margin *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
function getVerticalDistance(orient) {
|
||||
if (orient.indexOf('top') != -1) {
|
||||
return -1 * rowSize;
|
||||
} else if (orient.indexOf('bottom') != -1) {
|
||||
return rowSize;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
var position = {
|
||||
x: sourceTrbl.right + horizontalDistance + element.width / 2,
|
||||
y: sourceMid.y + getVerticalDistance(orientation)
|
||||
};
|
||||
|
||||
var escapeDirection = {
|
||||
y: {
|
||||
margin: margin,
|
||||
rowSize: rowSize
|
||||
}
|
||||
};
|
||||
|
||||
return deconflictPosition(source, element, position, escapeDirection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute best distance between source and target,
|
||||
* based on existing connections to and from source.
|
||||
*
|
||||
* @param {djs.model.Shape} source
|
||||
* @param {djs.model.Shape} element
|
||||
*
|
||||
* @return {Number} distance
|
||||
*/
|
||||
export function getFlowNodeDistance(source, element) {
|
||||
|
||||
var sourceTrbl = asTRBL(source);
|
||||
|
||||
// is connection a reference to consider?
|
||||
function isReference(c) {
|
||||
return is(c, 'bpmn:SequenceFlow');
|
||||
}
|
||||
|
||||
function toTargetNode(weight) {
|
||||
|
||||
return function(shape) {
|
||||
return {
|
||||
shape: shape,
|
||||
weight: weight,
|
||||
distanceTo: function(shape) {
|
||||
var shapeTrbl = asTRBL(shape);
|
||||
|
||||
return shapeTrbl.left - sourceTrbl.right;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
function toSourceNode(weight) {
|
||||
return function(shape) {
|
||||
return {
|
||||
shape: shape,
|
||||
weight: weight,
|
||||
distanceTo: function(shape) {
|
||||
var shapeTrbl = asTRBL(shape);
|
||||
|
||||
return sourceTrbl.left - shapeTrbl.right;
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// we create a list of nodes to take into consideration
|
||||
// for calculating the optimal flow node distance
|
||||
//
|
||||
// * weight existing target nodes higher than source nodes
|
||||
// * only take into account individual nodes once
|
||||
//
|
||||
var nodes = reduce([].concat(
|
||||
getTargets(source, isReference).map(toTargetNode(5)),
|
||||
getSources(source, isReference).map(toSourceNode(1))
|
||||
), function(nodes, node) {
|
||||
// filter out shapes connected twice via source or target
|
||||
nodes[node.shape.id + '__weight_' + node.weight] = node;
|
||||
|
||||
return nodes;
|
||||
}, {});
|
||||
|
||||
// compute distances between source and incoming nodes;
|
||||
// group at the same time by distance and expose the
|
||||
// favourite distance as { fav: { count, value } }.
|
||||
var distancesGrouped = reduce(nodes, function(result, node) {
|
||||
|
||||
var shape = node.shape,
|
||||
weight = node.weight,
|
||||
distanceTo = node.distanceTo;
|
||||
|
||||
var fav = result.fav,
|
||||
currentDistance,
|
||||
currentDistanceCount,
|
||||
currentDistanceEntry;
|
||||
|
||||
currentDistance = distanceTo(shape);
|
||||
|
||||
// ignore too far away peers
|
||||
// or non-left to right modeled nodes
|
||||
if (currentDistance < 0 || currentDistance > MAX_HORIZONTAL_DISTANCE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
currentDistanceEntry = result[String(currentDistance)] =
|
||||
result[String(currentDistance)] || {
|
||||
value: currentDistance,
|
||||
count: 0
|
||||
};
|
||||
|
||||
// inc diff count
|
||||
currentDistanceCount = currentDistanceEntry.count += 1 * weight;
|
||||
|
||||
if (!fav || fav.count < currentDistanceCount) {
|
||||
result.fav = currentDistanceEntry;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, { });
|
||||
|
||||
|
||||
if (distancesGrouped.fav) {
|
||||
return distancesGrouped.fav.value;
|
||||
} else {
|
||||
return DEFAULT_HORIZONTAL_DISTANCE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Always try to place text annotations top right of source.
|
||||
*/
|
||||
export function getTextAnnotationPosition(source, element) {
|
||||
|
||||
var sourceTrbl = asTRBL(source);
|
||||
|
||||
var position = {
|
||||
x: sourceTrbl.right + element.width / 2,
|
||||
y: sourceTrbl.top - 50 - element.height / 2
|
||||
};
|
||||
|
||||
var escapeDirection = {
|
||||
y: {
|
||||
margin: -30,
|
||||
rowSize: 20
|
||||
}
|
||||
};
|
||||
|
||||
return deconflictPosition(source, element, position, escapeDirection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Always put element bottom right of source.
|
||||
*/
|
||||
export function getDataElementPosition(source, element) {
|
||||
|
||||
var sourceTrbl = asTRBL(source);
|
||||
|
||||
var position = {
|
||||
x: sourceTrbl.right - 10 + element.width / 2,
|
||||
y: sourceTrbl.bottom + 40 + element.width / 2
|
||||
};
|
||||
|
||||
var escapeDirection = {
|
||||
x: {
|
||||
margin: 30,
|
||||
rowSize: 30
|
||||
}
|
||||
};
|
||||
|
||||
return deconflictPosition(source, element, position, escapeDirection);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Always put element right of source per default.
|
||||
*/
|
||||
export function getDefaultPosition(source, element) {
|
||||
|
||||
var sourceTrbl = asTRBL(source);
|
||||
|
||||
var sourceMid = getMid(source);
|
||||
|
||||
// simply put element right next to source
|
||||
return {
|
||||
x: sourceTrbl.right + DEFAULT_HORIZONTAL_DISTANCE + element.width / 2,
|
||||
y: sourceMid.y
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns all connected elements around the given source.
|
||||
*
|
||||
* This includes:
|
||||
*
|
||||
* - connected elements
|
||||
* - host connected elements
|
||||
* - attachers connected elements
|
||||
*
|
||||
* @param {djs.model.Shape} source
|
||||
* @param {djs.model.Shape} element
|
||||
*
|
||||
* @return {Array<djs.model.Shape>}
|
||||
*/
|
||||
function getAutoPlaceClosure(source, element) {
|
||||
|
||||
var allConnected = getConnected(source);
|
||||
|
||||
if (source.host) {
|
||||
allConnected = allConnected.concat(getConnected(source.host));
|
||||
}
|
||||
|
||||
if (source.attachers) {
|
||||
allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) {
|
||||
return shapes.concat(getConnected(attacher));
|
||||
}, []));
|
||||
}
|
||||
|
||||
return allConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return target at given position, if defined.
|
||||
*
|
||||
* This takes connected elements from host and attachers
|
||||
* into account, too.
|
||||
*/
|
||||
export function getConnectedAtPosition(source, position, element) {
|
||||
|
||||
var bounds = {
|
||||
x: position.x - (element.width / 2),
|
||||
y: position.y - (element.height / 2),
|
||||
width: element.width,
|
||||
height: element.height
|
||||
};
|
||||
|
||||
var closure = getAutoPlaceClosure(source, element);
|
||||
|
||||
return find(closure, function(target) {
|
||||
|
||||
if (target === element) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);
|
||||
|
||||
return orientation === 'intersect';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a new, position for the given element
|
||||
* based on the given element that is not occupied
|
||||
* by some element connected to source.
|
||||
*
|
||||
* Take into account the escapeDirection (where to move
|
||||
* on positining clashes) in the computation.
|
||||
*
|
||||
* @param {djs.model.Shape} source
|
||||
* @param {djs.model.Shape} element
|
||||
* @param {Point} position
|
||||
* @param {Object} escapeDelta
|
||||
*
|
||||
* @return {Point}
|
||||
*/
|
||||
export function deconflictPosition(source, element, position, escapeDelta) {
|
||||
|
||||
function nextPosition(existingElement) {
|
||||
|
||||
var newPosition = {
|
||||
x: position.x,
|
||||
y: position.y
|
||||
};
|
||||
|
||||
[ 'x', 'y' ].forEach(function(axis) {
|
||||
|
||||
var axisDelta = escapeDelta[axis];
|
||||
|
||||
if (!axisDelta) {
|
||||
return;
|
||||
}
|
||||
|
||||
var dimension = axis === 'x' ? 'width' : 'height';
|
||||
|
||||
var margin = axisDelta.margin,
|
||||
rowSize = axisDelta.rowSize;
|
||||
|
||||
if (margin < 0) {
|
||||
newPosition[axis] = Math.min(
|
||||
existingElement[axis] + margin - element[dimension] / 2,
|
||||
position[axis] - rowSize + margin
|
||||
);
|
||||
} else {
|
||||
newPosition[axis] = Math.max(
|
||||
existingTarget[axis] + existingTarget[dimension] + margin + element[dimension] / 2,
|
||||
position[axis] + rowSize + margin
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
var existingTarget;
|
||||
|
||||
// deconflict position until free slot is found
|
||||
while ((existingTarget = getConnectedAtPosition(source, position, element))) {
|
||||
position = nextPosition(existingTarget);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function noneFilter() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getConnected(element, connectionFilter) {
|
||||
return [].concat(
|
||||
getTargets(element, connectionFilter),
|
||||
getSources(element, connectionFilter)
|
||||
);
|
||||
}
|
||||
|
||||
function getSources(shape, connectionFilter) {
|
||||
|
||||
if (!connectionFilter) {
|
||||
connectionFilter = noneFilter;
|
||||
}
|
||||
|
||||
return shape.incoming.filter(connectionFilter).map(function(c) {
|
||||
return c.source;
|
||||
});
|
||||
}
|
||||
|
||||
function getTargets(shape, connectionFilter) {
|
||||
|
||||
if (!connectionFilter) {
|
||||
connectionFilter = noneFilter;
|
||||
}
|
||||
|
||||
return shape.outgoing.filter(connectionFilter).map(function(c) {
|
||||
return c.target;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import AutoPlace from './AutoPlace';
|
||||
import AutoPlaceSelectionBehavior from './AutoPlaceSelectionBehavior';
|
||||
|
||||
export default {
|
||||
__init__: [ 'autoPlaceSelectionBehavior' ],
|
||||
autoPlace: [ 'type', AutoPlace ],
|
||||
autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import AutoResize from 'diagram-js/lib/features/auto-resize/AutoResize';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Sub class of the AutoResize module which implements a BPMN
|
||||
* specific resize function.
|
||||
*/
|
||||
export default function BpmnAutoResize(injector) {
|
||||
|
||||
injector.invoke(AutoResize, this);
|
||||
}
|
||||
|
||||
BpmnAutoResize.$inject = [
|
||||
'injector'
|
||||
];
|
||||
|
||||
inherits(BpmnAutoResize, AutoResize);
|
||||
|
||||
|
||||
/**
|
||||
* Resize shapes and lanes.
|
||||
*
|
||||
* @param {djs.model.Shape} target
|
||||
* @param {Bounds} newBounds
|
||||
* @param {Object} hints
|
||||
*/
|
||||
BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {
|
||||
|
||||
if (is(target, 'bpmn:Participant')) {
|
||||
this._modeling.resizeLane(target, newBounds, null, hints);
|
||||
} else {
|
||||
this._modeling.resizeShape(target, newBounds, null, hints);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,51 @@
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import { forEach } from 'min-dash';
|
||||
|
||||
import AutoResizeProvider from 'diagram-js/lib/features/auto-resize/AutoResizeProvider';
|
||||
|
||||
|
||||
/**
|
||||
* This module is a provider for automatically resizing parent BPMN elements
|
||||
*/
|
||||
export default function BpmnAutoResizeProvider(eventBus, modeling) {
|
||||
AutoResizeProvider.call(this, eventBus);
|
||||
|
||||
this._modeling = modeling;
|
||||
}
|
||||
|
||||
inherits(BpmnAutoResizeProvider, AutoResizeProvider);
|
||||
|
||||
BpmnAutoResizeProvider.$inject = [
|
||||
'eventBus',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Check if the given target can be expanded
|
||||
*
|
||||
* @param {djs.model.Shape} target
|
||||
*
|
||||
* @return {boolean}
|
||||
*/
|
||||
BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {
|
||||
|
||||
if (!is(target, 'bpmn:Participant') && !is(target, 'bpmn:Lane') && !(is(target, 'bpmn:SubProcess'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var canResize = true;
|
||||
|
||||
forEach(elements, function(element) {
|
||||
|
||||
if (is(element, 'bpmn:Lane') || element.labelTarget) {
|
||||
canResize = false;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return canResize;
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
import BpmnAutoResize from './BpmnAutoResize';
|
||||
import BpmnAutoResizeProvider from './BpmnAutoResizeProvider';
|
||||
|
||||
|
||||
export default {
|
||||
__init__: [
|
||||
'bpmnAutoResize',
|
||||
'bpmnAutoResizeProvider'
|
||||
],
|
||||
bpmnAutoResize: [ 'type', BpmnAutoResize ],
|
||||
bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
|
||||
};
|
||||
@@ -0,0 +1,434 @@
|
||||
import {
|
||||
assign,
|
||||
forEach,
|
||||
isArray
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
is
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isExpanded,
|
||||
isEventSubProcess
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from '../modeling/util/ModelingUtil';
|
||||
|
||||
import {
|
||||
getChildLanes
|
||||
} from '../modeling/util/LaneUtil';
|
||||
|
||||
import {
|
||||
hasPrimaryModifier
|
||||
} from 'diagram-js/lib/util/Mouse';
|
||||
|
||||
|
||||
/**
|
||||
* A provider for BPMN 2.0 elements context pad
|
||||
*/
|
||||
export default function ContextPadProvider(
|
||||
config, injector, eventBus,
|
||||
contextPad, modeling, elementFactory,
|
||||
connect, create, popupMenu,
|
||||
canvas, rules, translate) {
|
||||
|
||||
config = config || {};
|
||||
|
||||
contextPad.registerProvider(this);
|
||||
|
||||
this._contextPad = contextPad;
|
||||
|
||||
this._modeling = modeling;
|
||||
|
||||
this._elementFactory = elementFactory;
|
||||
this._connect = connect;
|
||||
this._create = create;
|
||||
this._popupMenu = popupMenu;
|
||||
this._canvas = canvas;
|
||||
this._rules = rules;
|
||||
this._translate = translate;
|
||||
|
||||
if (config.autoPlace !== false) {
|
||||
this._autoPlace = injector.get('autoPlace', false);
|
||||
}
|
||||
|
||||
eventBus.on('create.end', 250, function(event) {
|
||||
var shape = event.context.shape;
|
||||
|
||||
if (!hasPrimaryModifier(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = contextPad.getEntries(shape);
|
||||
|
||||
if (entries.replace) {
|
||||
entries.replace.action.click(event, shape);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ContextPadProvider.$inject = [
|
||||
'config.contextPad',
|
||||
'injector',
|
||||
'eventBus',
|
||||
'contextPad',
|
||||
'modeling',
|
||||
'elementFactory',
|
||||
'connect',
|
||||
'create',
|
||||
'popupMenu',
|
||||
'canvas',
|
||||
'rules',
|
||||
'translate'
|
||||
];
|
||||
|
||||
|
||||
ContextPadProvider.prototype.getContextPadEntries = function(element) {
|
||||
|
||||
var contextPad = this._contextPad,
|
||||
modeling = this._modeling,
|
||||
|
||||
elementFactory = this._elementFactory,
|
||||
connect = this._connect,
|
||||
create = this._create,
|
||||
popupMenu = this._popupMenu,
|
||||
canvas = this._canvas,
|
||||
rules = this._rules,
|
||||
autoPlace = this._autoPlace,
|
||||
translate = this._translate;
|
||||
|
||||
var actions = {};
|
||||
|
||||
if (element.type === 'label') {
|
||||
return actions;
|
||||
}
|
||||
|
||||
var businessObject = element.businessObject;
|
||||
|
||||
function startConnect(event, element) {
|
||||
connect.start(event, element);
|
||||
}
|
||||
|
||||
function removeElement(e) {
|
||||
modeling.removeElements([ element ]);
|
||||
}
|
||||
|
||||
function getReplaceMenuPosition(element) {
|
||||
|
||||
var Y_OFFSET = 5;
|
||||
|
||||
var diagramContainer = canvas.getContainer(),
|
||||
pad = contextPad.getPad(element).html;
|
||||
|
||||
var diagramRect = diagramContainer.getBoundingClientRect(),
|
||||
padRect = pad.getBoundingClientRect();
|
||||
|
||||
var top = padRect.top - diagramRect.top;
|
||||
var left = padRect.left - diagramRect.left;
|
||||
|
||||
var pos = {
|
||||
x: left,
|
||||
y: top + padRect.height + Y_OFFSET
|
||||
};
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create an append action
|
||||
*
|
||||
* @param {String} type
|
||||
* @param {String} className
|
||||
* @param {String} [title]
|
||||
* @param {Object} [options]
|
||||
*
|
||||
* @return {Object} descriptor
|
||||
*/
|
||||
function appendAction(type, className, title, options) {
|
||||
|
||||
if (typeof title !== 'string') {
|
||||
options = title;
|
||||
title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
|
||||
}
|
||||
|
||||
function appendStart(event, element) {
|
||||
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
create.start(event, shape, element);
|
||||
}
|
||||
|
||||
|
||||
var append = autoPlace ? function(event, element) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
autoPlace.append(element, shape);
|
||||
} : appendStart;
|
||||
|
||||
|
||||
return {
|
||||
group: 'model',
|
||||
className: className,
|
||||
title: title,
|
||||
action: {
|
||||
dragstart: appendStart,
|
||||
click: append
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function splitLaneHandler(count) {
|
||||
|
||||
return function(event, element) {
|
||||
// actual split
|
||||
modeling.splitLane(element, count);
|
||||
|
||||
// refresh context pad after split to
|
||||
// get rid of split icons
|
||||
contextPad.open(element, true);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(businessObject)) {
|
||||
|
||||
var childLanes = getChildLanes(element);
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-above': {
|
||||
group: 'lane-insert-above',
|
||||
className: 'bpmn-icon-lane-insert-above',
|
||||
title: translate('Add Lane above'),
|
||||
action: {
|
||||
click: function(event, element) {
|
||||
modeling.addLane(element, 'top');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (childLanes.length < 2) {
|
||||
|
||||
if (element.height >= 120) {
|
||||
assign(actions, {
|
||||
'lane-divide-two': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-two',
|
||||
title: translate('Divide into two Lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(2)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (element.height >= 180) {
|
||||
assign(actions, {
|
||||
'lane-divide-three': {
|
||||
group: 'lane-divide',
|
||||
className: 'bpmn-icon-lane-divide-three',
|
||||
title: translate('Divide into three Lanes'),
|
||||
action: {
|
||||
click: splitLaneHandler(3)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'lane-insert-below': {
|
||||
group: 'lane-insert-below',
|
||||
className: 'bpmn-icon-lane-insert-below',
|
||||
title: translate('Add Lane below'),
|
||||
action: {
|
||||
click: function(event, element) {
|
||||
modeling.addLane(element, 'bottom');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:FlowNode')) {
|
||||
|
||||
if (is(businessObject, 'bpmn:EventBasedGateway')) {
|
||||
|
||||
assign(actions, {
|
||||
'append.receive-task': appendAction(
|
||||
'bpmn:ReceiveTask',
|
||||
'bpmn-icon-receive-task'
|
||||
),
|
||||
'append.message-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-message',
|
||||
translate('Append MessageIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:MessageEventDefinition' }
|
||||
),
|
||||
'append.timer-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-timer',
|
||||
translate('Append TimerIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:TimerEventDefinition' }
|
||||
),
|
||||
'append.condtion-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-condition',
|
||||
translate('Append ConditionIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
|
||||
),
|
||||
'append.signal-intermediate-event': appendAction(
|
||||
'bpmn:IntermediateCatchEvent',
|
||||
'bpmn-icon-intermediate-event-catch-signal',
|
||||
translate('Append SignalIntermediateCatchEvent'),
|
||||
{ eventDefinitionType: 'bpmn:SignalEventDefinition' }
|
||||
)
|
||||
});
|
||||
} else
|
||||
|
||||
if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
|
||||
|
||||
assign(actions, {
|
||||
'append.compensation-activity':
|
||||
appendAction(
|
||||
'bpmn:Task',
|
||||
'bpmn-icon-task',
|
||||
translate('Append compensation activity'),
|
||||
{
|
||||
isForCompensation: true
|
||||
}
|
||||
)
|
||||
});
|
||||
} else
|
||||
|
||||
if (!is(businessObject, 'bpmn:EndEvent') &&
|
||||
!businessObject.isForCompensation &&
|
||||
!isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
|
||||
!isEventSubProcess(businessObject)) {
|
||||
|
||||
assign(actions, {
|
||||
'append.end-event': appendAction(
|
||||
'bpmn:EndEvent',
|
||||
'bpmn-icon-end-event-none',
|
||||
translate('Append EndEvent')
|
||||
),
|
||||
'append.gateway': appendAction(
|
||||
'bpmn:ExclusiveGateway',
|
||||
'bpmn-icon-gateway-none',
|
||||
translate('Append Gateway')
|
||||
),
|
||||
'append.append-task': appendAction(
|
||||
'bpmn:Task',
|
||||
'bpmn-icon-task',
|
||||
translate('Append Task')
|
||||
),
|
||||
'append.intermediate-event': appendAction(
|
||||
'bpmn:IntermediateThrowEvent',
|
||||
'bpmn-icon-intermediate-event-none',
|
||||
translate('Append Intermediate/Boundary Event')
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
|
||||
// Replace menu entry
|
||||
assign(actions, {
|
||||
'replace': {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-screw-wrench',
|
||||
title: translate('Change type'),
|
||||
action: {
|
||||
click: function(event, element) {
|
||||
|
||||
var position = assign(getReplaceMenuPosition(element), {
|
||||
cursor: { x: event.x, y: event.y }
|
||||
});
|
||||
|
||||
popupMenu.open(element, 'bpmn-replace', position);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isAny(businessObject, [
|
||||
'bpmn:FlowNode',
|
||||
'bpmn:InteractionNode',
|
||||
'bpmn:DataObjectReference',
|
||||
'bpmn:DataStoreReference'
|
||||
])) {
|
||||
|
||||
assign(actions, {
|
||||
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation'),
|
||||
|
||||
'connect': {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect using ' +
|
||||
(businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or ') +
|
||||
'Association'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
|
||||
assign(actions, {
|
||||
'connect': {
|
||||
group: 'connect',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Connect using DataInputAssociation'),
|
||||
action: {
|
||||
click: startConnect,
|
||||
dragstart: startConnect
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// delete element entry, only show if allowed by rules
|
||||
var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
|
||||
|
||||
if (isArray(deleteAllowed)) {
|
||||
// was the element returned as a deletion candidate?
|
||||
deleteAllowed = deleteAllowed[0] === element;
|
||||
}
|
||||
|
||||
if (deleteAllowed) {
|
||||
assign(actions, {
|
||||
'delete': {
|
||||
group: 'edit',
|
||||
className: 'bpmn-icon-trash',
|
||||
title: translate('Remove'),
|
||||
action: {
|
||||
click: removeElement
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
function isEventType(eventBo, type, definition) {
|
||||
|
||||
var isType = eventBo.$instanceOf(type);
|
||||
var isDefinition = false;
|
||||
|
||||
var definitions = eventBo.eventDefinitions || [];
|
||||
forEach(definitions, function(def) {
|
||||
if (def.$type === definition) {
|
||||
isDefinition = true;
|
||||
}
|
||||
});
|
||||
|
||||
return isType && isDefinition;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import DirectEditingModule from 'diagram-js-direct-editing';
|
||||
import ContextPadModule from 'diagram-js/lib/features/context-pad';
|
||||
import SelectionModule from 'diagram-js/lib/features/selection';
|
||||
import ConnectModule from 'diagram-js/lib/features/connect';
|
||||
import CreateModule from 'diagram-js/lib/features/create';
|
||||
import PopupMenuModule from '../popup-menu';
|
||||
|
||||
import ContextPadProvider from './ContextPadProvider';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
DirectEditingModule,
|
||||
ContextPadModule,
|
||||
SelectionModule,
|
||||
ConnectModule,
|
||||
CreateModule,
|
||||
PopupMenuModule
|
||||
],
|
||||
__init__: [ 'contextPadProvider' ],
|
||||
contextPadProvider: [ 'type', ContextPadProvider ]
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import ModelCloneHelper from '../../util/model/ModelCloneHelper';
|
||||
|
||||
import {
|
||||
getProperties,
|
||||
IGNORED_PROPERTIES
|
||||
} from '../../util/model/ModelCloneUtils';
|
||||
|
||||
import {
|
||||
filter,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
function setProperties(descriptor, data, properties) {
|
||||
forEach(properties, function(property) {
|
||||
if (data[property] !== undefined) {
|
||||
descriptor[property] = data[property];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeProperties(element, properties) {
|
||||
forEach(properties, function(prop) {
|
||||
if (element[prop]) {
|
||||
delete element[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default function BpmnCopyPaste(
|
||||
bpmnFactory, eventBus, copyPaste,
|
||||
clipboard, canvas, bpmnRules) {
|
||||
|
||||
var helper = new ModelCloneHelper(eventBus, bpmnFactory);
|
||||
|
||||
copyPaste.registerDescriptor(function(element, descriptor) {
|
||||
var businessObject = descriptor.oldBusinessObject = getBusinessObject(element);
|
||||
|
||||
var colors = {};
|
||||
|
||||
descriptor.type = element.type;
|
||||
|
||||
setProperties(descriptor, businessObject.di, [ 'isExpanded' ]);
|
||||
|
||||
setProperties(colors, businessObject.di, [ 'fill', 'stroke' ]);
|
||||
|
||||
descriptor.colors = colors;
|
||||
|
||||
if (element.type === 'label') {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
setProperties(descriptor, businessObject, [
|
||||
'processRef',
|
||||
'triggeredByEvent'
|
||||
]);
|
||||
|
||||
if (businessObject.default) {
|
||||
descriptor.default = businessObject.default.id;
|
||||
}
|
||||
|
||||
return descriptor;
|
||||
});
|
||||
|
||||
eventBus.on('element.paste', function(context) {
|
||||
var descriptor = context.descriptor,
|
||||
createdElements = context.createdElements,
|
||||
parent = descriptor.parent,
|
||||
rootElement = canvas.getRootElement(),
|
||||
oldBusinessObject = descriptor.oldBusinessObject,
|
||||
newBusinessObject,
|
||||
source,
|
||||
target,
|
||||
canConnect;
|
||||
|
||||
newBusinessObject = bpmnFactory.create(oldBusinessObject.$type);
|
||||
|
||||
var properties = getProperties(oldBusinessObject.$descriptor);
|
||||
|
||||
properties = filter(properties, function(property) {
|
||||
return IGNORED_PROPERTIES.indexOf(property.replace(/bpmn:/, '')) === -1;
|
||||
});
|
||||
|
||||
descriptor.businessObject = helper.clone(oldBusinessObject, newBusinessObject, properties);
|
||||
|
||||
if (descriptor.type === 'label') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is(parent, 'bpmn:Process')) {
|
||||
descriptor.parent = is(rootElement, 'bpmn:Collaboration') ? rootElement : parent;
|
||||
}
|
||||
|
||||
if (descriptor.type === 'bpmn:DataOutputAssociation' ||
|
||||
descriptor.type === 'bpmn:DataInputAssociation' ||
|
||||
descriptor.type === 'bpmn:MessageFlow') {
|
||||
descriptor.parent = rootElement;
|
||||
}
|
||||
|
||||
if (is(parent, 'bpmn:Lane')) {
|
||||
descriptor.parent = parent.parent;
|
||||
}
|
||||
|
||||
// make sure that the correct type of connection is created
|
||||
if (descriptor.waypoints) {
|
||||
source = createdElements[descriptor.source];
|
||||
target = createdElements[descriptor.target];
|
||||
|
||||
if (source && target) {
|
||||
source = source.element;
|
||||
target = target.element;
|
||||
}
|
||||
|
||||
canConnect = bpmnRules.canConnect(source, target);
|
||||
|
||||
if (canConnect) {
|
||||
descriptor.type = canConnect.type;
|
||||
}
|
||||
}
|
||||
|
||||
// remove the id or else we cannot paste multiple times
|
||||
delete newBusinessObject.id;
|
||||
|
||||
// assign an ID
|
||||
bpmnFactory._ensureId(newBusinessObject);
|
||||
|
||||
if (descriptor.type === 'bpmn:Participant' && descriptor.processRef) {
|
||||
descriptor.processRef = newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
|
||||
}
|
||||
|
||||
setProperties(newBusinessObject, descriptor, [
|
||||
'isExpanded',
|
||||
'triggeredByEvent'
|
||||
]);
|
||||
|
||||
removeProperties(descriptor, [
|
||||
'triggeredByEvent'
|
||||
]);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
BpmnCopyPaste.$inject = [
|
||||
'bpmnFactory',
|
||||
'eventBus',
|
||||
'copyPaste',
|
||||
'clipboard',
|
||||
'canvas',
|
||||
'bpmnRules'
|
||||
];
|
||||
@@ -0,0 +1,11 @@
|
||||
import CopyPasteModule from 'diagram-js/lib/features/copy-paste';
|
||||
|
||||
import BpmnCopyPaste from './BpmnCopyPaste';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
CopyPasteModule
|
||||
],
|
||||
__init__: [ 'bpmnCopyPaste' ],
|
||||
bpmnCopyPaste: [ 'type', BpmnCopyPaste ]
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
filter
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from '../modeling/util/ModelingUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Registers element exclude filters for elements that
|
||||
* currently do not support distribution.
|
||||
*/
|
||||
export default function BpmnDistributeElements(distributeElements) {
|
||||
|
||||
distributeElements.registerFilter(function(elements) {
|
||||
return filter(elements, function(element) {
|
||||
var cannotDistribute = isAny(element, [
|
||||
'bpmn:Association',
|
||||
'bpmn:BoundaryEvent',
|
||||
'bpmn:DataInputAssociation',
|
||||
'bpmn:DataOutputAssociation',
|
||||
'bpmn:Lane',
|
||||
'bpmn:MessageFlow',
|
||||
'bpmn:Participant',
|
||||
'bpmn:SequenceFlow',
|
||||
'bpmn:TextAnnotation'
|
||||
]);
|
||||
|
||||
return !(element.labelTarget || cannotDistribute);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
BpmnDistributeElements.$inject = [ 'distributeElements' ];
|
||||
@@ -0,0 +1,12 @@
|
||||
import DistributeElementsModule from 'diagram-js/lib/features/distribute-elements';
|
||||
|
||||
import BpmnDistributeElements from './BpmnDistributeElements';
|
||||
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
DistributeElementsModule
|
||||
],
|
||||
__init__: [ 'bpmnDistributeElements' ],
|
||||
bpmnDistributeElements: [ 'type', BpmnDistributeElements ]
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import EditorActions from 'diagram-js/lib/features/editor-actions/EditorActions';
|
||||
|
||||
import { filter } from 'min-dash';
|
||||
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
getBBox
|
||||
} from 'diagram-js/lib/util/Elements';
|
||||
|
||||
|
||||
/**
|
||||
* Registers and executes BPMN specific editor actions.
|
||||
*
|
||||
* @param {Injector} injector
|
||||
*/
|
||||
export default function BpmnEditorActions(injector) {
|
||||
injector.invoke(EditorActions, this);
|
||||
}
|
||||
|
||||
inherits(BpmnEditorActions, EditorActions);
|
||||
|
||||
BpmnEditorActions.$inject = [
|
||||
'injector'
|
||||
];
|
||||
|
||||
/**
|
||||
* Register default actions.
|
||||
*
|
||||
* @param {Injector} injector
|
||||
*/
|
||||
BpmnEditorActions.prototype._registerDefaultActions = function(injector) {
|
||||
|
||||
// (0) invoke super method
|
||||
|
||||
EditorActions.prototype._registerDefaultActions.call(this, injector);
|
||||
|
||||
// (1) retrieve optional components to integrate with
|
||||
|
||||
var canvas = injector.get('canvas', false);
|
||||
var elementRegistry = injector.get('elementRegistry', false);
|
||||
var selection = injector.get('selection', false);
|
||||
var spaceTool = injector.get('spaceTool', false);
|
||||
var lassoTool = injector.get('lassoTool', false);
|
||||
var handTool = injector.get('handTool', false);
|
||||
var globalConnect = injector.get('globalConnect', false);
|
||||
var distributeElements = injector.get('distributeElements', false);
|
||||
var alignElements = injector.get('alignElements', false);
|
||||
var directEditing = injector.get('directEditing', false);
|
||||
var searchPad = injector.get('searchPad', false);
|
||||
var modeling = injector.get('modeling', false);
|
||||
|
||||
// (2) check components and register actions
|
||||
|
||||
if (canvas && elementRegistry && selection) {
|
||||
this._registerAction('selectElements', function() {
|
||||
// select all elements except for the invisible
|
||||
// root element
|
||||
var rootElement = canvas.getRootElement();
|
||||
|
||||
var elements = elementRegistry.filter(function(element) {
|
||||
return element !== rootElement;
|
||||
});
|
||||
|
||||
selection.select(elements);
|
||||
|
||||
return elements;
|
||||
});
|
||||
}
|
||||
|
||||
if (spaceTool) {
|
||||
this._registerAction('spaceTool', function() {
|
||||
spaceTool.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
if (lassoTool) {
|
||||
this._registerAction('lassoTool', function() {
|
||||
lassoTool.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
if (handTool) {
|
||||
this._registerAction('handTool', function() {
|
||||
handTool.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
if (globalConnect) {
|
||||
this._registerAction('globalConnectTool', function() {
|
||||
globalConnect.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
if (selection && distributeElements) {
|
||||
this._registerAction('distributeElements', function(opts) {
|
||||
var currentSelection = selection.get(),
|
||||
type = opts.type;
|
||||
|
||||
if (currentSelection.length) {
|
||||
distributeElements.trigger(currentSelection, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (selection && alignElements) {
|
||||
this._registerAction('alignElements', function(opts) {
|
||||
var currentSelection = selection.get(),
|
||||
aligneableElements = [],
|
||||
type = opts.type;
|
||||
|
||||
if (currentSelection.length) {
|
||||
aligneableElements = filter(currentSelection, function(element) {
|
||||
return !is(element, 'bpmn:Lane');
|
||||
});
|
||||
|
||||
alignElements.trigger(aligneableElements, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (selection && modeling) {
|
||||
this._registerAction('setColor', function(opts) {
|
||||
var currentSelection = selection.get();
|
||||
|
||||
if (currentSelection.length) {
|
||||
modeling.setColor(currentSelection, opts);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (selection && directEditing) {
|
||||
this._registerAction('directEditing', function() {
|
||||
var currentSelection = selection.get();
|
||||
|
||||
if (currentSelection.length) {
|
||||
directEditing.activate(currentSelection[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (searchPad) {
|
||||
this._registerAction('find', function() {
|
||||
searchPad.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
if (canvas && modeling) {
|
||||
this._registerAction('moveToOrigin', function() {
|
||||
var rootElement = canvas.getRootElement(),
|
||||
boundingBox,
|
||||
elements;
|
||||
|
||||
if (is(rootElement, 'bpmn:Collaboration')) {
|
||||
elements = elementRegistry.filter(function(element) {
|
||||
return is(element.parent, 'bpmn:Collaboration');
|
||||
});
|
||||
} else {
|
||||
elements = elementRegistry.filter(function(element) {
|
||||
return element !== rootElement && !is(element.parent, 'bpmn:SubProcess');
|
||||
});
|
||||
}
|
||||
|
||||
boundingBox = getBBox(elements);
|
||||
|
||||
modeling.moveElements(
|
||||
elements,
|
||||
{ x: -boundingBox.x, y: -boundingBox.y },
|
||||
rootElement
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import EditorActionsModule from 'diagram-js/lib/features/editor-actions';
|
||||
|
||||
import BpmnEditorActions from './BpmnEditorActions';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
EditorActionsModule
|
||||
],
|
||||
editorActions: [ 'type', BpmnEditorActions ]
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { isAny } from '../modeling/util/ModelingUtil';
|
||||
|
||||
export default function BpmnGridSnapping(eventBus) {
|
||||
eventBus.on([
|
||||
'create.init',
|
||||
'shape.move.init'
|
||||
], function(event) {
|
||||
var context = event.context,
|
||||
shape = event.shape;
|
||||
|
||||
if (isAny(shape, [
|
||||
'bpmn:Participant',
|
||||
'bpmn:SubProcess',
|
||||
'bpmn:TextAnnotation'
|
||||
])) {
|
||||
if (!context.gridSnappingContext) {
|
||||
context.gridSnappingContext = {};
|
||||
}
|
||||
|
||||
context.gridSnappingContext.snapLocation = 'top-left';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BpmnGridSnapping.$inject = [ 'eventBus' ];
|
||||
@@ -0,0 +1,57 @@
|
||||
import { getNewShapePosition } from '../../auto-place/AutoPlaceUtil';
|
||||
|
||||
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
export default function AutoPlaceBehavior(eventBus, gridSnapping) {
|
||||
eventBus.on('autoPlace', function(context) {
|
||||
var source = context.source,
|
||||
sourceMid = getMid(source),
|
||||
shape = context.shape;
|
||||
|
||||
var position = getNewShapePosition(source, shape);
|
||||
|
||||
[ 'x', 'y' ].forEach(function(axis) {
|
||||
var options = {};
|
||||
|
||||
// do not snap if x/y equal
|
||||
if (position[ axis ] === sourceMid[ axis ]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (position[ axis ] > sourceMid[ axis ]) {
|
||||
options.min = position[ axis ];
|
||||
} else {
|
||||
options.max = position[ axis ];
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:TextAnnotation')) {
|
||||
|
||||
if (isHorizontal(axis)) {
|
||||
options.offset = -shape.width / 2;
|
||||
} else {
|
||||
options.offset = -shape.height / 2;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
position[ axis ] = gridSnapping.snapValue(position[ axis ], options);
|
||||
|
||||
});
|
||||
|
||||
// must be returned to be considered by auto place
|
||||
return position;
|
||||
});
|
||||
}
|
||||
|
||||
AutoPlaceBehavior.$inject = [
|
||||
'eventBus',
|
||||
'gridSnapping'
|
||||
];
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function isHorizontal(axis) {
|
||||
return axis === 'x';
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
var HIGHER_PRIORITY = 1750;
|
||||
|
||||
|
||||
export default function CreateParticipantBehavior(canvas, eventBus, gridSnapping) {
|
||||
eventBus.on([
|
||||
'create.start',
|
||||
'shape.move.start'
|
||||
], HIGHER_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;
|
||||
}
|
||||
|
||||
var createConstraints = context.createConstraints;
|
||||
|
||||
if (!createConstraints) {
|
||||
return;
|
||||
}
|
||||
|
||||
shape.width = gridSnapping.snapValue(shape.width, { min: shape.width });
|
||||
shape.height = gridSnapping.snapValue(shape.height, { min: shape.height });
|
||||
});
|
||||
}
|
||||
|
||||
CreateParticipantBehavior.$inject = [
|
||||
'canvas',
|
||||
'eventBus',
|
||||
'gridSnapping'
|
||||
];
|
||||
@@ -0,0 +1,144 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import { pointsAligned } from 'diagram-js/lib/util/Geometry';
|
||||
|
||||
import {
|
||||
assign
|
||||
} from 'min-dash';
|
||||
|
||||
var HIGH_PRIORITY = 3000;
|
||||
|
||||
|
||||
/**
|
||||
* Snaps connections with Manhattan layout.
|
||||
*/
|
||||
export default function LayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this._gridSnapping = gridSnapping;
|
||||
|
||||
var self = this;
|
||||
|
||||
this.postExecuted([
|
||||
'connection.create',
|
||||
'connection.layout'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
connection = context.connection,
|
||||
hints = context.hints || {},
|
||||
waypoints = connection.waypoints;
|
||||
|
||||
if (hints.connectionStart || hints.connectionEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasMiddleSegments(waypoints)) {
|
||||
return;
|
||||
}
|
||||
|
||||
modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints));
|
||||
});
|
||||
}
|
||||
|
||||
LayoutConnectionBehavior.$inject = [
|
||||
'eventBus',
|
||||
'gridSnapping',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
inherits(LayoutConnectionBehavior, CommandInterceptor);
|
||||
|
||||
/**
|
||||
* Snap middle segments of a given connection.
|
||||
*
|
||||
* @param {Array<Point>} waypoints
|
||||
*
|
||||
* @returns {Array<Point>}
|
||||
*/
|
||||
LayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
|
||||
var gridSnapping = this._gridSnapping,
|
||||
snapped;
|
||||
|
||||
waypoints = waypoints.slice();
|
||||
|
||||
for (var i = 1; i < waypoints.length - 2; i++) {
|
||||
|
||||
snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]);
|
||||
|
||||
waypoints[i] = snapped[0];
|
||||
waypoints[i + 1] = snapped[1];
|
||||
}
|
||||
|
||||
return waypoints;
|
||||
};
|
||||
|
||||
|
||||
// helpers //////////
|
||||
|
||||
/**
|
||||
* Check wether a connection has a middle segments.
|
||||
*
|
||||
* @param {Array} waypoints
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasMiddleSegments(waypoints) {
|
||||
return waypoints.length > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether an alignment is horizontal.
|
||||
*
|
||||
* @param {string} aligned
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function horizontallyAligned(aligned) {
|
||||
return aligned === 'h';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check wether an alignment is vertical.
|
||||
*
|
||||
* @param {string} aligned
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function verticallyAligned(aligned) {
|
||||
return aligned === 'v';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get middle segments from a given connection.
|
||||
*
|
||||
* @param {Array} waypoints
|
||||
*
|
||||
* @returns {Array}
|
||||
*/
|
||||
function snapSegment(gridSnapping, segmentStart, segmentEnd) {
|
||||
|
||||
var aligned = pointsAligned(segmentStart, segmentEnd);
|
||||
|
||||
var snapped = {};
|
||||
|
||||
if (horizontallyAligned(aligned)) {
|
||||
|
||||
// snap horizontally
|
||||
snapped.y = gridSnapping.snapValue(segmentStart.y);
|
||||
}
|
||||
|
||||
if (verticallyAligned(aligned)) {
|
||||
|
||||
// snap vertically
|
||||
snapped.x = gridSnapping.snapValue(segmentStart.x);
|
||||
}
|
||||
|
||||
if ('x' in snapped || 'y' in snapped) {
|
||||
segmentStart = assign({}, segmentStart, snapped);
|
||||
segmentEnd = assign({}, segmentEnd, snapped);
|
||||
}
|
||||
|
||||
return [ segmentStart, segmentEnd ];
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import AutoPlaceBehavior from './AutoPlaceBehavior';
|
||||
import CreateParticipantBehavior from './CreateParticipantBehavior';
|
||||
import LayoutConnectionBehavior from './LayoutConnectionBehavior';
|
||||
|
||||
export default {
|
||||
__init__: [
|
||||
'gridSnappingAutoPlaceBehavior',
|
||||
'gridSnappingCreateParticipantBehavior',
|
||||
'gridSnappingLayoutConnectionBehavior',
|
||||
],
|
||||
gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ],
|
||||
gridSnappingCreateParticipantBehavior: [ 'type', CreateParticipantBehavior ],
|
||||
gridSnappingLayoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ]
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
import BpmnGridSnapping from './BpmnGridSnapping';
|
||||
import GridSnappingModule from 'diagram-js/lib/features/grid-snapping';
|
||||
|
||||
import GridSnappingBehaviorModule from './behavior';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
GridSnappingModule,
|
||||
GridSnappingBehaviorModule
|
||||
],
|
||||
__init__: [ 'bpmnGridSnapping' ],
|
||||
bpmnGridSnapping: [ 'type', BpmnGridSnapping ]
|
||||
};
|
||||
@@ -0,0 +1,158 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import KeyboardBindings from 'diagram-js/lib/features/keyboard/KeyboardBindings';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN 2.0 specific keyboard bindings.
|
||||
*
|
||||
* @param {Injector} injector
|
||||
*/
|
||||
export default function BpmnKeyboardBindings(injector) {
|
||||
injector.invoke(KeyboardBindings, this);
|
||||
}
|
||||
|
||||
inherits(BpmnKeyboardBindings, KeyboardBindings);
|
||||
|
||||
BpmnKeyboardBindings.$inject = [
|
||||
'injector'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Register available keyboard bindings.
|
||||
*
|
||||
* @param {Keyboard} keyboard
|
||||
* @param {EditorActions} editorActions
|
||||
*/
|
||||
BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
|
||||
|
||||
// inherit default bindings
|
||||
KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);
|
||||
|
||||
/**
|
||||
* Add keyboard binding if respective editor action
|
||||
* is registered.
|
||||
*
|
||||
* @param {String} action name
|
||||
* @param {Function} fn that implements the key binding
|
||||
*/
|
||||
function addListener(action, fn) {
|
||||
|
||||
if (editorActions.isRegistered(action)) {
|
||||
keyboard.addListener(fn);
|
||||
}
|
||||
}
|
||||
|
||||
// select all elements
|
||||
// CTRL + A
|
||||
addListener('selectElements', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.isKey(['a', 'A'], event) && keyboard.isCmd(event)) {
|
||||
editorActions.trigger('selectElements');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// search labels
|
||||
// CTRL + F
|
||||
addListener('find', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.isKey(['f', 'F'], event) && keyboard.isCmd(event)) {
|
||||
editorActions.trigger('find');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// activate space tool
|
||||
// S
|
||||
addListener('spaceTool', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.hasModifier(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard.isKey(['s', 'S'], event)) {
|
||||
editorActions.trigger('spaceTool');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// activate lasso tool
|
||||
// L
|
||||
addListener('lassoTool', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.hasModifier(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard.isKey(['l', 'L'], event)) {
|
||||
editorActions.trigger('lassoTool');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// activate hand tool
|
||||
// H
|
||||
addListener('handTool', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.hasModifier(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard.isKey(['h', 'H'], event)) {
|
||||
editorActions.trigger('handTool');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// activate global connect tool
|
||||
// C
|
||||
addListener('globalConnectTool', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.hasModifier(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard.isKey(['c', 'C'], event)) {
|
||||
editorActions.trigger('globalConnectTool');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// activate direct editing
|
||||
// E
|
||||
addListener('directEditing', function(context) {
|
||||
|
||||
var event = context.keyEvent;
|
||||
|
||||
if (keyboard.hasModifier(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyboard.isKey(['e', 'E'], event)) {
|
||||
editorActions.trigger('directEditing');
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import KeyboardModule from 'diagram-js/lib/features/keyboard';
|
||||
|
||||
import BpmnKeyboardBindings from './BpmnKeyboardBindings';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
KeyboardModule
|
||||
],
|
||||
__init__: [ 'keyboardBindings' ],
|
||||
keyboardBindings: [ 'type', BpmnKeyboardBindings ]
|
||||
};
|
||||
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
append as svgAppend,
|
||||
attr as svgAttr,
|
||||
create as svgCreate,
|
||||
remove as svgRemove
|
||||
} from 'tiny-svg';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
translate
|
||||
} from 'diagram-js/lib/util/SvgTransformUtil';
|
||||
|
||||
var MARKER_HIDDEN = 'djs-element-hidden',
|
||||
MARKER_LABEL_HIDDEN = 'djs-label-hidden';
|
||||
|
||||
|
||||
export default function LabelEditingPreview(
|
||||
eventBus, canvas, elementRegistry,
|
||||
pathMap) {
|
||||
|
||||
var self = this;
|
||||
|
||||
var defaultLayer = canvas.getDefaultLayer();
|
||||
|
||||
var element, absoluteElementBBox, gfx;
|
||||
|
||||
eventBus.on('directEditing.activate', function(context) {
|
||||
var activeProvider = context.active;
|
||||
|
||||
element = activeProvider.element.label || activeProvider.element;
|
||||
|
||||
// text annotation
|
||||
if (is(element, 'bpmn:TextAnnotation')) {
|
||||
absoluteElementBBox = canvas.getAbsoluteBBox(element);
|
||||
|
||||
gfx = svgCreate('g');
|
||||
|
||||
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
|
||||
xScaleFactor: 1,
|
||||
yScaleFactor: 1,
|
||||
containerWidth: element.width,
|
||||
containerHeight: element.height,
|
||||
position: {
|
||||
mx: 0.0,
|
||||
my: 0.0
|
||||
}
|
||||
});
|
||||
|
||||
var path = self.path = svgCreate('path');
|
||||
|
||||
svgAttr(path, {
|
||||
d: textPathData,
|
||||
strokeWidth: 2,
|
||||
stroke: getStrokeColor(element)
|
||||
});
|
||||
|
||||
svgAppend(gfx, path);
|
||||
|
||||
svgAppend(defaultLayer, gfx);
|
||||
|
||||
translate(gfx, element.x, element.y);
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:TextAnnotation') ||
|
||||
element.labelTarget) {
|
||||
canvas.addMarker(element, MARKER_HIDDEN);
|
||||
} else if (is(element, 'bpmn:Task') ||
|
||||
is(element, 'bpmn:CallActivity') ||
|
||||
is(element, 'bpmn:SubProcess') ||
|
||||
is(element, 'bpmn:Participant')) {
|
||||
canvas.addMarker(element, MARKER_LABEL_HIDDEN);
|
||||
}
|
||||
});
|
||||
|
||||
eventBus.on('directEditing.resize', function(context) {
|
||||
|
||||
// text annotation
|
||||
if (is(element, 'bpmn:TextAnnotation')) {
|
||||
var height = context.height,
|
||||
dy = context.dy;
|
||||
|
||||
var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);
|
||||
|
||||
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
|
||||
xScaleFactor: 1,
|
||||
yScaleFactor: 1,
|
||||
containerWidth: element.width,
|
||||
containerHeight: newElementHeight,
|
||||
position: {
|
||||
mx: 0.0,
|
||||
my: 0.0
|
||||
}
|
||||
});
|
||||
|
||||
svgAttr(self.path, {
|
||||
d: textPathData
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
|
||||
var activeProvider = context.active;
|
||||
|
||||
if (activeProvider) {
|
||||
canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
|
||||
canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
|
||||
}
|
||||
|
||||
element = undefined;
|
||||
absoluteElementBBox = undefined;
|
||||
|
||||
if (gfx) {
|
||||
svgRemove(gfx);
|
||||
|
||||
gfx = undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LabelEditingPreview.$inject = [
|
||||
'eventBus',
|
||||
'canvas',
|
||||
'elementRegistry',
|
||||
'pathMap'
|
||||
];
|
||||
|
||||
|
||||
// helpers ///////////////////
|
||||
|
||||
function getStrokeColor(element, defaultColor) {
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return bo.di.get('stroke') || defaultColor || 'black';
|
||||
}
|
||||
@@ -0,0 +1,429 @@
|
||||
import {
|
||||
assign
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
getLabel
|
||||
} from './LabelUtil';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
createCategoryValue
|
||||
} from '../modeling/behavior/util/CategoryUtil';
|
||||
|
||||
import { isAny } from '../modeling/util/ModelingUtil';
|
||||
import { isExpanded } from '../../util/DiUtil';
|
||||
|
||||
import {
|
||||
getExternalLabelMid,
|
||||
isLabelExternal,
|
||||
hasExternalLabel,
|
||||
isLabel
|
||||
} from '../../util/LabelUtil';
|
||||
|
||||
|
||||
export default function LabelEditingProvider(
|
||||
eventBus, bpmnFactory, canvas, directEditing,
|
||||
modeling, resizeHandles, textRenderer) {
|
||||
|
||||
this._bpmnFactory = bpmnFactory;
|
||||
this._canvas = canvas;
|
||||
this._modeling = modeling;
|
||||
this._textRenderer = textRenderer;
|
||||
|
||||
directEditing.registerProvider(this);
|
||||
|
||||
// listen to dblclick on non-root elements
|
||||
eventBus.on('element.dblclick', function(event) {
|
||||
activateDirectEdit(event.element, true);
|
||||
});
|
||||
|
||||
// complete on followup canvas operation
|
||||
eventBus.on([
|
||||
'element.mousedown',
|
||||
'drag.init',
|
||||
'canvas.viewbox.changing',
|
||||
'autoPlace',
|
||||
'popupMenu.open'
|
||||
], function(event) {
|
||||
|
||||
if (directEditing.isActive()) {
|
||||
directEditing.complete();
|
||||
}
|
||||
});
|
||||
|
||||
// cancel on command stack changes
|
||||
eventBus.on([ 'commandStack.changed' ], function(e) {
|
||||
if (directEditing.isActive()) {
|
||||
directEditing.cancel();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
eventBus.on('directEditing.activate', function(event) {
|
||||
resizeHandles.removeResizers();
|
||||
});
|
||||
|
||||
eventBus.on('create.end', 500, function(event) {
|
||||
|
||||
var element = event.shape,
|
||||
canExecute = event.context.canExecute,
|
||||
isTouch = event.isTouch;
|
||||
|
||||
// TODO(nikku): we need to find a way to support the
|
||||
// direct editing on mobile devices; right now this will
|
||||
// break for desworkflowediting on mobile devices
|
||||
// as it breaks the user interaction workflow
|
||||
|
||||
// TODO(nre): we should temporarily focus the edited element
|
||||
// here and release the focused viewport after the direct edit
|
||||
// operation is finished
|
||||
if (isTouch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canExecute) {
|
||||
return;
|
||||
}
|
||||
|
||||
activateDirectEdit(element);
|
||||
});
|
||||
|
||||
eventBus.on('autoPlace.end', 500, function(event) {
|
||||
activateDirectEdit(event.shape);
|
||||
});
|
||||
|
||||
|
||||
function activateDirectEdit(element, force) {
|
||||
if (force ||
|
||||
isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Group' ]) ||
|
||||
isCollapsedSubProcess(element)) {
|
||||
|
||||
directEditing.activate(element);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
LabelEditingProvider.$inject = [
|
||||
'eventBus',
|
||||
'bpmnFactory',
|
||||
'canvas',
|
||||
'directEditing',
|
||||
'modeling',
|
||||
'resizeHandles',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Activate direct editing for activities and text annotations.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Object} an object with properties bounds (position and size), text and options
|
||||
*/
|
||||
LabelEditingProvider.prototype.activate = function(element) {
|
||||
|
||||
// text
|
||||
var text = getLabel(element);
|
||||
|
||||
if (text === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
var context = {
|
||||
text: text
|
||||
};
|
||||
|
||||
// bounds
|
||||
var bounds = this.getEditingBBox(element);
|
||||
|
||||
assign(context, bounds);
|
||||
|
||||
var options = {};
|
||||
|
||||
// tasks
|
||||
if (
|
||||
isAny(element, [
|
||||
'bpmn:Task',
|
||||
'bpmn:Participant',
|
||||
'bpmn:Lane',
|
||||
'bpmn:CallActivity'
|
||||
]) ||
|
||||
isCollapsedSubProcess(element)
|
||||
) {
|
||||
assign(options, {
|
||||
centerVertically: true
|
||||
});
|
||||
}
|
||||
|
||||
// external labels
|
||||
if (isLabelExternal(element)) {
|
||||
assign(options, {
|
||||
autoResize: true
|
||||
});
|
||||
}
|
||||
|
||||
// text annotations
|
||||
if (is(element, 'bpmn:TextAnnotation')) {
|
||||
assign(options, {
|
||||
resizable: true,
|
||||
autoResize: true
|
||||
});
|
||||
}
|
||||
|
||||
assign(context, {
|
||||
options: options
|
||||
});
|
||||
|
||||
return context;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the editing bounding box based on the element's size and position
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Object} an object containing information about position
|
||||
* and size (fixed or minimum and/or maximum)
|
||||
*/
|
||||
LabelEditingProvider.prototype.getEditingBBox = function(element) {
|
||||
var canvas = this._canvas;
|
||||
|
||||
var target = element.label || element;
|
||||
|
||||
var bbox = canvas.getAbsoluteBBox(target);
|
||||
|
||||
var mid = {
|
||||
x: bbox.x + bbox.width / 2,
|
||||
y: bbox.y + bbox.height / 2
|
||||
};
|
||||
|
||||
// default position
|
||||
var bounds = { x: bbox.x, y: bbox.y };
|
||||
|
||||
var zoom = canvas.zoom();
|
||||
|
||||
var defaultStyle = this._textRenderer.getDefaultStyle(),
|
||||
externalStyle = this._textRenderer.getExternalStyle();
|
||||
|
||||
// take zoom into account
|
||||
var externalFontSize = externalStyle.fontSize * zoom,
|
||||
externalLineHeight = externalStyle.lineHeight,
|
||||
defaultFontSize = defaultStyle.fontSize * zoom,
|
||||
defaultLineHeight = defaultStyle.lineHeight;
|
||||
|
||||
var style = {
|
||||
fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
|
||||
fontWeight: this._textRenderer.getDefaultStyle().fontWeight
|
||||
};
|
||||
|
||||
// adjust for expanded pools AND lanes
|
||||
if (is(element, 'bpmn:Lane') || isExpandedPool(element)) {
|
||||
|
||||
assign(bounds, {
|
||||
width: bbox.height,
|
||||
height: 30 * zoom,
|
||||
x: bbox.x - bbox.height / 2 + (15 * zoom),
|
||||
y: mid.y - (30 * zoom) / 2
|
||||
});
|
||||
|
||||
assign(style, {
|
||||
fontSize: defaultFontSize + 'px',
|
||||
lineHeight: defaultLineHeight,
|
||||
paddingTop: (7 * zoom) + 'px',
|
||||
paddingBottom: (7 * zoom) + 'px',
|
||||
paddingLeft: (5 * zoom) + 'px',
|
||||
paddingRight: (5 * zoom) + 'px',
|
||||
transform: 'rotate(-90deg)'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// internal labels for tasks and collapsed call activities,
|
||||
// sub processes and participants
|
||||
if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity']) ||
|
||||
isCollapsedPool(element) ||
|
||||
isCollapsedSubProcess(element)) {
|
||||
|
||||
assign(bounds, {
|
||||
width: bbox.width,
|
||||
height: bbox.height
|
||||
});
|
||||
|
||||
assign(style, {
|
||||
fontSize: defaultFontSize + 'px',
|
||||
lineHeight: defaultLineHeight,
|
||||
paddingTop: (7 * zoom) + 'px',
|
||||
paddingBottom: (7 * zoom) + 'px',
|
||||
paddingLeft: (5 * zoom) + 'px',
|
||||
paddingRight: (5 * zoom) + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// internal labels for expanded sub processes
|
||||
if (isExpandedSubProcess(element)) {
|
||||
assign(bounds, {
|
||||
width: bbox.width,
|
||||
x: bbox.x
|
||||
});
|
||||
|
||||
assign(style, {
|
||||
fontSize: defaultFontSize + 'px',
|
||||
lineHeight: defaultLineHeight,
|
||||
paddingTop: (7 * zoom) + 'px',
|
||||
paddingBottom: (7 * zoom) + 'px',
|
||||
paddingLeft: (5 * zoom) + 'px',
|
||||
paddingRight: (5 * zoom) + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
var width = 90 * zoom,
|
||||
paddingTop = 7 * zoom,
|
||||
paddingBottom = 4 * zoom;
|
||||
|
||||
// external labels for events, data elements, gateways, groups and connections
|
||||
if (target.labelTarget) {
|
||||
assign(bounds, {
|
||||
width: width,
|
||||
height: bbox.height + paddingTop + paddingBottom,
|
||||
x: mid.x - width / 2,
|
||||
y: bbox.y - paddingTop
|
||||
});
|
||||
|
||||
assign(style, {
|
||||
fontSize: externalFontSize + 'px',
|
||||
lineHeight: externalLineHeight,
|
||||
paddingTop: paddingTop + 'px',
|
||||
paddingBottom: paddingBottom + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
// external label not yet created
|
||||
if (isLabelExternal(target)
|
||||
&& !hasExternalLabel(target)
|
||||
&& !isLabel(target)) {
|
||||
|
||||
var externalLabelMid = getExternalLabelMid(element);
|
||||
|
||||
var absoluteBBox = canvas.getAbsoluteBBox({
|
||||
x: externalLabelMid.x,
|
||||
y: externalLabelMid.y,
|
||||
width: 0,
|
||||
height: 0
|
||||
});
|
||||
|
||||
var height = externalFontSize + paddingTop + paddingBottom;
|
||||
|
||||
assign(bounds, {
|
||||
width: width,
|
||||
height: height,
|
||||
x: absoluteBBox.x - width / 2,
|
||||
y: absoluteBBox.y - height / 2
|
||||
});
|
||||
|
||||
assign(style, {
|
||||
fontSize: externalFontSize + 'px',
|
||||
lineHeight: externalLineHeight,
|
||||
paddingTop: paddingTop + 'px',
|
||||
paddingBottom: paddingBottom + 'px'
|
||||
});
|
||||
}
|
||||
|
||||
// text annotations
|
||||
if (is(element, 'bpmn:TextAnnotation')) {
|
||||
assign(bounds, {
|
||||
width: bbox.width,
|
||||
height: bbox.height,
|
||||
minWidth: 30 * zoom,
|
||||
minHeight: 10 * zoom
|
||||
});
|
||||
|
||||
assign(style, {
|
||||
textAlign: 'left',
|
||||
paddingTop: (5 * zoom) + 'px',
|
||||
paddingBottom: (7 * zoom) + 'px',
|
||||
paddingLeft: (7 * zoom) + 'px',
|
||||
paddingRight: (5 * zoom) + 'px',
|
||||
fontSize: defaultFontSize + 'px',
|
||||
lineHeight: defaultLineHeight
|
||||
});
|
||||
}
|
||||
|
||||
return { bounds: bounds, style: style };
|
||||
};
|
||||
|
||||
|
||||
LabelEditingProvider.prototype.update = function(
|
||||
element, newLabel,
|
||||
activeContextText, bounds) {
|
||||
|
||||
var newBounds,
|
||||
bbox;
|
||||
|
||||
if (is(element, 'bpmn:TextAnnotation')) {
|
||||
|
||||
bbox = this._canvas.getAbsoluteBBox(element);
|
||||
|
||||
newBounds = {
|
||||
x: element.x,
|
||||
y: element.y,
|
||||
width: element.width / bbox.width * bounds.width,
|
||||
height: element.height / bbox.height * bounds.height
|
||||
};
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:Group')) {
|
||||
|
||||
var businessObject = getBusinessObject(element);
|
||||
|
||||
// initialize categoryValue if not existing
|
||||
if (!businessObject.categoryValueRef) {
|
||||
|
||||
var rootElement = this._canvas.getRootElement(),
|
||||
definitions = getBusinessObject(rootElement).$parent;
|
||||
|
||||
var categoryValue = createCategoryValue(definitions, this._bpmnFactory);
|
||||
|
||||
getBusinessObject(element).categoryValueRef = categoryValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (isEmptyText(newLabel)) {
|
||||
newLabel = null;
|
||||
}
|
||||
|
||||
this._modeling.updateLabel(element, newLabel, newBounds);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
function isCollapsedSubProcess(element) {
|
||||
return is(element, 'bpmn:SubProcess') && !isExpanded(element);
|
||||
}
|
||||
|
||||
function isExpandedSubProcess(element) {
|
||||
return is(element, 'bpmn:SubProcess') && isExpanded(element);
|
||||
}
|
||||
|
||||
function isCollapsedPool(element) {
|
||||
return is(element, 'bpmn:Participant') && !isExpanded(element);
|
||||
}
|
||||
|
||||
function isExpandedPool(element) {
|
||||
return is(element, 'bpmn:Participant') && isExpanded(element);
|
||||
}
|
||||
|
||||
function isEmptyText(label) {
|
||||
return !label || !label.trim();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
function getLabelAttr(semantic) {
|
||||
if (
|
||||
is(semantic, 'bpmn:FlowElement') ||
|
||||
is(semantic, 'bpmn:Participant') ||
|
||||
is(semantic, 'bpmn:Lane') ||
|
||||
is(semantic, 'bpmn:SequenceFlow') ||
|
||||
is(semantic, 'bpmn:MessageFlow') ||
|
||||
is(semantic, 'bpmn:DataInput') ||
|
||||
is(semantic, 'bpmn:DataOutput')
|
||||
) {
|
||||
return 'name';
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:TextAnnotation')) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Group')) {
|
||||
return 'categoryValueRef';
|
||||
}
|
||||
}
|
||||
|
||||
function getCategoryValue(semantic) {
|
||||
var categoryValueRef = semantic['categoryValueRef'];
|
||||
|
||||
if (!categoryValueRef) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
return categoryValueRef.value || '';
|
||||
}
|
||||
|
||||
export function getLabel(element) {
|
||||
var semantic = element.businessObject,
|
||||
attr = getLabelAttr(semantic);
|
||||
|
||||
if (attr) {
|
||||
|
||||
if (attr === 'categoryValueRef') {
|
||||
|
||||
return getCategoryValue(semantic);
|
||||
}
|
||||
|
||||
return semantic[attr] || '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function setLabel(element, text, isExternal) {
|
||||
var semantic = element.businessObject,
|
||||
attr = getLabelAttr(semantic);
|
||||
|
||||
if (attr) {
|
||||
|
||||
if (attr === 'categoryValueRef') {
|
||||
semantic['categoryValueRef'].value = text;
|
||||
} else {
|
||||
semantic[attr] = text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
setLabel,
|
||||
getLabel
|
||||
} from '../LabelUtil';
|
||||
|
||||
import {
|
||||
getExternalLabelMid,
|
||||
isLabelExternal,
|
||||
hasExternalLabel,
|
||||
isLabel
|
||||
} from '../../../util/LabelUtil';
|
||||
|
||||
import {
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
var NULL_DIMENSIONS = {
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* A handler that updates the text of a BPMN element.
|
||||
*/
|
||||
export default function UpdateLabelHandler(modeling, textRenderer) {
|
||||
|
||||
/**
|
||||
* Set the label and return the changed elements.
|
||||
*
|
||||
* Element parameter can be label itself or connection (i.e. sequence flow).
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
* @param {String} text
|
||||
*/
|
||||
function setText(element, text) {
|
||||
|
||||
// external label if present
|
||||
var label = element.label || element;
|
||||
|
||||
var labelTarget = element.labelTarget || element;
|
||||
|
||||
setLabel(label, text, labelTarget !== label);
|
||||
|
||||
return [ label, labelTarget ];
|
||||
}
|
||||
|
||||
function preExecute(ctx) {
|
||||
var element = ctx.element,
|
||||
businessObject = element.businessObject,
|
||||
newLabel = ctx.newLabel;
|
||||
|
||||
if (!isLabel(element)
|
||||
&& isLabelExternal(element)
|
||||
&& !hasExternalLabel(element)
|
||||
&& !isEmptyText(newLabel)) {
|
||||
|
||||
// create label
|
||||
var paddingTop = 7;
|
||||
|
||||
var labelCenter = getExternalLabelMid(element);
|
||||
|
||||
labelCenter = {
|
||||
x: labelCenter.x,
|
||||
y: labelCenter.y + paddingTop
|
||||
};
|
||||
|
||||
modeling.createLabel(element, labelCenter, {
|
||||
id: businessObject.id + '_label',
|
||||
businessObject: businessObject
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function execute(ctx) {
|
||||
ctx.oldLabel = getLabel(ctx.element);
|
||||
return setText(ctx.element, ctx.newLabel);
|
||||
}
|
||||
|
||||
function revert(ctx) {
|
||||
return setText(ctx.element, ctx.oldLabel);
|
||||
}
|
||||
|
||||
function postExecute(ctx) {
|
||||
var element = ctx.element,
|
||||
label = element.label || element,
|
||||
newLabel = ctx.newLabel,
|
||||
newBounds = ctx.newBounds,
|
||||
hints = ctx.hints || {};
|
||||
|
||||
if (isLabel(label) && isEmptyText(newLabel)) {
|
||||
|
||||
if (hints.removeShape !== false) {
|
||||
modeling.removeShape(label, { unsetLabel: false });
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ignore internal labels for elements except text annotations
|
||||
if (!isLabelExternal(element) && !is(element, 'bpmn:TextAnnotation')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var text = getLabel(label);
|
||||
|
||||
// don't resize without text
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
// resize element based on label _or_ pre-defined bounds
|
||||
if (typeof newBounds === 'undefined') {
|
||||
newBounds = textRenderer.getExternalLabelBounds(label, text);
|
||||
}
|
||||
|
||||
// setting newBounds to false or _null_ will
|
||||
// disable the postExecute resize operation
|
||||
if (newBounds) {
|
||||
modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
|
||||
}
|
||||
}
|
||||
|
||||
// API
|
||||
|
||||
this.preExecute = preExecute;
|
||||
this.execute = execute;
|
||||
this.revert = revert;
|
||||
this.postExecute = postExecute;
|
||||
}
|
||||
|
||||
UpdateLabelHandler.$inject = [
|
||||
'modeling',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
// helpers ///////////////////////
|
||||
|
||||
function isEmptyText(label) {
|
||||
return !label || !label.trim();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
|
||||
import ResizeModule from 'diagram-js/lib/features/resize';
|
||||
import DirectEditingModule from 'diagram-js-direct-editing';
|
||||
|
||||
import LabelEditingProvider from './LabelEditingProvider';
|
||||
import LabelEditingPreview from './LabelEditingPreview';
|
||||
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
ChangeSupportModule,
|
||||
ResizeModule,
|
||||
DirectEditingModule
|
||||
],
|
||||
__init__: [
|
||||
'labelEditingProvider',
|
||||
'labelEditingPreview'
|
||||
],
|
||||
labelEditingProvider: [ 'type', LabelEditingProvider ],
|
||||
labelEditingPreview: [ 'type', LabelEditingPreview ]
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import {
|
||||
map,
|
||||
assign,
|
||||
pick
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from './util/ModelingUtil';
|
||||
|
||||
|
||||
export default function BpmnFactory(moddle) {
|
||||
this._model = moddle;
|
||||
}
|
||||
|
||||
BpmnFactory.$inject = [ 'moddle' ];
|
||||
|
||||
|
||||
BpmnFactory.prototype._needsId = function(element) {
|
||||
return isAny(element, [
|
||||
'bpmn:RootElement',
|
||||
'bpmn:FlowElement',
|
||||
'bpmn:MessageFlow',
|
||||
'bpmn:DataAssociation',
|
||||
'bpmn:Artifact',
|
||||
'bpmn:Participant',
|
||||
'bpmn:Lane',
|
||||
'bpmn:LaneSet',
|
||||
'bpmn:Process',
|
||||
'bpmn:Collaboration',
|
||||
'bpmndi:BPMNShape',
|
||||
'bpmndi:BPMNEdge',
|
||||
'bpmndi:BPMNDiagram',
|
||||
'bpmndi:BPMNPlane',
|
||||
'bpmn:Property',
|
||||
'bpmn:CategoryValue'
|
||||
]);
|
||||
};
|
||||
|
||||
BpmnFactory.prototype._ensureId = function(element) {
|
||||
|
||||
// generate semantic ids for elements
|
||||
// bpmn:SequenceFlow -> SequenceFlow_ID
|
||||
var prefix = (element.$type || '').replace(/^[^:]*:/g, '') + '_';
|
||||
|
||||
if (!element.id && this._needsId(element)) {
|
||||
element.id = this._model.ids.nextPrefixed(prefix, element);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BpmnFactory.prototype.create = function(type, attrs) {
|
||||
var element = this._model.create(type, attrs || {});
|
||||
|
||||
this._ensureId(element);
|
||||
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
BpmnFactory.prototype.createDiLabel = function() {
|
||||
return this.create('bpmndi:BPMNLabel', {
|
||||
bounds: this.createDiBounds()
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
BpmnFactory.prototype.createDiShape = function(semantic, bounds, attrs) {
|
||||
|
||||
return this.create('bpmndi:BPMNShape', assign({
|
||||
bpmnElement: semantic,
|
||||
bounds: this.createDiBounds(bounds)
|
||||
}, attrs));
|
||||
};
|
||||
|
||||
|
||||
BpmnFactory.prototype.createDiBounds = function(bounds) {
|
||||
return this.create('dc:Bounds', bounds);
|
||||
};
|
||||
|
||||
|
||||
BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
|
||||
var self = this;
|
||||
|
||||
return map(waypoints, function(pos) {
|
||||
return self.createDiWaypoint(pos);
|
||||
});
|
||||
};
|
||||
|
||||
BpmnFactory.prototype.createDiWaypoint = function(point) {
|
||||
return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
|
||||
};
|
||||
|
||||
|
||||
BpmnFactory.prototype.createDiEdge = function(semantic, waypoints, attrs) {
|
||||
return this.create('bpmndi:BPMNEdge', assign({
|
||||
bpmnElement: semantic
|
||||
}, attrs));
|
||||
};
|
||||
|
||||
BpmnFactory.prototype.createDiPlane = function(semantic) {
|
||||
return this.create('bpmndi:BPMNPlane', {
|
||||
bpmnElement: semantic
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,393 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
assign
|
||||
} from 'min-dash';
|
||||
|
||||
import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter';
|
||||
|
||||
import {
|
||||
repairConnection,
|
||||
withoutRedundantPoints
|
||||
} from 'diagram-js/lib/layout/ManhattanLayout';
|
||||
|
||||
import {
|
||||
getMid,
|
||||
getOrientation
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
isExpanded
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
|
||||
var BOUNDARY_TO_HOST_THRESHOLD = 40;
|
||||
|
||||
export default function BpmnLayouter() {}
|
||||
|
||||
inherits(BpmnLayouter, BaseLayouter);
|
||||
|
||||
|
||||
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
|
||||
hints = hints || {};
|
||||
|
||||
var source = hints.source || connection.source,
|
||||
target = hints.target || connection.target,
|
||||
waypoints = connection.waypoints,
|
||||
start = hints.connectionStart,
|
||||
end = hints.connectionEnd;
|
||||
|
||||
var manhattanOptions,
|
||||
updatedWaypoints;
|
||||
|
||||
if (!start) {
|
||||
start = getConnectionDocking(waypoints && waypoints[0], source);
|
||||
}
|
||||
|
||||
if (!end) {
|
||||
end = getConnectionDocking(waypoints && waypoints[waypoints.length - 1], target);
|
||||
}
|
||||
|
||||
// TODO(nikku): support vertical modeling
|
||||
// and invert preferredLayouts accordingly
|
||||
|
||||
if (is(connection, 'bpmn:Association') ||
|
||||
is(connection, 'bpmn:DataAssociation')) {
|
||||
|
||||
if (waypoints && !isCompensationAssociation(source, target)) {
|
||||
return [].concat([ start ], waypoints.slice(1, -1), [ end ]);
|
||||
}
|
||||
}
|
||||
|
||||
// manhattan layout sequence / message flows
|
||||
if (is(connection, 'bpmn:MessageFlow')) {
|
||||
manhattanOptions = getMessageFlowManhattanOptions(source, target);
|
||||
|
||||
} else
|
||||
|
||||
|
||||
// layout all connection between flow elements h:h,
|
||||
//
|
||||
// except for
|
||||
//
|
||||
// (1) outgoing of BoundaryEvents -> layout based on attach orientation and target orientation
|
||||
// (2) incoming / outgoing of Gateway -> v:h (outgoing), h:v (incoming)
|
||||
// (3) loops from / to the same element
|
||||
//
|
||||
if (is(connection, 'bpmn:SequenceFlow') ||
|
||||
isCompensationAssociation(source, target)) {
|
||||
|
||||
if (source === target) {
|
||||
manhattanOptions = {
|
||||
preferredLayouts: [ 'b:l' ]
|
||||
};
|
||||
} else
|
||||
|
||||
if (is(source, 'bpmn:BoundaryEvent')) {
|
||||
|
||||
manhattanOptions = {
|
||||
preferredLayouts: getBoundaryEventPreferredLayouts(source, target, end)
|
||||
};
|
||||
|
||||
} else
|
||||
|
||||
if (is(source, 'bpmn:Gateway')) {
|
||||
|
||||
manhattanOptions = {
|
||||
preferredLayouts: [ 'v:h' ]
|
||||
};
|
||||
} else
|
||||
|
||||
if (is(target, 'bpmn:Gateway')) {
|
||||
|
||||
manhattanOptions = {
|
||||
preferredLayouts: [ 'h:v' ]
|
||||
};
|
||||
}
|
||||
|
||||
else {
|
||||
manhattanOptions = {
|
||||
preferredLayouts: [ 'h:h' ]
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (manhattanOptions) {
|
||||
|
||||
manhattanOptions = assign(manhattanOptions, hints);
|
||||
|
||||
updatedWaypoints =
|
||||
withoutRedundantPoints(
|
||||
repairConnection(
|
||||
source, target,
|
||||
start, end,
|
||||
waypoints,
|
||||
manhattanOptions
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return updatedWaypoints || [ start, end ];
|
||||
};
|
||||
|
||||
|
||||
function getAttachOrientation(attachedElement) {
|
||||
|
||||
var hostElement = attachedElement.host,
|
||||
padding = -10;
|
||||
|
||||
return getOrientation(getMid(attachedElement), hostElement, padding);
|
||||
}
|
||||
|
||||
|
||||
function getMessageFlowManhattanOptions(source, target) {
|
||||
return {
|
||||
preferredLayouts: [ 'straight', 'v:v' ],
|
||||
preserveDocking: getMessageFlowPreserveDocking(source, target)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function getMessageFlowPreserveDocking(source, target) {
|
||||
|
||||
// (1) docking element connected to participant has precedence
|
||||
|
||||
if (is(target, 'bpmn:Participant')) {
|
||||
return 'source';
|
||||
}
|
||||
|
||||
if (is(source, 'bpmn:Participant')) {
|
||||
return 'target';
|
||||
}
|
||||
|
||||
// (2) docking element connected to expanded sub-process has precedence
|
||||
|
||||
if (isExpandedSubProcess(target)) {
|
||||
return 'source';
|
||||
}
|
||||
|
||||
if (isExpandedSubProcess(source)) {
|
||||
return 'target';
|
||||
}
|
||||
|
||||
// (3) docking event has precedence
|
||||
|
||||
if (is(target, 'bpmn:Event')) {
|
||||
return 'target';
|
||||
}
|
||||
|
||||
if (is(source, 'bpmn:Event')) {
|
||||
return 'source';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
function getConnectionDocking(point, shape) {
|
||||
return point ? (point.original || point) : getMid(shape);
|
||||
}
|
||||
|
||||
function isCompensationAssociation(source, target) {
|
||||
return is(target, 'bpmn:Activity') &&
|
||||
is(source, 'bpmn:BoundaryEvent') &&
|
||||
target.businessObject.isForCompensation;
|
||||
}
|
||||
|
||||
|
||||
function isExpandedSubProcess(element) {
|
||||
return is(element, 'bpmn:SubProcess') && isExpanded(element);
|
||||
}
|
||||
|
||||
function isSame(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
function isAnyOrientation(orientation, orientations) {
|
||||
return orientations.indexOf(orientation) !== -1;
|
||||
}
|
||||
|
||||
var oppositeOrientationMapping = {
|
||||
'top': 'bottom',
|
||||
'top-right': 'bottom-left',
|
||||
'top-left': 'bottom-right',
|
||||
'right': 'left',
|
||||
'bottom': 'top',
|
||||
'bottom-right': 'top-left',
|
||||
'bottom-left': 'top-right',
|
||||
'left': 'right'
|
||||
};
|
||||
|
||||
var orientationDirectionMapping = {
|
||||
top: 't',
|
||||
right: 'r',
|
||||
bottom: 'b',
|
||||
left: 'l'
|
||||
};
|
||||
|
||||
function getHorizontalOrientation(orientation) {
|
||||
var matches = /right|left/.exec(orientation);
|
||||
|
||||
return matches && matches[0];
|
||||
}
|
||||
|
||||
function getVerticalOrientation(orientation) {
|
||||
var matches = /top|bottom/.exec(orientation);
|
||||
|
||||
return matches && matches[0];
|
||||
}
|
||||
|
||||
function isOppositeOrientation(a, b) {
|
||||
return oppositeOrientationMapping[a] === b;
|
||||
}
|
||||
|
||||
function isOppositeHorizontalOrientation(a, b) {
|
||||
var horizontalOrientation = getHorizontalOrientation(a);
|
||||
|
||||
var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
|
||||
|
||||
return b.indexOf(oppositeHorizontalOrientation) !== -1;
|
||||
}
|
||||
|
||||
function isOppositeVerticalOrientation(a, b) {
|
||||
var verticalOrientation = getVerticalOrientation(a);
|
||||
|
||||
var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
|
||||
|
||||
return b.indexOf(oppositeVerticalOrientation) !== -1;
|
||||
}
|
||||
|
||||
function isHorizontalOrientation(orientation) {
|
||||
return orientation === 'right' || orientation === 'left';
|
||||
}
|
||||
|
||||
function getBoundaryEventPreferredLayouts(source, target, end) {
|
||||
var sourceMid = getMid(source),
|
||||
targetMid = getMid(target),
|
||||
attachOrientation = getAttachOrientation(source),
|
||||
sourceLayout,
|
||||
targetLayout;
|
||||
|
||||
var isLoop = isSame(source.host, target);
|
||||
|
||||
var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
|
||||
|
||||
var targetOrientation = getOrientation(targetMid, sourceMid, {
|
||||
x: source.width / 2 + target.width / 2,
|
||||
y: source.height / 2 + target.height / 2
|
||||
});
|
||||
|
||||
if (isLoop) {
|
||||
return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end);
|
||||
}
|
||||
|
||||
// source layout
|
||||
sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide);
|
||||
|
||||
// target layout
|
||||
targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide);
|
||||
|
||||
return [ sourceLayout + ':' + targetLayout ];
|
||||
}
|
||||
|
||||
function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) {
|
||||
|
||||
var sourceLayout = orientationDirectionMapping[attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation)],
|
||||
targetLayout;
|
||||
|
||||
if (attachedToSide) {
|
||||
if (isHorizontalOrientation(attachOrientation)) {
|
||||
targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b';
|
||||
} else {
|
||||
targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l';
|
||||
}
|
||||
} else {
|
||||
targetLayout = 'v';
|
||||
}
|
||||
|
||||
return [ sourceLayout + ':' + targetLayout ];
|
||||
}
|
||||
|
||||
function shouldConnectToSameSide(axis, source, target, end) {
|
||||
var threshold = BOUNDARY_TO_HOST_THRESHOLD;
|
||||
|
||||
return !(
|
||||
areCloseOnAxis(axis, end, target, threshold) ||
|
||||
areCloseOnAxis(axis, end, { x: target.x + target.width, y: target.y + target.height }, threshold) ||
|
||||
areCloseOnAxis(axis, end, getMid(source), threshold)
|
||||
);
|
||||
}
|
||||
|
||||
function areCloseOnAxis(axis, a, b, threshold) {
|
||||
return Math.abs(a[axis] - b[axis]) < threshold;
|
||||
}
|
||||
|
||||
function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) {
|
||||
|
||||
// attached to either top, right, bottom or left side
|
||||
if (attachedToSide) {
|
||||
return orientationDirectionMapping[attachOrientation];
|
||||
}
|
||||
|
||||
// attached to either top-right, top-left, bottom-right or bottom-left corner
|
||||
|
||||
// same vertical or opposite horizontal orientation
|
||||
if (isSame(
|
||||
getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
|
||||
) || isOppositeOrientation(
|
||||
getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
|
||||
)) {
|
||||
return orientationDirectionMapping[getVerticalOrientation(attachOrientation)];
|
||||
}
|
||||
|
||||
// fallback
|
||||
return orientationDirectionMapping[getHorizontalOrientation(attachOrientation)];
|
||||
}
|
||||
|
||||
function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) {
|
||||
|
||||
// attached to either top, right, bottom or left side
|
||||
if (attachedToSide) {
|
||||
if (isHorizontalOrientation(attachOrientation)) {
|
||||
// orientation is 'right' or 'left'
|
||||
|
||||
// opposite horizontal orientation or same orientation
|
||||
if (
|
||||
isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
|
||||
isSame(attachOrientation, targetOrientation)
|
||||
) {
|
||||
return 'h';
|
||||
}
|
||||
|
||||
// fallback
|
||||
return 'v';
|
||||
} else {
|
||||
// orientation is 'top' or 'bottom'
|
||||
|
||||
// opposite vertical orientation or same orientation
|
||||
if (
|
||||
isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
|
||||
isSame(attachOrientation, targetOrientation)
|
||||
) {
|
||||
return 'v';
|
||||
}
|
||||
|
||||
// fallback
|
||||
return 'h';
|
||||
}
|
||||
}
|
||||
// attached to either top-right, top-left, bottom-right or bottom-left corner
|
||||
|
||||
// orientation is 'right', 'left'
|
||||
// or same vertical orientation but also 'right' or 'left'
|
||||
if (isHorizontalOrientation(targetOrientation) ||
|
||||
(isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
|
||||
getHorizontalOrientation(targetOrientation))) {
|
||||
return 'h';
|
||||
} else {
|
||||
return 'v';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,726 @@
|
||||
import {
|
||||
assign,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
remove as collectionRemove,
|
||||
add as collectionAdd
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import {
|
||||
Label
|
||||
} from 'diagram-js/lib/model';
|
||||
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
/**
|
||||
* A handler responsible for updating the underlying BPMN 2.0 XML + DI
|
||||
* once changes on the diagram happen
|
||||
*/
|
||||
export default function BpmnUpdater(
|
||||
eventBus, bpmnFactory, connectionDocking,
|
||||
translate) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
this._bpmnFactory = bpmnFactory;
|
||||
this._translate = translate;
|
||||
|
||||
var self = this;
|
||||
|
||||
|
||||
|
||||
// connection cropping //////////////////////
|
||||
|
||||
// crop connection ends during create/update
|
||||
function cropConnection(e) {
|
||||
var context = e.context,
|
||||
connection;
|
||||
|
||||
if (!context.cropped) {
|
||||
connection = context.connection;
|
||||
connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
|
||||
context.cropped = true;
|
||||
}
|
||||
}
|
||||
|
||||
this.executed([
|
||||
'connection.layout',
|
||||
'connection.create'
|
||||
], cropConnection);
|
||||
|
||||
this.reverted([ 'connection.layout' ], function(e) {
|
||||
delete e.context.cropped;
|
||||
});
|
||||
|
||||
|
||||
|
||||
// BPMN + DI update //////////////////////
|
||||
|
||||
|
||||
// update parent
|
||||
function updateParent(e) {
|
||||
var context = e.context;
|
||||
|
||||
self.updateParent(context.shape || context.connection, context.oldParent);
|
||||
}
|
||||
|
||||
function reverseUpdateParent(e) {
|
||||
var context = e.context;
|
||||
|
||||
var element = context.shape || context.connection,
|
||||
// oldParent is the (old) new parent, because we are undoing
|
||||
oldParent = context.parent || context.newParent;
|
||||
|
||||
self.updateParent(element, oldParent);
|
||||
}
|
||||
|
||||
this.executed([
|
||||
'shape.move',
|
||||
'shape.create',
|
||||
'shape.delete',
|
||||
'connection.create',
|
||||
'connection.move',
|
||||
'connection.delete'
|
||||
], ifBpmn(updateParent));
|
||||
|
||||
this.reverted([
|
||||
'shape.move',
|
||||
'shape.create',
|
||||
'shape.delete',
|
||||
'connection.create',
|
||||
'connection.move',
|
||||
'connection.delete'
|
||||
], ifBpmn(reverseUpdateParent));
|
||||
|
||||
/*
|
||||
* ## Updating Parent
|
||||
*
|
||||
* When morphing a Process into a Collaboration or vice-versa,
|
||||
* make sure that both the *semantic* and *di* parent of each element
|
||||
* is updated.
|
||||
*
|
||||
*/
|
||||
function updateRoot(event) {
|
||||
var context = event.context,
|
||||
oldRoot = context.oldRoot,
|
||||
children = oldRoot.children;
|
||||
|
||||
forEach(children, function(child) {
|
||||
if (is(child, 'bpmn:BaseElement')) {
|
||||
self.updateParent(child);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.executed([ 'canvas.updateRoot' ], updateRoot);
|
||||
this.reverted([ 'canvas.updateRoot' ], updateRoot);
|
||||
|
||||
|
||||
// update bounds
|
||||
function updateBounds(e) {
|
||||
var shape = e.context.shape;
|
||||
|
||||
if (!is(shape, 'bpmn:BaseElement')) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.updateBounds(shape);
|
||||
}
|
||||
|
||||
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
|
||||
|
||||
// exclude labels because they're handled separately during shape.changed
|
||||
if (event.context.shape.type === 'label') {
|
||||
return;
|
||||
}
|
||||
|
||||
updateBounds(event);
|
||||
}));
|
||||
|
||||
this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
|
||||
|
||||
// exclude labels because they're handled separately during shape.changed
|
||||
if (event.context.shape.type === 'label') {
|
||||
return;
|
||||
}
|
||||
|
||||
updateBounds(event);
|
||||
}));
|
||||
|
||||
// Handle labels separately. This is necessary, because the label bounds have to be updated
|
||||
// every time its shape changes, not only on move, create and resize.
|
||||
eventBus.on('shape.changed', function(event) {
|
||||
if (event.element.type === 'label') {
|
||||
updateBounds({ context: { shape: event.element } });
|
||||
}
|
||||
});
|
||||
|
||||
// attach / detach connection
|
||||
function updateConnection(e) {
|
||||
self.updateConnection(e.context);
|
||||
}
|
||||
|
||||
this.executed([
|
||||
'connection.create',
|
||||
'connection.move',
|
||||
'connection.delete',
|
||||
'connection.reconnectEnd',
|
||||
'connection.reconnectStart'
|
||||
], ifBpmn(updateConnection));
|
||||
|
||||
this.reverted([
|
||||
'connection.create',
|
||||
'connection.move',
|
||||
'connection.delete',
|
||||
'connection.reconnectEnd',
|
||||
'connection.reconnectStart'
|
||||
], ifBpmn(updateConnection));
|
||||
|
||||
|
||||
// update waypoints
|
||||
function updateConnectionWaypoints(e) {
|
||||
self.updateConnectionWaypoints(e.context.connection);
|
||||
}
|
||||
|
||||
this.executed([
|
||||
'connection.layout',
|
||||
'connection.move',
|
||||
'connection.updateWaypoints',
|
||||
], ifBpmn(updateConnectionWaypoints));
|
||||
|
||||
this.reverted([
|
||||
'connection.layout',
|
||||
'connection.move',
|
||||
'connection.updateWaypoints',
|
||||
], ifBpmn(updateConnectionWaypoints));
|
||||
|
||||
|
||||
// update Default & Conditional flows
|
||||
this.executed([
|
||||
'connection.reconnectEnd',
|
||||
'connection.reconnectStart'
|
||||
], ifBpmn(function(e) {
|
||||
var context = e.context,
|
||||
connection = context.connection,
|
||||
businessObject = getBusinessObject(connection),
|
||||
oldSource = getBusinessObject(context.oldSource),
|
||||
oldTarget = getBusinessObject(context.oldTarget),
|
||||
newSource = getBusinessObject(connection.source),
|
||||
newTarget = getBusinessObject(connection.target);
|
||||
|
||||
if (oldSource === newSource || oldTarget === newTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
// on reconnectStart -> default flow
|
||||
if (oldSource && oldSource.default === businessObject) {
|
||||
context.default = oldSource.default;
|
||||
oldSource.default = undefined;
|
||||
}
|
||||
|
||||
// on reconnectEnd -> default flow
|
||||
if ((businessObject.sourceRef && businessObject.sourceRef.default) &&
|
||||
!(is(newTarget, 'bpmn:Activity') ||
|
||||
is(newTarget, 'bpmn:EndEvent') ||
|
||||
is(newTarget, 'bpmn:Gateway') ||
|
||||
is(newTarget, 'bpmn:IntermediateThrowEvent'))) {
|
||||
context.default = businessObject.sourceRef.default;
|
||||
businessObject.sourceRef.default = undefined;
|
||||
}
|
||||
|
||||
// on reconnectStart -> conditional flow
|
||||
if (oldSource && (businessObject.conditionExpression) &&
|
||||
!(is(newSource, 'bpmn:Activity') ||
|
||||
is(newSource, 'bpmn:Gateway'))) {
|
||||
context.conditionExpression = businessObject.conditionExpression;
|
||||
businessObject.conditionExpression = undefined;
|
||||
}
|
||||
|
||||
// on reconnectEnd -> conditional flow
|
||||
if (oldTarget && (businessObject.conditionExpression) &&
|
||||
!(is(newTarget, 'bpmn:Activity') ||
|
||||
is(newTarget, 'bpmn:EndEvent') ||
|
||||
is(newTarget, 'bpmn:Gateway') ||
|
||||
is(newTarget, 'bpmn:IntermediateThrowEvent'))) {
|
||||
context.conditionExpression = businessObject.conditionExpression;
|
||||
businessObject.conditionExpression = undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
this.reverted([
|
||||
'connection.reconnectEnd',
|
||||
'connection.reconnectStart'
|
||||
], ifBpmn(function(e) {
|
||||
var context = e.context,
|
||||
connection = context.connection,
|
||||
businessObject = getBusinessObject(connection),
|
||||
newSource = getBusinessObject(connection.source);
|
||||
|
||||
// default flow
|
||||
if (context.default) {
|
||||
if (is(newSource, 'bpmn:ExclusiveGateway') || is(newSource, 'bpmn:InclusiveGateway') ||
|
||||
is(newSource, 'bpmn:Activity')) {
|
||||
newSource.default = context.default;
|
||||
}
|
||||
}
|
||||
|
||||
// conditional flow
|
||||
if (context.conditionExpression && is(newSource, 'bpmn:Activity')) {
|
||||
businessObject.conditionExpression = context.conditionExpression;
|
||||
}
|
||||
}));
|
||||
|
||||
// update attachments
|
||||
function updateAttachment(e) {
|
||||
self.updateAttachment(e.context);
|
||||
}
|
||||
|
||||
this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
|
||||
this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
|
||||
}
|
||||
|
||||
inherits(BpmnUpdater, CommandInterceptor);
|
||||
|
||||
BpmnUpdater.$inject = [
|
||||
'eventBus',
|
||||
'bpmnFactory',
|
||||
'connectionDocking',
|
||||
'translate'
|
||||
];
|
||||
|
||||
|
||||
// implementation //////////////////////
|
||||
|
||||
BpmnUpdater.prototype.updateAttachment = function(context) {
|
||||
|
||||
var shape = context.shape,
|
||||
businessObject = shape.businessObject,
|
||||
host = shape.host;
|
||||
|
||||
businessObject.attachedToRef = host && host.businessObject;
|
||||
};
|
||||
|
||||
BpmnUpdater.prototype.updateParent = function(element, oldParent) {
|
||||
// do not update BPMN 2.0 label parent
|
||||
if (element instanceof Label) {
|
||||
return;
|
||||
}
|
||||
|
||||
// data stores in collaborations are handled seperately by DataStoreBehavior
|
||||
if (is(element, 'bpmn:DataStoreReference') &&
|
||||
element.parent &&
|
||||
is(element.parent, 'bpmn:Collaboration')) {
|
||||
return;
|
||||
}
|
||||
|
||||
var parentShape = element.parent;
|
||||
|
||||
var businessObject = element.businessObject,
|
||||
parentBusinessObject = parentShape && parentShape.businessObject,
|
||||
parentDi = parentBusinessObject && parentBusinessObject.di;
|
||||
|
||||
if (is(element, 'bpmn:FlowNode')) {
|
||||
this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:DataOutputAssociation')) {
|
||||
if (element.source) {
|
||||
parentBusinessObject = element.source.businessObject;
|
||||
} else {
|
||||
parentBusinessObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:DataInputAssociation')) {
|
||||
if (element.target) {
|
||||
parentBusinessObject = element.target.businessObject;
|
||||
} else {
|
||||
parentBusinessObject = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.updateSemanticParent(businessObject, parentBusinessObject);
|
||||
|
||||
if (is(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
|
||||
this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
|
||||
}
|
||||
|
||||
this.updateDiParent(businessObject.di, parentDi);
|
||||
};
|
||||
|
||||
|
||||
BpmnUpdater.prototype.updateBounds = function(shape) {
|
||||
|
||||
var di = shape.businessObject.di;
|
||||
|
||||
var target = (shape instanceof Label) ? this._getLabel(di) : di;
|
||||
|
||||
var bounds = target.bounds;
|
||||
|
||||
if (!bounds) {
|
||||
bounds = this._bpmnFactory.createDiBounds();
|
||||
target.set('bounds', bounds);
|
||||
}
|
||||
|
||||
assign(bounds, {
|
||||
x: shape.x,
|
||||
y: shape.y,
|
||||
width: shape.width,
|
||||
height: shape.height
|
||||
});
|
||||
};
|
||||
|
||||
BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
|
||||
|
||||
if (oldContainment === newContainment) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldRefs, newRefs;
|
||||
|
||||
if (is (oldContainment, 'bpmn:Lane')) {
|
||||
oldRefs = oldContainment.get('flowNodeRef');
|
||||
collectionRemove(oldRefs, businessObject);
|
||||
}
|
||||
|
||||
if (is(newContainment, 'bpmn:Lane')) {
|
||||
newRefs = newContainment.get('flowNodeRef');
|
||||
collectionAdd(newRefs, businessObject);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// update existing sourceElement and targetElement di information
|
||||
BpmnUpdater.prototype.updateDiConnection = function(di, newSource, newTarget) {
|
||||
|
||||
if (di.sourceElement && di.sourceElement.bpmnElement !== newSource) {
|
||||
di.sourceElement = newSource && newSource.di;
|
||||
}
|
||||
|
||||
if (di.targetElement && di.targetElement.bpmnElement !== newTarget) {
|
||||
di.targetElement = newTarget && newTarget.di;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
|
||||
|
||||
if (parentDi && !is(parentDi, 'bpmndi:BPMNPlane')) {
|
||||
parentDi = parentDi.$parent;
|
||||
}
|
||||
|
||||
if (di.$parent === parentDi) {
|
||||
return;
|
||||
}
|
||||
|
||||
var planeElements = (parentDi || di.$parent).get('planeElement');
|
||||
|
||||
if (parentDi) {
|
||||
planeElements.push(di);
|
||||
di.$parent = parentDi;
|
||||
} else {
|
||||
collectionRemove(planeElements, di);
|
||||
di.$parent = null;
|
||||
}
|
||||
};
|
||||
|
||||
function getDefinitions(element) {
|
||||
while (element && !is(element, 'bpmn:Definitions')) {
|
||||
element = element.$parent;
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
BpmnUpdater.prototype.getLaneSet = function(container) {
|
||||
|
||||
var laneSet, laneSets;
|
||||
|
||||
// bpmn:Lane
|
||||
if (is(container, 'bpmn:Lane')) {
|
||||
laneSet = container.childLaneSet;
|
||||
|
||||
if (!laneSet) {
|
||||
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
|
||||
container.childLaneSet = laneSet;
|
||||
laneSet.$parent = container;
|
||||
}
|
||||
|
||||
return laneSet;
|
||||
}
|
||||
|
||||
// bpmn:Participant
|
||||
if (is(container, 'bpmn:Participant')) {
|
||||
container = container.processRef;
|
||||
}
|
||||
|
||||
// bpmn:FlowElementsContainer
|
||||
laneSets = container.get('laneSets');
|
||||
laneSet = laneSets[0];
|
||||
|
||||
if (!laneSet) {
|
||||
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
|
||||
laneSet.$parent = container;
|
||||
laneSets.push(laneSet);
|
||||
}
|
||||
|
||||
return laneSet;
|
||||
};
|
||||
|
||||
BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {
|
||||
|
||||
var containment,
|
||||
translate = this._translate;
|
||||
|
||||
if (businessObject.$parent === newParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:DataInput') || is(businessObject, 'bpmn:DataOutput')) {
|
||||
|
||||
if (is(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
|
||||
newParent = newParent.processRef;
|
||||
}
|
||||
|
||||
// already in correct ioSpecification
|
||||
if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:Lane')) {
|
||||
|
||||
if (newParent) {
|
||||
newParent = this.getLaneSet(newParent);
|
||||
}
|
||||
|
||||
containment = 'lanes';
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:FlowElement')) {
|
||||
|
||||
if (newParent) {
|
||||
|
||||
if (is(newParent, 'bpmn:Participant')) {
|
||||
newParent = newParent.processRef;
|
||||
} else
|
||||
|
||||
if (is(newParent, 'bpmn:Lane')) {
|
||||
do {
|
||||
// unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
|
||||
newParent = newParent.$parent.$parent;
|
||||
} while (is(newParent, 'bpmn:Lane'));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
containment = 'flowElements';
|
||||
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:Artifact')) {
|
||||
|
||||
while (newParent &&
|
||||
!is(newParent, 'bpmn:Process') &&
|
||||
!is(newParent, 'bpmn:SubProcess') &&
|
||||
!is(newParent, 'bpmn:Collaboration')) {
|
||||
|
||||
if (is(newParent, 'bpmn:Participant')) {
|
||||
newParent = newParent.processRef;
|
||||
break;
|
||||
} else {
|
||||
newParent = newParent.$parent;
|
||||
}
|
||||
}
|
||||
|
||||
containment = 'artifacts';
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:MessageFlow')) {
|
||||
containment = 'messageFlows';
|
||||
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:Participant')) {
|
||||
containment = 'participants';
|
||||
|
||||
// make sure the participants process is properly attached / detached
|
||||
// from the XML document
|
||||
|
||||
var process = businessObject.processRef,
|
||||
definitions;
|
||||
|
||||
if (process) {
|
||||
definitions = getDefinitions(businessObject.$parent || newParent);
|
||||
|
||||
if (businessObject.$parent) {
|
||||
collectionRemove(definitions.get('rootElements'), process);
|
||||
process.$parent = null;
|
||||
}
|
||||
|
||||
if (newParent) {
|
||||
collectionAdd(definitions.get('rootElements'), process);
|
||||
process.$parent = definitions;
|
||||
}
|
||||
}
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:DataOutputAssociation')) {
|
||||
containment = 'dataOutputAssociations';
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:DataInputAssociation')) {
|
||||
containment = 'dataInputAssociations';
|
||||
}
|
||||
|
||||
if (!containment) {
|
||||
throw new Error(translate(
|
||||
'no parent for {element} in {parent}',
|
||||
{
|
||||
element: businessObject.id,
|
||||
parent: newParent.id
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
var children;
|
||||
|
||||
if (businessObject.$parent) {
|
||||
// remove from old parent
|
||||
children = businessObject.$parent.get(containment);
|
||||
collectionRemove(children, businessObject);
|
||||
}
|
||||
|
||||
if (!newParent) {
|
||||
businessObject.$parent = null;
|
||||
} else {
|
||||
// add to new parent
|
||||
children = newParent.get(containment);
|
||||
children.push(businessObject);
|
||||
businessObject.$parent = newParent;
|
||||
}
|
||||
|
||||
if (visualParent) {
|
||||
var diChildren = visualParent.get(containment);
|
||||
|
||||
collectionRemove(children, businessObject);
|
||||
|
||||
if (newParent) {
|
||||
|
||||
if (!diChildren) {
|
||||
diChildren = [];
|
||||
newParent.set(containment, diChildren);
|
||||
}
|
||||
|
||||
diChildren.push(businessObject);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
|
||||
connection.businessObject.di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
|
||||
};
|
||||
|
||||
|
||||
BpmnUpdater.prototype.updateConnection = function(context) {
|
||||
|
||||
var connection = context.connection,
|
||||
businessObject = getBusinessObject(connection),
|
||||
newSource = getBusinessObject(connection.source),
|
||||
newTarget = getBusinessObject(connection.target),
|
||||
visualParent;
|
||||
|
||||
if (!is(businessObject, 'bpmn:DataAssociation')) {
|
||||
|
||||
var inverseSet = is(businessObject, 'bpmn:SequenceFlow');
|
||||
|
||||
if (businessObject.sourceRef !== newSource) {
|
||||
if (inverseSet) {
|
||||
collectionRemove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);
|
||||
|
||||
if (newSource && newSource.get('outgoing')) {
|
||||
newSource.get('outgoing').push(businessObject);
|
||||
}
|
||||
}
|
||||
|
||||
businessObject.sourceRef = newSource;
|
||||
}
|
||||
|
||||
if (businessObject.targetRef !== newTarget) {
|
||||
if (inverseSet) {
|
||||
collectionRemove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);
|
||||
|
||||
if (newTarget && newTarget.get('incoming')) {
|
||||
newTarget.get('incoming').push(businessObject);
|
||||
}
|
||||
}
|
||||
|
||||
businessObject.targetRef = newTarget;
|
||||
}
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:DataInputAssociation')) {
|
||||
// handle obnoxious isMsome sourceRef
|
||||
businessObject.get('sourceRef')[0] = newSource;
|
||||
|
||||
visualParent = context.parent || context.newParent || newTarget;
|
||||
|
||||
this.updateSemanticParent(businessObject, newTarget, visualParent);
|
||||
} else
|
||||
|
||||
if (is(businessObject, 'bpmn:DataOutputAssociation')) {
|
||||
visualParent = context.parent || context.newParent || newSource;
|
||||
|
||||
this.updateSemanticParent(businessObject, newSource, visualParent);
|
||||
|
||||
// targetRef = new target
|
||||
businessObject.targetRef = newTarget;
|
||||
}
|
||||
|
||||
this.updateConnectionWaypoints(connection);
|
||||
|
||||
this.updateDiConnection(businessObject.di, newSource, newTarget);
|
||||
};
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
BpmnUpdater.prototype._getLabel = function(di) {
|
||||
if (!di.label) {
|
||||
di.label = this._bpmnFactory.createDiLabel();
|
||||
}
|
||||
|
||||
return di.label;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make sure the event listener is only called
|
||||
* if the touched element is a BPMN element.
|
||||
*
|
||||
* @param {Function} fn
|
||||
* @return {Function} guarded function
|
||||
*/
|
||||
function ifBpmn(fn) {
|
||||
|
||||
return function(event) {
|
||||
|
||||
var context = event.context,
|
||||
element = context.shape || context.connection;
|
||||
|
||||
if (is(element, 'bpmn:BaseElement')) {
|
||||
fn(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
import {
|
||||
assign,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isExpanded
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import BaseElementFactory from 'diagram-js/lib/core/ElementFactory';
|
||||
|
||||
import {
|
||||
DEFAULT_LABEL_SIZE
|
||||
} from '../../util/LabelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* A bpmn-aware factory for diagram-js shapes
|
||||
*/
|
||||
export default function ElementFactory(bpmnFactory, moddle, translate) {
|
||||
BaseElementFactory.call(this);
|
||||
|
||||
this._bpmnFactory = bpmnFactory;
|
||||
this._moddle = moddle;
|
||||
this._translate = translate;
|
||||
}
|
||||
|
||||
inherits(ElementFactory, BaseElementFactory);
|
||||
|
||||
ElementFactory.$inject = [
|
||||
'bpmnFactory',
|
||||
'moddle',
|
||||
'translate'
|
||||
];
|
||||
|
||||
ElementFactory.prototype.baseCreate = BaseElementFactory.prototype.create;
|
||||
|
||||
ElementFactory.prototype.create = function(elementType, attrs) {
|
||||
// no special magic for labels,
|
||||
// we assume their businessObjects have already been created
|
||||
// and wired via attrs
|
||||
if (elementType === 'label') {
|
||||
return this.baseCreate(elementType, assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs));
|
||||
}
|
||||
|
||||
return this.createBpmnElement(elementType, attrs);
|
||||
};
|
||||
|
||||
ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
|
||||
var size,
|
||||
translate = this._translate;
|
||||
|
||||
attrs = attrs || {};
|
||||
|
||||
var businessObject = attrs.businessObject;
|
||||
|
||||
if (!businessObject) {
|
||||
if (!attrs.type) {
|
||||
throw new Error(translate('no shape type specified'));
|
||||
}
|
||||
|
||||
businessObject = this._bpmnFactory.create(attrs.type);
|
||||
}
|
||||
|
||||
if (!businessObject.di) {
|
||||
if (elementType === 'root') {
|
||||
businessObject.di = this._bpmnFactory.createDiPlane(businessObject, [], {
|
||||
id: businessObject.id + '_di'
|
||||
});
|
||||
} else
|
||||
if (elementType === 'connection') {
|
||||
businessObject.di = this._bpmnFactory.createDiEdge(businessObject, [], {
|
||||
id: businessObject.id + '_di'
|
||||
});
|
||||
} else {
|
||||
businessObject.di = this._bpmnFactory.createDiShape(businessObject, {}, {
|
||||
id: businessObject.id + '_di'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:Group')) {
|
||||
attrs = assign({
|
||||
isFrame: true
|
||||
}, attrs);
|
||||
}
|
||||
|
||||
if (attrs.colors) {
|
||||
assign(businessObject.di, attrs.colors);
|
||||
|
||||
delete attrs.colors;
|
||||
}
|
||||
|
||||
applyAttributes(businessObject, attrs, [
|
||||
'processRef',
|
||||
'isInterrupting',
|
||||
'associationDirection',
|
||||
'isForCompensation'
|
||||
]);
|
||||
|
||||
if (attrs.isExpanded) {
|
||||
applyAttribute(businessObject.di, attrs, 'isExpanded');
|
||||
}
|
||||
|
||||
if (is(businessObject, 'bpmn:ExclusiveGateway')) {
|
||||
businessObject.di.isMarkerVisible = true;
|
||||
}
|
||||
|
||||
var eventDefinitions,
|
||||
newEventDefinition;
|
||||
|
||||
if (attrs.eventDefinitionType) {
|
||||
eventDefinitions = businessObject.get('eventDefinitions') || [];
|
||||
newEventDefinition = this._moddle.create(attrs.eventDefinitionType);
|
||||
|
||||
if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') {
|
||||
newEventDefinition.condition = this._moddle.create('bpmn:FormalExpression');
|
||||
}
|
||||
|
||||
eventDefinitions.push(newEventDefinition);
|
||||
|
||||
newEventDefinition.$parent = businessObject;
|
||||
businessObject.eventDefinitions = eventDefinitions;
|
||||
|
||||
delete attrs.eventDefinitionType;
|
||||
}
|
||||
|
||||
size = this._getDefaultSize(businessObject);
|
||||
|
||||
attrs = assign({
|
||||
businessObject: businessObject,
|
||||
id: businessObject.id
|
||||
}, size, attrs);
|
||||
|
||||
return this.baseCreate(elementType, attrs);
|
||||
};
|
||||
|
||||
|
||||
ElementFactory.prototype._getDefaultSize = function(semantic) {
|
||||
|
||||
if (is(semantic, 'bpmn:SubProcess')) {
|
||||
|
||||
if (isExpanded(semantic)) {
|
||||
return { width: 350, height: 200 };
|
||||
} else {
|
||||
return { width: 100, height: 80 };
|
||||
}
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Task')) {
|
||||
return { width: 100, height: 80 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Gateway')) {
|
||||
return { width: 50, height: 50 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Event')) {
|
||||
return { width: 36, height: 36 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Participant')) {
|
||||
if (!isExpanded(semantic)) {
|
||||
return { width: 400, height: 60 };
|
||||
} else {
|
||||
return { width: 600, height: 250 };
|
||||
}
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Lane')) {
|
||||
return { width: 400, height: 100 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:DataObjectReference')) {
|
||||
return { width: 36, height: 50 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:DataStoreReference')) {
|
||||
return { width: 50, height: 50 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:TextAnnotation')) {
|
||||
return { width: 100, height: 30 };
|
||||
}
|
||||
|
||||
if (is(semantic, 'bpmn:Group')) {
|
||||
return { width: 300, height: 300 };
|
||||
}
|
||||
|
||||
return { width: 100, height: 80 };
|
||||
};
|
||||
|
||||
|
||||
ElementFactory.prototype.createParticipantShape = function(collapsed) {
|
||||
|
||||
var attrs = { type: 'bpmn:Participant' };
|
||||
|
||||
if (!collapsed) {
|
||||
attrs.processRef = this._bpmnFactory.create('bpmn:Process');
|
||||
}
|
||||
|
||||
return this.createShape(attrs);
|
||||
};
|
||||
|
||||
|
||||
// helpers //////////////////////
|
||||
|
||||
/**
|
||||
* Apply attributes from a map to the given element,
|
||||
* remove attribute from the map on application.
|
||||
*
|
||||
* @param {Base} element
|
||||
* @param {Object} attrs (in/out map of attributes)
|
||||
* @param {Array<String>} attributeNames name of attributes to apply
|
||||
*/
|
||||
function applyAttributes(element, attrs, attributeNames) {
|
||||
|
||||
forEach(attributeNames, function(property) {
|
||||
if (attrs[property] !== undefined) {
|
||||
applyAttribute(element, attrs, property);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply named property to element and drain it from the attrs
|
||||
* collection.
|
||||
*
|
||||
* @param {Base} element
|
||||
* @param {Object} attrs (in/out map of attributes)
|
||||
* @param {String} attributeName to apply
|
||||
*/
|
||||
function applyAttribute(element, attrs, attributeName) {
|
||||
element[attributeName] = attrs[attributeName];
|
||||
|
||||
delete attrs[attributeName];
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import BaseModeling from 'diagram-js/lib/features/modeling/Modeling';
|
||||
|
||||
import UpdatePropertiesHandler from './cmd/UpdatePropertiesHandler';
|
||||
import UpdateCanvasRootHandler from './cmd/UpdateCanvasRootHandler';
|
||||
import AddLaneHandler from './cmd/AddLaneHandler';
|
||||
import SplitLaneHandler from './cmd/SplitLaneHandler';
|
||||
import ResizeLaneHandler from './cmd/ResizeLaneHandler';
|
||||
import UpdateFlowNodeRefsHandler from './cmd/UpdateFlowNodeRefsHandler';
|
||||
import IdClaimHandler from './cmd/IdClaimHandler';
|
||||
import SetColorHandler from './cmd/SetColorHandler';
|
||||
|
||||
import UpdateLabelHandler from '../label-editing/cmd/UpdateLabelHandler';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN 2.0 modeling features activator
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {ElementFactory} elementFactory
|
||||
* @param {CommandStack} commandStack
|
||||
* @param {BpmnRules} bpmnRules
|
||||
*/
|
||||
export default function Modeling(
|
||||
eventBus, elementFactory, commandStack,
|
||||
bpmnRules) {
|
||||
|
||||
BaseModeling.call(this, eventBus, elementFactory, commandStack);
|
||||
|
||||
this._bpmnRules = bpmnRules;
|
||||
}
|
||||
|
||||
inherits(Modeling, BaseModeling);
|
||||
|
||||
Modeling.$inject = [
|
||||
'eventBus',
|
||||
'elementFactory',
|
||||
'commandStack',
|
||||
'bpmnRules'
|
||||
];
|
||||
|
||||
|
||||
Modeling.prototype.getHandlers = function() {
|
||||
var handlers = BaseModeling.prototype.getHandlers.call(this);
|
||||
|
||||
handlers['element.updateProperties'] = UpdatePropertiesHandler;
|
||||
handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
|
||||
handlers['lane.add'] = AddLaneHandler;
|
||||
handlers['lane.resize'] = ResizeLaneHandler;
|
||||
handlers['lane.split'] = SplitLaneHandler;
|
||||
handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler;
|
||||
handlers['id.updateClaim'] = IdClaimHandler;
|
||||
handlers['element.setColor'] = SetColorHandler;
|
||||
handlers['element.updateLabel'] = UpdateLabelHandler;
|
||||
|
||||
return handlers;
|
||||
};
|
||||
|
||||
|
||||
Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
|
||||
this._commandStack.execute('element.updateLabel', {
|
||||
element: element,
|
||||
newLabel: newLabel,
|
||||
newBounds: newBounds,
|
||||
hints: hints || {}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Modeling.prototype.connect = function(source, target, attrs, hints) {
|
||||
|
||||
var bpmnRules = this._bpmnRules;
|
||||
|
||||
if (!attrs) {
|
||||
attrs = bpmnRules.canConnect(source, target);
|
||||
}
|
||||
|
||||
if (!attrs) {
|
||||
return;
|
||||
}
|
||||
|
||||
return this.createConnection(source, target, attrs, source.parent, hints);
|
||||
};
|
||||
|
||||
|
||||
Modeling.prototype.updateProperties = function(element, properties) {
|
||||
this._commandStack.execute('element.updateProperties', {
|
||||
element: element,
|
||||
properties: properties
|
||||
});
|
||||
};
|
||||
|
||||
Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) {
|
||||
this._commandStack.execute('lane.resize', {
|
||||
shape: laneShape,
|
||||
newBounds: newBounds,
|
||||
balanced: balanced
|
||||
});
|
||||
};
|
||||
|
||||
Modeling.prototype.addLane = function(targetLaneShape, location) {
|
||||
var context = {
|
||||
shape: targetLaneShape,
|
||||
location: location
|
||||
};
|
||||
|
||||
this._commandStack.execute('lane.add', context);
|
||||
|
||||
return context.newLane;
|
||||
};
|
||||
|
||||
Modeling.prototype.splitLane = function(targetLane, count) {
|
||||
this._commandStack.execute('lane.split', {
|
||||
shape: targetLane,
|
||||
count: count
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform the current diagram into a collaboration.
|
||||
*
|
||||
* @return {djs.model.Root} the new root element
|
||||
*/
|
||||
Modeling.prototype.makeCollaboration = function() {
|
||||
|
||||
var collaborationElement = this._create('root', {
|
||||
type: 'bpmn:Collaboration'
|
||||
});
|
||||
|
||||
var context = {
|
||||
newRoot: collaborationElement
|
||||
};
|
||||
|
||||
this._commandStack.execute('canvas.updateRoot', context);
|
||||
|
||||
return collaborationElement;
|
||||
};
|
||||
|
||||
Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {
|
||||
|
||||
this._commandStack.execute('lane.updateRefs', {
|
||||
flowNodeShapes: flowNodeShapes,
|
||||
laneShapes: laneShapes
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Transform the current diagram into a process.
|
||||
*
|
||||
* @return {djs.model.Root} the new root element
|
||||
*/
|
||||
Modeling.prototype.makeProcess = function() {
|
||||
|
||||
var processElement = this._create('root', {
|
||||
type: 'bpmn:Process'
|
||||
});
|
||||
|
||||
var context = {
|
||||
newRoot: processElement
|
||||
};
|
||||
|
||||
this._commandStack.execute('canvas.updateRoot', context);
|
||||
};
|
||||
|
||||
|
||||
Modeling.prototype.claimId = function(id, moddleElement) {
|
||||
this._commandStack.execute('id.updateClaim', {
|
||||
id: id,
|
||||
element: moddleElement,
|
||||
claiming: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Modeling.prototype.unclaimId = function(id, moddleElement) {
|
||||
this._commandStack.execute('id.updateClaim', {
|
||||
id: id,
|
||||
element: moddleElement
|
||||
});
|
||||
};
|
||||
|
||||
Modeling.prototype.setColor = function(elements, colors) {
|
||||
if (!elements.length) {
|
||||
elements = [ elements ];
|
||||
}
|
||||
|
||||
this._commandStack.execute('element.setColor', {
|
||||
elements: elements,
|
||||
colors: colors
|
||||
});
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
filter
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
eachElement
|
||||
} from 'diagram-js/lib/util/Elements';
|
||||
|
||||
import {
|
||||
getLanesRoot,
|
||||
getChildLanes,
|
||||
LANE_INDENTATION
|
||||
} from '../util/LaneUtil';
|
||||
|
||||
|
||||
/**
|
||||
* A handler that allows us to add a new lane
|
||||
* above or below an existing one.
|
||||
*
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function AddLaneHandler(modeling, spaceTool) {
|
||||
this._modeling = modeling;
|
||||
this._spaceTool = spaceTool;
|
||||
}
|
||||
|
||||
AddLaneHandler.$inject = [
|
||||
'modeling',
|
||||
'spaceTool'
|
||||
];
|
||||
|
||||
|
||||
AddLaneHandler.prototype.preExecute = function(context) {
|
||||
|
||||
var spaceTool = this._spaceTool,
|
||||
modeling = this._modeling;
|
||||
|
||||
var shape = context.shape,
|
||||
location = context.location;
|
||||
|
||||
var lanesRoot = getLanesRoot(shape);
|
||||
|
||||
var isRoot = lanesRoot === shape,
|
||||
laneParent = isRoot ? shape : shape.parent;
|
||||
|
||||
var existingChildLanes = getChildLanes(laneParent);
|
||||
|
||||
// (0) add a lane if we currently got none and are adding to root
|
||||
if (!existingChildLanes.length) {
|
||||
modeling.createShape({ type: 'bpmn:Lane' }, {
|
||||
x: shape.x + LANE_INDENTATION,
|
||||
y: shape.y,
|
||||
width: shape.width - LANE_INDENTATION,
|
||||
height: shape.height
|
||||
}, laneParent);
|
||||
}
|
||||
|
||||
// (1) collect affected elements to create necessary space
|
||||
var allAffected = [];
|
||||
|
||||
eachElement(lanesRoot, function(element) {
|
||||
allAffected.push(element);
|
||||
|
||||
if (element === shape) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return filter(element.children, function(c) {
|
||||
return c !== shape;
|
||||
});
|
||||
});
|
||||
|
||||
var offset = location === 'top' ? -120 : 120,
|
||||
lanePosition = location === 'top' ? shape.y : shape.y + shape.height,
|
||||
spacePos = lanePosition + (location === 'top' ? 10 : -10),
|
||||
direction = location === 'top' ? 'n' : 's';
|
||||
|
||||
var adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
|
||||
|
||||
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: offset }, direction);
|
||||
|
||||
// (2) create new lane at open space
|
||||
context.newLane = modeling.createShape({ type: 'bpmn:Lane' }, {
|
||||
x: shape.x + (isRoot ? LANE_INDENTATION : 0),
|
||||
y: lanePosition - (location === 'top' ? 120 : 0),
|
||||
width: shape.width - (isRoot ? LANE_INDENTATION : 0),
|
||||
height: 120
|
||||
}, laneParent);
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
export default function IdClaimHandler(moddle) {
|
||||
this._moddle = moddle;
|
||||
}
|
||||
|
||||
IdClaimHandler.$inject = [ 'moddle' ];
|
||||
|
||||
|
||||
IdClaimHandler.prototype.execute = function(context) {
|
||||
var ids = this._moddle.ids,
|
||||
id = context.id,
|
||||
element = context.element,
|
||||
claiming = context.claiming;
|
||||
|
||||
if (claiming) {
|
||||
ids.claim(id, element);
|
||||
} else {
|
||||
ids.unclaim(id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Command revert implementation.
|
||||
*/
|
||||
IdClaimHandler.prototype.revert = function(context) {
|
||||
var ids = this._moddle.ids,
|
||||
id = context.id,
|
||||
element = context.element,
|
||||
claiming = context.claiming;
|
||||
|
||||
if (claiming) {
|
||||
ids.unclaim(id);
|
||||
} else {
|
||||
ids.claim(id, element);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
getLanesRoot,
|
||||
computeLanesResize
|
||||
} from '../util/LaneUtil';
|
||||
|
||||
import {
|
||||
eachElement
|
||||
} from 'diagram-js/lib/util/Elements';
|
||||
|
||||
import {
|
||||
asTRBL
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
substractTRBL
|
||||
} from 'diagram-js/lib/features/resize/ResizeUtil';
|
||||
|
||||
|
||||
/**
|
||||
* A handler that resizes a lane.
|
||||
*
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function ResizeLaneHandler(modeling, spaceTool) {
|
||||
this._modeling = modeling;
|
||||
this._spaceTool = spaceTool;
|
||||
}
|
||||
|
||||
ResizeLaneHandler.$inject = [
|
||||
'modeling',
|
||||
'spaceTool'
|
||||
];
|
||||
|
||||
|
||||
ResizeLaneHandler.prototype.preExecute = function(context) {
|
||||
|
||||
var shape = context.shape,
|
||||
newBounds = context.newBounds,
|
||||
balanced = context.balanced;
|
||||
|
||||
if (balanced !== false) {
|
||||
this.resizeBalanced(shape, newBounds);
|
||||
} else {
|
||||
this.resizeSpace(shape, newBounds);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resize balanced, adjusting next / previous lane sizes.
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
* @param {Bounds} newBounds
|
||||
*/
|
||||
ResizeLaneHandler.prototype.resizeBalanced = function(shape, newBounds) {
|
||||
|
||||
var modeling = this._modeling;
|
||||
|
||||
var resizeNeeded = computeLanesResize(shape, newBounds);
|
||||
|
||||
// resize the lane
|
||||
modeling.resizeShape(shape, newBounds);
|
||||
|
||||
// resize other lanes as needed
|
||||
resizeNeeded.forEach(function(r) {
|
||||
modeling.resizeShape(r.shape, r.newBounds);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Resize, making actual space and moving below / above elements.
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
* @param {Bounds} newBounds
|
||||
*/
|
||||
ResizeLaneHandler.prototype.resizeSpace = function(shape, newBounds) {
|
||||
var spaceTool = this._spaceTool;
|
||||
|
||||
var shapeTrbl = asTRBL(shape),
|
||||
newTrbl = asTRBL(newBounds);
|
||||
|
||||
var trblDiff = substractTRBL(newTrbl, shapeTrbl);
|
||||
|
||||
var lanesRoot = getLanesRoot(shape);
|
||||
|
||||
var allAffected = [],
|
||||
allLanes = [];
|
||||
|
||||
eachElement(lanesRoot, function(element) {
|
||||
allAffected.push(element);
|
||||
|
||||
if (is(element, 'bpmn:Lane') || is(element, 'bpmn:Participant')) {
|
||||
allLanes.push(element);
|
||||
}
|
||||
|
||||
return element.children;
|
||||
});
|
||||
|
||||
var change,
|
||||
spacePos,
|
||||
direction,
|
||||
offset,
|
||||
adjustments;
|
||||
|
||||
if (trblDiff.bottom || trblDiff.top) {
|
||||
|
||||
change = trblDiff.bottom || trblDiff.top;
|
||||
spacePos = shape.y + (trblDiff.bottom ? shape.height : 0) + (trblDiff.bottom ? -10 : 10);
|
||||
direction = trblDiff.bottom ? 's' : 'n';
|
||||
|
||||
offset = trblDiff.top > 0 || trblDiff.bottom < 0 ? -change : change;
|
||||
|
||||
adjustments = spaceTool.calculateAdjustments(allAffected, 'y', offset, spacePos);
|
||||
|
||||
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: 0, y: change }, direction);
|
||||
}
|
||||
|
||||
|
||||
if (trblDiff.left || trblDiff.right) {
|
||||
|
||||
change = trblDiff.right || trblDiff.left;
|
||||
spacePos = shape.x + (trblDiff.right ? shape.width : 0) + (trblDiff.right ? -10 : 100);
|
||||
direction = trblDiff.right ? 'e' : 'w';
|
||||
|
||||
offset = trblDiff.left > 0 || trblDiff.right < 0 ? -change : change;
|
||||
|
||||
adjustments = spaceTool.calculateAdjustments(allLanes, 'x', offset, spacePos);
|
||||
|
||||
spaceTool.makeSpace(adjustments.movingShapes, adjustments.resizingShapes, { x: change, y: 0 }, direction);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import {
|
||||
assign,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
|
||||
var DEFAULT_COLORS = {
|
||||
fill: undefined,
|
||||
stroke: undefined
|
||||
};
|
||||
|
||||
|
||||
export default function SetColorHandler(commandStack) {
|
||||
this._commandStack = commandStack;
|
||||
}
|
||||
|
||||
SetColorHandler.$inject = [
|
||||
'commandStack'
|
||||
];
|
||||
|
||||
|
||||
SetColorHandler.prototype.postExecute = function(context) {
|
||||
var elements = context.elements,
|
||||
colors = context.colors || DEFAULT_COLORS;
|
||||
|
||||
var self = this;
|
||||
|
||||
var di = {};
|
||||
|
||||
if ('fill' in colors) {
|
||||
assign(di, { fill: colors.fill });
|
||||
}
|
||||
|
||||
if ('stroke' in colors) {
|
||||
assign(di, { stroke: colors.stroke });
|
||||
}
|
||||
|
||||
forEach(elements, function(element) {
|
||||
|
||||
self._commandStack.execute('element.updateProperties', {
|
||||
element: element,
|
||||
properties: {
|
||||
di: di
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import {
|
||||
getChildLanes,
|
||||
LANE_INDENTATION
|
||||
} from '../util/LaneUtil';
|
||||
|
||||
|
||||
/**
|
||||
* A handler that splits a lane into a number of sub-lanes,
|
||||
* creating new sub lanes, if neccessary.
|
||||
*
|
||||
* @param {Modeling} modeling
|
||||
*/
|
||||
export default function SplitLaneHandler(modeling, translate) {
|
||||
this._modeling = modeling;
|
||||
this._translate = translate;
|
||||
}
|
||||
|
||||
SplitLaneHandler.$inject = [
|
||||
'modeling',
|
||||
'translate'
|
||||
];
|
||||
|
||||
|
||||
SplitLaneHandler.prototype.preExecute = function(context) {
|
||||
|
||||
var modeling = this._modeling,
|
||||
translate = this._translate;
|
||||
|
||||
var shape = context.shape,
|
||||
newLanesCount = context.count;
|
||||
|
||||
var childLanes = getChildLanes(shape),
|
||||
existingLanesCount = childLanes.length;
|
||||
|
||||
if (existingLanesCount > newLanesCount) {
|
||||
throw new Error(translate('more than {count} child lanes', { count: newLanesCount }));
|
||||
}
|
||||
|
||||
var newLanesHeight = Math.round(shape.height / newLanesCount);
|
||||
|
||||
// Iterate from top to bottom in child lane order,
|
||||
// resizing existing lanes and creating new ones
|
||||
// so that they split the parent proportionally.
|
||||
//
|
||||
// Due to rounding related errors, the bottom lane
|
||||
// needs to take up all the remaining space.
|
||||
var laneY,
|
||||
laneHeight,
|
||||
laneBounds,
|
||||
newLaneAttrs,
|
||||
idx;
|
||||
|
||||
for (idx = 0; idx < newLanesCount; idx++) {
|
||||
|
||||
laneY = shape.y + idx * newLanesHeight;
|
||||
|
||||
// if bottom lane
|
||||
if (idx === newLanesCount - 1) {
|
||||
laneHeight = shape.height - (newLanesHeight * idx);
|
||||
} else {
|
||||
laneHeight = newLanesHeight;
|
||||
}
|
||||
|
||||
laneBounds = {
|
||||
x: shape.x + LANE_INDENTATION,
|
||||
y: laneY,
|
||||
width: shape.width - LANE_INDENTATION,
|
||||
height: laneHeight
|
||||
};
|
||||
|
||||
if (idx < existingLanesCount) {
|
||||
// resize existing lane
|
||||
modeling.resizeShape(childLanes[idx], laneBounds);
|
||||
} else {
|
||||
// create a new lane at position
|
||||
newLaneAttrs = {
|
||||
type: 'bpmn:Lane'
|
||||
};
|
||||
|
||||
modeling.createShape(newLaneAttrs, laneBounds, shape);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
import {
|
||||
add as collectionAdd,
|
||||
remove as collectionRemove
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
|
||||
export default function UpdateCanvasRootHandler(canvas, modeling) {
|
||||
this._canvas = canvas;
|
||||
this._modeling = modeling;
|
||||
}
|
||||
|
||||
UpdateCanvasRootHandler.$inject = [
|
||||
'canvas',
|
||||
'modeling'
|
||||
];
|
||||
|
||||
|
||||
UpdateCanvasRootHandler.prototype.execute = function(context) {
|
||||
|
||||
var canvas = this._canvas;
|
||||
|
||||
var newRoot = context.newRoot,
|
||||
newRootBusinessObject = newRoot.businessObject,
|
||||
oldRoot = canvas.getRootElement(),
|
||||
oldRootBusinessObject = oldRoot.businessObject,
|
||||
bpmnDefinitions = oldRootBusinessObject.$parent,
|
||||
diPlane = oldRootBusinessObject.di;
|
||||
|
||||
// (1) replace process old <> new root
|
||||
canvas.setRootElement(newRoot, true);
|
||||
|
||||
// (2) update root elements
|
||||
collectionAdd(bpmnDefinitions.rootElements, newRootBusinessObject);
|
||||
newRootBusinessObject.$parent = bpmnDefinitions;
|
||||
|
||||
collectionRemove(bpmnDefinitions.rootElements, oldRootBusinessObject);
|
||||
oldRootBusinessObject.$parent = null;
|
||||
|
||||
// (3) wire di
|
||||
oldRootBusinessObject.di = null;
|
||||
|
||||
diPlane.bpmnElement = newRootBusinessObject;
|
||||
newRootBusinessObject.di = diPlane;
|
||||
|
||||
context.oldRoot = oldRoot;
|
||||
|
||||
// TODO(nikku): return changed elements?
|
||||
// return [ newRoot, oldRoot ];
|
||||
};
|
||||
|
||||
|
||||
UpdateCanvasRootHandler.prototype.revert = function(context) {
|
||||
|
||||
var canvas = this._canvas;
|
||||
|
||||
var newRoot = context.newRoot,
|
||||
newRootBusinessObject = newRoot.businessObject,
|
||||
oldRoot = context.oldRoot,
|
||||
oldRootBusinessObject = oldRoot.businessObject,
|
||||
bpmnDefinitions = newRootBusinessObject.$parent,
|
||||
diPlane = newRootBusinessObject.di;
|
||||
|
||||
// (1) replace process old <> new root
|
||||
canvas.setRootElement(oldRoot, true);
|
||||
|
||||
// (2) update root elements
|
||||
collectionRemove(bpmnDefinitions.rootElements, newRootBusinessObject);
|
||||
newRootBusinessObject.$parent = null;
|
||||
|
||||
collectionAdd(bpmnDefinitions.rootElements, oldRootBusinessObject);
|
||||
oldRootBusinessObject.$parent = bpmnDefinitions;
|
||||
|
||||
// (3) wire di
|
||||
newRootBusinessObject.di = null;
|
||||
|
||||
diPlane.bpmnElement = oldRootBusinessObject;
|
||||
oldRootBusinessObject.di = diPlane;
|
||||
|
||||
// TODO(nikku): return changed elements?
|
||||
// return [ newRoot, oldRoot ];
|
||||
};
|
||||
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
collectLanes,
|
||||
getLanesRoot
|
||||
} from '../util/LaneUtil';
|
||||
|
||||
import {
|
||||
is
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
add as collectionAdd,
|
||||
remove as collectionRemove
|
||||
} from 'diagram-js/lib/util/Collections';
|
||||
|
||||
import {
|
||||
asTRBL
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
var FLOW_NODE_REFS_ATTR = 'flowNodeRef',
|
||||
LANES_ATTR = 'lanes';
|
||||
|
||||
|
||||
/**
|
||||
* A handler that updates lane refs on changed elements
|
||||
*/
|
||||
export default function UpdateFlowNodeRefsHandler(elementRegistry) {
|
||||
this._elementRegistry = elementRegistry;
|
||||
}
|
||||
|
||||
UpdateFlowNodeRefsHandler.$inject = [
|
||||
'elementRegistry'
|
||||
];
|
||||
|
||||
|
||||
UpdateFlowNodeRefsHandler.prototype.computeUpdates = function(flowNodeShapes, laneShapes) {
|
||||
|
||||
var handledNodes = {};
|
||||
|
||||
var updates = [];
|
||||
|
||||
var participantCache = {};
|
||||
|
||||
var allFlowNodeShapes = [];
|
||||
|
||||
function isInLaneShape(element, laneShape) {
|
||||
|
||||
var laneTrbl = asTRBL(laneShape);
|
||||
|
||||
var elementMid = {
|
||||
x: element.x + element.width / 2,
|
||||
y: element.y + element.height / 2
|
||||
};
|
||||
|
||||
return elementMid.x > laneTrbl.left &&
|
||||
elementMid.x < laneTrbl.right &&
|
||||
elementMid.y > laneTrbl.top &&
|
||||
elementMid.y < laneTrbl.bottom;
|
||||
}
|
||||
|
||||
function addFlowNodeShape(flowNodeShape) {
|
||||
if (!handledNodes[flowNodeShape.id]) {
|
||||
allFlowNodeShapes.push(flowNodeShape);
|
||||
handledNodes[flowNodeShape.id] = flowNodeShape;
|
||||
}
|
||||
}
|
||||
|
||||
function getAllLaneShapes(flowNodeShape) {
|
||||
|
||||
var root = getLanesRoot(flowNodeShape);
|
||||
|
||||
if (!participantCache[root.id]) {
|
||||
participantCache[root.id] = collectLanes(root);
|
||||
}
|
||||
|
||||
return participantCache[root.id];
|
||||
}
|
||||
|
||||
function getNewLanes(flowNodeShape) {
|
||||
if (!flowNodeShape.parent) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var allLaneShapes = getAllLaneShapes(flowNodeShape);
|
||||
|
||||
return allLaneShapes.filter(function(l) {
|
||||
return isInLaneShape(flowNodeShape, l);
|
||||
}).map(function(shape) {
|
||||
return shape.businessObject;
|
||||
});
|
||||
}
|
||||
|
||||
laneShapes.forEach(function(laneShape) {
|
||||
var root = getLanesRoot(laneShape);
|
||||
|
||||
if (!root || handledNodes[root.id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var children = root.children.filter(function(c) {
|
||||
return is(c, 'bpmn:FlowNode');
|
||||
});
|
||||
|
||||
children.forEach(addFlowNodeShape);
|
||||
|
||||
handledNodes[root.id] = root;
|
||||
});
|
||||
|
||||
flowNodeShapes.forEach(addFlowNodeShape);
|
||||
|
||||
|
||||
allFlowNodeShapes.forEach(function(flowNodeShape) {
|
||||
|
||||
var flowNode = flowNodeShape.businessObject;
|
||||
|
||||
var lanes = flowNode.get(LANES_ATTR),
|
||||
remove = lanes.slice(),
|
||||
add = getNewLanes(flowNodeShape);
|
||||
|
||||
updates.push({ flowNode: flowNode, remove: remove, add: add });
|
||||
});
|
||||
|
||||
laneShapes.forEach(function(laneShape) {
|
||||
|
||||
var lane = laneShape.businessObject;
|
||||
|
||||
// lane got removed XX-)
|
||||
if (!laneShape.parent) {
|
||||
lane.get(FLOW_NODE_REFS_ATTR).forEach(function(flowNode) {
|
||||
updates.push({ flowNode: flowNode, remove: [ lane ], add: [] });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return updates;
|
||||
};
|
||||
|
||||
UpdateFlowNodeRefsHandler.prototype.execute = function(context) {
|
||||
|
||||
var updates = context.updates;
|
||||
|
||||
if (!updates) {
|
||||
updates = context.updates = this.computeUpdates(context.flowNodeShapes, context.laneShapes);
|
||||
}
|
||||
|
||||
|
||||
updates.forEach(function(update) {
|
||||
|
||||
var flowNode = update.flowNode,
|
||||
lanes = flowNode.get(LANES_ATTR);
|
||||
|
||||
// unwire old
|
||||
update.remove.forEach(function(oldLane) {
|
||||
collectionRemove(lanes, oldLane);
|
||||
collectionRemove(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
|
||||
});
|
||||
|
||||
// wire new
|
||||
update.add.forEach(function(newLane) {
|
||||
collectionAdd(lanes, newLane);
|
||||
collectionAdd(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(nikku): return changed elements
|
||||
// return [ ... ];
|
||||
};
|
||||
|
||||
|
||||
UpdateFlowNodeRefsHandler.prototype.revert = function(context) {
|
||||
|
||||
var updates = context.updates;
|
||||
|
||||
updates.forEach(function(update) {
|
||||
|
||||
var flowNode = update.flowNode,
|
||||
lanes = flowNode.get(LANES_ATTR);
|
||||
|
||||
// unwire new
|
||||
update.add.forEach(function(newLane) {
|
||||
collectionRemove(lanes, newLane);
|
||||
collectionRemove(newLane.get(FLOW_NODE_REFS_ATTR), flowNode);
|
||||
});
|
||||
|
||||
// wire old
|
||||
update.remove.forEach(function(oldLane) {
|
||||
collectionAdd(lanes, oldLane);
|
||||
collectionAdd(oldLane.get(FLOW_NODE_REFS_ATTR), flowNode);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(nikku): return changed elements
|
||||
// return [ ... ];
|
||||
};
|
||||
@@ -0,0 +1,234 @@
|
||||
import {
|
||||
reduce,
|
||||
keys,
|
||||
forEach,
|
||||
assign
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
getBusinessObject
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
var DEFAULT_FLOW = 'default',
|
||||
ID = 'id',
|
||||
DI = 'di';
|
||||
|
||||
var NULL_DIMENSIONS = {
|
||||
width: 0,
|
||||
height: 0
|
||||
};
|
||||
|
||||
/**
|
||||
* A handler that implements a BPMN 2.0 property update.
|
||||
*
|
||||
* This should be used to set simple properties on elements with
|
||||
* an underlying BPMN business object.
|
||||
*
|
||||
* Use respective diagram-js provided handlers if you would
|
||||
* like to perform automated modeling.
|
||||
*/
|
||||
export default function UpdatePropertiesHandler(
|
||||
elementRegistry, moddle, translate,
|
||||
modeling, textRenderer) {
|
||||
|
||||
this._elementRegistry = elementRegistry;
|
||||
this._moddle = moddle;
|
||||
this._translate = translate;
|
||||
this._modeling = modeling;
|
||||
this._textRenderer = textRenderer;
|
||||
}
|
||||
|
||||
UpdatePropertiesHandler.$inject = [
|
||||
'elementRegistry',
|
||||
'moddle',
|
||||
'translate',
|
||||
'modeling',
|
||||
'textRenderer'
|
||||
];
|
||||
|
||||
|
||||
// api //////////////////////
|
||||
|
||||
/**
|
||||
* Updates a BPMN element with a list of new properties
|
||||
*
|
||||
* @param {Object} context
|
||||
* @param {djs.model.Base} context.element the element to update
|
||||
* @param {Object} context.properties a list of properties to set on the element's
|
||||
* businessObject (the BPMN model element)
|
||||
*
|
||||
* @return {Array<djs.model.Base>} the updated element
|
||||
*/
|
||||
UpdatePropertiesHandler.prototype.execute = function(context) {
|
||||
|
||||
var element = context.element,
|
||||
changed = [ element ],
|
||||
translate = this._translate;
|
||||
|
||||
if (!element) {
|
||||
throw new Error(translate('element required'));
|
||||
}
|
||||
|
||||
var elementRegistry = this._elementRegistry,
|
||||
ids = this._moddle.ids;
|
||||
|
||||
var businessObject = element.businessObject,
|
||||
properties = unwrapBusinessObjects(context.properties),
|
||||
oldProperties = context.oldProperties || getProperties(businessObject, properties);
|
||||
|
||||
if (isIdChange(properties, businessObject)) {
|
||||
ids.unclaim(businessObject[ID]);
|
||||
|
||||
elementRegistry.updateId(element, properties[ID]);
|
||||
|
||||
ids.claim(properties[ID], businessObject);
|
||||
}
|
||||
|
||||
// correctly indicate visual changes on default flow updates
|
||||
if (DEFAULT_FLOW in properties) {
|
||||
|
||||
if (properties[DEFAULT_FLOW]) {
|
||||
changed.push(elementRegistry.get(properties[DEFAULT_FLOW].id));
|
||||
}
|
||||
|
||||
if (businessObject[DEFAULT_FLOW]) {
|
||||
changed.push(elementRegistry.get(businessObject[DEFAULT_FLOW].id));
|
||||
}
|
||||
}
|
||||
|
||||
// update properties
|
||||
setProperties(businessObject, properties);
|
||||
|
||||
// store old values
|
||||
context.oldProperties = oldProperties;
|
||||
context.changed = changed;
|
||||
|
||||
// indicate changed on objects affected by the update
|
||||
return changed;
|
||||
};
|
||||
|
||||
|
||||
UpdatePropertiesHandler.prototype.postExecute = function(context) {
|
||||
var element = context.element,
|
||||
label = element.label;
|
||||
|
||||
var text = label && getBusinessObject(label).name;
|
||||
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
|
||||
// get layouted text bounds and resize external
|
||||
// external label accordingly
|
||||
var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text);
|
||||
|
||||
this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts the update on a BPMN elements properties.
|
||||
*
|
||||
* @param {Object} context
|
||||
*
|
||||
* @return {djs.model.Base} the updated element
|
||||
*/
|
||||
UpdatePropertiesHandler.prototype.revert = function(context) {
|
||||
|
||||
var element = context.element,
|
||||
properties = context.properties,
|
||||
oldProperties = context.oldProperties,
|
||||
businessObject = element.businessObject,
|
||||
elementRegistry = this._elementRegistry,
|
||||
ids = this._moddle.ids;
|
||||
|
||||
// update properties
|
||||
setProperties(businessObject, oldProperties);
|
||||
|
||||
if (isIdChange(properties, businessObject)) {
|
||||
ids.unclaim(properties[ID]);
|
||||
|
||||
elementRegistry.updateId(element, oldProperties[ID]);
|
||||
|
||||
ids.claim(oldProperties[ID], businessObject);
|
||||
}
|
||||
|
||||
return context.changed;
|
||||
};
|
||||
|
||||
|
||||
function isIdChange(properties, businessObject) {
|
||||
return ID in properties && properties[ID] !== businessObject[ID];
|
||||
}
|
||||
|
||||
|
||||
function getProperties(businessObject, properties) {
|
||||
var propertyNames = keys(properties);
|
||||
|
||||
return reduce(propertyNames, function(result, key) {
|
||||
|
||||
// handle DI seperately
|
||||
if (key !== DI) {
|
||||
result[key] = businessObject.get(key);
|
||||
} else {
|
||||
result[key] = getDiProperties(businessObject.di, keys(properties.di));
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
||||
function getDiProperties(di, propertyNames) {
|
||||
return reduce(propertyNames, function(result, key) {
|
||||
result[key] = di.get(key);
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
||||
function setProperties(businessObject, properties) {
|
||||
forEach(properties, function(value, key) {
|
||||
|
||||
if (key !== DI) {
|
||||
businessObject.set(key, value);
|
||||
} else {
|
||||
// only update, if businessObject.di exists
|
||||
if (businessObject.di) {
|
||||
setDiProperties(businessObject.di, value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function setDiProperties(di, properties) {
|
||||
forEach(properties, function(value, key) {
|
||||
di.set(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var referencePropertyNames = [ 'default' ];
|
||||
|
||||
/**
|
||||
* Make sure we unwrap the actual business object
|
||||
* behind diagram element that may have been
|
||||
* passed as arguments.
|
||||
*
|
||||
* @param {Object} properties
|
||||
*
|
||||
* @return {Object} unwrappedProps
|
||||
*/
|
||||
function unwrapBusinessObjects(properties) {
|
||||
|
||||
var unwrappedProps = assign({}, properties);
|
||||
|
||||
referencePropertyNames.forEach(function(name) {
|
||||
if (name in properties) {
|
||||
unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
|
||||
}
|
||||
});
|
||||
|
||||
return unwrappedProps;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
export default function UpdateSemanticParentHandler(bpmnUpdater) {
|
||||
this._bpmnUpdater = bpmnUpdater;
|
||||
}
|
||||
|
||||
UpdateSemanticParentHandler.$inject = [ 'bpmnUpdater' ];
|
||||
|
||||
|
||||
UpdateSemanticParentHandler.prototype.execute = function(context) {
|
||||
var dataStoreBo = context.dataStoreBo,
|
||||
newSemanticParent = context.newSemanticParent,
|
||||
newDiParent = context.newDiParent;
|
||||
|
||||
context.oldSemanticParent = dataStoreBo.$parent;
|
||||
context.oldDiParent = dataStoreBo.di.$parent;
|
||||
|
||||
// update semantic parent
|
||||
this._bpmnUpdater.updateSemanticParent(dataStoreBo, newSemanticParent);
|
||||
|
||||
// update DI parent
|
||||
this._bpmnUpdater.updateDiParent(dataStoreBo.di, newDiParent);
|
||||
};
|
||||
|
||||
UpdateSemanticParentHandler.prototype.revert = function(context) {
|
||||
var dataStoreBo = context.dataStoreBo,
|
||||
oldSemanticParent = context.oldSemanticParent,
|
||||
oldDiParent = context.oldDiParent;
|
||||
|
||||
// update semantic parent
|
||||
this._bpmnUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent);
|
||||
|
||||
// update DI parent
|
||||
this._bpmnUpdater.updateDiParent(dataStoreBo.di, oldDiParent);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import BehaviorModule from './behavior';
|
||||
import RulesModule from '../rules';
|
||||
import OrderingModule from '../ordering';
|
||||
import ReplaceModule from '../replace';
|
||||
|
||||
import CommandModule from 'diagram-js/lib/command';
|
||||
import TooltipsModule from 'diagram-js/lib/features/tooltips';
|
||||
import LabelSupportModule from 'diagram-js/lib/features/label-support';
|
||||
import AttachSupportModule from 'diagram-js/lib/features/attach-support';
|
||||
import SelectionModule from 'diagram-js/lib/features/selection';
|
||||
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
|
||||
import SpaceToolModule from 'diagram-js/lib/features/space-tool';
|
||||
|
||||
import BpmnFactory from './BpmnFactory';
|
||||
import BpmnUpdater from './BpmnUpdater';
|
||||
import ElementFactory from './ElementFactory';
|
||||
import Modeling from './Modeling';
|
||||
import BpmnLayouter from './BpmnLayouter';
|
||||
import CroppingConnectionDocking from 'diagram-js/lib/layout/CroppingConnectionDocking';
|
||||
|
||||
|
||||
export default {
|
||||
__init__: [
|
||||
'modeling',
|
||||
'bpmnUpdater'
|
||||
],
|
||||
__depends__: [
|
||||
BehaviorModule,
|
||||
RulesModule,
|
||||
OrderingModule,
|
||||
ReplaceModule,
|
||||
CommandModule,
|
||||
TooltipsModule,
|
||||
LabelSupportModule,
|
||||
AttachSupportModule,
|
||||
SelectionModule,
|
||||
ChangeSupportModule,
|
||||
SpaceToolModule
|
||||
],
|
||||
bpmnFactory: [ 'type', BpmnFactory ],
|
||||
bpmnUpdater: [ 'type', BpmnUpdater ],
|
||||
elementFactory: [ 'type', ElementFactory ],
|
||||
modeling: [ 'type', Modeling ],
|
||||
layouter: [ 'type', BpmnLayouter ],
|
||||
connectionDocking: [ 'type', CroppingConnectionDocking ]
|
||||
};
|
||||
@@ -0,0 +1,154 @@
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
getParent
|
||||
} from './ModelingUtil';
|
||||
|
||||
import {
|
||||
asTRBL
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import {
|
||||
substractTRBL,
|
||||
resizeTRBL
|
||||
} from 'diagram-js/lib/features/resize/ResizeUtil';
|
||||
|
||||
var abs = Math.abs;
|
||||
|
||||
|
||||
function getTRBLResize(oldBounds, newBounds) {
|
||||
return substractTRBL(asTRBL(newBounds), asTRBL(oldBounds));
|
||||
}
|
||||
|
||||
|
||||
var LANE_PARENTS = [
|
||||
'bpmn:Participant',
|
||||
'bpmn:Process',
|
||||
'bpmn:SubProcess'
|
||||
];
|
||||
|
||||
export var LANE_INDENTATION = 30;
|
||||
|
||||
|
||||
/**
|
||||
* Collect all lane shapes in the given paren
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
* @param {Array<djs.model.Base>} [collectedShapes]
|
||||
*
|
||||
* @return {Array<djs.model.Base>}
|
||||
*/
|
||||
export function collectLanes(shape, collectedShapes) {
|
||||
|
||||
collectedShapes = collectedShapes || [];
|
||||
|
||||
shape.children.filter(function(s) {
|
||||
if (is(s, 'bpmn:Lane')) {
|
||||
collectLanes(s, collectedShapes);
|
||||
|
||||
collectedShapes.push(s);
|
||||
}
|
||||
});
|
||||
|
||||
return collectedShapes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the lane children of the given element.
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
*
|
||||
* @return {Array<djs.model.Shape>}
|
||||
*/
|
||||
export function getChildLanes(shape) {
|
||||
return shape.children.filter(function(c) {
|
||||
return is(c, 'bpmn:Lane');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the root element containing the given lane shape
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
*
|
||||
* @return {djs.model.Shape}
|
||||
*/
|
||||
export function getLanesRoot(shape) {
|
||||
return getParent(shape, LANE_PARENTS) || shape;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute the required resize operations for lanes
|
||||
* adjacent to the given shape, assuming it will be
|
||||
* resized to the given new bounds.
|
||||
*
|
||||
* @param {djs.model.Shape} shape
|
||||
* @param {Bounds} newBounds
|
||||
*
|
||||
* @return {Array<Object>}
|
||||
*/
|
||||
export function computeLanesResize(shape, newBounds) {
|
||||
|
||||
var rootElement = getLanesRoot(shape);
|
||||
|
||||
var initialShapes = is(rootElement, 'bpmn:Process') ? [] : [ rootElement ];
|
||||
|
||||
var allLanes = collectLanes(rootElement, initialShapes),
|
||||
shapeTrbl = asTRBL(shape),
|
||||
shapeNewTrbl = asTRBL(newBounds),
|
||||
trblResize = getTRBLResize(shape, newBounds),
|
||||
resizeNeeded = [];
|
||||
|
||||
allLanes.forEach(function(other) {
|
||||
|
||||
if (other === shape) {
|
||||
return;
|
||||
}
|
||||
|
||||
var topResize = 0,
|
||||
rightResize = trblResize.right,
|
||||
bottomResize = 0,
|
||||
leftResize = trblResize.left;
|
||||
|
||||
var otherTrbl = asTRBL(other);
|
||||
|
||||
if (trblResize.top) {
|
||||
if (abs(otherTrbl.bottom - shapeTrbl.top) < 10) {
|
||||
bottomResize = shapeNewTrbl.top - otherTrbl.bottom;
|
||||
}
|
||||
|
||||
if (abs(otherTrbl.top - shapeTrbl.top) < 5) {
|
||||
topResize = shapeNewTrbl.top - otherTrbl.top;
|
||||
}
|
||||
}
|
||||
|
||||
if (trblResize.bottom) {
|
||||
if (abs(otherTrbl.top - shapeTrbl.bottom) < 10) {
|
||||
topResize = shapeNewTrbl.bottom - otherTrbl.top;
|
||||
}
|
||||
|
||||
if (abs(otherTrbl.bottom - shapeTrbl.bottom) < 5) {
|
||||
bottomResize = shapeNewTrbl.bottom - otherTrbl.bottom;
|
||||
}
|
||||
}
|
||||
|
||||
if (topResize || rightResize || bottomResize || leftResize) {
|
||||
|
||||
resizeNeeded.push({
|
||||
shape: other,
|
||||
newBounds: resizeTRBL(other, {
|
||||
top: topResize,
|
||||
right: rightResize,
|
||||
bottom: bottomResize,
|
||||
left: leftResize
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return resizeNeeded;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
some
|
||||
} from 'min-dash';
|
||||
|
||||
import { is } from '../../../util/ModelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Return true if element has any of the given types.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
* @param {Array<String>} types
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isAny(element, types) {
|
||||
return some(types, function(t) {
|
||||
return is(element, t);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the parent of the element with any of the given types.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
* @param {String|Array<String>} anyType
|
||||
*
|
||||
* @return {djs.model.Base}
|
||||
*/
|
||||
export function getParent(element, anyType) {
|
||||
|
||||
if (typeof anyType === 'string') {
|
||||
anyType = [ anyType ];
|
||||
}
|
||||
|
||||
while ((element = element.parent)) {
|
||||
if (isAny(element, anyType)) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import OrderingProvider from 'diagram-js/lib/features/ordering/OrderingProvider';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from '../modeling/util/ModelingUtil';
|
||||
|
||||
import {
|
||||
findIndex,
|
||||
find
|
||||
} from 'min-dash';
|
||||
|
||||
|
||||
/**
|
||||
* a simple ordering provider that makes sure:
|
||||
*
|
||||
* (0) labels and groups are rendered always on top
|
||||
* (1) elements are ordered by a {level} property
|
||||
*/
|
||||
export default function BpmnOrderingProvider(eventBus, canvas, translate) {
|
||||
|
||||
OrderingProvider.call(this, eventBus);
|
||||
|
||||
var orders = [
|
||||
{ type: 'bpmn:SubProcess', order: { level: 6 } },
|
||||
{
|
||||
type: 'bpmn:SequenceFlow',
|
||||
order: {
|
||||
level: 3,
|
||||
containers: [
|
||||
'bpmn:Participant',
|
||||
'bpmn:FlowElementsContainer'
|
||||
]
|
||||
}
|
||||
},
|
||||
// handle DataAssociation(s) like message flows and render them always on top
|
||||
{
|
||||
type: 'bpmn:DataAssociation',
|
||||
order: {
|
||||
level: 9,
|
||||
containers: [
|
||||
'bpmn:Collaboration',
|
||||
'bpmn:Process'
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'bpmn:MessageFlow', order: {
|
||||
level: 9,
|
||||
containers: [ 'bpmn:Collaboration' ]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'bpmn:Association',
|
||||
order: {
|
||||
level: 6,
|
||||
containers: [
|
||||
'bpmn:Participant',
|
||||
'bpmn:FlowElementsContainer',
|
||||
'bpmn:Collaboration'
|
||||
]
|
||||
}
|
||||
},
|
||||
{ type: 'bpmn:BoundaryEvent', order: { level: 8 } },
|
||||
{
|
||||
type: 'bpmn:Group',
|
||||
order: {
|
||||
level: 10,
|
||||
containers: [
|
||||
'bpmn:Collaboration',
|
||||
'bpmn:Process'
|
||||
]
|
||||
}
|
||||
},
|
||||
{ type: 'bpmn:FlowElement', order: { level: 5 } },
|
||||
{ type: 'bpmn:Participant', order: { level: -2 } },
|
||||
{ type: 'bpmn:Lane', order: { level: -1 } }
|
||||
];
|
||||
|
||||
function computeOrder(element) {
|
||||
if (element.labelTarget) {
|
||||
return { level: 10 };
|
||||
}
|
||||
|
||||
var entry = find(orders, function(o) {
|
||||
return isAny(element, [ o.type ]);
|
||||
});
|
||||
|
||||
return entry && entry.order || { level: 1 };
|
||||
}
|
||||
|
||||
function getOrder(element) {
|
||||
|
||||
var order = element.order;
|
||||
|
||||
if (!order) {
|
||||
element.order = order = computeOrder(element);
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
function findActualParent(element, newParent, containers) {
|
||||
|
||||
var actualParent = newParent;
|
||||
|
||||
while (actualParent) {
|
||||
|
||||
if (isAny(actualParent, containers)) {
|
||||
break;
|
||||
}
|
||||
|
||||
actualParent = actualParent.parent;
|
||||
}
|
||||
|
||||
if (!actualParent) {
|
||||
throw new Error(translate('no parent for {element} in {parent}', {
|
||||
element: element.id,
|
||||
parent: newParent.id
|
||||
}));
|
||||
}
|
||||
|
||||
return actualParent;
|
||||
}
|
||||
|
||||
this.getOrdering = function(element, newParent) {
|
||||
|
||||
// render labels always on top
|
||||
if (element.labelTarget) {
|
||||
return {
|
||||
parent: canvas.getRootElement(),
|
||||
index: -1
|
||||
};
|
||||
}
|
||||
|
||||
var elementOrder = getOrder(element);
|
||||
|
||||
|
||||
if (elementOrder.containers) {
|
||||
newParent = findActualParent(element, newParent, elementOrder.containers);
|
||||
}
|
||||
|
||||
|
||||
var currentIndex = newParent.children.indexOf(element);
|
||||
|
||||
var insertIndex = findIndex(newParent.children, function(child) {
|
||||
|
||||
// do not compare with labels, they are created
|
||||
// in the wrong order (right after elements) during import and
|
||||
// mess up the positioning.
|
||||
if (!element.labelTarget && child.labelTarget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return elementOrder.level < getOrder(child).level;
|
||||
});
|
||||
|
||||
|
||||
// if the element is already in the child list at
|
||||
// a smaller index, we need to adjust the insert index.
|
||||
// this takes into account that the element is being removed
|
||||
// before being re-inserted
|
||||
if (insertIndex !== -1) {
|
||||
if (currentIndex !== -1 && currentIndex < insertIndex) {
|
||||
insertIndex -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
index: insertIndex,
|
||||
parent: newParent
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
BpmnOrderingProvider.$inject = [ 'eventBus', 'canvas', 'translate' ];
|
||||
|
||||
inherits(BpmnOrderingProvider, OrderingProvider);
|
||||
@@ -0,0 +1,11 @@
|
||||
import translate from 'diagram-js/lib/i18n/translate';
|
||||
|
||||
import BpmnOrderingProvider from './BpmnOrderingProvider';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
translate
|
||||
],
|
||||
__init__: [ 'bpmnOrderingProvider' ],
|
||||
bpmnOrderingProvider: [ 'type', BpmnOrderingProvider ]
|
||||
};
|
||||
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
assign
|
||||
} from 'min-dash';
|
||||
|
||||
|
||||
/**
|
||||
* A palette provider for BPMN 2.0 elements.
|
||||
*/
|
||||
export default function PaletteProvider(
|
||||
palette, create, elementFactory,
|
||||
spaceTool, lassoTool, handTool,
|
||||
globalConnect, translate) {
|
||||
|
||||
this._palette = palette;
|
||||
this._create = create;
|
||||
this._elementFactory = elementFactory;
|
||||
this._spaceTool = spaceTool;
|
||||
this._lassoTool = lassoTool;
|
||||
this._handTool = handTool;
|
||||
this._globalConnect = globalConnect;
|
||||
this._translate = translate;
|
||||
|
||||
palette.registerProvider(this);
|
||||
}
|
||||
|
||||
PaletteProvider.$inject = [
|
||||
'palette',
|
||||
'create',
|
||||
'elementFactory',
|
||||
'spaceTool',
|
||||
'lassoTool',
|
||||
'handTool',
|
||||
'globalConnect',
|
||||
'translate'
|
||||
];
|
||||
|
||||
|
||||
PaletteProvider.prototype.getPaletteEntries = function(element) {
|
||||
|
||||
var actions = {},
|
||||
create = this._create,
|
||||
elementFactory = this._elementFactory,
|
||||
spaceTool = this._spaceTool,
|
||||
lassoTool = this._lassoTool,
|
||||
handTool = this._handTool,
|
||||
globalConnect = this._globalConnect,
|
||||
translate = this._translate;
|
||||
|
||||
function createAction(type, group, className, title, options) {
|
||||
|
||||
function createListener(event) {
|
||||
var shape = elementFactory.createShape(assign({ type: type }, options));
|
||||
|
||||
if (options) {
|
||||
shape.businessObject.di.isExpanded = options.isExpanded;
|
||||
}
|
||||
|
||||
create.start(event, shape);
|
||||
}
|
||||
|
||||
var shortType = type.replace(/^bpmn:/, '');
|
||||
|
||||
return {
|
||||
group: group,
|
||||
className: className,
|
||||
title: title || translate('Create {type}', { type: shortType }),
|
||||
action: {
|
||||
dragstart: createListener,
|
||||
click: createListener
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createParticipant(event, collapsed) {
|
||||
create.start(event, elementFactory.createParticipantShape(collapsed));
|
||||
}
|
||||
|
||||
assign(actions, {
|
||||
'hand-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-hand-tool',
|
||||
title: translate('Activate the hand tool'),
|
||||
action: {
|
||||
click: function(event) {
|
||||
handTool.activateHand(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
'lasso-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-lasso-tool',
|
||||
title: translate('Activate the lasso tool'),
|
||||
action: {
|
||||
click: function(event) {
|
||||
lassoTool.activateSelection(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
'space-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-space-tool',
|
||||
title: translate('Activate the create/remove space tool'),
|
||||
action: {
|
||||
click: function(event) {
|
||||
spaceTool.activateSelection(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
'global-connect-tool': {
|
||||
group: 'tools',
|
||||
className: 'bpmn-icon-connection-multi',
|
||||
title: translate('Activate the global connect tool'),
|
||||
action: {
|
||||
click: function(event) {
|
||||
globalConnect.toggle(event);
|
||||
}
|
||||
}
|
||||
},
|
||||
'tool-separator': {
|
||||
group: 'tools',
|
||||
separator: true
|
||||
},
|
||||
'create.start-event': createAction(
|
||||
'bpmn:StartEvent', 'event', 'bpmn-icon-start-event-none',
|
||||
translate('Create StartEvent')
|
||||
),
|
||||
'create.intermediate-event': createAction(
|
||||
'bpmn:IntermediateThrowEvent', 'event', 'bpmn-icon-intermediate-event-none',
|
||||
translate('Create Intermediate/Boundary Event')
|
||||
),
|
||||
'create.end-event': createAction(
|
||||
'bpmn:EndEvent', 'event', 'bpmn-icon-end-event-none',
|
||||
translate('Create EndEvent')
|
||||
),
|
||||
'create.exclusive-gateway': createAction(
|
||||
'bpmn:ExclusiveGateway', 'gateway', 'bpmn-icon-gateway-none',
|
||||
translate('Create Gateway')
|
||||
),
|
||||
'create.task': createAction(
|
||||
'bpmn:Task', 'activity', 'bpmn-icon-task',
|
||||
translate('Create Task')
|
||||
),
|
||||
'create.data-object': createAction(
|
||||
'bpmn:DataObjectReference', 'data-object', 'bpmn-icon-data-object',
|
||||
translate('Create DataObjectReference')
|
||||
),
|
||||
'create.data-store': createAction(
|
||||
'bpmn:DataStoreReference', 'data-store', 'bpmn-icon-data-store',
|
||||
translate('Create DataStoreReference')
|
||||
),
|
||||
'create.subprocess-expanded': createAction(
|
||||
'bpmn:SubProcess', 'activity', 'bpmn-icon-subprocess-expanded',
|
||||
translate('Create expanded SubProcess'),
|
||||
{ isExpanded: true }
|
||||
),
|
||||
'create.participant-expanded': {
|
||||
group: 'collaboration',
|
||||
className: 'bpmn-icon-participant',
|
||||
title: translate('Create Pool/Participant'),
|
||||
action: {
|
||||
dragstart: createParticipant,
|
||||
click: createParticipant
|
||||
}
|
||||
},
|
||||
'create.group': createAction(
|
||||
'bpmn:Group', 'artifact', 'bpmn-icon-group'
|
||||
),
|
||||
});
|
||||
|
||||
return actions;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import PaletteModule from 'diagram-js/lib/features/palette';
|
||||
import CreateModule from 'diagram-js/lib/features/create';
|
||||
import SpaceToolModule from 'diagram-js/lib/features/space-tool';
|
||||
import LassoToolModule from 'diagram-js/lib/features/lasso-tool';
|
||||
import HandToolModule from 'diagram-js/lib/features/hand-tool';
|
||||
import GlobalConnectModule from 'diagram-js/lib/features/global-connect';
|
||||
import translate from 'diagram-js/lib/i18n/translate';
|
||||
|
||||
import PaletteProvider from './PaletteProvider';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
PaletteModule,
|
||||
CreateModule,
|
||||
SpaceToolModule,
|
||||
LassoToolModule,
|
||||
HandToolModule,
|
||||
GlobalConnectModule,
|
||||
translate
|
||||
],
|
||||
__init__: [ 'paletteProvider' ],
|
||||
paletteProvider: [ 'type', PaletteProvider ]
|
||||
};
|
||||
@@ -0,0 +1,493 @@
|
||||
import {
|
||||
getBusinessObject,
|
||||
is
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isEventSubProcess,
|
||||
isExpanded
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import {
|
||||
isDifferentType
|
||||
} from './util/TypeUtil';
|
||||
|
||||
import {
|
||||
forEach,
|
||||
filter
|
||||
} from 'min-dash';
|
||||
|
||||
import * as replaceOptions from '../replace/ReplaceOptions';
|
||||
|
||||
|
||||
/**
|
||||
* This module is an element agnostic replace menu provider for the popup menu.
|
||||
*/
|
||||
export default function ReplaceMenuProvider(
|
||||
popupMenu, modeling, moddle,
|
||||
bpmnReplace, rules, translate) {
|
||||
|
||||
this._popupMenu = popupMenu;
|
||||
this._modeling = modeling;
|
||||
this._moddle = moddle;
|
||||
this._bpmnReplace = bpmnReplace;
|
||||
this._rules = rules;
|
||||
this._translate = translate;
|
||||
|
||||
this.register();
|
||||
}
|
||||
|
||||
ReplaceMenuProvider.$inject = [
|
||||
'popupMenu',
|
||||
'modeling',
|
||||
'moddle',
|
||||
'bpmnReplace',
|
||||
'rules',
|
||||
'translate'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Register replace menu provider in the popup menu
|
||||
*/
|
||||
ReplaceMenuProvider.prototype.register = function() {
|
||||
this._popupMenu.registerProvider('bpmn-replace', this);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get all entries from replaceOptions for the given element and apply filters
|
||||
* on them. Get for example only elements, which are different from the current one.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Array<Object>} a list of menu entry items
|
||||
*/
|
||||
ReplaceMenuProvider.prototype.getEntries = function(element) {
|
||||
|
||||
var businessObject = element.businessObject;
|
||||
|
||||
var rules = this._rules;
|
||||
|
||||
var entries;
|
||||
|
||||
if (!rules.allowed('shape.replace', { element: element })) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var differentType = isDifferentType(element);
|
||||
|
||||
// start events outside event sub processes
|
||||
if (is(businessObject, 'bpmn:StartEvent') && !isEventSubProcess(businessObject.$parent)) {
|
||||
|
||||
entries = filter(replaceOptions.START_EVENT, differentType);
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// expanded/collapsed pools
|
||||
if (is(businessObject, 'bpmn:Participant')) {
|
||||
|
||||
entries = filter(replaceOptions.PARTICIPANT, function(entry) {
|
||||
return isExpanded(businessObject) !== entry.target.isExpanded;
|
||||
});
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// start events inside event sub processes
|
||||
if (is(businessObject, 'bpmn:StartEvent') && isEventSubProcess(businessObject.$parent)) {
|
||||
|
||||
entries = filter(replaceOptions.EVENT_SUB_PROCESS_START_EVENT, function(entry) {
|
||||
|
||||
var target = entry.target;
|
||||
|
||||
var isInterrupting = target.isInterrupting !== false;
|
||||
|
||||
var isInterruptingEqual = getBusinessObject(element).isInterrupting === isInterrupting;
|
||||
|
||||
// filters elements which types and event definition are equal but have have different interrupting types
|
||||
return differentType(entry) || !differentType(entry) && !isInterruptingEqual;
|
||||
|
||||
});
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// end events
|
||||
if (is(businessObject, 'bpmn:EndEvent')) {
|
||||
|
||||
entries = filter(replaceOptions.END_EVENT, function(entry) {
|
||||
var target = entry.target;
|
||||
|
||||
// hide cancel end events outside transactions
|
||||
if (target.eventDefinitionType == 'bpmn:CancelEventDefinition' && !is(businessObject.$parent, 'bpmn:Transaction')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return differentType(entry);
|
||||
});
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// boundary events
|
||||
if (is(businessObject, 'bpmn:BoundaryEvent')) {
|
||||
|
||||
entries = filter(replaceOptions.BOUNDARY_EVENT, function(entry) {
|
||||
|
||||
var target = entry.target;
|
||||
|
||||
if (target.eventDefinition == 'bpmn:CancelEventDefinition' &&
|
||||
!is(businessObject.attachedToRef, 'bpmn:Transaction')) {
|
||||
return false;
|
||||
}
|
||||
var cancelActivity = target.cancelActivity !== false;
|
||||
|
||||
var isCancelActivityEqual = businessObject.cancelActivity == cancelActivity;
|
||||
|
||||
return differentType(entry) || !differentType(entry) && !isCancelActivityEqual;
|
||||
});
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// intermediate events
|
||||
if (is(businessObject, 'bpmn:IntermediateCatchEvent') ||
|
||||
is(businessObject, 'bpmn:IntermediateThrowEvent')) {
|
||||
|
||||
entries = filter(replaceOptions.INTERMEDIATE_EVENT, differentType);
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// gateways
|
||||
if (is(businessObject, 'bpmn:Gateway')) {
|
||||
|
||||
entries = filter(replaceOptions.GATEWAY, differentType);
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// transactions
|
||||
if (is(businessObject, 'bpmn:Transaction')) {
|
||||
|
||||
entries = filter(replaceOptions.TRANSACTION, differentType);
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// expanded event sub processes
|
||||
if (isEventSubProcess(businessObject) && isExpanded(businessObject)) {
|
||||
|
||||
entries = filter(replaceOptions.EVENT_SUB_PROCESS, differentType);
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// expanded sub processes
|
||||
if (is(businessObject, 'bpmn:SubProcess') && isExpanded(businessObject)) {
|
||||
|
||||
entries = filter(replaceOptions.SUBPROCESS_EXPANDED, differentType);
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// collapsed ad hoc sub processes
|
||||
if (is(businessObject, 'bpmn:AdHocSubProcess') && !isExpanded(businessObject)) {
|
||||
|
||||
entries = filter(replaceOptions.TASK, function(entry) {
|
||||
|
||||
var target = entry.target;
|
||||
|
||||
var isTargetSubProcess = target.type === 'bpmn:SubProcess';
|
||||
|
||||
var isTargetExpanded = target.isExpanded === true;
|
||||
|
||||
return isDifferentType(element, target) && (!isTargetSubProcess || isTargetExpanded);
|
||||
});
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
// sequence flows
|
||||
if (is(businessObject, 'bpmn:SequenceFlow')) {
|
||||
return this._createSequenceFlowEntries(element, replaceOptions.SEQUENCE_FLOW);
|
||||
}
|
||||
|
||||
// flow nodes
|
||||
if (is(businessObject, 'bpmn:FlowNode')) {
|
||||
entries = filter(replaceOptions.TASK, differentType);
|
||||
|
||||
// collapsed SubProcess can not be replaced with itself
|
||||
if (is(businessObject, 'bpmn:SubProcess') && !isExpanded(businessObject)) {
|
||||
entries = filter(entries, function(entry) {
|
||||
return entry.label !== 'Sub Process (collapsed)';
|
||||
});
|
||||
}
|
||||
|
||||
return this._createEntries(element, entries);
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of header items for the given element. This includes buttons
|
||||
* for multi instance markers and for the ad hoc marker.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Array<Object>} a list of menu entry items
|
||||
*/
|
||||
ReplaceMenuProvider.prototype.getHeaderEntries = function(element) {
|
||||
|
||||
var headerEntries = [];
|
||||
|
||||
if (is(element, 'bpmn:Activity') && !isEventSubProcess(element)) {
|
||||
headerEntries = headerEntries.concat(this._getLoopEntries(element));
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:SubProcess') &&
|
||||
!is(element, 'bpmn:Transaction') &&
|
||||
!isEventSubProcess(element)) {
|
||||
headerEntries.push(this._getAdHocEntry(element));
|
||||
}
|
||||
|
||||
return headerEntries;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates an array of menu entry objects for a given element and filters the replaceOptions
|
||||
* according to a filter function.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
* @param {Object} replaceOptions
|
||||
*
|
||||
* @return {Array<Object>} a list of menu items
|
||||
*/
|
||||
ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) {
|
||||
var menuEntries = [];
|
||||
|
||||
var self = this;
|
||||
|
||||
forEach(replaceOptions, function(definition) {
|
||||
var entry = self._createMenuEntry(definition, element);
|
||||
|
||||
menuEntries.push(entry);
|
||||
});
|
||||
|
||||
return menuEntries;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an array of menu entry objects for a given sequence flow.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
* @param {Object} replaceOptions
|
||||
|
||||
* @return {Array<Object>} a list of menu items
|
||||
*/
|
||||
ReplaceMenuProvider.prototype._createSequenceFlowEntries = function(element, replaceOptions) {
|
||||
|
||||
var businessObject = getBusinessObject(element);
|
||||
|
||||
var menuEntries = [];
|
||||
|
||||
var modeling = this._modeling,
|
||||
moddle = this._moddle;
|
||||
|
||||
var self = this;
|
||||
|
||||
forEach(replaceOptions, function(entry) {
|
||||
|
||||
switch (entry.actionName) {
|
||||
case 'replace-with-default-flow':
|
||||
if (businessObject.sourceRef.default !== businessObject &&
|
||||
(is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
|
||||
is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
|
||||
is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
|
||||
is(businessObject.sourceRef, 'bpmn:Activity'))) {
|
||||
|
||||
menuEntries.push(self._createMenuEntry(entry, element, function() {
|
||||
modeling.updateProperties(element.source, { default: businessObject });
|
||||
}));
|
||||
}
|
||||
break;
|
||||
case 'replace-with-conditional-flow':
|
||||
if (!businessObject.conditionExpression && is(businessObject.sourceRef, 'bpmn:Activity')) {
|
||||
|
||||
menuEntries.push(self._createMenuEntry(entry, element, function() {
|
||||
var conditionExpression = moddle.create('bpmn:FormalExpression', { body: '' });
|
||||
|
||||
modeling.updateProperties(element, { conditionExpression: conditionExpression });
|
||||
}));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// default flows
|
||||
if (is(businessObject.sourceRef, 'bpmn:Activity') && businessObject.conditionExpression) {
|
||||
return menuEntries.push(self._createMenuEntry(entry, element, function() {
|
||||
modeling.updateProperties(element, { conditionExpression: undefined });
|
||||
}));
|
||||
}
|
||||
// conditional flows
|
||||
if ((is(businessObject.sourceRef, 'bpmn:ExclusiveGateway') ||
|
||||
is(businessObject.sourceRef, 'bpmn:InclusiveGateway') ||
|
||||
is(businessObject.sourceRef, 'bpmn:ComplexGateway') ||
|
||||
is(businessObject.sourceRef, 'bpmn:Activity')) &&
|
||||
businessObject.sourceRef.default === businessObject) {
|
||||
|
||||
return menuEntries.push(self._createMenuEntry(entry, element, function() {
|
||||
modeling.updateProperties(element.source, { default: undefined });
|
||||
}));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return menuEntries;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Creates and returns a single menu entry item.
|
||||
*
|
||||
* @param {Object} definition a single replace options definition object
|
||||
* @param {djs.model.Base} element
|
||||
* @param {Function} [action] an action callback function which gets called when
|
||||
* the menu entry is being triggered.
|
||||
*
|
||||
* @return {Object} menu entry item
|
||||
*/
|
||||
ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) {
|
||||
var translate = this._translate;
|
||||
var replaceElement = this._bpmnReplace.replaceElement;
|
||||
|
||||
var replaceAction = function() {
|
||||
return replaceElement(element, definition.target);
|
||||
};
|
||||
|
||||
action = action || replaceAction;
|
||||
|
||||
var menuEntry = {
|
||||
label: translate(definition.label),
|
||||
className: definition.className,
|
||||
id: definition.actionName,
|
||||
action: action
|
||||
};
|
||||
|
||||
return menuEntry;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list of menu items containing buttons for multi instance markers
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Array<Object>} a list of menu items
|
||||
*/
|
||||
ReplaceMenuProvider.prototype._getLoopEntries = function(element) {
|
||||
|
||||
var self = this;
|
||||
var translate = this._translate;
|
||||
|
||||
function toggleLoopEntry(event, entry) {
|
||||
var loopCharacteristics;
|
||||
|
||||
if (entry.active) {
|
||||
loopCharacteristics = undefined;
|
||||
} else {
|
||||
loopCharacteristics = self._moddle.create(entry.options.loopCharacteristics);
|
||||
|
||||
if (entry.options.isSequential) {
|
||||
loopCharacteristics.isSequential = entry.options.isSequential;
|
||||
}
|
||||
}
|
||||
self._modeling.updateProperties(element, { loopCharacteristics: loopCharacteristics });
|
||||
}
|
||||
|
||||
var businessObject = getBusinessObject(element),
|
||||
loopCharacteristics = businessObject.loopCharacteristics;
|
||||
|
||||
var isSequential,
|
||||
isLoop,
|
||||
isParallel;
|
||||
|
||||
if (loopCharacteristics) {
|
||||
isSequential = loopCharacteristics.isSequential;
|
||||
isLoop = loopCharacteristics.isSequential === undefined;
|
||||
isParallel = loopCharacteristics.isSequential !== undefined && !loopCharacteristics.isSequential;
|
||||
}
|
||||
|
||||
|
||||
var loopEntries = [
|
||||
{
|
||||
id: 'toggle-parallel-mi',
|
||||
className: 'bpmn-icon-parallel-mi-marker',
|
||||
title: translate('Parallel Multi Instance'),
|
||||
active: isParallel,
|
||||
action: toggleLoopEntry,
|
||||
options: {
|
||||
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
|
||||
isSequential: false
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'toggle-sequential-mi',
|
||||
className: 'bpmn-icon-sequential-mi-marker',
|
||||
title: translate('Sequential Multi Instance'),
|
||||
active: isSequential,
|
||||
action: toggleLoopEntry,
|
||||
options: {
|
||||
loopCharacteristics: 'bpmn:MultiInstanceLoopCharacteristics',
|
||||
isSequential: true
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'toggle-loop',
|
||||
className: 'bpmn-icon-loop-marker',
|
||||
title: translate('Loop'),
|
||||
active: isLoop,
|
||||
action: toggleLoopEntry,
|
||||
options: {
|
||||
loopCharacteristics: 'bpmn:StandardLoopCharacteristics'
|
||||
}
|
||||
}
|
||||
];
|
||||
return loopEntries;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the menu items containing a button for the ad hoc marker
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Object} a menu item
|
||||
*/
|
||||
ReplaceMenuProvider.prototype._getAdHocEntry = function(element) {
|
||||
var translate = this._translate;
|
||||
var businessObject = getBusinessObject(element);
|
||||
|
||||
var isAdHoc = is(businessObject, 'bpmn:AdHocSubProcess');
|
||||
|
||||
var replaceElement = this._bpmnReplace.replaceElement;
|
||||
|
||||
var adHocEntry = {
|
||||
id: 'toggle-adhoc',
|
||||
className: 'bpmn-icon-ad-hoc-marker',
|
||||
title: translate('Ad-hoc'),
|
||||
active: isAdHoc,
|
||||
action: function(event, entry) {
|
||||
if (isAdHoc) {
|
||||
return replaceElement(element, { type: 'bpmn:SubProcess' });
|
||||
} else {
|
||||
return replaceElement(element, { type: 'bpmn:AdHocSubProcess' });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return adHocEntry;
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import PopupMenuModule from 'diagram-js/lib/features/popup-menu';
|
||||
import ReplaceModule from '../replace';
|
||||
|
||||
import ReplaceMenuProvider from './ReplaceMenuProvider';
|
||||
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
PopupMenuModule,
|
||||
ReplaceModule
|
||||
],
|
||||
__init__: [ 'replaceMenuProvider' ],
|
||||
replaceMenuProvider: [ 'type', ReplaceMenuProvider ]
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
getBusinessObject
|
||||
} from '../../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isExpanded
|
||||
} from '../../../util/DiUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Returns true, if an element is from a different type
|
||||
* than a target definition. Takes into account the type,
|
||||
* event definition type and triggeredByEvent property.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
export function isDifferentType(element) {
|
||||
|
||||
return function(entry) {
|
||||
var target = entry.target;
|
||||
|
||||
var businessObject = getBusinessObject(element),
|
||||
eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0];
|
||||
|
||||
var isTypeEqual = businessObject.$type === target.type;
|
||||
|
||||
var isEventDefinitionEqual = (
|
||||
(eventDefinition && eventDefinition.$type) === target.eventDefinitionType
|
||||
);
|
||||
|
||||
var isTriggeredByEventEqual = (
|
||||
businessObject.triggeredByEvent === target.triggeredByEvent
|
||||
);
|
||||
|
||||
var isExpandedEqual = (
|
||||
target.isExpanded === undefined ||
|
||||
target.isExpanded === isExpanded(businessObject)
|
||||
);
|
||||
|
||||
return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual || !isExpandedEqual;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import cssEscape from 'css.escape';
|
||||
|
||||
import {
|
||||
assign,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
query as domQuery
|
||||
} from 'min-dom';
|
||||
|
||||
import {
|
||||
attr as svgAttr
|
||||
} from 'tiny-svg';
|
||||
|
||||
var LOW_PRIORITY = 250;
|
||||
|
||||
|
||||
export default function BpmnReplacePreview(
|
||||
eventBus, elementRegistry, elementFactory,
|
||||
canvas, previewSupport) {
|
||||
|
||||
CommandInterceptor.call(this, eventBus);
|
||||
|
||||
/**
|
||||
* Replace the visuals of all elements in the context which can be replaced
|
||||
*
|
||||
* @param {Object} context
|
||||
*/
|
||||
function replaceVisual(context) {
|
||||
|
||||
var replacements = context.canExecute.replacements;
|
||||
|
||||
forEach(replacements, function(replacement) {
|
||||
|
||||
var id = replacement.oldElementId;
|
||||
|
||||
var newElement = {
|
||||
type: replacement.newElementType
|
||||
};
|
||||
|
||||
// if the visual of the element is already replaced
|
||||
if (context.visualReplacements[id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
var element = elementRegistry.get(id);
|
||||
|
||||
assign(newElement, { x: element.x, y: element.y });
|
||||
|
||||
// create a temporary shape
|
||||
var tempShape = elementFactory.createShape(newElement);
|
||||
|
||||
canvas.addShape(tempShape, element.parent);
|
||||
|
||||
// select the original SVG element related to the element and hide it
|
||||
var gfx = domQuery('[data-element-id="' + cssEscape(element.id) + '"]', context.dragGroup);
|
||||
|
||||
if (gfx) {
|
||||
svgAttr(gfx, { display: 'none' });
|
||||
}
|
||||
|
||||
// clone the gfx of the temporary shape and add it to the drag group
|
||||
var dragger = previewSupport.addDragger(tempShape, context.dragGroup);
|
||||
|
||||
context.visualReplacements[id] = dragger;
|
||||
|
||||
canvas.removeShape(tempShape);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the original visuals of the previously replaced elements
|
||||
*
|
||||
* @param {Object} context
|
||||
*/
|
||||
function restoreVisual(context) {
|
||||
|
||||
var visualReplacements = context.visualReplacements;
|
||||
|
||||
forEach(visualReplacements, function(dragger, id) {
|
||||
|
||||
var originalGfx = domQuery('[data-element-id="' + cssEscape(id) + '"]', context.dragGroup);
|
||||
|
||||
if (originalGfx) {
|
||||
svgAttr(originalGfx, { display: 'inline' });
|
||||
}
|
||||
|
||||
dragger.remove();
|
||||
|
||||
if (visualReplacements[id]) {
|
||||
delete visualReplacements[id];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
eventBus.on('shape.move.move', LOW_PRIORITY, function(event) {
|
||||
|
||||
var context = event.context,
|
||||
canExecute = context.canExecute;
|
||||
|
||||
if (!context.visualReplacements) {
|
||||
context.visualReplacements = {};
|
||||
}
|
||||
|
||||
if (canExecute && canExecute.replacements) {
|
||||
replaceVisual(context);
|
||||
} else {
|
||||
restoreVisual(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BpmnReplacePreview.$inject = [
|
||||
'eventBus',
|
||||
'elementRegistry',
|
||||
'elementFactory',
|
||||
'canvas',
|
||||
'previewSupport'
|
||||
];
|
||||
|
||||
inherits(BpmnReplacePreview, CommandInterceptor);
|
||||
@@ -0,0 +1,11 @@
|
||||
import PreviewSupportModule from 'diagram-js/lib/features/preview-support';
|
||||
|
||||
import BpmnReplacePreview from './BpmnReplacePreview';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
PreviewSupportModule
|
||||
],
|
||||
__init__: [ 'bpmnReplacePreview' ],
|
||||
bpmnReplacePreview: [ 'type', BpmnReplacePreview ]
|
||||
};
|
||||
@@ -0,0 +1,274 @@
|
||||
import {
|
||||
pick,
|
||||
assign,
|
||||
filter,
|
||||
has
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
is,
|
||||
getBusinessObject
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from '../modeling/util/ModelingUtil';
|
||||
|
||||
import {
|
||||
isExpanded,
|
||||
isEventSubProcess
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import {
|
||||
getProperties,
|
||||
IGNORED_PROPERTIES
|
||||
} from '../../util/model/ModelCloneUtils';
|
||||
|
||||
import ModelCloneHelper from '../../util/model/ModelCloneHelper';
|
||||
|
||||
var CUSTOM_PROPERTIES = [
|
||||
'cancelActivity',
|
||||
'instantiate',
|
||||
'eventGatewayType',
|
||||
'triggeredByEvent',
|
||||
'isInterrupting'
|
||||
];
|
||||
|
||||
|
||||
function toggeling(element, target) {
|
||||
|
||||
var oldCollapsed = (
|
||||
element && has(element, 'collapsed') ? element.collapsed : !isExpanded(element)
|
||||
);
|
||||
|
||||
var targetCollapsed;
|
||||
|
||||
if (target && (has(target, 'collapsed') || has(target, 'isExpanded'))) {
|
||||
// property is explicitly set so use it
|
||||
targetCollapsed = (
|
||||
has(target, 'collapsed') ? target.collapsed : !target.isExpanded
|
||||
);
|
||||
} else {
|
||||
// keep old state
|
||||
targetCollapsed = oldCollapsed;
|
||||
}
|
||||
|
||||
if (oldCollapsed !== targetCollapsed) {
|
||||
element.collapsed = oldCollapsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This module takes care of replacing BPMN elements
|
||||
*/
|
||||
export default function BpmnReplace(
|
||||
bpmnFactory, elementFactory, replace,
|
||||
selection, modeling, eventBus
|
||||
) {
|
||||
|
||||
var helper = new ModelCloneHelper(eventBus, bpmnFactory);
|
||||
|
||||
/**
|
||||
* Prepares a new business object for the replacement element
|
||||
* and triggers the replace operation.
|
||||
*
|
||||
* @param {djs.model.Base} element
|
||||
* @param {Object} target
|
||||
* @param {Object} [hints]
|
||||
*
|
||||
* @return {djs.model.Base} the newly created element
|
||||
*/
|
||||
function replaceElement(element, target, hints) {
|
||||
|
||||
hints = hints || {};
|
||||
|
||||
var type = target.type,
|
||||
oldBusinessObject = element.businessObject;
|
||||
|
||||
if (isSubProcess(oldBusinessObject)) {
|
||||
if (type === 'bpmn:SubProcess') {
|
||||
if (toggeling(element, target)) {
|
||||
// expanding or collapsing process
|
||||
modeling.toggleCollapse(element);
|
||||
|
||||
return element;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newBusinessObject = bpmnFactory.create(type);
|
||||
|
||||
var newElement = {
|
||||
type: type,
|
||||
businessObject: newBusinessObject
|
||||
};
|
||||
|
||||
var elementProps = getProperties(oldBusinessObject.$descriptor),
|
||||
newElementProps = getProperties(newBusinessObject.$descriptor, true),
|
||||
copyProps = intersection(elementProps, newElementProps);
|
||||
|
||||
// initialize special properties defined in target definition
|
||||
assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES));
|
||||
|
||||
var properties = filter(copyProps, function(property) {
|
||||
var propName = property.replace(/bpmn:/, '');
|
||||
|
||||
// copying event definitions, unless we replace
|
||||
if (propName === 'eventDefinitions') {
|
||||
return hasEventDefinition(element, target.eventDefinitionType);
|
||||
}
|
||||
|
||||
// retain loop characteristics if the target element
|
||||
// is not an event sub process
|
||||
if (propName === 'loopCharacteristics') {
|
||||
return !isEventSubProcess(newBusinessObject);
|
||||
}
|
||||
|
||||
// so the applied properties from 'target' don't get lost
|
||||
if (property in newBusinessObject) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (propName === 'processRef' && target.isExpanded === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (propName === 'triggeredByEvent') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IGNORED_PROPERTIES.indexOf(propName) === -1;
|
||||
});
|
||||
|
||||
newBusinessObject = helper.clone(oldBusinessObject, newBusinessObject, properties);
|
||||
|
||||
// initialize custom BPMN extensions
|
||||
if (target.eventDefinitionType) {
|
||||
|
||||
// only initialize with new eventDefinition
|
||||
// if we did not set an event definition yet,
|
||||
// i.e. because we cloned it
|
||||
if (!hasEventDefinition(newBusinessObject, target.eventDefinitionType)) {
|
||||
newElement.eventDefinitionType = target.eventDefinitionType;
|
||||
}
|
||||
}
|
||||
|
||||
if (is(oldBusinessObject, 'bpmn:Activity')) {
|
||||
|
||||
if (isSubProcess(oldBusinessObject)) {
|
||||
// no toggeling, so keep old state
|
||||
newElement.isExpanded = isExpanded(oldBusinessObject);
|
||||
}
|
||||
// else if property is explicitly set, use it
|
||||
else if (target && has(target, 'isExpanded')) {
|
||||
newElement.isExpanded = target.isExpanded;
|
||||
}
|
||||
|
||||
// TODO: need also to respect min/max Size
|
||||
// copy size, from an expanded subprocess to an expanded alternative subprocess
|
||||
// except bpmn:Task, because Task is always expanded
|
||||
if ((isExpanded(oldBusinessObject) && !is(oldBusinessObject, 'bpmn:Task')) && newElement.isExpanded) {
|
||||
newElement.width = element.width;
|
||||
newElement.height = element.height;
|
||||
}
|
||||
}
|
||||
|
||||
// remove children if not expanding sub process
|
||||
if (isSubProcess(oldBusinessObject) && !isSubProcess(newBusinessObject)) {
|
||||
hints.moveChildren = false;
|
||||
}
|
||||
|
||||
// transform collapsed/expanded pools
|
||||
if (is(oldBusinessObject, 'bpmn:Participant')) {
|
||||
|
||||
// create expanded pool
|
||||
if (target.isExpanded === true) {
|
||||
newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
|
||||
} else {
|
||||
// remove children when transforming to collapsed pool
|
||||
hints.moveChildren = false;
|
||||
}
|
||||
|
||||
// apply same width and default height
|
||||
newElement.width = element.width;
|
||||
newElement.height = elementFactory._getDefaultSize(newBusinessObject).height;
|
||||
}
|
||||
|
||||
newBusinessObject.name = oldBusinessObject.name;
|
||||
|
||||
// retain default flow's reference between inclusive <-> exclusive gateways and activities
|
||||
if (
|
||||
isAny(oldBusinessObject, [
|
||||
'bpmn:ExclusiveGateway',
|
||||
'bpmn:InclusiveGateway',
|
||||
'bpmn:Activity'
|
||||
]) &&
|
||||
isAny(newBusinessObject, [
|
||||
'bpmn:ExclusiveGateway',
|
||||
'bpmn:InclusiveGateway',
|
||||
'bpmn:Activity'
|
||||
])
|
||||
) {
|
||||
newBusinessObject.default = oldBusinessObject.default;
|
||||
}
|
||||
|
||||
if (
|
||||
target.host &&
|
||||
!is(oldBusinessObject, 'bpmn:BoundaryEvent') &&
|
||||
is(newBusinessObject, 'bpmn:BoundaryEvent')
|
||||
) {
|
||||
newElement.host = target.host;
|
||||
}
|
||||
|
||||
if ('fill' in oldBusinessObject.di || 'stroke' in oldBusinessObject.di) {
|
||||
assign(newElement, { colors: pick(oldBusinessObject.di, [ 'fill', 'stroke' ]) });
|
||||
}
|
||||
|
||||
newElement = replace.replaceElement(element, newElement, hints);
|
||||
|
||||
if (hints.select !== false) {
|
||||
selection.select(newElement);
|
||||
}
|
||||
|
||||
return newElement;
|
||||
}
|
||||
|
||||
this.replaceElement = replaceElement;
|
||||
}
|
||||
|
||||
BpmnReplace.$inject = [
|
||||
'bpmnFactory',
|
||||
'elementFactory',
|
||||
'replace',
|
||||
'selection',
|
||||
'modeling',
|
||||
'eventBus'
|
||||
];
|
||||
|
||||
|
||||
function isSubProcess(bo) {
|
||||
return is(bo, 'bpmn:SubProcess');
|
||||
}
|
||||
|
||||
function hasEventDefinition(element, type) {
|
||||
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return type && bo.get('eventDefinitions').some(function(definition) {
|
||||
return is(definition, type);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute intersection between two arrays.
|
||||
*/
|
||||
function intersection(a1, a2) {
|
||||
return a1.filter(function(el) {
|
||||
return a2.indexOf(el) !== -1;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,785 @@
|
||||
export var START_EVENT = [
|
||||
{
|
||||
label: 'Start Event',
|
||||
actionName: 'replace-with-none-start',
|
||||
className: 'bpmn-icon-start-event-none',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Intermediate Throw Event',
|
||||
actionName: 'replace-with-none-intermediate-throwing',
|
||||
className: 'bpmn-icon-intermediate-event-none',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'End Event',
|
||||
actionName: 'replace-with-none-end',
|
||||
className: 'bpmn-icon-end-event-none',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Message Start Event',
|
||||
actionName: 'replace-with-message-start',
|
||||
className: 'bpmn-icon-start-event-message',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Timer Start Event',
|
||||
actionName: 'replace-with-timer-start',
|
||||
className: 'bpmn-icon-start-event-timer',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:TimerEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Conditional Start Event',
|
||||
actionName: 'replace-with-conditional-start',
|
||||
className: 'bpmn-icon-start-event-condition',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Start Event',
|
||||
actionName: 'replace-with-signal-start',
|
||||
className: 'bpmn-icon-start-event-signal',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var INTERMEDIATE_EVENT = [
|
||||
{
|
||||
label: 'Start Event',
|
||||
actionName: 'replace-with-none-start',
|
||||
className: 'bpmn-icon-start-event-none',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Intermediate Throw Event',
|
||||
actionName: 'replace-with-none-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-none',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'End Event',
|
||||
actionName: 'replace-with-none-end',
|
||||
className: 'bpmn-icon-end-event-none',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Message Intermediate Catch Event',
|
||||
actionName: 'replace-with-message-intermediate-catch',
|
||||
className: 'bpmn-icon-intermediate-event-catch-message',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateCatchEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Message Intermediate Throw Event',
|
||||
actionName: 'replace-with-message-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-throw-message',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Timer Intermediate Catch Event',
|
||||
actionName: 'replace-with-timer-intermediate-catch',
|
||||
className: 'bpmn-icon-intermediate-event-catch-timer',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateCatchEvent',
|
||||
eventDefinitionType: 'bpmn:TimerEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Escalation Intermediate Throw Event',
|
||||
actionName: 'replace-with-escalation-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-throw-escalation',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent',
|
||||
eventDefinitionType: 'bpmn:EscalationEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Conditional Intermediate Catch Event',
|
||||
actionName: 'replace-with-conditional-intermediate-catch',
|
||||
className: 'bpmn-icon-intermediate-event-catch-condition',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateCatchEvent',
|
||||
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Link Intermediate Catch Event',
|
||||
actionName: 'replace-with-link-intermediate-catch',
|
||||
className: 'bpmn-icon-intermediate-event-catch-link',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateCatchEvent',
|
||||
eventDefinitionType: 'bpmn:LinkEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Link Intermediate Throw Event',
|
||||
actionName: 'replace-with-link-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-throw-link',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent',
|
||||
eventDefinitionType: 'bpmn:LinkEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Compensation Intermediate Throw Event',
|
||||
actionName: 'replace-with-compensation-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-throw-compensation',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent',
|
||||
eventDefinitionType: 'bpmn:CompensateEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Intermediate Catch Event',
|
||||
actionName: 'replace-with-signal-intermediate-catch',
|
||||
className: 'bpmn-icon-intermediate-event-catch-signal',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateCatchEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Intermediate Throw Event',
|
||||
actionName: 'replace-with-signal-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-throw-signal',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var END_EVENT = [
|
||||
{
|
||||
label: 'Start Event',
|
||||
actionName: 'replace-with-none-start',
|
||||
className: 'bpmn-icon-start-event-none',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Intermediate Throw Event',
|
||||
actionName: 'replace-with-none-intermediate-throw',
|
||||
className: 'bpmn-icon-intermediate-event-none',
|
||||
target: {
|
||||
type: 'bpmn:IntermediateThrowEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'End Event',
|
||||
actionName: 'replace-with-none-end',
|
||||
className: 'bpmn-icon-end-event-none',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Message End Event',
|
||||
actionName: 'replace-with-message-end',
|
||||
className: 'bpmn-icon-end-event-message',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Escalation End Event',
|
||||
actionName: 'replace-with-escalation-end',
|
||||
className: 'bpmn-icon-end-event-escalation',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:EscalationEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Error End Event',
|
||||
actionName: 'replace-with-error-end',
|
||||
className: 'bpmn-icon-end-event-error',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:ErrorEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel End Event',
|
||||
actionName: 'replace-with-cancel-end',
|
||||
className: 'bpmn-icon-end-event-cancel',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:CancelEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Compensation End Event',
|
||||
actionName: 'replace-with-compensation-end',
|
||||
className: 'bpmn-icon-end-event-compensation',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:CompensateEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal End Event',
|
||||
actionName: 'replace-with-signal-end',
|
||||
className: 'bpmn-icon-end-event-signal',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Terminate End Event',
|
||||
actionName: 'replace-with-terminate-end',
|
||||
className: 'bpmn-icon-end-event-terminate',
|
||||
target: {
|
||||
type: 'bpmn:EndEvent',
|
||||
eventDefinitionType: 'bpmn:TerminateEventDefinition'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var GATEWAY = [
|
||||
{
|
||||
label: 'Exclusive Gateway',
|
||||
actionName: 'replace-with-exclusive-gateway',
|
||||
className: 'bpmn-icon-gateway-xor',
|
||||
target: {
|
||||
type: 'bpmn:ExclusiveGateway'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Parallel Gateway',
|
||||
actionName: 'replace-with-parallel-gateway',
|
||||
className: 'bpmn-icon-gateway-parallel',
|
||||
target: {
|
||||
type: 'bpmn:ParallelGateway'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Inclusive Gateway',
|
||||
actionName: 'replace-with-inclusive-gateway',
|
||||
className: 'bpmn-icon-gateway-or',
|
||||
target: {
|
||||
type: 'bpmn:InclusiveGateway'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Complex Gateway',
|
||||
actionName: 'replace-with-complex-gateway',
|
||||
className: 'bpmn-icon-gateway-complex',
|
||||
target: {
|
||||
type: 'bpmn:ComplexGateway'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Event based Gateway',
|
||||
actionName: 'replace-with-event-based-gateway',
|
||||
className: 'bpmn-icon-gateway-eventbased',
|
||||
target: {
|
||||
type: 'bpmn:EventBasedGateway',
|
||||
instantiate: false,
|
||||
eventGatewayType: 'Exclusive'
|
||||
}
|
||||
}
|
||||
// Gateways deactivated until https://github.com/bpmn-io/bpmn-js/issues/194
|
||||
// {
|
||||
// label: 'Event based instantiating Gateway',
|
||||
// actionName: 'replace-with-exclusive-event-based-gateway',
|
||||
// className: 'bpmn-icon-exclusive-event-based',
|
||||
// target: {
|
||||
// type: 'bpmn:EventBasedGateway'
|
||||
// },
|
||||
// options: {
|
||||
// businessObject: { instantiate: true, eventGatewayType: 'Exclusive' }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// label: 'Parallel Event based instantiating Gateway',
|
||||
// actionName: 'replace-with-parallel-event-based-instantiate-gateway',
|
||||
// className: 'bpmn-icon-parallel-event-based-instantiate-gateway',
|
||||
// target: {
|
||||
// type: 'bpmn:EventBasedGateway'
|
||||
// },
|
||||
// options: {
|
||||
// businessObject: { instantiate: true, eventGatewayType: 'Parallel' }
|
||||
// }
|
||||
// }
|
||||
];
|
||||
|
||||
export var SUBPROCESS_EXPANDED = [
|
||||
{
|
||||
label: 'Transaction',
|
||||
actionName: 'replace-with-transaction',
|
||||
className: 'bpmn-icon-transaction',
|
||||
target: {
|
||||
type: 'bpmn:Transaction',
|
||||
isExpanded: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Event Sub Process',
|
||||
actionName: 'replace-with-event-subprocess',
|
||||
className: 'bpmn-icon-event-subprocess-expanded',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
triggeredByEvent: true,
|
||||
isExpanded: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sub Process (collapsed)',
|
||||
actionName: 'replace-with-collapsed-subprocess',
|
||||
className: 'bpmn-icon-subprocess-collapsed',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var TRANSACTION = [
|
||||
{
|
||||
label: 'Sub Process',
|
||||
actionName: 'replace-with-subprocess',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Event Sub Process',
|
||||
actionName: 'replace-with-event-subprocess',
|
||||
className: 'bpmn-icon-event-subprocess-expanded',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
triggeredByEvent: true,
|
||||
isExpanded: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var EVENT_SUB_PROCESS = [
|
||||
{
|
||||
label: 'Sub Process',
|
||||
actionName: 'replace-with-subprocess',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Transaction',
|
||||
actionName: 'replace-with-transaction',
|
||||
className: 'bpmn-icon-transaction',
|
||||
target: {
|
||||
type: 'bpmn:Transaction',
|
||||
isExpanded: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var TASK = [
|
||||
{
|
||||
label: 'Task',
|
||||
actionName: 'replace-with-task',
|
||||
className: 'bpmn-icon-task',
|
||||
target: {
|
||||
type: 'bpmn:Task'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Send Task',
|
||||
actionName: 'replace-with-send-task',
|
||||
className: 'bpmn-icon-send',
|
||||
target: {
|
||||
type: 'bpmn:SendTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Receive Task',
|
||||
actionName: 'replace-with-receive-task',
|
||||
className: 'bpmn-icon-receive',
|
||||
target: {
|
||||
type: 'bpmn:ReceiveTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'User Task',
|
||||
actionName: 'replace-with-user-task',
|
||||
className: 'bpmn-icon-user',
|
||||
target: {
|
||||
type: 'bpmn:UserTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Manual Task',
|
||||
actionName: 'replace-with-manual-task',
|
||||
className: 'bpmn-icon-manual',
|
||||
target: {
|
||||
type: 'bpmn:ManualTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Business Rule Task',
|
||||
actionName: 'replace-with-rule-task',
|
||||
className: 'bpmn-icon-business-rule',
|
||||
target: {
|
||||
type: 'bpmn:BusinessRuleTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Service Task',
|
||||
actionName: 'replace-with-service-task',
|
||||
className: 'bpmn-icon-service',
|
||||
target: {
|
||||
type: 'bpmn:ServiceTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Script Task',
|
||||
actionName: 'replace-with-script-task',
|
||||
className: 'bpmn-icon-script',
|
||||
target: {
|
||||
type: 'bpmn:ScriptTask'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Call Activity',
|
||||
actionName: 'replace-with-call-activity',
|
||||
className: 'bpmn-icon-call-activity',
|
||||
target: {
|
||||
type: 'bpmn:CallActivity'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sub Process (collapsed)',
|
||||
actionName: 'replace-with-collapsed-subprocess',
|
||||
className: 'bpmn-icon-subprocess-collapsed',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sub Process (expanded)',
|
||||
actionName: 'replace-with-expanded-subprocess',
|
||||
className: 'bpmn-icon-subprocess-expanded',
|
||||
target: {
|
||||
type: 'bpmn:SubProcess',
|
||||
isExpanded: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var BOUNDARY_EVENT = [
|
||||
{
|
||||
label: 'Message Boundary Event',
|
||||
actionName: 'replace-with-message-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-message',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Timer Boundary Event',
|
||||
actionName: 'replace-with-timer-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-timer',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:TimerEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Escalation Boundary Event',
|
||||
actionName: 'replace-with-escalation-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-escalation',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:EscalationEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Conditional Boundary Event',
|
||||
actionName: 'replace-with-conditional-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-condition',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Error Boundary Event',
|
||||
actionName: 'replace-with-error-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-error',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:ErrorEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Cancel Boundary Event',
|
||||
actionName: 'replace-with-cancel-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-cancel',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:CancelEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Boundary Event',
|
||||
actionName: 'replace-with-signal-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-signal',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Compensation Boundary Event',
|
||||
actionName: 'replace-with-compensation-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-compensation',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:CompensateEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Message Boundary Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-message-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-message',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition',
|
||||
cancelActivity: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Timer Boundary Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-timer-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-timer',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:TimerEventDefinition',
|
||||
cancelActivity: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Escalation Boundary Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-escalation-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-escalation',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:EscalationEventDefinition',
|
||||
cancelActivity: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Conditional Boundary Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-conditional-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-condition',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
|
||||
cancelActivity: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Boundary Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-signal-boundary',
|
||||
className: 'bpmn-icon-intermediate-event-catch-non-interrupting-signal',
|
||||
target: {
|
||||
type: 'bpmn:BoundaryEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition',
|
||||
cancelActivity: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var EVENT_SUB_PROCESS_START_EVENT = [
|
||||
{
|
||||
label: 'Message Start Event',
|
||||
actionName: 'replace-with-message-start',
|
||||
className: 'bpmn-icon-start-event-message',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Timer Start Event',
|
||||
actionName: 'replace-with-timer-start',
|
||||
className: 'bpmn-icon-start-event-timer',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:TimerEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Conditional Start Event',
|
||||
actionName: 'replace-with-conditional-start',
|
||||
className: 'bpmn-icon-start-event-condition',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:ConditionalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Start Event',
|
||||
actionName: 'replace-with-signal-start',
|
||||
className: 'bpmn-icon-start-event-signal',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Error Start Event',
|
||||
actionName: 'replace-with-error-start',
|
||||
className: 'bpmn-icon-start-event-error',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:ErrorEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Escalation Start Event',
|
||||
actionName: 'replace-with-escalation-start',
|
||||
className: 'bpmn-icon-start-event-escalation',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:EscalationEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Compensation Start Event',
|
||||
actionName: 'replace-with-compensation-start',
|
||||
className: 'bpmn-icon-start-event-compensation',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:CompensateEventDefinition'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Message Start Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-message-start',
|
||||
className: 'bpmn-icon-start-event-non-interrupting-message',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:MessageEventDefinition',
|
||||
isInterrupting: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Timer Start Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-timer-start',
|
||||
className: 'bpmn-icon-start-event-non-interrupting-timer',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:TimerEventDefinition',
|
||||
isInterrupting: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Conditional Start Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-conditional-start',
|
||||
className: 'bpmn-icon-start-event-non-interrupting-condition',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:ConditionalEventDefinition',
|
||||
isInterrupting: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Signal Start Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-signal-start',
|
||||
className: 'bpmn-icon-start-event-non-interrupting-signal',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:SignalEventDefinition',
|
||||
isInterrupting: false
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Escalation Start Event (non-interrupting)',
|
||||
actionName: 'replace-with-non-interrupting-escalation-start',
|
||||
className: 'bpmn-icon-start-event-non-interrupting-escalation',
|
||||
target: {
|
||||
type: 'bpmn:StartEvent',
|
||||
eventDefinitionType: 'bpmn:EscalationEventDefinition',
|
||||
isInterrupting: false
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export var SEQUENCE_FLOW = [
|
||||
{
|
||||
label: 'Sequence Flow',
|
||||
actionName: 'replace-with-sequence-flow',
|
||||
className: 'bpmn-icon-connection'
|
||||
},
|
||||
{
|
||||
label: 'Default Flow',
|
||||
actionName: 'replace-with-default-flow',
|
||||
className: 'bpmn-icon-default-flow'
|
||||
},
|
||||
{
|
||||
label: 'Conditional Flow',
|
||||
actionName: 'replace-with-conditional-flow',
|
||||
className: 'bpmn-icon-conditional-flow'
|
||||
}
|
||||
];
|
||||
|
||||
export var PARTICIPANT = [
|
||||
{
|
||||
label: 'Expanded Pool',
|
||||
actionName: 'replace-with-expanded-pool',
|
||||
className: 'bpmn-icon-participant',
|
||||
target: {
|
||||
type: 'bpmn:Participant',
|
||||
isExpanded: true
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Collapsed Pool',
|
||||
actionName: 'replace-with-collapsed-pool',
|
||||
// TODO(@janstuemmel): maybe design new icon
|
||||
className: 'bpmn-icon-lane',
|
||||
target: {
|
||||
type: 'bpmn:Participant',
|
||||
isExpanded: false
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
import SelectionModule from 'diagram-js/lib/features/selection';
|
||||
import ReplaceModule from 'diagram-js/lib/features/replace';
|
||||
|
||||
import BpmnReplace from './BpmnReplace';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
SelectionModule,
|
||||
ReplaceModule
|
||||
],
|
||||
bpmnReplace: [ 'type', BpmnReplace ]
|
||||
};
|
||||
@@ -0,0 +1,962 @@
|
||||
import {
|
||||
find,
|
||||
some,
|
||||
every,
|
||||
forEach
|
||||
} from 'min-dash';
|
||||
|
||||
import inherits from 'inherits';
|
||||
|
||||
import {
|
||||
is,
|
||||
getBusinessObject
|
||||
} from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
isAny
|
||||
} from '../modeling/util/ModelingUtil';
|
||||
|
||||
import {
|
||||
isLabel
|
||||
} from '../../util/LabelUtil';
|
||||
|
||||
import {
|
||||
isExpanded,
|
||||
isEventSubProcess,
|
||||
isInterrupting,
|
||||
hasErrorEventDefinition,
|
||||
hasEscalationEventDefinition,
|
||||
hasCompensateEventDefinition
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider';
|
||||
|
||||
import {
|
||||
getBoundaryAttachment as isBoundaryAttachment
|
||||
} from '../snapping/BpmnSnappingUtil';
|
||||
|
||||
|
||||
/**
|
||||
* BPMN specific modeling rule
|
||||
*/
|
||||
export default function BpmnRules(eventBus) {
|
||||
RuleProvider.call(this, eventBus);
|
||||
}
|
||||
|
||||
inherits(BpmnRules, RuleProvider);
|
||||
|
||||
BpmnRules.$inject = [ 'eventBus' ];
|
||||
|
||||
BpmnRules.prototype.init = function() {
|
||||
|
||||
this.addRule('connection.start', function(context) {
|
||||
var source = context.source;
|
||||
|
||||
return canStartConnection(source);
|
||||
});
|
||||
|
||||
this.addRule('connection.create', function(context) {
|
||||
var source = context.source,
|
||||
target = context.target,
|
||||
hints = context.hints || {},
|
||||
targetParent = hints.targetParent,
|
||||
targetAttach = hints.targetAttach;
|
||||
|
||||
// don't allow incoming connections on
|
||||
// newly created boundary events
|
||||
// to boundary events
|
||||
if (targetAttach) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// temporarily set target parent for scoping
|
||||
// checks to work
|
||||
if (targetParent) {
|
||||
target.parent = targetParent;
|
||||
}
|
||||
|
||||
try {
|
||||
return canConnect(source, target);
|
||||
} finally {
|
||||
// unset temporary target parent
|
||||
if (targetParent) {
|
||||
target.parent = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.addRule('connection.reconnectStart', function(context) {
|
||||
|
||||
var connection = context.connection,
|
||||
source = context.hover || context.source,
|
||||
target = connection.target;
|
||||
|
||||
return canConnect(source, target, connection);
|
||||
});
|
||||
|
||||
this.addRule('connection.reconnectEnd', function(context) {
|
||||
|
||||
var connection = context.connection,
|
||||
source = connection.source,
|
||||
target = context.hover || context.target;
|
||||
|
||||
return canConnect(source, target, connection);
|
||||
});
|
||||
|
||||
this.addRule('connection.updateWaypoints', function(context) {
|
||||
return {
|
||||
type: context.connection.type
|
||||
};
|
||||
});
|
||||
|
||||
this.addRule('shape.resize', function(context) {
|
||||
|
||||
var shape = context.shape,
|
||||
newBounds = context.newBounds;
|
||||
|
||||
return canResize(shape, newBounds);
|
||||
});
|
||||
|
||||
this.addRule('elements.move', function(context) {
|
||||
|
||||
var target = context.target,
|
||||
shapes = context.shapes,
|
||||
position = context.position;
|
||||
|
||||
return canAttach(shapes, target, null, position) ||
|
||||
canReplace(shapes, target, position) ||
|
||||
canMove(shapes, target, position) ||
|
||||
canInsert(shapes, target, position);
|
||||
});
|
||||
|
||||
this.addRule('shape.create', function(context) {
|
||||
return canCreate(
|
||||
context.shape,
|
||||
context.target,
|
||||
context.source,
|
||||
context.position
|
||||
);
|
||||
});
|
||||
|
||||
this.addRule('shape.attach', function(context) {
|
||||
|
||||
return canAttach(
|
||||
context.shape,
|
||||
context.target,
|
||||
null,
|
||||
context.position
|
||||
);
|
||||
});
|
||||
|
||||
this.addRule('element.copy', function(context) {
|
||||
var collection = context.collection,
|
||||
element = context.element;
|
||||
|
||||
return canCopy(collection, element);
|
||||
});
|
||||
|
||||
this.addRule('element.paste', function(context) {
|
||||
var parent = context.parent,
|
||||
element = context.element,
|
||||
position = context.position,
|
||||
source = context.source,
|
||||
target = context.target;
|
||||
|
||||
if (source || target) {
|
||||
return canConnect(source, target);
|
||||
}
|
||||
|
||||
return canAttach([ element ], parent, null, position) || canCreate(element, parent, null, position);
|
||||
});
|
||||
|
||||
this.addRule('elements.paste', function(context) {
|
||||
var tree = context.tree,
|
||||
target = context.target;
|
||||
|
||||
return canPaste(tree, target);
|
||||
});
|
||||
};
|
||||
|
||||
BpmnRules.prototype.canConnectMessageFlow = canConnectMessageFlow;
|
||||
|
||||
BpmnRules.prototype.canConnectSequenceFlow = canConnectSequenceFlow;
|
||||
|
||||
BpmnRules.prototype.canConnectDataAssociation = canConnectDataAssociation;
|
||||
|
||||
BpmnRules.prototype.canConnectAssociation = canConnectAssociation;
|
||||
|
||||
BpmnRules.prototype.canMove = canMove;
|
||||
|
||||
BpmnRules.prototype.canAttach = canAttach;
|
||||
|
||||
BpmnRules.prototype.canReplace = canReplace;
|
||||
|
||||
BpmnRules.prototype.canDrop = canDrop;
|
||||
|
||||
BpmnRules.prototype.canInsert = canInsert;
|
||||
|
||||
BpmnRules.prototype.canCreate = canCreate;
|
||||
|
||||
BpmnRules.prototype.canConnect = canConnect;
|
||||
|
||||
BpmnRules.prototype.canResize = canResize;
|
||||
|
||||
BpmnRules.prototype.canCopy = canCopy;
|
||||
|
||||
/**
|
||||
* Utility functions for rule checking
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if given element can be used for starting connection.
|
||||
*
|
||||
* @param {Element} source
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function canStartConnection(element) {
|
||||
if (nonExistingOrLabel(element)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isAny(element, [
|
||||
'bpmn:FlowNode',
|
||||
'bpmn:InteractionNode',
|
||||
'bpmn:DataObjectReference',
|
||||
'bpmn:DataStoreReference'
|
||||
]);
|
||||
}
|
||||
|
||||
function nonExistingOrLabel(element) {
|
||||
return !element || isLabel(element);
|
||||
}
|
||||
|
||||
function isSame(a, b) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
function getOrganizationalParent(element) {
|
||||
|
||||
do {
|
||||
if (is(element, 'bpmn:Process')) {
|
||||
return getBusinessObject(element);
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:Participant')) {
|
||||
return (
|
||||
getBusinessObject(element).processRef ||
|
||||
getBusinessObject(element)
|
||||
);
|
||||
}
|
||||
} while ((element = element.parent));
|
||||
|
||||
}
|
||||
|
||||
function isTextAnnotation(element) {
|
||||
return is(element, 'bpmn:TextAnnotation');
|
||||
}
|
||||
|
||||
function isGroup(element) {
|
||||
return is(element, 'bpmn:Group') && !element.labelTarget;
|
||||
}
|
||||
|
||||
function isCompensationBoundary(element) {
|
||||
return is(element, 'bpmn:BoundaryEvent') &&
|
||||
hasEventDefinition(element, 'bpmn:CompensateEventDefinition');
|
||||
}
|
||||
|
||||
function isForCompensation(e) {
|
||||
return getBusinessObject(e).isForCompensation;
|
||||
}
|
||||
|
||||
function isSameOrganization(a, b) {
|
||||
var parentA = getOrganizationalParent(a),
|
||||
parentB = getOrganizationalParent(b);
|
||||
|
||||
return parentA === parentB;
|
||||
}
|
||||
|
||||
function isMessageFlowSource(element) {
|
||||
return (
|
||||
is(element, 'bpmn:InteractionNode') && (
|
||||
!is(element, 'bpmn:Event') || (
|
||||
is(element, 'bpmn:ThrowEvent') &&
|
||||
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isMessageFlowTarget(element) {
|
||||
return (
|
||||
is(element, 'bpmn:InteractionNode') &&
|
||||
!isForCompensation(element) && (
|
||||
!is(element, 'bpmn:Event') || (
|
||||
is(element, 'bpmn:CatchEvent') &&
|
||||
hasEventDefinitionOrNone(element, 'bpmn:MessageEventDefinition')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function getScopeParent(element) {
|
||||
|
||||
var parent = element;
|
||||
|
||||
while ((parent = parent.parent)) {
|
||||
|
||||
if (is(parent, 'bpmn:FlowElementsContainer')) {
|
||||
return getBusinessObject(parent);
|
||||
}
|
||||
|
||||
if (is(parent, 'bpmn:Participant')) {
|
||||
return getBusinessObject(parent).processRef;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function isSameScope(a, b) {
|
||||
var scopeParentA = getScopeParent(a),
|
||||
scopeParentB = getScopeParent(b);
|
||||
|
||||
return scopeParentA && (scopeParentA === scopeParentB);
|
||||
}
|
||||
|
||||
function hasEventDefinition(element, eventDefinition) {
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return !!find(bo.eventDefinitions || [], function(definition) {
|
||||
return is(definition, eventDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
function hasEventDefinitionOrNone(element, eventDefinition) {
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return (bo.eventDefinitions || []).every(function(definition) {
|
||||
return is(definition, eventDefinition);
|
||||
});
|
||||
}
|
||||
|
||||
function isSequenceFlowSource(element) {
|
||||
return (
|
||||
is(element, 'bpmn:FlowNode') &&
|
||||
!is(element, 'bpmn:EndEvent') &&
|
||||
!isEventSubProcess(element) &&
|
||||
!(is(element, 'bpmn:IntermediateThrowEvent') &&
|
||||
hasEventDefinition(element, 'bpmn:LinkEventDefinition')
|
||||
) &&
|
||||
!isCompensationBoundary(element) &&
|
||||
!isForCompensation(element)
|
||||
);
|
||||
}
|
||||
|
||||
function isSequenceFlowTarget(element) {
|
||||
return (
|
||||
is(element, 'bpmn:FlowNode') &&
|
||||
!is(element, 'bpmn:StartEvent') &&
|
||||
!is(element, 'bpmn:BoundaryEvent') &&
|
||||
!isEventSubProcess(element) &&
|
||||
!(is(element, 'bpmn:IntermediateCatchEvent') &&
|
||||
hasEventDefinition(element, 'bpmn:LinkEventDefinition')
|
||||
) &&
|
||||
!isForCompensation(element)
|
||||
);
|
||||
}
|
||||
|
||||
function isEventBasedTarget(element) {
|
||||
return (
|
||||
is(element, 'bpmn:ReceiveTask') || (
|
||||
is(element, 'bpmn:IntermediateCatchEvent') && (
|
||||
hasEventDefinition(element, 'bpmn:MessageEventDefinition') ||
|
||||
hasEventDefinition(element, 'bpmn:TimerEventDefinition') ||
|
||||
hasEventDefinition(element, 'bpmn:ConditionalEventDefinition') ||
|
||||
hasEventDefinition(element, 'bpmn:SignalEventDefinition')
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function isConnection(element) {
|
||||
return element.waypoints;
|
||||
}
|
||||
|
||||
function getParents(element) {
|
||||
|
||||
var parents = [];
|
||||
|
||||
while (element) {
|
||||
element = element.parent;
|
||||
|
||||
if (element) {
|
||||
parents.push(element);
|
||||
}
|
||||
}
|
||||
|
||||
return parents;
|
||||
}
|
||||
|
||||
function isParent(possibleParent, element) {
|
||||
var allParents = getParents(element);
|
||||
return allParents.indexOf(possibleParent) !== -1;
|
||||
}
|
||||
|
||||
function canConnect(source, target, connection) {
|
||||
|
||||
if (nonExistingOrLabel(source) || nonExistingOrLabel(target)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!is(connection, 'bpmn:DataAssociation')) {
|
||||
|
||||
if (canConnectMessageFlow(source, target)) {
|
||||
return { type: 'bpmn:MessageFlow' };
|
||||
}
|
||||
|
||||
if (canConnectSequenceFlow(source, target)) {
|
||||
return { type: 'bpmn:SequenceFlow' };
|
||||
}
|
||||
}
|
||||
|
||||
var connectDataAssociation = canConnectDataAssociation(source, target);
|
||||
|
||||
if (connectDataAssociation) {
|
||||
return connectDataAssociation;
|
||||
}
|
||||
|
||||
if (isCompensationBoundary(source) && isForCompensation(target)) {
|
||||
return {
|
||||
type: 'bpmn:Association',
|
||||
associationDirection: 'One'
|
||||
};
|
||||
}
|
||||
|
||||
if (canConnectAssociation(source, target)) {
|
||||
|
||||
return {
|
||||
type: 'bpmn:Association'
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can an element be dropped into the target element
|
||||
*
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function canDrop(element, target, position) {
|
||||
|
||||
// can move labels and groups everywhere
|
||||
if (isLabel(element) || isGroup(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// disallow to create elements on collapsed pools
|
||||
if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow to create new participants on
|
||||
// existing collaboration and process diagrams
|
||||
if (is(element, 'bpmn:Participant')) {
|
||||
return is(target, 'bpmn:Process') || is(target, 'bpmn:Collaboration');
|
||||
}
|
||||
|
||||
// allow moving DataInput / DataOutput within its original container only
|
||||
if (isAny(element, [ 'bpmn:DataInput', 'bpmn:DataOutput' ])) {
|
||||
|
||||
if (element.parent) {
|
||||
return target === element.parent;
|
||||
}
|
||||
}
|
||||
|
||||
// allow creating lanes on participants and other lanes only
|
||||
if (is(element, 'bpmn:Lane')) {
|
||||
return is(target, 'bpmn:Participant') || is(target, 'bpmn:Lane');
|
||||
}
|
||||
|
||||
// disallow dropping boundary events which cannot replace with intermediate event
|
||||
if (is(element, 'bpmn:BoundaryEvent') && !isDroppableBoundaryEvent(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// drop flow elements onto flow element containers
|
||||
// and participants
|
||||
if (is(element, 'bpmn:FlowElement') && !is(element, 'bpmn:DataStoreReference')) {
|
||||
if (is(target, 'bpmn:FlowElementsContainer')) {
|
||||
return isExpanded(target);
|
||||
}
|
||||
|
||||
return isAny(target, [ 'bpmn:Participant', 'bpmn:Lane' ]);
|
||||
}
|
||||
|
||||
// account for the fact that data associations are always
|
||||
// rendered and moved to top (Process or Collaboration level)
|
||||
//
|
||||
// artifacts may be placed wherever, too
|
||||
if (isAny(element, [ 'bpmn:Artifact', 'bpmn:DataAssociation', 'bpmn:DataStoreReference' ])) {
|
||||
return isAny(target, [
|
||||
'bpmn:Collaboration',
|
||||
'bpmn:Lane',
|
||||
'bpmn:Participant',
|
||||
'bpmn:Process',
|
||||
'bpmn:SubProcess' ]);
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:MessageFlow')) {
|
||||
return is(target, 'bpmn:Collaboration')
|
||||
|| element.source.parent == target
|
||||
|| element.target.parent == target;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function isDroppableBoundaryEvent(event) {
|
||||
return getBusinessObject(event).cancelActivity && (
|
||||
hasNoEventDefinition(event) || hasCommonBoundaryIntermediateEventDefinition(event)
|
||||
);
|
||||
}
|
||||
|
||||
function canPaste(tree, target) {
|
||||
var topLevel = tree[0],
|
||||
participants;
|
||||
|
||||
if (is(target, 'bpmn:Collaboration')) {
|
||||
return every(topLevel, function(e) {
|
||||
return e.type === 'bpmn:Participant';
|
||||
});
|
||||
}
|
||||
|
||||
if (is(target, 'bpmn:Process')) {
|
||||
participants = some(topLevel, function(e) {
|
||||
return e.type === 'bpmn:Participant';
|
||||
});
|
||||
|
||||
return !(participants && target.children.length > 0);
|
||||
}
|
||||
|
||||
// disallow to create elements on collapsed pools
|
||||
if (is(target, 'bpmn:Participant') && !isExpanded(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is(target, 'bpmn:FlowElementsContainer')) {
|
||||
return isExpanded(target);
|
||||
}
|
||||
|
||||
return isAny(target, [
|
||||
'bpmn:Collaboration',
|
||||
'bpmn:Lane',
|
||||
'bpmn:Participant',
|
||||
'bpmn:Process',
|
||||
'bpmn:SubProcess' ]);
|
||||
}
|
||||
|
||||
function isBoundaryEvent(element) {
|
||||
return !isLabel(element) && is(element, 'bpmn:BoundaryEvent');
|
||||
}
|
||||
|
||||
function isLane(element) {
|
||||
return is(element, 'bpmn:Lane');
|
||||
}
|
||||
|
||||
/**
|
||||
* We treat IntermediateThrowEvents as boundary events during create,
|
||||
* this must be reflected in the rules.
|
||||
*/
|
||||
function isBoundaryCandidate(element) {
|
||||
if (isBoundaryEvent(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:IntermediateThrowEvent') && hasNoEventDefinition(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
is(element, 'bpmn:IntermediateCatchEvent') &&
|
||||
hasCommonBoundaryIntermediateEventDefinition(element)
|
||||
);
|
||||
}
|
||||
|
||||
function hasNoEventDefinition(element) {
|
||||
var bo = getBusinessObject(element);
|
||||
|
||||
return bo && !(bo.eventDefinitions && bo.eventDefinitions.length);
|
||||
}
|
||||
|
||||
function hasCommonBoundaryIntermediateEventDefinition(element) {
|
||||
return hasOneOfEventDefinitions(element, [
|
||||
'bpmn:MessageEventDefinition',
|
||||
'bpmn:TimerEventDefinition',
|
||||
'bpmn:SignalEventDefinition',
|
||||
'bpmn:ConditionalEventDefinition'
|
||||
]);
|
||||
}
|
||||
|
||||
function hasOneOfEventDefinitions(element, eventDefinitions) {
|
||||
return eventDefinitions.some(function(definition) {
|
||||
return hasEventDefinition(element, definition);
|
||||
});
|
||||
}
|
||||
|
||||
function isReceiveTaskAfterEventBasedGateway(element) {
|
||||
return (
|
||||
is(element, 'bpmn:ReceiveTask') &&
|
||||
find(element.incoming, function(incoming) {
|
||||
return is(incoming.source, 'bpmn:EventBasedGateway');
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function canAttach(elements, target, source, position) {
|
||||
|
||||
if (!Array.isArray(elements)) {
|
||||
elements = [ elements ];
|
||||
}
|
||||
|
||||
// only (re-)attach one element at a time
|
||||
if (elements.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var element = elements[0];
|
||||
|
||||
// do not attach labels
|
||||
if (isLabel(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only handle boundary events
|
||||
if (!isBoundaryCandidate(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// disallow drop on event sub processes
|
||||
if (isEventSubProcess(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only allow drop on non compensation activities
|
||||
if (!is(target, 'bpmn:Activity') || isForCompensation(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only attach to subprocess border
|
||||
if (position && !isBoundaryAttachment(position, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// do not attach on receive tasks after event based gateways
|
||||
if (isReceiveTaskAfterEventBasedGateway(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'attach';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines how to replace elements for a given target.
|
||||
*
|
||||
* Returns an array containing all elements which will be replaced.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* [{ id: 'IntermediateEvent_2',
|
||||
* type: 'bpmn:StartEvent'
|
||||
* },
|
||||
* { id: 'IntermediateEvent_5',
|
||||
* type: 'bpmn:EndEvent'
|
||||
* }]
|
||||
*
|
||||
* @param {Array} elements
|
||||
* @param {Object} target
|
||||
*
|
||||
* @return {Object} an object containing all elements which have to be replaced
|
||||
*/
|
||||
function canReplace(elements, target, position) {
|
||||
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var canExecute = {
|
||||
replacements: []
|
||||
};
|
||||
|
||||
forEach(elements, function(element) {
|
||||
|
||||
if (!isEventSubProcess(target)) {
|
||||
|
||||
if (is(element, 'bpmn:StartEvent') &&
|
||||
element.type !== 'label' &&
|
||||
canDrop(element, target)) {
|
||||
|
||||
// replace a non-interrupting start event by a blank interrupting start event
|
||||
// when the target is not an event sub process
|
||||
if (!isInterrupting(element)) {
|
||||
canExecute.replacements.push({
|
||||
oldElementId: element.id,
|
||||
newElementType: 'bpmn:StartEvent'
|
||||
});
|
||||
}
|
||||
|
||||
// replace an error/escalation/compansate start event by a blank interrupting start event
|
||||
// when the target is not an event sub process
|
||||
if (hasErrorEventDefinition(element) ||
|
||||
hasEscalationEventDefinition(element) ||
|
||||
hasCompensateEventDefinition(element)) {
|
||||
canExecute.replacements.push({
|
||||
oldElementId: element.id,
|
||||
newElementType: 'bpmn:StartEvent'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!is(target, 'bpmn:Transaction')) {
|
||||
if (hasEventDefinition(element, 'bpmn:CancelEventDefinition') &&
|
||||
element.type !== 'label') {
|
||||
|
||||
if (is(element, 'bpmn:EndEvent') && canDrop(element, target)) {
|
||||
canExecute.replacements.push({
|
||||
oldElementId: element.id,
|
||||
newElementType: 'bpmn:EndEvent'
|
||||
});
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:BoundaryEvent') && canAttach(element, target, null, position)) {
|
||||
canExecute.replacements.push({
|
||||
oldElementId: element.id,
|
||||
newElementType: 'bpmn:BoundaryEvent'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return canExecute.replacements.length ? canExecute : false;
|
||||
}
|
||||
|
||||
function canMove(elements, target) {
|
||||
|
||||
// do not move selection containing lanes
|
||||
if (some(elements, isLane)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow default move check to start move operation
|
||||
if (!target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return elements.every(function(element) {
|
||||
return canDrop(element, target);
|
||||
});
|
||||
}
|
||||
|
||||
function canCreate(shape, target, source, position) {
|
||||
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isLabel(shape) || isGroup(shape)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isSame(source, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// ensure we do not drop the element
|
||||
// into source
|
||||
if (source && isParent(source, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return canDrop(shape, target, position) || canInsert(shape, target, position);
|
||||
}
|
||||
|
||||
function canResize(shape, newBounds) {
|
||||
if (is(shape, 'bpmn:SubProcess')) {
|
||||
return (
|
||||
isExpanded(shape) && (
|
||||
!newBounds || (newBounds.width >= 100 && newBounds.height >= 80)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:Lane')) {
|
||||
return !newBounds || (newBounds.width >= 130 && newBounds.height >= 60);
|
||||
}
|
||||
|
||||
if (is(shape, 'bpmn:Participant')) {
|
||||
return !newBounds || (newBounds.width >= 250 && newBounds.height >= 50);
|
||||
}
|
||||
|
||||
if (isTextAnnotation(shape)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isGroup(shape)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check, whether one side of the relationship
|
||||
* is a text annotation.
|
||||
*/
|
||||
function isOneTextAnnotation(source, target) {
|
||||
|
||||
var sourceTextAnnotation = isTextAnnotation(source),
|
||||
targetTextAnnotation = isTextAnnotation(target);
|
||||
|
||||
return (
|
||||
(sourceTextAnnotation || targetTextAnnotation) &&
|
||||
(sourceTextAnnotation !== targetTextAnnotation)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function canConnectAssociation(source, target) {
|
||||
|
||||
// do not connect connections
|
||||
if (isConnection(source) || isConnection(target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// compensation boundary events are exception
|
||||
if (isCompensationBoundary(source) && isForCompensation(target)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// don't connect parent <-> child
|
||||
if (isParent(target, source) || isParent(source, target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// allow connection of associations between <!TextAnnotation> and <TextAnnotation>
|
||||
return isOneTextAnnotation(source, target);
|
||||
}
|
||||
|
||||
function canConnectMessageFlow(source, target) {
|
||||
|
||||
// handle the case where target does not have a parent,
|
||||
// because it is not dropped within the diagram (bpmn-io/bpmn-js#1033)
|
||||
if (!target.parent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
isMessageFlowSource(source) &&
|
||||
isMessageFlowTarget(target) &&
|
||||
!isSameOrganization(source, target)
|
||||
);
|
||||
}
|
||||
|
||||
function canConnectSequenceFlow(source, target) {
|
||||
|
||||
if (
|
||||
isEventBasedTarget(target) &&
|
||||
target.incoming.length > 0 &&
|
||||
areOutgoingEventBasedGatewayConnections(target.incoming) &&
|
||||
!is(source, 'bpmn:EventBasedGateway')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isSequenceFlowSource(source) &&
|
||||
isSequenceFlowTarget(target) &&
|
||||
isSameScope(source, target) &&
|
||||
!(is(source, 'bpmn:EventBasedGateway') && !isEventBasedTarget(target));
|
||||
}
|
||||
|
||||
|
||||
function canConnectDataAssociation(source, target) {
|
||||
|
||||
if (isAny(source, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
|
||||
isAny(target, [ 'bpmn:Activity', 'bpmn:ThrowEvent' ])) {
|
||||
return { type: 'bpmn:DataInputAssociation' };
|
||||
}
|
||||
|
||||
if (isAny(target, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ]) &&
|
||||
isAny(source, [ 'bpmn:Activity', 'bpmn:CatchEvent' ])) {
|
||||
return { type: 'bpmn:DataOutputAssociation' };
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function canInsert(shape, flow, position) {
|
||||
|
||||
if (!flow) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Array.isArray(shape)) {
|
||||
if (shape.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
shape = shape[0];
|
||||
}
|
||||
|
||||
if (flow.source === shape ||
|
||||
flow.target === shape) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// return true if we can drop on the
|
||||
// underlying flow parent
|
||||
//
|
||||
// at this point we are not really able to talk
|
||||
// about connection rules (yet)
|
||||
|
||||
return (
|
||||
isAny(flow, [ 'bpmn:SequenceFlow', 'bpmn:MessageFlow' ]) &&
|
||||
!isLabel(flow) &&
|
||||
is(shape, 'bpmn:FlowNode') &&
|
||||
!is(shape, 'bpmn:BoundaryEvent') &&
|
||||
canDrop(shape, flow.parent, position));
|
||||
}
|
||||
|
||||
function contains(collection, element) {
|
||||
return (collection && element) && collection.indexOf(element) !== -1;
|
||||
}
|
||||
|
||||
function canCopy(collection, element) {
|
||||
if (is(element, 'bpmn:Lane') && !contains(collection, element.parent)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (is(element, 'bpmn:BoundaryEvent') && !contains(collection, element.host)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isOutgoingEventBasedGatewayConnection(connection) {
|
||||
|
||||
if (connection && connection.source) {
|
||||
return is(connection.source, 'bpmn:EventBasedGateway');
|
||||
}
|
||||
}
|
||||
|
||||
function areOutgoingEventBasedGatewayConnections(connections) {
|
||||
|
||||
connections = connections || [];
|
||||
|
||||
return connections.some(isOutgoingEventBasedGatewayConnection);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import RulesModule from 'diagram-js/lib/features/rules';
|
||||
|
||||
import BpmnRules from './BpmnRules';
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
RulesModule
|
||||
],
|
||||
__init__: [ 'bpmnRules' ],
|
||||
bpmnRules: [ 'type', BpmnRules ]
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
import {
|
||||
map,
|
||||
filter,
|
||||
sortBy
|
||||
} from 'min-dash';
|
||||
|
||||
import {
|
||||
getLabel
|
||||
} from '../label-editing/LabelUtil';
|
||||
|
||||
|
||||
/**
|
||||
* Provides ability to search through BPMN elements
|
||||
*/
|
||||
export default function BpmnSearchProvider(elementRegistry, searchPad, canvas) {
|
||||
|
||||
this._elementRegistry = elementRegistry;
|
||||
this._canvas = canvas;
|
||||
|
||||
searchPad.registerProvider(this);
|
||||
}
|
||||
|
||||
BpmnSearchProvider.$inject = [
|
||||
'elementRegistry',
|
||||
'searchPad',
|
||||
'canvas'
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* Finds all elements that match given pattern
|
||||
*
|
||||
* <Result> :
|
||||
* {
|
||||
* primaryTokens: <Array<Token>>,
|
||||
* secondaryTokens: <Array<Token>>,
|
||||
* element: <Element>
|
||||
* }
|
||||
*
|
||||
* <Token> :
|
||||
* {
|
||||
* normal|matched: <String>
|
||||
* }
|
||||
*
|
||||
* @param {String} pattern
|
||||
* @return {Array<Result>}
|
||||
*/
|
||||
BpmnSearchProvider.prototype.find = function(pattern) {
|
||||
var rootElement = this._canvas.getRootElement();
|
||||
|
||||
var elements = this._elementRegistry.filter(function(element) {
|
||||
if (element.labelTarget) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// do not include root element
|
||||
elements = filter(elements, function(element) {
|
||||
return element !== rootElement;
|
||||
});
|
||||
|
||||
elements = map(elements, function(element) {
|
||||
return {
|
||||
primaryTokens: matchAndSplit(getLabel(element), pattern),
|
||||
secondaryTokens: matchAndSplit(element.id, pattern),
|
||||
element: element
|
||||
};
|
||||
});
|
||||
|
||||
// exclude non-matched elements
|
||||
elements = filter(elements, function(element) {
|
||||
return hasMatched(element.primaryTokens) || hasMatched(element.secondaryTokens);
|
||||
});
|
||||
|
||||
elements = sortBy(elements, function(element) {
|
||||
return getLabel(element.element) + element.element.id;
|
||||
});
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
|
||||
function hasMatched(tokens) {
|
||||
var matched = filter(tokens, function(t) {
|
||||
return !!t.matched;
|
||||
});
|
||||
|
||||
return matched.length > 0;
|
||||
}
|
||||
|
||||
|
||||
function matchAndSplit(text, pattern) {
|
||||
var tokens = [],
|
||||
originalText = text;
|
||||
|
||||
if (!text) {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
text = text.toLowerCase();
|
||||
pattern = pattern.toLowerCase();
|
||||
|
||||
var i = text.indexOf(pattern);
|
||||
|
||||
if (i > -1) {
|
||||
if (i !== 0) {
|
||||
tokens.push({
|
||||
normal: originalText.substr(0, i)
|
||||
});
|
||||
}
|
||||
|
||||
tokens.push({
|
||||
matched: originalText.substr(i, pattern.length)
|
||||
});
|
||||
|
||||
if (pattern.length + i < text.length) {
|
||||
tokens.push({
|
||||
normal: originalText.substr(pattern.length + i, text.length)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
tokens.push({
|
||||
normal: originalText
|
||||
});
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import SearchPadModule from 'diagram-js/lib/features/search-pad';
|
||||
|
||||
import BpmnSearchProvider from './BpmnSearchProvider';
|
||||
|
||||
|
||||
export default {
|
||||
__depends__: [
|
||||
SearchPadModule
|
||||
],
|
||||
__init__: [ 'bpmnSearch'],
|
||||
bpmnSearch: [ 'type', BpmnSearchProvider ]
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
import {
|
||||
mid,
|
||||
setSnapped
|
||||
} from 'diagram-js/lib/features/snapping/SnapUtil';
|
||||
|
||||
import { isCmd } from 'diagram-js/lib/features/keyboard/KeyboardUtil';
|
||||
|
||||
import {
|
||||
getOrientation
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
every,
|
||||
some
|
||||
} from 'min-dash';
|
||||
|
||||
import { isAny } from '../modeling/util/ModelingUtil';
|
||||
|
||||
var HIGHER_PRIORITY = 1250;
|
||||
|
||||
var BOUNDARY_TO_HOST_THRESHOLD = 40;
|
||||
|
||||
var TARGET_BOUNDS_PADDING = 20;
|
||||
|
||||
var TARGET_CENTER_PADDING = 20;
|
||||
|
||||
var AXES = [ 'x', 'y' ];
|
||||
|
||||
var abs = Math.abs;
|
||||
|
||||
/**
|
||||
* Snap during connect.
|
||||
*
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Rules} rules
|
||||
*/
|
||||
export default function BpmnConnectSnapping(eventBus, rules) {
|
||||
eventBus.on([
|
||||
'connect.hover',
|
||||
'connect.move',
|
||||
'connect.end',
|
||||
], HIGHER_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
source = context.source,
|
||||
target = context.target;
|
||||
|
||||
if (event.originalEvent && isCmd(event.originalEvent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.initialSourcePosition) {
|
||||
context.initialSourcePosition = context.sourcePosition;
|
||||
}
|
||||
|
||||
var connectionAttrs = rules.allowed('connection.create', {
|
||||
source: source,
|
||||
target: target
|
||||
});
|
||||
|
||||
if (target && connectionAttrs) {
|
||||
snapInsideTarget(event, target, getTargetBoundsPadding(target));
|
||||
}
|
||||
|
||||
if (target && isAnyType(connectionAttrs, [
|
||||
'bpmn:Association',
|
||||
'bpmn:DataInputAssociation',
|
||||
'bpmn:DataOutputAssociation',
|
||||
'bpmn:SequenceFlow'
|
||||
])) {
|
||||
|
||||
// snap source
|
||||
context.sourcePosition = mid(source);
|
||||
|
||||
if (isAny(target, ['bpmn:Event', 'bpmn:Gateway'])) {
|
||||
snapToPosition(event, mid(target));
|
||||
}
|
||||
|
||||
if (is(target, 'bpmn:Task')) {
|
||||
snapTargetMidOnCenter(event, target);
|
||||
}
|
||||
|
||||
if (is(source, 'bpmn:BoundaryEvent') && target === source.host) {
|
||||
snapBoundaryEventLoop(event, source, target);
|
||||
}
|
||||
|
||||
} else if (isType(connectionAttrs, 'bpmn:MessageFlow')) {
|
||||
|
||||
if (is(source, 'bpmn:Event')) {
|
||||
|
||||
// snap source
|
||||
context.sourcePosition = mid(source);
|
||||
}
|
||||
|
||||
if (is(target, 'bpmn:Event')) {
|
||||
|
||||
// snap target
|
||||
snapToPosition(event, mid(target));
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// un-snap source
|
||||
context.sourcePosition = context.initialSourcePosition;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BpmnConnectSnapping.$inject = [
|
||||
'eventBus',
|
||||
'rules'
|
||||
];
|
||||
|
||||
function snapInsideTarget(event, target, padding) {
|
||||
|
||||
AXES.forEach(function(axis) {
|
||||
var matchingTargetDimension = getDimensionForAxis(axis, target),
|
||||
newCoordinate;
|
||||
|
||||
if (event[axis] < target[axis] + padding) {
|
||||
newCoordinate = target[axis] + padding;
|
||||
} else if (event[axis] > target[axis] + matchingTargetDimension - padding) {
|
||||
newCoordinate = target[axis] + matchingTargetDimension - padding;
|
||||
}
|
||||
|
||||
if (newCoordinate) {
|
||||
setSnapped(event, axis, newCoordinate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// snap to target mid if event position in center area
|
||||
function snapTargetMidOnCenter(event, target) {
|
||||
|
||||
var isCenter = every(AXES, function(axis) {
|
||||
var coordinate = event[axis],
|
||||
matchingTargetDimension = getDimensionForAxis(axis, target);
|
||||
|
||||
return coordinate > target[axis] + TARGET_CENTER_PADDING
|
||||
&& coordinate < target[axis] + matchingTargetDimension - TARGET_CENTER_PADDING;
|
||||
});
|
||||
|
||||
if (isCenter) {
|
||||
snapToPosition(event, mid(target));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// snap outside of Boundary Event surroundings
|
||||
function snapBoundaryEventLoop(event, source, target) {
|
||||
var sourceMid = mid(source),
|
||||
orientation = getOrientation(sourceMid, target, -10),
|
||||
snappingAxes = [];
|
||||
|
||||
if (/top|bottom/.test(orientation)) {
|
||||
snappingAxes.push('x');
|
||||
}
|
||||
|
||||
if (/left|right/.test(orientation)) {
|
||||
snappingAxes.push('y');
|
||||
}
|
||||
|
||||
snappingAxes.forEach(function(axis) {
|
||||
var coordinate = event[axis], newCoordinate;
|
||||
|
||||
if (abs(coordinate - sourceMid[axis]) < BOUNDARY_TO_HOST_THRESHOLD) {
|
||||
if (coordinate > sourceMid[axis]) {
|
||||
newCoordinate = sourceMid[axis] + BOUNDARY_TO_HOST_THRESHOLD;
|
||||
}
|
||||
else {
|
||||
newCoordinate = sourceMid[axis] - BOUNDARY_TO_HOST_THRESHOLD;
|
||||
}
|
||||
|
||||
setSnapped(event, axis, newCoordinate);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function snapToPosition(event, position) {
|
||||
setSnapped(event, 'x', position.x);
|
||||
setSnapped(event, 'y', position.y);
|
||||
}
|
||||
|
||||
function isType(attrs, type) {
|
||||
return attrs && attrs.type === type;
|
||||
}
|
||||
|
||||
function isAnyType(attrs, types) {
|
||||
return some(types, function(type) {
|
||||
return isType(attrs, type);
|
||||
});
|
||||
}
|
||||
|
||||
function getDimensionForAxis(axis, element) {
|
||||
return axis === 'x' ? element.width : element.height;
|
||||
}
|
||||
|
||||
function getTargetBoundsPadding(target) {
|
||||
if (is(target, 'bpmn:Task')) {
|
||||
return 10;
|
||||
} else {
|
||||
return TARGET_BOUNDS_PADDING;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user