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 { isAny } from '../modeling/util/ModelingUtil'; import { some } from 'min-dash'; var HIGHER_PRIORITY = 1250; var BOUNDARY_TO_HOST_THRESHOLD = 40; var TARGET_BOUNDS_PADDING = 20, TASK_BOUNDS_PADDING = 10; var TARGET_CENTER_PADDING = 20; var AXES = [ 'x', 'y' ]; var abs = Math.abs; /** * Snap during connect. * * @param {EventBus} eventBus */ export default function BpmnConnectSnapping(eventBus) { eventBus.on([ 'connect.hover', 'connect.move', 'connect.end', ], HIGHER_PRIORITY, function(event) { var context = event.context, canExecute = context.canExecute, start = context.start, hover = context.hover, source = context.source, target = context.target; // do NOT snap on CMD if (event.originalEvent && isCmd(event.originalEvent)) { return; } if (!context.initialConnectionStart) { context.initialConnectionStart = context.connectionStart; } // snap hover if (canExecute && hover) { snapToShape(event, hover, getTargetBoundsPadding(hover)); } if (hover && isAnyType(canExecute, [ 'bpmn:Association', 'bpmn:DataInputAssociation', 'bpmn:DataOutputAssociation', 'bpmn:SequenceFlow' ])) { context.connectionStart = mid(start); // snap hover if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) { snapToPosition(event, mid(hover)); } // snap hover if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) { snapToTargetMid(event, hover); } // snap source and target if (is(source, 'bpmn:BoundaryEvent') && target === source.host) { snapBoundaryEventLoop(event); } } else if (isType(canExecute, 'bpmn:MessageFlow')) { if (is(start, 'bpmn:Event')) { // snap start context.connectionStart = mid(start); } if (is(hover, 'bpmn:Event')) { // snap hover snapToPosition(event, mid(hover)); } } else { // un-snap source context.connectionStart = context.initialConnectionStart; } }); } BpmnConnectSnapping.$inject = [ 'eventBus' ]; // helpers ////////// // snap to target if event in target function snapToShape(event, target, padding) { AXES.forEach(function(axis) { var dimensionForAxis = getDimensionForAxis(axis, target); if (event[ axis ] < target[ axis ] + padding) { setSnapped(event, axis, target[ axis ] + padding); } else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) { setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding); } }); } // snap to target mid if event in target mid function snapToTargetMid(event, target) { var targetMid = mid(target); AXES.forEach(function(axis) { if (isMid(event, target, axis)) { setSnapped(event, axis, targetMid[ axis ]); } }); } // snap to prevent loop overlapping boundary event function snapBoundaryEventLoop(event) { var context = event.context, source = context.source, target = context.target; if (isReverse(context)) { return; } var sourceMid = mid(source), orientation = getOrientation(sourceMid, target, -10), axes = []; if (/top|bottom/.test(orientation)) { axes.push('x'); } if (/left|right/.test(orientation)) { axes.push('y'); } axes.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); } }); } 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 TASK_BOUNDS_PADDING; } else { return TARGET_BOUNDS_PADDING; } } function isMid(event, target, axis) { return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING && event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING; } function isReverse(context) { var hover = context.hover, source = context.source; return hover && source && hover === source; }