golfnet-editor
Version:
311 lines (223 loc) • 6.6 kB
JavaScript
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);
}
};
}