BpmnConnectSnapping.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. import {
  2. mid,
  3. setSnapped
  4. } from 'diagram-js/lib/features/snapping/SnapUtil';
  5. import { isCmd } from 'diagram-js/lib/features/keyboard/KeyboardUtil';
  6. import {
  7. getOrientation
  8. } from 'diagram-js/lib/layout/LayoutUtil';
  9. import { is } from '../../util/ModelUtil';
  10. import { isAny } from '../modeling/util/ModelingUtil';
  11. import { some } from 'min-dash';
  12. var HIGHER_PRIORITY = 1250;
  13. var BOUNDARY_TO_HOST_THRESHOLD = 40;
  14. var TARGET_BOUNDS_PADDING = 20,
  15. TASK_BOUNDS_PADDING = 10;
  16. var TARGET_CENTER_PADDING = 20;
  17. var AXES = [ 'x', 'y' ];
  18. var abs = Math.abs;
  19. /**
  20. * Snap during connect.
  21. *
  22. * @param {EventBus} eventBus
  23. */
  24. export default function BpmnConnectSnapping(eventBus) {
  25. eventBus.on([
  26. 'connect.hover',
  27. 'connect.move',
  28. 'connect.end',
  29. ], HIGHER_PRIORITY, function(event) {
  30. var context = event.context,
  31. canExecute = context.canExecute,
  32. start = context.start,
  33. hover = context.hover,
  34. source = context.source,
  35. target = context.target;
  36. // do NOT snap on CMD
  37. if (event.originalEvent && isCmd(event.originalEvent)) {
  38. return;
  39. }
  40. if (!context.initialConnectionStart) {
  41. context.initialConnectionStart = context.connectionStart;
  42. }
  43. // snap hover
  44. if (canExecute && hover) {
  45. snapToShape(event, hover, getTargetBoundsPadding(hover));
  46. }
  47. if (hover && isAnyType(canExecute, [
  48. 'bpmn:Association',
  49. 'bpmn:DataInputAssociation',
  50. 'bpmn:DataOutputAssociation',
  51. 'bpmn:SequenceFlow'
  52. ])) {
  53. context.connectionStart = mid(start);
  54. // snap hover
  55. if (isAny(hover, [ 'bpmn:Event', 'bpmn:Gateway' ])) {
  56. snapToPosition(event, mid(hover));
  57. }
  58. // snap hover
  59. if (isAny(hover, [ 'bpmn:Task', 'bpmn:SubProcess' ])) {
  60. snapToTargetMid(event, hover);
  61. }
  62. // snap source and target
  63. if (is(source, 'bpmn:BoundaryEvent') && target === source.host) {
  64. snapBoundaryEventLoop(event);
  65. }
  66. } else if (isType(canExecute, 'bpmn:MessageFlow')) {
  67. if (is(start, 'bpmn:Event')) {
  68. // snap start
  69. context.connectionStart = mid(start);
  70. }
  71. if (is(hover, 'bpmn:Event')) {
  72. // snap hover
  73. snapToPosition(event, mid(hover));
  74. }
  75. } else {
  76. // un-snap source
  77. context.connectionStart = context.initialConnectionStart;
  78. }
  79. });
  80. }
  81. BpmnConnectSnapping.$inject = [ 'eventBus' ];
  82. // helpers //////////
  83. // snap to target if event in target
  84. function snapToShape(event, target, padding) {
  85. AXES.forEach(function(axis) {
  86. var dimensionForAxis = getDimensionForAxis(axis, target);
  87. if (event[ axis ] < target[ axis ] + padding) {
  88. setSnapped(event, axis, target[ axis ] + padding);
  89. } else if (event[ axis ] > target[ axis ] + dimensionForAxis - padding) {
  90. setSnapped(event, axis, target[ axis ] + dimensionForAxis - padding);
  91. }
  92. });
  93. }
  94. // snap to target mid if event in target mid
  95. function snapToTargetMid(event, target) {
  96. var targetMid = mid(target);
  97. AXES.forEach(function(axis) {
  98. if (isMid(event, target, axis)) {
  99. setSnapped(event, axis, targetMid[ axis ]);
  100. }
  101. });
  102. }
  103. // snap to prevent loop overlapping boundary event
  104. function snapBoundaryEventLoop(event) {
  105. var context = event.context,
  106. source = context.source,
  107. target = context.target;
  108. if (isReverse(context)) {
  109. return;
  110. }
  111. var sourceMid = mid(source),
  112. orientation = getOrientation(sourceMid, target, -10),
  113. axes = [];
  114. if (/top|bottom/.test(orientation)) {
  115. axes.push('x');
  116. }
  117. if (/left|right/.test(orientation)) {
  118. axes.push('y');
  119. }
  120. axes.forEach(function(axis) {
  121. var coordinate = event[ axis ], newCoordinate;
  122. if (abs(coordinate - sourceMid[ axis ]) < BOUNDARY_TO_HOST_THRESHOLD) {
  123. if (coordinate > sourceMid[ axis ]) {
  124. newCoordinate = sourceMid[ axis ] + BOUNDARY_TO_HOST_THRESHOLD;
  125. }
  126. else {
  127. newCoordinate = sourceMid[ axis ] - BOUNDARY_TO_HOST_THRESHOLD;
  128. }
  129. setSnapped(event, axis, newCoordinate);
  130. }
  131. });
  132. }
  133. function snapToPosition(event, position) {
  134. setSnapped(event, 'x', position.x);
  135. setSnapped(event, 'y', position.y);
  136. }
  137. function isType(attrs, type) {
  138. return attrs && attrs.type === type;
  139. }
  140. function isAnyType(attrs, types) {
  141. return some(types, function(type) {
  142. return isType(attrs, type);
  143. });
  144. }
  145. function getDimensionForAxis(axis, element) {
  146. return axis === 'x' ? element.width : element.height;
  147. }
  148. function getTargetBoundsPadding(target) {
  149. if (is(target, 'bpmn:Task')) {
  150. return TASK_BOUNDS_PADDING;
  151. } else {
  152. return TARGET_BOUNDS_PADDING;
  153. }
  154. }
  155. function isMid(event, target, axis) {
  156. return event[ axis ] > target[ axis ] + TARGET_CENTER_PADDING
  157. && event[ axis ] < target[ axis ] + getDimensionForAxis(axis, target) - TARGET_CENTER_PADDING;
  158. }
  159. function isReverse(context) {
  160. var hover = context.hover,
  161. source = context.source;
  162. return hover && source && hover === source;
  163. }