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 re-select it when operation is finished 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.reconnect', replaceReconnectedConnection); this.postExecuted('element.updateProperties', function(event) { var context = event.context, properties = context.properties, element = context.element, businessObject = element.businessObject, connection; // remove condition on change to default if (properties.default) { connection = find( element.outgoing, matchPattern({ id: element.businessObject.default.id }) ); if (connection) { modeling.updateProperties(connection, { conditionExpression: undefined }); } } // remove default from source on change to conditional if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) { modeling.updateProperties(element.source, { default: undefined }); } }); } inherits(ReplaceConnectionBehavior, CommandInterceptor); ReplaceConnectionBehavior.$inject = [ 'eventBus', 'modeling', 'bpmnRules', 'injector' ];