update
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import inherits from 'inherits';
|
||||
|
||||
import CreateMoveSnapping from 'diagram-js/lib/features/snapping/CreateMoveSnapping';
|
||||
|
||||
import {
|
||||
isSnapped,
|
||||
setSnapped,
|
||||
topLeft,
|
||||
bottomRight
|
||||
} from 'diagram-js/lib/features/snapping/SnapUtil';
|
||||
|
||||
import {
|
||||
isExpanded
|
||||
} from '../../util/DiUtil';
|
||||
|
||||
import { is } from '../../util/ModelUtil';
|
||||
|
||||
import {
|
||||
asTRBL,
|
||||
getMid
|
||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
import { getBoundaryAttachment } from './BpmnSnappingUtil';
|
||||
|
||||
import { forEach } from 'min-dash';
|
||||
|
||||
var HIGH_PRIORITY = 1500;
|
||||
|
||||
|
||||
/**
|
||||
* Snap during create and move.
|
||||
*
|
||||
* @param {BpmnRules} bpmnRules
|
||||
* @param {EventBus} eventBus
|
||||
* @param {Injector} injector
|
||||
*/
|
||||
export default function BpmnCreateMoveSnapping(bpmnRules, eventBus, injector) {
|
||||
injector.invoke(CreateMoveSnapping, this);
|
||||
|
||||
// creating first participant
|
||||
eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
|
||||
|
||||
function canAttach(shape, target, position) {
|
||||
return bpmnRules.canAttach([ shape ], target, null, position) === 'attach';
|
||||
}
|
||||
|
||||
// snap boundary events
|
||||
eventBus.on([
|
||||
'create.move',
|
||||
'create.end',
|
||||
'shape.move.move',
|
||||
'shape.move.end'
|
||||
], HIGH_PRIORITY, function(event) {
|
||||
var context = event.context,
|
||||
target = context.target,
|
||||
shape = context.shape;
|
||||
|
||||
if (target && canAttach(shape, target, event) && !isSnapped(event)) {
|
||||
snapBoundaryEvent(event, target);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
inherits(BpmnCreateMoveSnapping, CreateMoveSnapping);
|
||||
|
||||
BpmnCreateMoveSnapping.$inject = [
|
||||
'bpmnRules',
|
||||
'eventBus',
|
||||
'injector'
|
||||
];
|
||||
|
||||
BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
|
||||
var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
|
||||
|
||||
var shape = event.shape;
|
||||
|
||||
// snap to docking points
|
||||
forEach(shape.outgoing, function(connection) {
|
||||
var docking = connection.waypoints[0];
|
||||
|
||||
docking = docking.original || docking;
|
||||
|
||||
snapContext.setSnapOrigin(connection.id + '-docking', {
|
||||
x: docking.x - event.x,
|
||||
y: docking.y - event.y
|
||||
});
|
||||
});
|
||||
|
||||
forEach(shape.incoming, function(connection) {
|
||||
var docking = connection.waypoints[connection.waypoints.length - 1];
|
||||
|
||||
docking = docking.original || docking;
|
||||
|
||||
snapContext.setSnapOrigin(connection.id + '-docking', {
|
||||
x: docking.x - event.x,
|
||||
y: docking.y - event.y
|
||||
});
|
||||
});
|
||||
|
||||
if (is(shape, 'bpmn:Participant')) {
|
||||
|
||||
// snap to borders with higher priority
|
||||
snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
|
||||
}
|
||||
|
||||
return snapContext;
|
||||
};
|
||||
|
||||
BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
|
||||
CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
|
||||
|
||||
var snapTargets = this.getSnapTargets(shape, target);
|
||||
|
||||
forEach(snapTargets, function(snapTarget) {
|
||||
|
||||
// handle TRBL alignment
|
||||
//
|
||||
// * with container elements
|
||||
// * with text annotations
|
||||
if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
|
||||
snapPoints.add('top-left', topLeft(snapTarget));
|
||||
snapPoints.add('bottom-right', bottomRight(snapTarget));
|
||||
}
|
||||
});
|
||||
|
||||
// snap to docking points
|
||||
forEach(shape.incoming, function(connection) {
|
||||
|
||||
if (!includes(snapTargets, connection.source)) {
|
||||
snapPoints.add('mid', getMid(connection.source));
|
||||
}
|
||||
|
||||
var docking = connection.waypoints[0];
|
||||
|
||||
snapPoints.add(connection.id + '-docking', docking.original || docking);
|
||||
});
|
||||
|
||||
|
||||
forEach(shape.outgoing, function(connection) {
|
||||
|
||||
if (!includes(snapTargets, connection.target)) {
|
||||
snapPoints.add('mid', getMid(connection.target));
|
||||
}
|
||||
|
||||
var docking = connection.waypoints[ connection.waypoints.length - 1 ];
|
||||
|
||||
snapPoints.add(connection.id + '-docking', docking.original || docking);
|
||||
});
|
||||
|
||||
// add sequence flow parents as snap targets
|
||||
if (is(target, 'bpmn:SequenceFlow')) {
|
||||
snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
|
||||
}
|
||||
|
||||
return snapPoints;
|
||||
};
|
||||
|
||||
BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
|
||||
return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
|
||||
.filter(function(snapTarget) {
|
||||
|
||||
// do not snap to lanes
|
||||
return !is(snapTarget, 'bpmn:Lane');
|
||||
});
|
||||
};
|
||||
|
||||
// helpers //////////
|
||||
|
||||
function snapBoundaryEvent(event, target) {
|
||||
var targetTRBL = asTRBL(target);
|
||||
|
||||
var direction = getBoundaryAttachment(event, target);
|
||||
|
||||
if (/top/.test(direction)) {
|
||||
setSnapped(event, 'y', targetTRBL.top);
|
||||
} else
|
||||
if (/bottom/.test(direction)) {
|
||||
setSnapped(event, 'y', targetTRBL.bottom);
|
||||
}
|
||||
|
||||
if (/left/.test(direction)) {
|
||||
setSnapped(event, 'x', targetTRBL.left);
|
||||
} else
|
||||
if (/right/.test(direction)) {
|
||||
setSnapped(event, 'x', targetTRBL.right);
|
||||
}
|
||||
}
|
||||
|
||||
function areAll(elements, type) {
|
||||
return elements.every(function(el) {
|
||||
return is(el, type);
|
||||
});
|
||||
}
|
||||
|
||||
function isContainer(element) {
|
||||
if (is(element, 'bpmn:SubProcess') && isExpanded(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return is(element, 'bpmn:Participant');
|
||||
}
|
||||
|
||||
|
||||
function setSnappedIfConstrained(event) {
|
||||
var context = event.context,
|
||||
createConstraints = context.createConstraints;
|
||||
|
||||
if (!createConstraints) {
|
||||
return;
|
||||
}
|
||||
|
||||
var top = createConstraints.top,
|
||||
right = createConstraints.right,
|
||||
bottom = createConstraints.bottom,
|
||||
left = createConstraints.left;
|
||||
|
||||
if ((left && left >= event.x) || (right && right <= event.x)) {
|
||||
setSnapped(event, 'x', event.x);
|
||||
}
|
||||
|
||||
if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
|
||||
setSnapped(event, 'y', event.y);
|
||||
}
|
||||
}
|
||||
|
||||
function includes(array, value) {
|
||||
return array.indexOf(value) !== -1;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { getOrientation } from 'diagram-js/lib/layout/LayoutUtil';
|
||||
|
||||
export function getBoundaryAttachment(position, targetBounds) {
|
||||
|
||||
var orientation = getOrientation(position, targetBounds, -15);
|
||||
|
||||
if (orientation !== 'intersect') {
|
||||
return orientation;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import BpmnConnectSnapping from './BpmnConnectSnapping';
|
||||
import BpmnCreateMoveSnapping from './BpmnCreateMoveSnapping';
|
||||
import SnappingModule from 'diagram-js/lib/features/snapping';
|
||||
|
||||
export default {
|
||||
__depends__: [ SnappingModule ],
|
||||
__init__: [
|
||||
'connectSnapping',
|
||||
'createMoveSnapping'
|
||||
],
|
||||
connectSnapping: [ 'type', BpmnConnectSnapping ],
|
||||
createMoveSnapping: [ 'type', BpmnCreateMoveSnapping ]
|
||||
};
|
||||
Reference in New Issue
Block a user