bpmn-js/lib/features/snapping/BpmnConnectSnapping.js

208 lines
4.9 KiB
JavaScript

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;
}
}