sprotty
Version:
A next-gen framework for graphical views
277 lines • 11.5 kB
JavaScript
"use strict";
/********************************************************************************
* Copyright (c) 2017-2018 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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.LocalModelSource = void 0;
const file_saver_1 = require("file-saver");
const inversify_1 = require("inversify");
const actions_1 = require("sprotty-protocol/lib/actions");
const sprotty_protocol_1 = require("sprotty-protocol");
const model_utils_1 = require("sprotty-protocol/lib/utils/model-utils");
const types_1 = require("../base/types");
const smodel_factory_1 = require("../base/model/smodel-factory");
const model_matching_1 = require("../features/update/model-matching");
const model_source_1 = require("./model-source");
/**
* A model source that allows to set and modify the model through function calls.
* This class can be used as a facade over the action-based API of sprotty. It handles
* actions for bounds calculation and model updates.
*/
let LocalModelSource = class LocalModelSource extends model_source_1.ModelSource {
constructor() {
super(...arguments);
this.currentRoot = smodel_factory_1.EMPTY_ROOT;
}
get model() {
return this.currentRoot;
}
set model(root) {
this.setModel(root);
}
initialize(registry) {
super.initialize(registry);
// Register actions to be handled by the `handle` method
registry.register(actions_1.ComputedBoundsAction.KIND, this);
registry.register(actions_1.RequestPopupModelAction.KIND, this);
}
/**
* Set the model without incremental update.
*/
setModel(newRoot) {
this.currentRoot = newRoot;
return this.submitModel(newRoot, false);
}
commitModel(newRoot) {
const previousRoot = this.currentRoot;
this.currentRoot = newRoot;
return previousRoot;
}
/**
* Apply an incremental update to the model with an animation showing the transition to
* the new state. If `newRoot` is undefined, the current root is submitted; in that case
* it is assumed that it has been modified before.
*/
updateModel(newRoot) {
if (newRoot === undefined) {
return this.submitModel(this.currentRoot, true);
}
else {
this.currentRoot = newRoot;
return this.submitModel(newRoot, true);
}
}
/**
* Get the current selection from the model.
*/
async getSelection() {
const res = await this.actionDispatcher.request(sprotty_protocol_1.GetSelectionAction.create());
const result = [];
this.gatherSelectedElements(this.currentRoot, new Set(res.selectedElementsIDs), result);
return result;
}
gatherSelectedElements(element, selected, result) {
if (selected.has(element.id)) {
result.push(element);
}
if (element.children) {
for (const child of element.children) {
this.gatherSelectedElements(child, selected, result);
}
}
}
/**
* Get the current viewport from the model.
*/
async getViewport() {
const res = await this.actionDispatcher.request(sprotty_protocol_1.GetViewportAction.create());
return {
scroll: res.viewport.scroll,
zoom: res.viewport.zoom,
canvasBounds: res.canvasBounds
};
}
/**
* If client layout is active, run a `RequestBoundsAction` and wait for the resulting
* `ComputedBoundsAction`, otherwise call `doSubmitModel(…)` directly.
*/
async submitModel(newRoot, update, cause) {
if (this.viewerOptions.needsClientLayout) {
const computedBounds = await this.actionDispatcher.request(actions_1.RequestBoundsAction.create(newRoot));
const index = this.computedBoundsApplicator.apply(this.currentRoot, computedBounds);
await this.doSubmitModel(newRoot, update, cause, index);
}
else {
await this.doSubmitModel(newRoot, update, cause);
}
}
/**
* Submit the given model with an `UpdateModelAction` or a `SetModelAction` depending on the
* `update` argument. If available, the model layout engine is invoked first.
*/
async doSubmitModel(newRoot, update, cause, index) {
if (this.layoutEngine !== undefined) {
try {
const layoutResult = this.layoutEngine.layout(newRoot, index);
if (layoutResult instanceof Promise)
newRoot = await layoutResult;
else if (layoutResult !== undefined)
newRoot = layoutResult;
}
catch (error) {
this.logger.error(this, error.toString(), error.stack);
}
}
const lastSubmittedModelType = this.lastSubmittedModelType;
this.lastSubmittedModelType = newRoot.type;
if (cause && cause.kind === actions_1.RequestModelAction.KIND && cause.requestId) {
const request = cause;
await this.actionDispatcher.dispatch(actions_1.SetModelAction.create(newRoot, request.requestId));
}
else if (update && newRoot.type === lastSubmittedModelType) {
const input = Array.isArray(update) ? update : newRoot;
await this.actionDispatcher.dispatch(actions_1.UpdateModelAction.create(input, { animate: true, cause }));
}
else {
await this.actionDispatcher.dispatch(actions_1.SetModelAction.create(newRoot));
}
}
/**
* Modify the current model with an array of matches.
*/
applyMatches(matches) {
const root = this.currentRoot;
(0, model_matching_1.applyMatches)(root, matches);
return this.submitModel(root, matches);
}
/**
* Modify the current model by adding new elements.
*/
addElements(elements) {
const matches = [];
for (const e of elements) {
const anye = e;
if (typeof anye.element === 'object' && typeof anye.parentId === 'string') {
matches.push({
right: anye.element,
rightParentId: anye.parentId
});
}
else if (typeof anye.id === 'string') {
matches.push({
right: anye,
rightParentId: this.currentRoot.id
});
}
}
return this.applyMatches(matches);
}
/**
* Modify the current model by removing elements.
*/
removeElements(elements) {
const matches = [];
const index = new model_utils_1.SModelIndex();
index.add(this.currentRoot);
for (const e of elements) {
const anye = e;
if (anye.elementId !== undefined && anye.parentId !== undefined) {
const element = index.getById(anye.elementId);
if (element !== undefined) {
matches.push({
left: element,
leftParentId: anye.parentId
});
}
}
else {
const element = index.getById(anye);
if (element !== undefined) {
matches.push({
left: element,
leftParentId: this.currentRoot.id
});
}
}
}
return this.applyMatches(matches);
}
// ----- Methods for handling incoming actions ----------------------------
handle(action) {
switch (action.kind) {
case actions_1.RequestModelAction.KIND:
this.handleRequestModel(action);
break;
case actions_1.ComputedBoundsAction.KIND:
this.computedBoundsApplicator.apply(this.currentRoot, action);
break;
case actions_1.RequestPopupModelAction.KIND:
this.handleRequestPopupModel(action);
break;
case actions_1.ExportSvgAction.KIND:
this.handleExportSvgAction(action);
break;
}
}
handleRequestModel(action) {
this.submitModel(this.currentRoot, false, action);
}
handleRequestPopupModel(action) {
if (this.popupModelProvider !== undefined) {
const element = (0, model_utils_1.findElement)(this.currentRoot, action.elementId);
const popupRoot = this.popupModelProvider.getPopupModel(action, element);
if (popupRoot !== undefined) {
popupRoot.canvasBounds = action.bounds;
this.actionDispatcher.dispatch(actions_1.SetPopupModelAction.create(popupRoot, action.requestId));
}
}
}
handleExportSvgAction(action) {
const blob = new Blob([action.svg], { type: 'text/plain;charset=utf-8' });
(0, file_saver_1.saveAs)(blob, 'diagram.svg');
}
};
exports.LocalModelSource = LocalModelSource;
__decorate([
(0, inversify_1.inject)(types_1.TYPES.ILogger),
__metadata("design:type", Object)
], LocalModelSource.prototype, "logger", void 0);
__decorate([
(0, inversify_1.inject)(model_source_1.ComputedBoundsApplicator),
__metadata("design:type", model_source_1.ComputedBoundsApplicator)
], LocalModelSource.prototype, "computedBoundsApplicator", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.IPopupModelProvider),
(0, inversify_1.optional)(),
__metadata("design:type", Object)
], LocalModelSource.prototype, "popupModelProvider", void 0);
__decorate([
(0, inversify_1.inject)(types_1.TYPES.IModelLayoutEngine),
(0, inversify_1.optional)(),
__metadata("design:type", Object)
], LocalModelSource.prototype, "layoutEngine", void 0);
exports.LocalModelSource = LocalModelSource = __decorate([
(0, inversify_1.injectable)()
], LocalModelSource);
//# sourceMappingURL=local-model-source.js.map