import { forEach, reduce } from 'min-dash'; import { getChildren, getVisual } from '../util/GraphicsUtil'; import { translate } from '../util/SvgTransformUtil'; import { clear as domClear } from 'min-dom'; import { append as svgAppend, attr as svgAttr, classes as svgClasses, create as svgCreate, remove as svgRemove } from 'tiny-svg'; import { isFrameElement } from '../util/Elements'; /** * A factory that creates graphical elements * * @param {EventBus} eventBus * @param {ElementRegistry} elementRegistry */ export default function GraphicsFactory(eventBus, elementRegistry) { this._eventBus = eventBus; this._elementRegistry = elementRegistry; } GraphicsFactory.$inject = [ 'eventBus' , 'elementRegistry' ]; GraphicsFactory.prototype._getChildrenContainer = function(element) { var gfx = this._elementRegistry.getGraphics(element); var childrenGfx; // root element if (!element.parent) { childrenGfx = gfx; } else { childrenGfx = getChildren(gfx); if (!childrenGfx) { childrenGfx = svgCreate('g'); svgClasses(childrenGfx).add('djs-children'); svgAppend(gfx.parentNode, childrenGfx); } } return childrenGfx; }; /** * Clears the graphical representation of the element and returns the * cleared visual (the element). */ GraphicsFactory.prototype._clear = function(gfx) { var visual = getVisual(gfx); domClear(visual); return visual; }; /** * Creates a gfx container for shapes and connections * * The layout is as follows: * * * * * * * * * * * * * * @param {string} type the type of the element, i.e. shape | connection * @param {SVGElement} [childrenGfx] * @param {number} [parentIndex] position to create container in parent * @param {boolean} [isFrame] is frame element * * @return {SVGElement} */ GraphicsFactory.prototype._createContainer = function( type, childrenGfx, parentIndex, isFrame ) { var outerGfx = svgCreate('g'); svgClasses(outerGfx).add('djs-group'); // insert node at position if (typeof parentIndex !== 'undefined') { prependTo(outerGfx, childrenGfx, childrenGfx.childNodes[parentIndex]); } else { svgAppend(childrenGfx, outerGfx); } var gfx = svgCreate('g'); svgClasses(gfx).add('djs-element'); svgClasses(gfx).add('djs-' + type); if (isFrame) { svgClasses(gfx).add('djs-frame'); } svgAppend(outerGfx, gfx); // create visual var visual = svgCreate('g'); svgClasses(visual).add('djs-visual'); svgAppend(gfx, visual); return gfx; }; GraphicsFactory.prototype.create = function(type, element, parentIndex) { var childrenGfx = this._getChildrenContainer(element.parent); return this._createContainer(type, childrenGfx, parentIndex, isFrameElement(element)); }; GraphicsFactory.prototype.updateContainments = function(elements) { var self = this, elementRegistry = this._elementRegistry, parents; parents = reduce(elements, function(map, e) { if (e.parent) { map[e.parent.id] = e.parent; } return map; }, {}); // update all parents of changed and reorganized their children // in the correct order (as indicated in our model) forEach(parents, function(parent) { var children = parent.children; if (!children) { return; } var childrenGfx = self._getChildrenContainer(parent); forEach(children.slice().reverse(), function(child) { var childGfx = elementRegistry.getGraphics(child); prependTo(childGfx.parentNode, childrenGfx); }); }); }; GraphicsFactory.prototype.drawShape = function(visual, element) { var eventBus = this._eventBus; return eventBus.fire('render.shape', { gfx: visual, element: element }); }; GraphicsFactory.prototype.getShapePath = function(element) { var eventBus = this._eventBus; return eventBus.fire('render.getShapePath', element); }; GraphicsFactory.prototype.drawConnection = function(visual, element) { var eventBus = this._eventBus; return eventBus.fire('render.connection', { gfx: visual, element: element }); }; GraphicsFactory.prototype.getConnectionPath = function(waypoints) { var eventBus = this._eventBus; return eventBus.fire('render.getConnectionPath', waypoints); }; GraphicsFactory.prototype.update = function(type, element, gfx) { // do NOT update root element if (!element.parent) { return; } var visual = this._clear(gfx); // redraw if (type === 'shape') { this.drawShape(visual, element); // update positioning translate(gfx, element.x, element.y); } else if (type === 'connection') { this.drawConnection(visual, element); } else { throw new Error('unknown type: ' + type); } if (element.hidden) { svgAttr(gfx, 'display', 'none'); } else { svgAttr(gfx, 'display', 'block'); } }; GraphicsFactory.prototype.remove = function(element) { var gfx = this._elementRegistry.getGraphics(element); // remove svgRemove(gfx.parentNode); }; // helpers ////////// function prependTo(newNode, parentNode, siblingNode) { var node = siblingNode || parentNode.firstChild; // do not prepend node to itself to prevent IE from crashing // https://github.com/bpmn-io/bpmn-js/issues/746 if (newNode === node) { return; } parentNode.insertBefore(newNode, node); }