DropOnFlowBehavior.js 5.5 KB

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