UNPKG

golfnet-editor

Version:
311 lines (223 loc) 6.6 kB
import { assign, forEach } from 'min-dash'; import inherits from 'inherits'; import { remove as collectionRemove } from 'diagram-js/lib/util/Collections'; import { Label } from 'diagram-js/lib/model'; import { is } from '../../util/ModelUtil'; import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; /** * A handler responsible for updating the underlying Postit XML + DI * once changes on the diagram happen */ export default function PostitUpdater( eventBus, postitFactory, connectionDocking, translate) { CommandInterceptor.call(this, eventBus); this._postitFactory = postitFactory; this._translate = translate; var self = this; // Postit + DI update ////////////////////// // update parent function updateParent(e) { var context = e.context; self.updateParent(context.shape || context.connection, context.oldParent); } function reverseUpdateParent(e) { var context = e.context; var element = context.shape || context.connection, // oldParent is the (old) new parent, because we are undoing oldParent = context.parent || context.newParent; self.updateParent(element, oldParent); } this.executed([ 'shape.move', 'shape.create', 'shape.delete' ], ifPostit(updateParent)); this.reverted([ 'shape.move', 'shape.create', 'shape.delete' ], ifPostit(reverseUpdateParent)); /* * ## Updating Parent * * When morphing a root element * make sure that both the *semantic* and *di* parent of each element * is updated. * */ function updateRoot(event) { var context = event.context, oldRoot = context.oldRoot, children = oldRoot.children; forEach(children, function(child) { if (is(child, 'postit:BoardElement')) { self.updateParent(child); } }); } this.executed([ 'canvas.updateRoot' ], updateRoot); this.reverted([ 'canvas.updateRoot' ], updateRoot); // update bounds function updateBounds(e) { var shape = e.context.shape; if (!is(shape, 'postit:BoardElement')) { return; } self.updateBounds(shape); } this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifPostit(function(event) { // exclude labels because they're handled separately during shape.changed if (event.context.shape.type === 'label') { return; } updateBounds(event); })); this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifPostit(function(event) { // exclude labels because they're handled separately during shape.changed if (event.context.shape.type === 'label') { return; } updateBounds(event); })); // Handle labels separately. This is necessary, because the label bounds have to be updated // every time its shape changes, not only on move, create and resize. eventBus.on('shape.changed', function(event) { if (event.element.type === 'label') { updateBounds({ context: { shape: event.element } }); } }); } inherits(PostitUpdater, CommandInterceptor); PostitUpdater.$inject = [ 'eventBus', 'postitFactory', 'connectionDocking', 'translate' ]; // implementation ////////////////////// PostitUpdater.prototype.updateAttachment = function(context) { var shape = context.shape, businessObject = shape.businessObject, host = shape.host; businessObject.attachedToRef = host && host.businessObject; }; PostitUpdater.prototype.updateParent = function(element, oldParent) { // do not update label parent if (element instanceof Label) { return; } var parentShape = element.parent; var businessObject = element.businessObject, parentBusinessObject = parentShape && parentShape.businessObject, parentDi = parentBusinessObject && parentBusinessObject.di; this.updateSemanticParent(businessObject, parentBusinessObject); this.updateDiParent(businessObject.di, parentDi); }; PostitUpdater.prototype.updateBounds = function(shape) { var di = shape.businessObject.di; var target = (shape instanceof Label) ? this._getLabel(di) : di; var bounds = target.bounds; if (!bounds) { bounds = this._postitFactory.createDiBounds(); target.set('bounds', bounds); } assign(bounds, { x: shape.x, y: shape.y, width: shape.width, height: shape.height }); }; PostitUpdater.prototype.updateDiParent = function(di, parentDi) { if (parentDi && !is(parentDi, 'postitDi:PostitPlane')) { parentDi = parentDi.$parent; } if (di.$parent === parentDi) { return; } var planeElements = (parentDi || di.$parent).get('planeElement'); if (parentDi) { planeElements.push(di); di.$parent = parentDi; } else { collectionRemove(planeElements, di); di.$parent = null; } }; PostitUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) { var containment, translate = this._translate; if (businessObject.$parent === newParent) { return; } if (is(businessObject, 'postit:BoardElement')) { containment = 'boardElements'; } if (!containment) { throw new Error(translate( 'no parent for {element} in {parent}', { element: businessObject.id, parent: newParent.id } )); } var children; if (businessObject.$parent) { // remove from old parent children = businessObject.$parent.get(containment); collectionRemove(children, businessObject); } if (!newParent) { businessObject.$parent = null; } else { // add to new parent children = newParent.get(containment); children.push(businessObject); businessObject.$parent = newParent; } if (visualParent) { var diChildren = visualParent.get(containment); collectionRemove(children, businessObject); if (newParent) { if (!diChildren) { diChildren = []; newParent.set(containment, diChildren); } diChildren.push(businessObject); } } }; // helpers ////////////////////// PostitUpdater.prototype._getLabel = function(di) { if (!di.label) { di.label = this._postitFactory.createDiLabel(); } return di.label; }; /** * Make sure the event listener is only called * if the touched element is a postit element. * * @param {Function} fn * @return {Function} guarded function */ function ifPostit(fn) { return function(event) { var context = event.context, element = context.shape || context.connection; if (is(element, 'postit:BoardElement')) { fn(event); } }; }