UNPKG

sprotty

Version:

A next-gen framework for graphical views

277 lines 11.5 kB
"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