UNPKG

sprotty

Version:

A next-gen framework for graphical views

647 lines 28.6 kB
"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