diagram-js
Version:
A toolbox for displaying and modifying diagrams on the web
257 lines (207 loc) • 6.53 kB
JavaScript
import {
append as svgAppend,
attr as svgAttr,
classes as svgClasses,
clone as svgClone,
create as svgCreate,
} from 'tiny-svg';
import { query as domQuery } from 'min-dom';
import { getVisual } from '../../util/GraphicsUtil';
import Ids from '../../util/IdGenerator';
/**
* @typedef {import('../../core/Types').ElementLike} Element
* @typedef {import('../../core/Types').ShapeLike} Shape
*
* @typedef {import('../../core/Canvas').default} Canvas
* @typedef {import('../../core/ElementRegistry').default} ElementRegistry
* @typedef {import('../../core/EventBus').default} EventBus
* @typedef {import('../../draw/Styles').default} Styles
*/
const cloneIds = new Ids('ps');
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.
*
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Canvas} canvas
* @param {Styles} styles
*/
export default function PreviewSupport(elementRegistry, eventBus, canvas, styles) {
this._elementRegistry = elementRegistry;
this._canvas = canvas;
this._styles = styles;
}
PreviewSupport.$inject = [
'elementRegistry',
'eventBus',
'canvas',
'styles'
];
// Markers are cleaned up with visuals, keep stub for compatibility
// cf. https://github.com/camunda/camunda-modeler/issues/4307
PreviewSupport.prototype.cleanUp = function() {
console.warn('PreviewSupport#cleanUp is deprecated and will be removed in future versions. You do not need to manually clean up previews anymore. cf. https://github.com/bpmn-io/diagram-js/pull/906');
};
/**
* Returns graphics of an element.
*
* @param {Element} 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 {Element} element The element to be moved.
* @param {SVGElement} group The SVG group to add the preview to.
* @param {SVGElement} [gfx] The optional graphical element of the element.
* @param {string} [className="djs-dragger"] The optional class name to add to the preview.
*
* @return {SVGElement} The preview.
*/
PreviewSupport.prototype.addDragger = function(element, group, gfx, className = 'djs-dragger') {
gfx = gfx || this.getGfx(element);
var dragger = svgClone(gfx);
var bbox = gfx.getBoundingClientRect();
this._cloneMarkers(getVisual(dragger), className);
svgAttr(dragger, this._styles.cls(className, [], {
x: bbox.top,
y: bbox.left
}));
svgAppend(group, dragger);
svgAttr(dragger, 'data-preview-support-element-id', element.id);
return dragger;
};
/**
* Adds a resize preview of a given shape to a given SVG group.
*
* @param {Shape} shape The element to be resized.
* @param {SVGElement} group The SVG group to add the preview to.
*
* @return {SVGElement} The preview.
*/
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);
svgAttr(frame, 'data-preview-support-element-id', shape.id);
return frame;
};
/**
* Clone all markers referenced by a node and its child nodes.
*
* @param {SVGElement} gfx
* @param {string} [className="djs-dragger"]
*/
PreviewSupport.prototype._cloneMarkers = function(gfx, className = 'djs-dragger', rootGfx = gfx) {
var self = this;
if (gfx.childNodes) {
gfx.childNodes.forEach((childNode) => {
self._cloneMarkers(childNode, className, rootGfx);
});
}
if (!canHaveMarker(gfx)) {
return;
}
MARKER_TYPES.forEach(function(markerType) {
if (svgAttr(gfx, markerType)) {
var marker = getMarker(gfx, markerType, self._canvas.getContainer());
// Only clone marker if it is already present on the DOM
marker && self._cloneMarker(rootGfx, gfx, marker, markerType, className);
}
});
};
/**
* Clone marker referenced by an element.
*
* @param {SVGElement} gfx
* @param {SVGElement} marker
* @param {string} markerType
* @param {string} [className="djs-dragger"]
*/
PreviewSupport.prototype._cloneMarker = function(parentGfx, gfx, marker, markerType, className = 'djs-dragger') {
// Add a random suffix to the marker ID in case the same marker is previewed multiple times
var clonedMarkerId = [ marker.id, className, cloneIds.next() ].join('-');
// reuse marker if it was part of original gfx
var copiedMarker = domQuery('marker#' + marker.id, parentGfx);
parentGfx = parentGfx || this._canvas._svg;
var clonedMarker = copiedMarker || svgClone(marker);
clonedMarker.id = clonedMarkerId;
svgClasses(clonedMarker).add(className);
var defs = domQuery(':scope > defs', parentGfx);
if (!defs) {
defs = svgCreate('defs');
svgAppend(parentGfx, defs);
}
svgAppend(defs, clonedMarker);
var reference = idToReference(clonedMarker.id);
svgAttr(gfx, markerType, reference);
};
// helpers //////////
/**
* Get marker of given type referenced by node.
*
* @param {HTMLElement} node
* @param {string} markerType
* @param {HTMLElement} [parentNode]
*
* @param {HTMLElement}
*/
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
*
* @return {string}
*/
function referenceToId(reference) {
return reference.match(/url\(['"]?#([^'"]*)['"]?\)/)[1];
}
/**
* Get functional IRI reference for given ID of fragment within current document.
*
* @param {string} id
*
* @return {string}
*/
function idToReference(id) {
return 'url(#' + id + ')';
}
/**
* Check wether node type can have marker attributes.
*
* @param {HTMLElement} node
*
* @return {boolean}
*/
function canHaveMarker(node) {
return NODES_CAN_HAVE_MARKER.indexOf(node.nodeName) !== -1;
}