sprotty
Version:
A next-gen framework for graphical views
647 lines • 28.6 kB
JavaScript
"use strict";
/********************************************************************************
* Copyright (c) 2017-2020 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var MoveCommand_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocationPostprocessor = exports.MoveMouseListener = exports.MorphEdgesAnimation = exports.MoveAnimation = exports.MoveCommand = void 0;
const inversify_1 = require("inversify");
const geometry_1 = require("sprotty-protocol/lib/utils/geometry");
const actions_1 = require("sprotty-protocol/lib/actions");
const animation_1 = require("../../base/animations/animation");
const command_1 = require("../../base/commands/command");
const smodel_1 = require("../../base/model/smodel");
const smodel_utils_1 = require("../../base/model/smodel-utils");
const types_1 = require("../../base/types");
const mouse_tool_1 = require("../../base/views/mouse-tool");
const vnode_utils_1 = require("../../base/views/vnode-utils");
const sgraph_1 = require("../../graph/sgraph");
const commit_model_1 = require("../../model-source/commit-model");
const model_1 = require("../bounds/model");
const create_on_drag_1 = require("../edit/create-on-drag");
const edit_routing_1 = require("../edit/edit-routing");
const reconnect_1 = require("../edit/reconnect");
const model_2 = require("../routing/model");
const routing_1 = require("../routing/routing");
const model_3 = require("../edge-layout/model");
const model_4 = require("../select/model");
const model_5 = require("../viewport/model");
const model_6 = require("./model");
let MoveCommand = MoveCommand_1 = class MoveCommand extends command_1.MergeableCommand {
constructor(action) {
super();
this.action = action;
this.resolvedMoves = new Map;
this.edgeMementi = [];
this.stoppableCommandKey = MoveCommand_1.KIND;
}
// stop the execution of the CompoundAnimation started below
stopExecution() {
if (this.animation) {
this.animation.stop();
this.animation = undefined;
}
}
execute(context) {
const index = context.root.index;
const edge2handleMoves = new Map();
const attachedEdgeShifts = new Map();
this.action.moves.forEach(move => {
const element = index.getById(move.elementId);
if (element instanceof model_2.SRoutingHandleImpl && this.edgeRouterRegistry) {
const edge = element.parent;
if (edge instanceof model_2.SRoutableElementImpl) {
const resolvedMove = this.resolveHandleMove(element, edge, move);
if (resolvedMove) {
let movesByEdge = edge2handleMoves.get(edge);
if (!movesByEdge) {
movesByEdge = [];
edge2handleMoves.set(edge, movesByEdge);
}
movesByEdge.push(resolvedMove);
}
}
}
else if (element && (0, model_6.isLocateable)(element)) {
const resolvedMove = this.resolveElementMove(element, move);
if (resolvedMove) {
this.resolvedMoves.set(resolvedMove.element.id, resolvedMove);
if (this.edgeRouterRegistry) {
const handleEdges = (el) => {
index.getAttachedElements(el).forEach(edge => {
if (edge instanceof model_2.SRoutableElementImpl && !this.isChildOfMovedElements(edge)) {
const existingDelta = attachedEdgeShifts.get(edge);
const newDelta = geometry_1.Point.subtract(resolvedMove.toPosition, resolvedMove.fromPosition);
const delta = (existingDelta)
? geometry_1.Point.linear(existingDelta, newDelta, 0.5)
: newDelta;
attachedEdgeShifts.set(edge, delta);
}
});
};
const handleEdgesForChildren = (el) => {
if ((0, smodel_1.isParent)(el)) {
el.children.forEach(childEl => {
if (childEl instanceof smodel_1.SModelElementImpl) {
if (childEl instanceof model_2.SConnectableElementImpl) {
handleEdges(childEl);
}
handleEdgesForChildren(childEl);
}
});
}
};
handleEdgesForChildren(element);
handleEdges(element);
}
}
}
});
this.doMove(edge2handleMoves, attachedEdgeShifts);
if (this.action.animate) {
this.undoMove();
return (this.animation = new animation_1.CompoundAnimation(context.root, context, [
new MoveAnimation(context.root, this.resolvedMoves, context, false),
new MorphEdgesAnimation(context.root, this.edgeMementi, context, false)
])).start();
}
return context.root;
}
resolveHandleMove(handle, edge, move) {
let fromPosition = move.fromPosition;
if (!fromPosition) {
const router = this.edgeRouterRegistry.get(edge.routerKind);
fromPosition = router.getHandlePosition(edge, router.route(edge), handle);
}
if (fromPosition)
return {
handle,
fromPosition,
toPosition: move.toPosition
};
return undefined;
}
resolveElementMove(element, move) {
const fromPosition = move.fromPosition
|| { x: element.position.x, y: element.position.y };
return {
element,
fromPosition,
toPosition: move.toPosition
};
}
doMove(edge2move, attachedEdgeShifts) {
this.resolvedMoves.forEach(res => {
res.element.position = res.toPosition;
});
// reset edges to state before
edge2move.forEach((moves, edge) => {
const router = this.edgeRouterRegistry.get(edge.routerKind);
const before = router.takeSnapshot(edge);
router.applyHandleMoves(edge, moves);
const after = router.takeSnapshot(edge);
this.edgeMementi.push({ edge, before, after });
});
attachedEdgeShifts.forEach((delta, edge) => {
if (!edge2move.get(edge)) {
const router = this.edgeRouterRegistry.get(edge.routerKind);
const before = router.takeSnapshot(edge);
if (this.isAttachedEdge(edge)) {
// move the entire edge when both source and target are moved
edge.routingPoints = edge.routingPoints.map(rp => geometry_1.Point.add(rp, delta));
}
else {
// add/remove RPs according to the new source/target positions
const updateHandles = (0, model_4.isSelectable)(edge) && edge.selected;
router.cleanupRoutingPoints(edge, edge.routingPoints, updateHandles, this.action.finished);
}
const after = router.takeSnapshot(edge);
this.edgeMementi.push({ edge, before, after });
}
});
}
isChildOfMovedElements(el) {
const parent = el.parent;
if (Array.from(this.resolvedMoves.values()).map(rm => rm.element.id).includes(parent.id)) {
return true;
}
if (parent instanceof smodel_1.SChildElementImpl) {
return this.isChildOfMovedElements(parent);
}
return false;
}
;
// tests if the edge is attached to the moved element directly or to on of their children
isAttachedEdge(edge) {
const source = edge.source;
const target = edge.target;
const checkMovedElementsAndChildren = (sourceOrTarget) => {
return Boolean(this.resolvedMoves.get(sourceOrTarget.id)) || this.isChildOfMovedElements(sourceOrTarget);
};
return Boolean(source &&
target &&
checkMovedElementsAndChildren(source) &&
checkMovedElementsAndChildren(target));
}
undoMove() {
this.resolvedMoves.forEach(res => {
res.element.position = res.fromPosition;
});
this.edgeMementi.forEach(memento => {
const router = this.edgeRouterRegistry.get(memento.edge.routerKind);
router.applySnapshot(memento.edge, memento.before);
});
}
undo(context) {
return new animation_1.CompoundAnimation(context.root, context, [
new MoveAnimation(context.root, this.resolvedMoves, context, true),
new MorphEdgesAnimation(context.root, this.edgeMementi, context, true)
]).start();
}
redo(context) {
return new animation_1.CompoundAnimation(context.root, context, [
new MoveAnimation(context.root, this.resolvedMoves, context, false),
new MorphEdgesAnimation(context.root, this.edgeMementi, context, false)
]).start();
}
merge(other, context) {
if (!this.action.animate && other instanceof MoveCommand_1) {
other.resolvedMoves.forEach((otherMove, otherElementId) => {
const existingMove = this.resolvedMoves.get(otherElementId);
if (existingMove) {
existingMove.toPosition = otherMove.toPosition;
}
else {
this.resolvedMoves.set(otherElementId, otherMove);
}
});
other.edgeMementi.forEach(otherMemento => {
const existingMemento = this.edgeMementi.find(edgeMemento => edgeMemento.edge.id === otherMemento.edge.id);
if (existingMemento) {
existingMemento.after = otherMemento.after;
}
else {
this.edgeMementi.push(otherMemento);
}
});
return true;
}
else if (other instanceof reconnect_1.ReconnectCommand) {
const otherMemento = other.memento;
if (otherMemento) {
const existingMemento = this.edgeMementi.find(edgeMemento => edgeMemento.edge.id === otherMemento.edge.id);
if (existingMemento) {
existingMemento.after = otherMemento.after;
}
else {
this.edgeMementi.push(otherMemento);
}
}
return true;
}
return false;
}
};
exports.MoveCommand = MoveCommand;
MoveCommand.KIND = actions_1.MoveAction.KIND;
__decorate([
(0, inversify_1.inject)(routing_1.EdgeRouterRegistry),
(0, inversify_1.optional)(),
__metadata("design:type", routing_1.EdgeRouterRegistry)
], MoveCommand.prototype, "edgeRouterRegistry", void 0);
exports.MoveCommand = MoveCommand = MoveCommand_1 = __decorate([
(0, inversify_1.injectable)(),
__param(0, (0, inversify_1.inject)(types_1.TYPES.Action)),
__metadata("design:paramtypes", [Object])
], MoveCommand);
class MoveAnimation extends animation_1.Animation {
constructor(model, elementMoves, context, reverse = false) {
super(context);
this.model = model;
this.elementMoves = elementMoves;
this.reverse = reverse;
}
tween(t) {
this.elementMoves.forEach((elementMove) => {
if (this.reverse) {
elementMove.element.position = {
x: (1 - t) * elementMove.toPosition.x + t * elementMove.fromPosition.x,
y: (1 - t) * elementMove.toPosition.y + t * elementMove.fromPosition.y
};
}
else {
elementMove.element.position = {
x: (1 - t) * elementMove.fromPosition.x + t * elementMove.toPosition.x,
y: (1 - t) * elementMove.fromPosition.y + t * elementMove.toPosition.y
};
}
});
return this.model;
}
}
exports.MoveAnimation = MoveAnimation;
class MorphEdgesAnimation extends animation_1.Animation {
constructor(model, originalMementi, context, reverse = false) {
super(context);
this.model = model;
this.reverse = reverse;
this.expanded = [];
originalMementi.forEach(edgeMemento => {
const start = this.reverse ? edgeMemento.after : edgeMemento.before;
const end = this.reverse ? edgeMemento.before : edgeMemento.after;
const startRoute = start.routedPoints;
const endRoute = end.routedPoints;
const maxRoutingPoints = Math.max(startRoute.length, endRoute.length);
this.expanded.push({
startExpandedRoute: this.growToSize(startRoute, maxRoutingPoints),
endExpandedRoute: this.growToSize(endRoute, maxRoutingPoints),
memento: edgeMemento
});
});
}
midPoint(edgeMemento) {
const edge = edgeMemento.edge;
const source = edgeMemento.edge.source;
const target = edgeMemento.edge.target;
return geometry_1.Point.linear((0, smodel_utils_1.translatePoint)(geometry_1.Bounds.center(source.bounds), source.parent, edge.parent), (0, smodel_utils_1.translatePoint)(geometry_1.Bounds.center(target.bounds), target.parent, edge.parent), 0.5);
}
start() {
this.expanded.forEach(morph => {
morph.memento.edge.removeAll(e => e instanceof model_2.SRoutingHandleImpl);
});
return super.start();
}
tween(t) {
if (t === 1) {
this.expanded.forEach(morph => {
const memento = morph.memento;
if (this.reverse)
memento.before.router.applySnapshot(memento.edge, memento.before);
else
memento.after.router.applySnapshot(memento.edge, memento.after);
});
}
else {
this.expanded.forEach(morph => {
const newRoutingPoints = [];
// ignore source and target anchor
for (let i = 1; i < morph.startExpandedRoute.length - 1; ++i)
newRoutingPoints.push(geometry_1.Point.linear(morph.startExpandedRoute[i], morph.endExpandedRoute[i], t));
const closestSnapshot = t < 0.5 ? morph.memento.before : morph.memento.after;
const newSnapshot = Object.assign(Object.assign({}, closestSnapshot), { routingPoints: newRoutingPoints, routingHandles: [] });
closestSnapshot.router.applySnapshot(morph.memento.edge, newSnapshot);
});
}
return this.model;
}
growToSize(route, targetSize) {
const diff = targetSize - route.length;
if (diff <= 0)
return route;
const result = [];
result.push(route[0]);
const deltaDiff = 1 / (diff + 1);
const deltaSmaller = 1 / (route.length - 1);
let nextInsertion = 1;
for (let i = 1; i < route.length; ++i) {
const pos = deltaSmaller * i;
let insertions = 0;
while (pos > (nextInsertion + insertions) * deltaDiff)
++insertions;
nextInsertion += insertions;
for (let j = 0; j < insertions; ++j) {
const p = geometry_1.Point.linear(route[i - 1], route[i], (j + 1) / (insertions + 1));
result.push(p);
}
result.push(route[i]);
}
return result;
}
}
exports.MorphEdgesAnimation = MorphEdgesAnimation;
class MoveMouseListener extends mouse_tool_1.MouseListener {
constructor() {
super(...arguments);
this.hasDragged = false;
this.elementId2startPos = new Map();
}
mouseDown(target, event) {
if (event.button === 0) {
const moveable = (0, smodel_utils_1.findParentByFeature)(target, model_6.isMoveable);
const isRoutingHandle = target instanceof model_2.SRoutingHandleImpl;
if (moveable !== undefined || isRoutingHandle || (0, create_on_drag_1.isCreatingOnDrag)(target)) {
this.startDragPosition = { x: event.pageX, y: event.pageY };
}
else {
this.startDragPosition = undefined;
}
this.hasDragged = false;
if ((0, create_on_drag_1.isCreatingOnDrag)(target)) {
return this.startCreatingOnDrag(target, event);
}
else if (isRoutingHandle) {
return this.activateRoutingHandle(target, event);
}
}
return [];
}
startCreatingOnDrag(target, event) {
const result = [];
result.push(actions_1.SelectAllAction.create({ select: false }));
result.push(target.createAction(model_2.edgeInProgressID));
result.push(actions_1.SelectAction.create({ selectedElementsIDs: [model_2.edgeInProgressID] }));
result.push(edit_routing_1.SwitchEditModeAction.create({ elementsToActivate: [model_2.edgeInProgressID] }));
result.push(actions_1.SelectAction.create({ selectedElementsIDs: [model_2.edgeInProgressTargetHandleID] }));
result.push(edit_routing_1.SwitchEditModeAction.create({ elementsToActivate: [model_2.edgeInProgressTargetHandleID] }));
return result;
}
activateRoutingHandle(target, event) {
return [edit_routing_1.SwitchEditModeAction.create({ elementsToActivate: [target.id] })];
}
mouseMove(target, event) {
const result = [];
if (event.buttons === 0)
this.mouseUp(target, event);
else if (this.startDragPosition) {
if (this.elementId2startPos.size === 0) {
this.collectStartPositions(target.root);
}
this.hasDragged = true;
const moveAction = this.getElementMoves(target, event, false);
if (moveAction)
result.push(moveAction);
}
return result;
}
collectStartPositions(root) {
const selectedElements = new Set(root.index.all()
.filter(element => (0, model_4.isSelectable)(element) && element.selected));
selectedElements.forEach(element => {
if (!this.isChildOfSelected(selectedElements, element)) {
if ((0, model_6.isMoveable)(element))
this.elementId2startPos.set(element.id, element.position);
else if (element instanceof model_2.SRoutingHandleImpl) {
const position = this.getHandlePosition(element);
if (position)
this.elementId2startPos.set(element.id, position);
}
}
});
}
isChildOfSelected(selectedElements, element) {
while (element instanceof smodel_1.SChildElementImpl) {
element = element.parent;
if ((0, model_6.isMoveable)(element) && selectedElements.has(element)) {
return true;
}
}
return false;
}
getElementMoves(target, event, isFinished) {
if (!this.startDragPosition)
return undefined;
const elementMoves = [];
const viewport = (0, smodel_utils_1.findParentByFeature)(target, model_5.isViewport);
const zoom = viewport ? viewport.zoom : 1;
const delta = {
x: (event.pageX - this.startDragPosition.x) / zoom,
y: (event.pageY - this.startDragPosition.y) / zoom
};
this.elementId2startPos.forEach((startPosition, elementId) => {
const element = target.root.index.getById(elementId);
if (element) {
const move = this.createElementMove(element, startPosition, delta, event);
if (move) {
elementMoves.push(move);
}
}
});
if (elementMoves.length > 0)
return actions_1.MoveAction.create(elementMoves, { animate: false, finished: isFinished });
else
return undefined;
}
createElementMove(element, startPosition, delta, event) {
const toPosition = this.snap({
x: startPosition.x + delta.x,
y: startPosition.y + delta.y
}, element, !event.shiftKey);
if ((0, model_6.isMoveable)(element)) {
return {
elementId: element.id,
elementType: element.type,
fromPosition: {
x: element.position.x,
y: element.position.y
},
toPosition
};
}
else if (element instanceof model_2.SRoutingHandleImpl) {
const point = this.getHandlePosition(element);
if (point !== undefined) {
return {
elementId: element.id,
elementType: element.type,
fromPosition: point,
toPosition
};
}
}
return undefined;
}
snap(position, element, isSnap) {
if (isSnap && this.snapper)
return this.snapper.snap(position, element);
else
return position;
}
getHandlePosition(handle) {
if (this.edgeRouterRegistry) {
const parent = handle.parent;
if (!(parent instanceof model_2.SRoutableElementImpl))
return undefined;
const router = this.edgeRouterRegistry.get(parent.routerKind);
const route = router.route(parent);
return router.getHandlePosition(parent, route, handle);
}
return undefined;
}
mouseEnter(target, event) {
if (target instanceof smodel_1.SModelRootImpl && event.buttons === 0 && !this.startDragPosition)
this.mouseUp(target, event);
return [];
}
mouseUp(target, event) {
const result = [];
if (this.startDragPosition) {
const moveAction = this.getElementMoves(target, event, true);
if (moveAction) {
result.push(moveAction);
}
target.root.index.all().forEach(element => {
if (element instanceof model_2.SRoutingHandleImpl) {
result.push(...this.deactivateRoutingHandle(element, target, event));
}
});
}
if (!result.some(a => a.kind === actions_1.ReconnectAction.KIND)) {
const edgeInProgress = target.root.index.getById(model_2.edgeInProgressID);
if (edgeInProgress instanceof smodel_1.SChildElementImpl) {
result.push(this.deleteEdgeInProgress(edgeInProgress));
}
}
if (this.hasDragged) {
result.push(commit_model_1.CommitModelAction.create());
}
this.hasDragged = false;
this.startDragPosition = undefined;
this.elementId2startPos.clear();
return result;
}
deactivateRoutingHandle(element, target, event) {
const result = [];
const parent = element.parent;
if (parent instanceof model_2.SRoutableElementImpl && element.danglingAnchor) {
const handlePos = this.getHandlePosition(element);
if (handlePos) {
const handlePosAbs = (0, smodel_utils_1.translatePoint)(handlePos, element.parent, element.root);
const newEnd = (0, model_1.findChildrenAtPosition)(target.root, handlePosAbs)
.find(e => (0, model_2.isConnectable)(e) && e.canConnect(parent, element.kind));
if (newEnd && this.hasDragged) {
result.push(actions_1.ReconnectAction.create({
routableId: element.parent.id,
newSourceId: element.kind === 'source' ? newEnd.id : parent.sourceId,
newTargetId: element.kind === 'target' ? newEnd.id : parent.targetId
}));
}
}
}
if (element.editMode) {
result.push(edit_routing_1.SwitchEditModeAction.create({ elementsToDeactivate: [element.id] }));
}
return result;
}
deleteEdgeInProgress(edgeInProgress) {
const deleteIds = [];
deleteIds.push(model_2.edgeInProgressID);
edgeInProgress.children.forEach(c => {
if (c instanceof model_2.SRoutingHandleImpl && c.danglingAnchor)
deleteIds.push(c.danglingAnchor.id);
});
return actions_1.DeleteElementAction.create(deleteIds);
}
decorate(vnode, element) {
return vnode;
}
}
exports.MoveMouseListener = MoveMouseListener;
__decorate([
(0, inversify_1.inject)(routing_1.EdgeRouterRegistry),
(0, inversify_1.optional)(),
__metadata("design:type", routing_1.EdgeRouterRegistry)
], MoveMouseListener.prototype, "edgeRouterRegistry", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.ISnapper),
(0, inversify_1.optional)(),
__metadata("design:type", Object)
], MoveMouseListener.prototype, "snapper", void 0);
let LocationPostprocessor = class LocationPostprocessor {
decorate(vnode, element) {
if ((0, model_3.isEdgeLayoutable)(element) && element.parent instanceof sgraph_1.SEdgeImpl) {
// The element is handled by EdgeLayoutDecorator
return vnode;
}
let translate = '';
if ((0, model_6.isLocateable)(element) && element instanceof smodel_1.SChildElementImpl && element.parent !== undefined) {
const pos = element.position;
if (pos.x !== 0 || pos.y !== 0) {
translate = 'translate(' + pos.x + ', ' + pos.y + ')';
}
}
if ((0, model_1.isAlignable)(element)) {
const ali = element.alignment;
if (ali.x !== 0 || ali.y !== 0) {
if (translate.length > 0) {
translate += ' ';
}
translate += 'translate(' + ali.x + ', ' + ali.y + ')';
}
}
if (translate.length > 0) {
(0, vnode_utils_1.setAttr)(vnode, 'transform', translate);
}
return vnode;
}
postUpdate() {
}
};
exports.LocationPostprocessor = LocationPostprocessor;
exports.LocationPostprocessor = LocationPostprocessor = __decorate([
(0, inversify_1.injectable)()
], LocationPostprocessor);
//# sourceMappingURL=move.js.map