Move.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import {
  2. assign,
  3. filter,
  4. groupBy,
  5. isObject
  6. } from 'min-dash';
  7. import {
  8. classes as svgClasses
  9. } from 'tiny-svg';
  10. var LOW_PRIORITY = 500,
  11. MEDIUM_PRIORITY = 1250,
  12. HIGH_PRIORITY = 1500;
  13. import { getOriginal as getOriginalEvent } from '../../util/Event';
  14. import {
  15. isPrimaryButton
  16. } from '../../util/Mouse';
  17. var round = Math.round;
  18. function mid(element) {
  19. return {
  20. x: element.x + round(element.width / 2),
  21. y: element.y + round(element.height / 2)
  22. };
  23. }
  24. /**
  25. * A plugin that makes shapes draggable / droppable.
  26. *
  27. * @param {EventBus} eventBus
  28. * @param {Dragging} dragging
  29. * @param {Modeling} modeling
  30. * @param {Selection} selection
  31. * @param {Rules} rules
  32. */
  33. export default function MoveEvents(
  34. eventBus, dragging, modeling,
  35. selection, rules) {
  36. // rules
  37. function canMove(shapes, delta, position, target) {
  38. return rules.allowed('elements.move', {
  39. shapes: shapes,
  40. delta: delta,
  41. position: position,
  42. target: target
  43. });
  44. }
  45. // move events
  46. // assign a high priority to this handler to setup the environment
  47. // others may hook up later, e.g. at default priority and modify
  48. // the move environment.
  49. //
  50. // This sets up the context with
  51. //
  52. // * shape: the primary shape being moved
  53. // * shapes: a list of shapes to be moved
  54. // * validatedShapes: a list of shapes that are being checked
  55. // against the rules before and during move
  56. //
  57. eventBus.on('shape.move.start', HIGH_PRIORITY, function(event) {
  58. var context = event.context,
  59. shape = event.shape,
  60. shapes = selection.get().slice();
  61. // move only single shape if the dragged element
  62. // is not part of the current selection
  63. if (shapes.indexOf(shape) === -1) {
  64. shapes = [ shape ];
  65. }
  66. // ensure we remove nested elements in the collection
  67. // and add attachers for a proper dragger
  68. shapes = removeNested(shapes);
  69. // attach shapes to drag context
  70. assign(context, {
  71. shapes: shapes,
  72. validatedShapes: shapes,
  73. shape: shape
  74. });
  75. });
  76. // assign a high priority to this handler to setup the environment
  77. // others may hook up later, e.g. at default priority and modify
  78. // the move environment
  79. //
  80. eventBus.on('shape.move.start', MEDIUM_PRIORITY, function(event) {
  81. var context = event.context,
  82. validatedShapes = context.validatedShapes,
  83. canExecute;
  84. canExecute = context.canExecute = canMove(validatedShapes);
  85. // check if we can move the elements
  86. if (!canExecute) {
  87. return false;
  88. }
  89. });
  90. // assign a low priority to this handler
  91. // to let others modify the move event before we update
  92. // the context
  93. //
  94. eventBus.on('shape.move.move', LOW_PRIORITY, function(event) {
  95. var context = event.context,
  96. validatedShapes = context.validatedShapes,
  97. hover = event.hover,
  98. delta = { x: event.dx, y: event.dy },
  99. position = { x: event.x, y: event.y },
  100. canExecute;
  101. // check if we can move the elements
  102. canExecute = canMove(validatedShapes, delta, position, hover);
  103. context.delta = delta;
  104. context.canExecute = canExecute;
  105. // simply ignore move over
  106. if (canExecute === null) {
  107. context.target = null;
  108. return;
  109. }
  110. context.target = hover;
  111. });
  112. eventBus.on('shape.move.end', function(event) {
  113. var context = event.context;
  114. var delta = context.delta,
  115. canExecute = context.canExecute,
  116. isAttach = canExecute === 'attach',
  117. shapes = context.shapes;
  118. if (canExecute === false) {
  119. return false;
  120. }
  121. // ensure we have actual pixel values deltas
  122. // (important when zoom level was > 1 during move)
  123. delta.x = round(delta.x);
  124. delta.y = round(delta.y);
  125. if (delta.x === 0 && delta.y === 0) {
  126. // didn't move
  127. return;
  128. }
  129. modeling.moveElements(shapes, delta, context.target, {
  130. primaryShape: context.shape,
  131. attach: isAttach
  132. });
  133. });
  134. // move activation
  135. eventBus.on('element.mousedown', function(event) {
  136. if (!isPrimaryButton(event)) {
  137. return;
  138. }
  139. var originalEvent = getOriginalEvent(event);
  140. if (!originalEvent) {
  141. throw new Error('must supply DOM mousedown event');
  142. }
  143. return start(originalEvent, event.element);
  144. });
  145. /**
  146. * Start move.
  147. *
  148. * @param {MouseEvent} event
  149. * @param {djs.model.Shape} shape
  150. * @param {boolean} [activate]
  151. * @param {Object} [context]
  152. */
  153. function start(event, element, activate, context) {
  154. if (isObject(activate)) {
  155. context = activate;
  156. activate = false;
  157. }
  158. // do not move connections or the root element
  159. if (element.waypoints || !element.parent) {
  160. return;
  161. }
  162. // ignore non-draggable hits
  163. if (svgClasses(event.target).has('djs-hit-no-move')) {
  164. return;
  165. }
  166. var referencePoint = mid(element);
  167. dragging.init(event, referencePoint, 'shape.move', {
  168. cursor: 'grabbing',
  169. autoActivate: activate,
  170. data: {
  171. shape: element,
  172. context: context || {}
  173. }
  174. });
  175. // we've handled the event
  176. return true;
  177. }
  178. // API
  179. this.start = start;
  180. }
  181. MoveEvents.$inject = [
  182. 'eventBus',
  183. 'dragging',
  184. 'modeling',
  185. 'selection',
  186. 'rules'
  187. ];
  188. /**
  189. * Return a filtered list of elements that do not contain
  190. * those nested into others.
  191. *
  192. * @param {Array<djs.model.Base>} elements
  193. *
  194. * @return {Array<djs.model.Base>} filtered
  195. */
  196. function removeNested(elements) {
  197. var ids = groupBy(elements, 'id');
  198. return filter(elements, function(element) {
  199. while ((element = element.parent)) {
  200. // parent in selection
  201. if (ids[element.id]) {
  202. return false;
  203. }
  204. }
  205. return true;
  206. });
  207. }