BpmnCreateMoveSnapping.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import inherits from 'inherits-browser';
  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. /**
  18. * @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
  19. * @typedef {import('didi').Injector} Injector
  20. *
  21. * @typedef {import('diagram-js/lib/features/snapping/SnapContext').default} SnapContext
  22. * @typedef {import('diagram-js/lib/features/snapping/SnapContext').SnapPoints} SnapPoints
  23. *
  24. * @typedef {import('diagram-js/lib/core/EventBus').Event} Event
  25. *
  26. * @typedef {import('../../model/Types').Element} Element
  27. * @typedef {import('../../model/Types').Shape} Shape
  28. */
  29. var HIGH_PRIORITY = 1500;
  30. /**
  31. * Snap during create and move.
  32. *
  33. * @param {EventBus} eventBus
  34. * @param {Injector} injector
  35. */
  36. export default function BpmnCreateMoveSnapping(eventBus, injector) {
  37. injector.invoke(CreateMoveSnapping, this);
  38. // creating first participant
  39. eventBus.on([ 'create.move', 'create.end' ], HIGH_PRIORITY, setSnappedIfConstrained);
  40. // snap boundary events
  41. eventBus.on([
  42. 'create.move',
  43. 'create.end',
  44. 'shape.move.move',
  45. 'shape.move.end'
  46. ], HIGH_PRIORITY, function(event) {
  47. var context = event.context,
  48. canExecute = context.canExecute,
  49. target = context.target;
  50. var canAttach = canExecute && (canExecute === 'attach' || canExecute.attach);
  51. if (canAttach && !isSnapped(event)) {
  52. snapBoundaryEvent(event, target);
  53. }
  54. });
  55. }
  56. inherits(BpmnCreateMoveSnapping, CreateMoveSnapping);
  57. BpmnCreateMoveSnapping.$inject = [
  58. 'eventBus',
  59. 'injector'
  60. ];
  61. /**
  62. * @param {Event} event
  63. *
  64. * @return {SnapContext}
  65. */
  66. BpmnCreateMoveSnapping.prototype.initSnap = function(event) {
  67. var snapContext = CreateMoveSnapping.prototype.initSnap.call(this, event);
  68. var shape = event.shape;
  69. var isMove = !!this._elementRegistry.get(shape.id);
  70. // snap to docking points
  71. forEach(shape.outgoing, function(connection) {
  72. var docking = connection.waypoints[0];
  73. docking = docking.original || docking;
  74. snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
  75. });
  76. forEach(shape.incoming, function(connection) {
  77. var docking = connection.waypoints[connection.waypoints.length - 1];
  78. docking = docking.original || docking;
  79. snapContext.setSnapOrigin(connection.id + '-docking', getDockingSnapOrigin(docking, isMove, event));
  80. });
  81. if (is(shape, 'bpmn:Participant')) {
  82. // snap to borders with higher priority
  83. snapContext.setSnapLocations([ 'top-left', 'bottom-right', 'mid' ]);
  84. }
  85. return snapContext;
  86. };
  87. /**
  88. * @param {SnapPoints} snapPoints
  89. * @param {Shape} shape
  90. * @param {Shape} target
  91. *
  92. * @return {SnapPoints}
  93. */
  94. BpmnCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) {
  95. CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target);
  96. var snapTargets = this.getSnapTargets(shape, target);
  97. forEach(snapTargets, function(snapTarget) {
  98. // handle TRBL alignment
  99. //
  100. // * with container elements
  101. // * with text annotations
  102. if (isContainer(snapTarget) || areAll([ shape, snapTarget ], 'bpmn:TextAnnotation')) {
  103. snapPoints.add('top-left', topLeft(snapTarget));
  104. snapPoints.add('bottom-right', bottomRight(snapTarget));
  105. }
  106. });
  107. var elementRegistry = this._elementRegistry;
  108. // snap to docking points if not create mode
  109. forEach(shape.incoming, function(connection) {
  110. if (elementRegistry.get(shape.id)) {
  111. if (!includes(snapTargets, connection.source)) {
  112. snapPoints.add('mid', getMid(connection.source));
  113. }
  114. var docking = connection.waypoints[0];
  115. snapPoints.add(connection.id + '-docking', docking.original || docking);
  116. }
  117. });
  118. forEach(shape.outgoing, function(connection) {
  119. if (elementRegistry.get(shape.id)) {
  120. if (!includes(snapTargets, connection.target)) {
  121. snapPoints.add('mid', getMid(connection.target));
  122. }
  123. var docking = connection.waypoints[ connection.waypoints.length - 1 ];
  124. snapPoints.add(connection.id + '-docking', docking.original || docking);
  125. }
  126. });
  127. // add sequence flow parents as snap targets
  128. if (is(target, 'bpmn:SequenceFlow')) {
  129. snapPoints = this.addSnapTargetPoints(snapPoints, shape, target.parent);
  130. }
  131. return snapPoints;
  132. };
  133. /**
  134. * @param {Shape} shape
  135. * @param {Shape} target
  136. *
  137. * @return {Shape[]}
  138. */
  139. BpmnCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) {
  140. return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target)
  141. .filter(function(snapTarget) {
  142. // do not snap to lanes
  143. return !is(snapTarget, 'bpmn:Lane');
  144. });
  145. };
  146. // helpers //////////
  147. /**
  148. * @param {Shape} event
  149. * @param {Shape} target
  150. */
  151. function snapBoundaryEvent(event, target) {
  152. var targetTRBL = asTRBL(target);
  153. var direction = getBoundaryAttachment(event, target);
  154. var context = event.context,
  155. shape = context.shape;
  156. var offset;
  157. if (shape.parent) {
  158. offset = { x: 0, y: 0 };
  159. } else {
  160. offset = getMid(shape);
  161. }
  162. if (/top/.test(direction)) {
  163. setSnapped(event, 'y', targetTRBL.top - offset.y);
  164. } else if (/bottom/.test(direction)) {
  165. setSnapped(event, 'y', targetTRBL.bottom - offset.y);
  166. }
  167. if (/left/.test(direction)) {
  168. setSnapped(event, 'x', targetTRBL.left - offset.x);
  169. } else if (/right/.test(direction)) {
  170. setSnapped(event, 'x', targetTRBL.right - offset.x);
  171. }
  172. }
  173. /**
  174. * @param {Element[]} elements
  175. * @param {string} type
  176. *
  177. * @return {boolean}
  178. */
  179. function areAll(elements, type) {
  180. return elements.every(function(el) {
  181. return is(el, type);
  182. });
  183. }
  184. /**
  185. * @param {Element} element
  186. */
  187. function isContainer(element) {
  188. if (is(element, 'bpmn:SubProcess') && isExpanded(element)) {
  189. return true;
  190. }
  191. return is(element, 'bpmn:Participant');
  192. }
  193. /**
  194. * @param {Event} event
  195. */
  196. function setSnappedIfConstrained(event) {
  197. var context = event.context,
  198. createConstraints = context.createConstraints;
  199. if (!createConstraints) {
  200. return;
  201. }
  202. var top = createConstraints.top,
  203. right = createConstraints.right,
  204. bottom = createConstraints.bottom,
  205. left = createConstraints.left;
  206. if ((left && left >= event.x) || (right && right <= event.x)) {
  207. setSnapped(event, 'x', event.x);
  208. }
  209. if ((top && top >= event.y) || (bottom && bottom <= event.y)) {
  210. setSnapped(event, 'y', event.y);
  211. }
  212. }
  213. function includes(array, value) {
  214. return array.indexOf(value) !== -1;
  215. }
  216. function getDockingSnapOrigin(docking, isMove, event) {
  217. return isMove ? (
  218. {
  219. x: docking.x - event.x,
  220. y: docking.y - event.y
  221. }
  222. ) : {
  223. x: docking.x,
  224. y: docking.y
  225. };
  226. }