import inherits from 'inherits'; import { assign, filter, find, isNumber } from 'min-dash'; import { getMid } from 'diagram-js/lib/layout/LayoutUtil'; 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, positionOrBounds) { var waypoints = targetFlow.waypoints, waypointsBefore, waypointsAfter, dockingPoint, source, target, incomingConnection, outgoingConnection, oldOutgoing = shape.outgoing.slice(), oldIncoming = shape.incoming.slice(); var mid; if (isNumber(positionOrBounds.width)) { mid = getMid(positionOrBounds); } else { mid = positionOrBounds; } var intersection = getApproxIntersection(waypoints, mid); 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] : mid; // 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 || mid); incomingConnection = targetFlow; } if (bpmnRules.canConnect(shape, target, targetFlow)) { if (!incomingConnection) { // reconnect inserted shape -> end modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid); 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.target === outgoingConnection.target; }) || [] ); 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, positionOrBounds = context.position; if (targetFlow) { insertShape(shape, targetFlow, positionOrBounds); } }, 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); }