import { forEach } from 'min-dash'; import { append as svgAppend, attr as svgAttr, classes as svgClasses, clone as svgClone, create as svgCreate, remove as svgRemove } from 'tiny-svg'; import { query as domQuery } from 'min-dom'; import { getVisual } from '../../util/GraphicsUtil'; var MARKER_TYPES = [ 'marker-start', 'marker-mid', 'marker-end' ]; var NODES_CAN_HAVE_MARKER = [ 'circle', 'ellipse', 'line', 'path', 'polygon', 'polyline', 'path', 'rect' ]; /** * Adds support for previews of moving/resizing elements. */ export default function PreviewSupport(elementRegistry, eventBus, canvas, styles) { this._elementRegistry = elementRegistry; this._canvas = canvas; this._styles = styles; this._clonedMarkers = {}; var self = this; eventBus.on('drag.cleanup', function() { forEach(self._clonedMarkers, function(clonedMarker) { svgRemove(clonedMarker); }); self._clonedMarkers = {}; }); } PreviewSupport.$inject = [ 'elementRegistry', 'eventBus', 'canvas', 'styles' ]; /** * Returns graphics of an element. * * @param {djs.model.Base} element * * @return {SVGElement} */ PreviewSupport.prototype.getGfx = function(element) { return this._elementRegistry.getGraphics(element); }; /** * Adds a move preview of a given shape to a given svg group. * * @param {djs.model.Base} element * @param {SVGElement} group * @param {SVGElement} [gfx] * * @return {SVGElement} dragger */ PreviewSupport.prototype.addDragger = function(element, group, gfx) { gfx = gfx || this.getGfx(element); var dragger = svgClone(gfx); var bbox = gfx.getBoundingClientRect(); this._cloneMarkers(getVisual(dragger)); svgAttr(dragger, this._styles.cls('djs-dragger', [], { x: bbox.top, y: bbox.left })); svgAppend(group, dragger); return dragger; }; /** * Adds a resize preview of a given shape to a given svg group. * * @param {djs.model.Base} element * @param {SVGElement} group * * @return {SVGElement} frame */ PreviewSupport.prototype.addFrame = function(shape, group) { var frame = svgCreate('rect', { class: 'djs-resize-overlay', width: shape.width, height: shape.height, x: shape.x, y: shape.y }); svgAppend(group, frame); return frame; }; /** * Clone all markers referenced by a node and its child nodes. * * @param {SVGElement} gfx */ PreviewSupport.prototype._cloneMarkers = function(gfx) { var self = this; if (gfx.childNodes) { // TODO: use forEach once we drop PhantomJS for (var i = 0; i < gfx.childNodes.length; i++) { // recursively clone markers of child nodes self._cloneMarkers(gfx.childNodes[ i ]); } } if (!canHaveMarker(gfx)) { return; } MARKER_TYPES.forEach(function(markerType) { if (svgAttr(gfx, markerType)) { var marker = getMarker(gfx, markerType, self._canvas.getContainer()); self._cloneMarker(gfx, marker, markerType); } }); }; /** * Clone marker referenced by an element. * * @param {SVGElement} gfx * @param {SVGElement} marker * @param {string} markerType */ PreviewSupport.prototype._cloneMarker = function(gfx, marker, markerType) { var markerId = marker.id; var clonedMarker = this._clonedMarkers[ markerId ]; if (!clonedMarker) { clonedMarker = svgClone(marker); var clonedMarkerId = markerId + '-clone'; clonedMarker.id = clonedMarkerId; svgClasses(clonedMarker) .add('djs-dragger') .add('djs-dragger-marker'); this._clonedMarkers[ markerId ] = clonedMarker; var defs = domQuery('defs', this._canvas._svg); if (!defs) { defs = svgCreate('defs'); svgAppend(this._canvas._svg, defs); } svgAppend(defs, clonedMarker); } var reference = idToReference(this._clonedMarkers[ markerId ].id); svgAttr(gfx, markerType, reference); }; // helpers ////////// /** * Get marker of given type referenced by node. * * @param {Node} node * @param {string} markerType * @param {Node} [parentNode] * * @param {Node} */ function getMarker(node, markerType, parentNode) { var id = referenceToId(svgAttr(node, markerType)); return domQuery('marker#' + id, parentNode || document); } /** * Get ID of fragment within current document from its functional IRI reference. * References may use single or double quotes. * * @param {string} reference * * @returns {string} */ function referenceToId(reference) { return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1]; } /** * Get functional IRI reference for given ID of fragment within current document. * * @param {string} id * * @returns {string} */ function idToReference(id) { return 'url(#' + id + ')'; } /** * Check wether node type can have marker attributes. * * @param {Node} node * * @returns {boolean} */ function canHaveMarker(node) { return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1; }