DropOnFlowBehavior.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. import inherits from 'inherits';
  2. import {
  3. assign,
  4. filter,
  5. find,
  6. isNumber
  7. } from 'min-dash';
  8. import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
  9. import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
  10. import {
  11. getApproxIntersection
  12. } from 'diagram-js/lib/util/LineIntersection';
  13. export default function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {
  14. CommandInterceptor.call(this, eventBus);
  15. /**
  16. * Reconnect start / end of a connection after
  17. * dropping an element on a flow.
  18. */
  19. function insertShape(shape, targetFlow, positionOrBounds) {
  20. var waypoints = targetFlow.waypoints,
  21. waypointsBefore,
  22. waypointsAfter,
  23. dockingPoint,
  24. source,
  25. target,
  26. incomingConnection,
  27. outgoingConnection,
  28. oldOutgoing = shape.outgoing.slice(),
  29. oldIncoming = shape.incoming.slice();
  30. var mid;
  31. if (isNumber(positionOrBounds.width)) {
  32. mid = getMid(positionOrBounds);
  33. } else {
  34. mid = positionOrBounds;
  35. }
  36. var intersection = getApproxIntersection(waypoints, mid);
  37. if (intersection) {
  38. waypointsBefore = waypoints.slice(0, intersection.index);
  39. waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));
  40. // due to inaccuracy intersection might have been found
  41. if (!waypointsBefore.length || !waypointsAfter.length) {
  42. return;
  43. }
  44. dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : mid;
  45. // if last waypointBefore is inside shape's bounds, ignore docking point
  46. if (!isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length-1])) {
  47. waypointsBefore.push(copy(dockingPoint));
  48. }
  49. // if first waypointAfter is inside shape's bounds, ignore docking point
  50. if (!isPointInsideBBox(shape, waypointsAfter[0])) {
  51. waypointsAfter.unshift(copy(dockingPoint));
  52. }
  53. }
  54. source = targetFlow.source;
  55. target = targetFlow.target;
  56. if (bpmnRules.canConnect(source, shape, targetFlow)) {
  57. // reconnect source -> inserted shape
  58. modeling.reconnectEnd(targetFlow, shape, waypointsBefore || mid);
  59. incomingConnection = targetFlow;
  60. }
  61. if (bpmnRules.canConnect(shape, target, targetFlow)) {
  62. if (!incomingConnection) {
  63. // reconnect inserted shape -> end
  64. modeling.reconnectStart(targetFlow, shape, waypointsAfter || mid);
  65. outgoingConnection = targetFlow;
  66. } else {
  67. outgoingConnection = modeling.connect(
  68. shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
  69. );
  70. }
  71. }
  72. var duplicateConnections = [].concat(
  73. incomingConnection && filter(oldIncoming, function(connection) {
  74. return connection.source === incomingConnection.source;
  75. }) || [],
  76. outgoingConnection && filter(oldOutgoing, function(connection) {
  77. return connection.target === outgoingConnection.target;
  78. }) || []
  79. );
  80. if (duplicateConnections.length) {
  81. modeling.removeElements(duplicateConnections);
  82. }
  83. }
  84. this.preExecute('elements.move', function(context) {
  85. var newParent = context.newParent,
  86. shapes = context.shapes,
  87. delta = context.delta,
  88. shape = shapes[0];
  89. if (!shape || !newParent) {
  90. return;
  91. }
  92. // if the new parent is a connection,
  93. // change it to the new parent's parent
  94. if (newParent && newParent.waypoints) {
  95. context.newParent = newParent = newParent.parent;
  96. }
  97. var shapeMid = getMid(shape);
  98. var newShapeMid = {
  99. x: shapeMid.x + delta.x,
  100. y: shapeMid.y + delta.y
  101. };
  102. // find a connection which intersects with the
  103. // element's mid point
  104. var connection = find(newParent.children, function(element) {
  105. var canInsert = bpmnRules.canInsert(shapes, element);
  106. return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
  107. });
  108. if (connection) {
  109. context.targetFlow = connection;
  110. context.position = newShapeMid;
  111. }
  112. }, true);
  113. this.postExecuted('elements.move', function(context) {
  114. var shapes = context.shapes,
  115. targetFlow = context.targetFlow,
  116. position = context.position;
  117. if (targetFlow) {
  118. insertShape(shapes[0], targetFlow, position);
  119. }
  120. }, true);
  121. this.preExecute('shape.create', function(context) {
  122. var parent = context.parent,
  123. shape = context.shape;
  124. if (bpmnRules.canInsert(shape, parent)) {
  125. context.targetFlow = parent;
  126. context.parent = parent.parent;
  127. }
  128. }, true);
  129. this.postExecuted('shape.create', function(context) {
  130. var shape = context.shape,
  131. targetFlow = context.targetFlow,
  132. positionOrBounds = context.position;
  133. if (targetFlow) {
  134. insertShape(shape, targetFlow, positionOrBounds);
  135. }
  136. }, true);
  137. }
  138. inherits(DropOnFlowBehavior, CommandInterceptor);
  139. DropOnFlowBehavior.$inject = [
  140. 'eventBus',
  141. 'bpmnRules',
  142. 'modeling'
  143. ];
  144. // helpers /////////////////////
  145. function isPointInsideBBox(bbox, point) {
  146. var x = point.x,
  147. y = point.y;
  148. return x >= bbox.x &&
  149. x <= bbox.x + bbox.width &&
  150. y >= bbox.y &&
  151. y <= bbox.y + bbox.height;
  152. }
  153. function copy(obj) {
  154. return assign({}, obj);
  155. }