BpmnCreateMoveSnapping.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import inherits from 'inherits';
  2. import CreateMoveSnapping from 'diagram-js/lib/features/snapping/CreateMoveSnapping';
  3. import {
  4. isSnapped,
  5. setSnapped,
  6. topLeft,
  7. bottomRight
  8. } from 'diagram-js/lib/features/snapping/SnapUtil';
  9. import { isExpanded } from '../../util/DiUtil';
  10. import { is } from '../../util/ModelUtil';
  11. import {
  12. asTRBL,
  13. getMid
  14. } from 'diagram-js/lib/layout/LayoutUtil';
  15. import { getBoundaryAttachment } from './BpmnSnappingUtil';
  16. import { forEach } from 'min-dash';
  17. var HIGH_PRIORITY = 1500;
  18. /**
  19. * Snap during create and move.
  20. *
  21. * @param {EventBus} eventBus
  22. * @param {Injector} injector
  23. */
  24. export default function BpmnCreateMoveSnapping(eventBus, injector) {
  25. injector.invoke(CreateMoveSnapping, this);
  26. // creating first participant
  27. eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
  28. // snap boundary events
  29. eventBus.on([
  30. 'create.move',
  31. 'create.end',
  32. 'shape.move.move',
  33. 'shape.move.end'
  34. ], HIGH_PRIORITY, function(event) {
  35. var context = event.context,
  36. canExecute = context.canExecute,
  37. target = context.target;
  38. var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach);
  39. if (canAttach && !isSnapped(event)) {
  40. snapBoundaryEvent(event, target);
  41. }
  42. });
  43. }
  44. inherits(BpmnCreateMoveSnapping, CreateMoveSnapping);
  45. BpmnCreateMoveSnapping.$inject = [
  46. 'eventBus',
  47. 'injector'
  48. ];
  49. BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
  50. var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
  51. var shape = event.shape;
  52. var isMove = !!this._elementRegistry.get(shape.id);
  53. // snap to docking points
  54. forEach(shape.outgoing, function(connection) {
  55. var docking = connection.waypoints[0];
  56. docking = docking.original || docking;
  57. snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
  58. });
  59. forEach(shape.incoming, function(connection) {
  60. var docking = connection.waypoints[connection.waypoints.length - 1];
  61. docking = docking.original || docking;
  62. snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
  63. });
  64. if (is(shape, 'bpmn:Participant')) {
  65. // snap to borders with higher priority
  66. snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
  67. }
  68. return snapContext;
  69. };
  70. BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
  71. CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
  72. var snapTargets = this.getSnapTargets(shape, target);
  73. forEach(snapTargets, function(snapTarget) {
  74. // handle TRBL alignment
  75. //
  76. // * with container elements
  77. // * with text annotations
  78. if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
  79. snapPoints.add('top-left', topLeft(snapTarget));
  80. snapPoints.add('bottom-right', bottomRight(snapTarget));
  81. }
  82. });
  83. var elementRegistry = this._elementRegistry;
  84. // snap to docking points if not create mode
  85. forEach(shape.incoming, function(connection) {
  86. if (elementRegistry.get(shape.id)) {
  87. if (!includes(snapTargets, connection.source)) {
  88. snapPoints.add('mid', getMid(connection.source));
  89. }
  90. var docking = connection.waypoints[0];
  91. snapPoints.add(connection.id + '-docking', docking.original || docking);
  92. }
  93. });
  94. forEach(shape.outgoing, function(connection) {
  95. if (elementRegistry.get(shape.id)) {
  96. if (!includes(snapTargets, connection.target)) {
  97. snapPoints.add('mid', getMid(connection.target));
  98. }
  99. var docking = connection.waypoints[ connection.waypoints.length - 1 ];
  100. snapPoints.add(connection.id + '-docking', docking.original || docking);
  101. }
  102. });
  103. // add sequence flow parents as snap targets
  104. if (is(target, 'bpmn:SequenceFlow')) {
  105. snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
  106. }
  107. return snapPoints;
  108. };
  109. BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
  110. return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
  111. .filter(function(snapTarget) {
  112. // do not snap to lanes
  113. return !is(snapTarget, 'bpmn:Lane');
  114. });
  115. };
  116. // helpers //////////
  117. function snapBoundaryEvent(event, target) {
  118. var targetTRBL = asTRBL(target);
  119. var direction = getBoundaryAttachment(event, target);
  120. var context = event.context,
  121. shape = context.shape;
  122. var offset;
  123. if (shape.parent) {
  124. offset = { x: 0, y: 0 };
  125. } else {
  126. offset = getMid(shape);
  127. }
  128. if (/top/.test(direction)) {
  129. setSnapped(event, 'y', targetTRBL.top - offset.y);
  130. } else if (/bottom/.test(direction)) {
  131. setSnapped(event, 'y', targetTRBL.bottom - offset.y);
  132. }
  133. if (/left/.test(direction)) {
  134. setSnapped(event, 'x', targetTRBL.left - offset.x);
  135. } else if (/right/.test(direction)) {
  136. setSnapped(event, 'x', targetTRBL.right - offset.x);
  137. }
  138. }
  139. function areAll(elements, type) {
  140. return elements.every(function(el) {
  141. return is(el, type);
  142. });
  143. }
  144. function isContainer(element) {
  145. if (is(element, 'bpmn:SubProcess') && isExpanded(element)) {
  146. return true;
  147. }
  148. return is(element, 'bpmn:Participant');
  149. }
  150. function setSnappedIfConstrained(event) {
  151. var context = event.context,
  152. createConstraints = context.createConstraints;
  153. if (!createConstraints) {
  154. return;
  155. }
  156. var top = createConstraints.top,
  157. right = createConstraints.right,
  158. bottom = createConstraints.bottom,
  159. left = createConstraints.left;
  160. if ((left && left >= event.x) || (right && right <= event.x)) {
  161. setSnapped(event, 'x', event.x);
  162. }
  163. if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
  164. setSnapped(event, 'y', event.y);
  165. }
  166. }
  167. function includes(array, value) {
  168. return array.indexOf(value) !== -1;
  169. }
  170. function getDockingSnapOrigin(docking, isMove, event) {
  171. return isMove ? (
  172. {
  173. x: docking.x - event.x,
  174. y: docking.y - event.y
  175. }
  176. ) : {
  177. x: docking.x,
  178. y: docking.y
  179. };
  180. }