PreviewSupport.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. import {
  2. forEach
  3. } from 'min-dash';
  4. import {
  5. append as svgAppend,
  6. attr as svgAttr,
  7. classes as svgClasses,
  8. clone as svgClone,
  9. create as svgCreate,
  10. remove as svgRemove
  11. } from 'tiny-svg';
  12. import { query as domQuery } from 'min-dom';
  13. import { getVisual } from '../../util/GraphicsUtil';
  14. var MARKER_TYPES = [
  15. 'marker-start',
  16. 'marker-mid',
  17. 'marker-end'
  18. ];
  19. var NODES_CAN_HAVE_MARKER = [
  20. 'circle',
  21. 'ellipse',
  22. 'line',
  23. 'path',
  24. 'polygon',
  25. 'polyline',
  26. 'path',
  27. 'rect'
  28. ];
  29. /**
  30. * Adds support for previews of moving/resizing elements.
  31. */
  32. export default function PreviewSupport(elementRegistry, eventBus, canvas, styles) {
  33. this._elementRegistry = elementRegistry;
  34. this._canvas = canvas;
  35. this._styles = styles;
  36. this._clonedMarkers = {};
  37. var self = this;
  38. eventBus.on('drag.cleanup', function() {
  39. forEach(self._clonedMarkers, function(clonedMarker) {
  40. svgRemove(clonedMarker);
  41. });
  42. self._clonedMarkers = {};
  43. });
  44. }
  45. PreviewSupport.$inject = [
  46. 'elementRegistry',
  47. 'eventBus',
  48. 'canvas',
  49. 'styles'
  50. ];
  51. /**
  52. * Returns graphics of an element.
  53. *
  54. * @param {djs.model.Base} element
  55. *
  56. * @return {SVGElement}
  57. */
  58. PreviewSupport.prototype.getGfx = function(element) {
  59. return this._elementRegistry.getGraphics(element);
  60. };
  61. /**
  62. * Adds a move preview of a given shape to a given svg group.
  63. *
  64. * @param {djs.model.Base} element
  65. * @param {SVGElement} group
  66. * @param {SVGElement} [gfx]
  67. *
  68. * @return {SVGElement} dragger
  69. */
  70. PreviewSupport.prototype.addDragger = function(element, group, gfx) {
  71. gfx = gfx || this.getGfx(element);
  72. var dragger = svgClone(gfx);
  73. var bbox = gfx.getBoundingClientRect();
  74. this._cloneMarkers(getVisual(dragger));
  75. svgAttr(dragger, this._styles.cls('djs-dragger', [], {
  76. x: bbox.top,
  77. y: bbox.left
  78. }));
  79. svgAppend(group, dragger);
  80. return dragger;
  81. };
  82. /**
  83. * Adds a resize preview of a given shape to a given svg group.
  84. *
  85. * @param {djs.model.Base} element
  86. * @param {SVGElement} group
  87. *
  88. * @return {SVGElement} frame
  89. */
  90. PreviewSupport.prototype.addFrame = function(shape, group) {
  91. var frame = svgCreate('rect', {
  92. class: 'djs-resize-overlay',
  93. width: shape.width,
  94. height: shape.height,
  95. x: shape.x,
  96. y: shape.y
  97. });
  98. svgAppend(group, frame);
  99. return frame;
  100. };
  101. /**
  102. * Clone all markers referenced by a node and its child nodes.
  103. *
  104. * @param {SVGElement} gfx
  105. */
  106. PreviewSupport.prototype._cloneMarkers = function(gfx) {
  107. var self = this;
  108. if (gfx.childNodes) {
  109. // TODO: use forEach once we drop PhantomJS
  110. for (var i = 0; i < gfx.childNodes.length; i++) {
  111. // recursively clone markers of child nodes
  112. self._cloneMarkers(gfx.childNodes[ i ]);
  113. }
  114. }
  115. if (!canHaveMarker(gfx)) {
  116. return;
  117. }
  118. MARKER_TYPES.forEach(function(markerType) {
  119. if (svgAttr(gfx, markerType)) {
  120. var marker = getMarker(gfx, markerType, self._canvas.getContainer());
  121. self._cloneMarker(gfx, marker, markerType);
  122. }
  123. });
  124. };
  125. /**
  126. * Clone marker referenced by an element.
  127. *
  128. * @param {SVGElement} gfx
  129. * @param {SVGElement} marker
  130. * @param {string} markerType
  131. */
  132. PreviewSupport.prototype._cloneMarker = function(gfx, marker, markerType) {
  133. var markerId = marker.id;
  134. var clonedMarker = this._clonedMarkers[ markerId ];
  135. if (!clonedMarker) {
  136. clonedMarker = svgClone(marker);
  137. var clonedMarkerId = markerId + '-clone';
  138. clonedMarker.id = clonedMarkerId;
  139. svgClasses(clonedMarker)
  140. .add('djs-dragger')
  141. .add('djs-dragger-marker');
  142. this._clonedMarkers[ markerId ] = clonedMarker;
  143. var defs = domQuery('defs', this._canvas._svg);
  144. if (!defs) {
  145. defs = svgCreate('defs');
  146. svgAppend(this._canvas._svg, defs);
  147. }
  148. svgAppend(defs, clonedMarker);
  149. }
  150. var reference = idToReference(this._clonedMarkers[ markerId ].id);
  151. svgAttr(gfx, markerType, reference);
  152. };
  153. // helpers //////////
  154. /**
  155. * Get marker of given type referenced by node.
  156. *
  157. * @param {Node} node
  158. * @param {string} markerType
  159. * @param {Node} [parentNode]
  160. *
  161. * @param {Node}
  162. */
  163. function getMarker(node, markerType, parentNode) {
  164. var id = referenceToId(svgAttr(node, markerType));
  165. return domQuery('marker#' + id, parentNode || document);
  166. }
  167. /**
  168. * Get ID of fragment within current document from its functional IRI reference.
  169. * References may use single or double quotes.
  170. *
  171. * @param {string} reference
  172. *
  173. * @returns {string}
  174. */
  175. function referenceToId(reference) {
  176. return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1];
  177. }
  178. /**
  179. * Get functional IRI reference for given ID of fragment within current document.
  180. *
  181. * @param {string} id
  182. *
  183. * @returns {string}
  184. */
  185. function idToReference(id) {
  186. return 'url(#' + id + ')';
  187. }
  188. /**
  189. * Check wether node type can have marker attributes.
  190. *
  191. * @param {Node} node
  192. *
  193. * @returns {boolean}
  194. */
  195. function canHaveMarker(node) {
  196. return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1;
  197. }