Create.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. var LOW_PRIORITY = 750;
  2. var MARKER_OK = 'drop-ok',
  3. MARKER_NOT_OK = 'drop-not-ok',
  4. MARKER_ATTACH = 'attach-ok',
  5. MARKER_NEW_PARENT = 'new-parent';
  6. import {
  7. append as svgAppend,
  8. attr as svgAttr,
  9. classes as svgClasses,
  10. create as svgCreate,
  11. remove as svgRemove
  12. } from 'tiny-svg';
  13. import {
  14. translate
  15. } from '../../util/SvgTransformUtil';
  16. /**
  17. * Adds the ability to create new shapes via drag and drop.
  18. *
  19. * Create must be activated via {@link Create#start}. From that
  20. * point on, create will invoke `shape.create` and `shape.attach`
  21. * rules to query whether or not creation or attachment on a certain
  22. * position is allowed.
  23. *
  24. * If create or attach is allowed and a source is given, Create it
  25. * will invoke `connection.create` rules to query whether a connection
  26. * can be drawn between source and new shape. During rule evaluation
  27. * the target is not attached yet, however
  28. *
  29. * hints = { targetParent, targetAttach }
  30. *
  31. * are passed to the evaluating rules.
  32. *
  33. *
  34. * ## Rule Return Values
  35. *
  36. * Return values interpreted from `shape.create`:
  37. *
  38. * * `true`: create is allowed
  39. * * `false`: create is disallowed
  40. * * `null`: create is not allowed but should be ignored visually
  41. *
  42. * Return values interpreted from `shape.attach`:
  43. *
  44. * * `true`: attach is allowed
  45. * * `Any`: attach is allowed with the constraints
  46. * * `false`: attach is disallowed
  47. *
  48. * Return values interpreted from `connection.create`:
  49. *
  50. * * `true`: connection can be created
  51. * * `Any`: connection with the given attributes can be created
  52. * * `false`: connection can't be created
  53. *
  54. *
  55. * @param {EventBus} eventBus
  56. * @param {Dragging} dragging
  57. * @param {Rules} rules
  58. * @param {Modeling} modeling
  59. * @param {Canvas} canvas
  60. * @param {Styles} styles
  61. * @param {GraphicsFactory} graphicsFactory
  62. */
  63. export default function Create(
  64. eventBus, dragging, rules, modeling,
  65. canvas, styles, graphicsFactory) {
  66. // rules
  67. function canCreate(shape, target, source, position) {
  68. if (!target) {
  69. return false;
  70. }
  71. var ctx = {
  72. source: source,
  73. shape: shape,
  74. target: target,
  75. position: position
  76. };
  77. var create,
  78. attach,
  79. connect;
  80. attach = rules.allowed('shape.attach', ctx);
  81. if (!attach) {
  82. create = rules.allowed('shape.create', ctx);
  83. }
  84. if (create || attach) {
  85. connect = source && rules.allowed('connection.create', {
  86. source: source,
  87. target: shape,
  88. hints: {
  89. targetParent: target,
  90. targetAttach: attach
  91. }
  92. });
  93. }
  94. if (create || attach) {
  95. return {
  96. attach: attach,
  97. connect: connect
  98. };
  99. }
  100. return false;
  101. }
  102. /** set drop marker on an element */
  103. function setMarker(element, marker) {
  104. [ MARKER_ATTACH, MARKER_OK, MARKER_NOT_OK, MARKER_NEW_PARENT ].forEach(function(m) {
  105. if (m === marker) {
  106. canvas.addMarker(element, m);
  107. } else {
  108. canvas.removeMarker(element, m);
  109. }
  110. });
  111. }
  112. // visual helpers
  113. function createVisual(shape) {
  114. var group, preview, visual;
  115. group = svgCreate('g');
  116. svgAttr(group, styles.cls('djs-drag-group', [ 'no-events' ]));
  117. svgAppend(canvas.getDefaultLayer(), group);
  118. preview = svgCreate('g');
  119. svgClasses(preview).add('djs-dragger');
  120. svgAppend(group, preview);
  121. translate(preview, shape.width / -2, shape.height / -2);
  122. var visualGroup = svgCreate('g');
  123. svgClasses(visualGroup).add('djs-visual');
  124. svgAppend(preview, visualGroup);
  125. visual = visualGroup;
  126. // hijack renderer to draw preview
  127. graphicsFactory.drawShape(visual, shape);
  128. return group;
  129. }
  130. // event handlers
  131. eventBus.on('create.move', function(event) {
  132. var context = event.context,
  133. hover = event.hover,
  134. shape = context.shape,
  135. source = context.source,
  136. canExecute;
  137. var position = {
  138. x: event.x,
  139. y: event.y
  140. };
  141. canExecute = context.canExecute = hover && canCreate(shape, hover, source, position);
  142. // ignore hover visually if canExecute is null
  143. if (hover && canExecute !== null) {
  144. context.target = hover;
  145. if (canExecute && canExecute.attach) {
  146. setMarker(hover, MARKER_ATTACH);
  147. } else {
  148. setMarker(hover, canExecute ? MARKER_NEW_PARENT : MARKER_NOT_OK);
  149. }
  150. }
  151. });
  152. eventBus.on('create.move', LOW_PRIORITY, function(event) {
  153. var context = event.context,
  154. shape = context.shape,
  155. visual = context.visual;
  156. // lazy init drag visual once we received the first real
  157. // drag move event (this allows us to get the proper canvas local coordinates)
  158. if (!visual) {
  159. visual = context.visual = createVisual(shape);
  160. }
  161. translate(visual, event.x, event.y);
  162. });
  163. eventBus.on([ 'create.end', 'create.out', 'create.cleanup' ], function(event) {
  164. var context = event.context,
  165. target = context.target;
  166. if (target) {
  167. setMarker(target, null);
  168. }
  169. });
  170. eventBus.on('create.end', function(event) {
  171. var context = event.context,
  172. source = context.source,
  173. shape = context.shape,
  174. target = context.target,
  175. canExecute = context.canExecute,
  176. attach = canExecute && canExecute.attach,
  177. connect = canExecute && canExecute.connect,
  178. position = {
  179. x: event.x,
  180. y: event.y
  181. };
  182. if (!canExecute) {
  183. return false;
  184. }
  185. if (connect) {
  186. // invoke append if connect is set via rules
  187. shape = modeling.appendShape(source, shape, position, target, {
  188. attach: attach,
  189. connection: connect === true ? {} : connect
  190. });
  191. } else {
  192. // invoke create, if connect is not set
  193. shape = modeling.createShape(shape, position, target, {
  194. attach: attach
  195. });
  196. }
  197. // make sure we provide the actual attached
  198. // shape with the context so that selection and
  199. // other components can use it right after the create
  200. // operation ends
  201. context.shape = shape;
  202. });
  203. eventBus.on('create.cleanup', function(event) {
  204. var context = event.context;
  205. if (context.visual) {
  206. svgRemove(context.visual);
  207. }
  208. });
  209. // API
  210. this.start = function(event, shape, source) {
  211. dragging.init(event, 'create', {
  212. cursor: 'grabbing',
  213. autoActivate: true,
  214. data: {
  215. shape: shape,
  216. context: {
  217. shape: shape,
  218. source: source
  219. }
  220. }
  221. });
  222. };
  223. }
  224. Create.$inject = [
  225. 'eventBus',
  226. 'dragging',
  227. 'rules',
  228. 'modeling',
  229. 'canvas',
  230. 'styles',
  231. 'graphicsFactory'
  232. ];