Create.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. var MARKER_OK = 'drop-ok',
  2. MARKER_NOT_OK = 'drop-not-ok',
  3. MARKER_ATTACH = 'attach-ok',
  4. MARKER_NEW_PARENT = 'new-parent';
  5. import {
  6. assign,
  7. filter,
  8. find,
  9. forEach,
  10. isArray,
  11. isNumber,
  12. map
  13. } from 'min-dash';
  14. import { getBBox } from '../../util/Elements';
  15. var PREFIX = 'create';
  16. var HIGH_PRIORITY = 2000;
  17. /**
  18. * Create new elements through drag and drop.
  19. *
  20. * @param {Canvas} canvas
  21. * @param {Dragging} dragging
  22. * @param {EventBus} eventBus
  23. * @param {Modeling} modeling
  24. * @param {Rules} rules
  25. */
  26. export default function Create(
  27. canvas,
  28. dragging,
  29. eventBus,
  30. modeling,
  31. rules
  32. ) {
  33. // rules //////////
  34. /**
  35. * Check wether elements can be created.
  36. *
  37. * @param {Array<djs.model.Base>} elements
  38. * @param {djs.model.Base} target
  39. * @param {Point} position
  40. * @param {djs.model.Base} [source]
  41. *
  42. * @returns {boolean|null|Object}
  43. */
  44. function canCreate(elements, target, position, source, hints) {
  45. if (!target) {
  46. return false;
  47. }
  48. // ignore child elements and external labels
  49. elements = filter(elements, function(element) {
  50. var labelTarget = element.labelTarget;
  51. return !element.parent && !(isLabel(element) && elements.indexOf(labelTarget) !== -1);
  52. });
  53. var shape = find(elements, function(element) {
  54. return !isConnection(element);
  55. });
  56. var attach = false,
  57. connect = false,
  58. create = false;
  59. // (1) attaching single shapes
  60. if (isSingleShape(elements)) {
  61. attach = rules.allowed('shape.attach', {
  62. position: position,
  63. shape: shape,
  64. target: target
  65. });
  66. }
  67. if (!attach) {
  68. // (2) creating elements
  69. if (isSingleShape(elements)) {
  70. create = rules.allowed('shape.create', {
  71. position: position,
  72. shape: shape,
  73. source: source,
  74. target: target
  75. });
  76. } else {
  77. create = rules.allowed('elements.create', {
  78. elements: elements,
  79. position: position,
  80. target: target
  81. });
  82. }
  83. }
  84. var connectionTarget = hints.connectionTarget;
  85. // (3) appending single shapes
  86. if (create || attach) {
  87. if (shape && source) {
  88. connect = rules.allowed('connection.create', {
  89. source: connectionTarget === source ? shape : source,
  90. target: connectionTarget === source ? source : shape,
  91. hints: {
  92. targetParent: target,
  93. targetAttach: attach
  94. }
  95. });
  96. }
  97. return {
  98. attach: attach,
  99. connect: connect
  100. };
  101. }
  102. // ignore wether or not elements can be created
  103. if (create === null || attach === null) {
  104. return null;
  105. }
  106. return false;
  107. }
  108. function setMarker(element, marker) {
  109. [ MARKER_ATTACH, MARKER_OK, MARKER_NOT_OK, MARKER_NEW_PARENT ].forEach(function(m) {
  110. if (m === marker) {
  111. canvas.addMarker(element, m);
  112. } else {
  113. canvas.removeMarker(element, m);
  114. }
  115. });
  116. }
  117. // event handling //////////
  118. eventBus.on([ 'create.move', 'create.hover' ], function(event) {
  119. var context = event.context,
  120. elements = context.elements,
  121. hover = event.hover,
  122. source = context.source,
  123. hints = context.hints || {};
  124. if (!hover) {
  125. context.canExecute = false;
  126. context.target = null;
  127. return;
  128. }
  129. ensureConstraints(event);
  130. var position = {
  131. x: event.x,
  132. y: event.y
  133. };
  134. var canExecute = context.canExecute = hover && canCreate(elements, hover, position, source, hints);
  135. if (hover && canExecute !== null) {
  136. context.target = hover;
  137. if (canExecute && canExecute.attach) {
  138. setMarker(hover, MARKER_ATTACH);
  139. } else {
  140. setMarker(hover, canExecute ? MARKER_NEW_PARENT : MARKER_NOT_OK);
  141. }
  142. }
  143. });
  144. eventBus.on([ 'create.end', 'create.out', 'create.cleanup' ], function(event) {
  145. var hover = event.hover;
  146. if (hover) {
  147. setMarker(hover, null);
  148. }
  149. });
  150. eventBus.on('create.end', function(event) {
  151. var context = event.context,
  152. source = context.source,
  153. shape = context.shape,
  154. elements = context.elements,
  155. target = context.target,
  156. canExecute = context.canExecute,
  157. attach = canExecute && canExecute.attach,
  158. connect = canExecute && canExecute.connect,
  159. hints = context.hints || {};
  160. if (canExecute === false || !target) {
  161. return false;
  162. }
  163. ensureConstraints(event);
  164. var position = {
  165. x: event.x,
  166. y: event.y
  167. };
  168. if (connect) {
  169. shape = modeling.appendShape(source, shape, position, target, {
  170. attach: attach,
  171. connection: connect === true ? {} : connect,
  172. connectionTarget: hints.connectionTarget
  173. });
  174. } else {
  175. elements = modeling.createElements(elements, position, target, assign({}, hints, {
  176. attach: attach
  177. }));
  178. // update shape
  179. shape = find(elements, function(element) {
  180. return !isConnection(element);
  181. });
  182. }
  183. // update elements and shape
  184. assign(context, {
  185. elements: elements,
  186. shape: shape
  187. });
  188. assign(event, {
  189. elements: elements,
  190. shape: shape
  191. });
  192. });
  193. function cancel() {
  194. var context = dragging.context();
  195. if (context && context.prefix === PREFIX) {
  196. dragging.cancel();
  197. }
  198. }
  199. // cancel on <elements.changed> that is not result of <drag.end>
  200. eventBus.on('create.init', function() {
  201. eventBus.on('elements.changed', cancel);
  202. eventBus.once([ 'create.cancel', 'create.end' ], HIGH_PRIORITY, function() {
  203. eventBus.off('elements.changed', cancel);
  204. });
  205. });
  206. // API //////////
  207. this.start = function(event, elements, context) {
  208. if (!isArray(elements)) {
  209. elements = [ elements ];
  210. }
  211. var shape = find(elements, function(element) {
  212. return !isConnection(element);
  213. });
  214. if (!shape) {
  215. // at least one shape is required
  216. return;
  217. }
  218. context = assign({
  219. elements: elements,
  220. hints: {},
  221. shape: shape
  222. }, context || {});
  223. // make sure each element has x and y
  224. forEach(elements, function(element) {
  225. if (!isNumber(element.x)) {
  226. element.x = 0;
  227. }
  228. if (!isNumber(element.y)) {
  229. element.y = 0;
  230. }
  231. });
  232. var visibleElements = filter(elements, function(element) {
  233. return !element.hidden;
  234. });
  235. var bbox = getBBox(visibleElements);
  236. // center elements around cursor
  237. forEach(elements, function(element) {
  238. if (isConnection(element)) {
  239. element.waypoints = map(element.waypoints, function(waypoint) {
  240. return {
  241. x: waypoint.x - bbox.x - bbox.width / 2,
  242. y: waypoint.y - bbox.y - bbox.height / 2
  243. };
  244. });
  245. }
  246. assign(element, {
  247. x: element.x - bbox.x - bbox.width / 2,
  248. y: element.y - bbox.y - bbox.height / 2
  249. });
  250. });
  251. dragging.init(event, PREFIX, {
  252. cursor: 'grabbing',
  253. autoActivate: true,
  254. data: {
  255. shape: shape,
  256. elements: elements,
  257. context: context
  258. }
  259. });
  260. };
  261. }
  262. Create.$inject = [
  263. 'canvas',
  264. 'dragging',
  265. 'eventBus',
  266. 'modeling',
  267. 'rules'
  268. ];
  269. // helpers //////////
  270. function ensureConstraints(event) {
  271. var context = event.context,
  272. createConstraints = context.createConstraints;
  273. if (!createConstraints) {
  274. return;
  275. }
  276. if (createConstraints.left) {
  277. event.x = Math.max(event.x, createConstraints.left);
  278. }
  279. if (createConstraints.right) {
  280. event.x = Math.min(event.x, createConstraints.right);
  281. }
  282. if (createConstraints.top) {
  283. event.y = Math.max(event.y, createConstraints.top);
  284. }
  285. if (createConstraints.bottom) {
  286. event.y = Math.min(event.y, createConstraints.bottom);
  287. }
  288. }
  289. function isConnection(element) {
  290. return !!element.waypoints;
  291. }
  292. function isSingleShape(elements) {
  293. return elements && elements.length === 1 && !isConnection(elements[0]);
  294. }
  295. function isLabel(element) {
  296. return !!element.labelTarget;
  297. }