@eclipse-emfcloud/model-manager
Version:
Command-based model editing with undo/redo.
216 lines • 7.53 kB
JavaScript
"use strict";
// *****************************************************************************
// Copyright (C) 2023-2024 STMicroelectronics.
//
// 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: MIT License which is
// available at https://opensource.org/licenses/MIT.
//
// SPDX-License-Identifier: EPL-2.0 OR MIT
// *****************************************************************************
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreModelManagerImpl = void 0;
const lodash_1 = require("lodash");
const core_1 = require("../core");
const core_command_stack_impl_1 = require("./core-command-stack-impl");
class CoreModelManagerImpl {
constructor(commandStackFactory = (workingCopyManager) => new core_command_stack_impl_1.CoreCommandStackImpl(workingCopyManager)) {
this._subscriptions = new Map();
this._allSubscriptions = new Array();
this._modelStore = new ModelStore(this.changeOnCommandStack.bind(this));
this._commandStack = commandStackFactory(this._modelStore);
}
/**
* The callback provided to the CoreCommandStackImpl to be able to notify
* subscribers of any command executed (execute, executeAndAppend, undo, redo )
*
* @param deltas the command result sent by 'execute, executeAndAppend, undo or redo' done in CoreCommandStackImpl
*/
changeOnCommandStack(deltas) {
(0, core_1.groupByModelId)(deltas).forEach((operations, modelId) => {
const model = this.getModel(modelId);
if (model === undefined) {
return;
}
for (const subscriber of this.subscriptions(modelId)) {
if (subscriber.onModelChanged) {
subscriber.onModelChanged(modelId, model, operations);
}
}
});
}
getModelId(model) {
return this._modelStore.getModelId(model);
}
getModel(modelId) {
return this._modelStore.getModel(modelId);
}
setModel(modelId, model) {
this._modelStore.addModel(modelId, model);
}
getModelIds() {
return this._modelStore.getModelIds();
}
removeModel(modelId) {
return this._modelStore.removeModel(modelId);
}
getCommandStack() {
return this._commandStack;
}
addSubscription(modelId, subscription) {
const existingSubscriptions = this._subscriptions.get(modelId) || [];
existingSubscriptions.push(subscription);
this._subscriptions.set(modelId, existingSubscriptions);
}
addAllSubscription(subscription) {
this._allSubscriptions.push(subscription);
}
deleteSubscription(modelId, subscription) {
const existingSubscriptions = this._subscriptions.get(modelId) || [];
const index = existingSubscriptions.indexOf(subscription);
if (index > -1) {
existingSubscriptions.splice(index, 1);
if (existingSubscriptions.length > 0) {
this._subscriptions.set(modelId, existingSubscriptions);
}
else {
this._subscriptions.delete(modelId);
}
}
}
deleteAllSubscription(subscription) {
const index = this._allSubscriptions.indexOf(subscription);
if (index > -1) {
this._allSubscriptions.splice(index, 1);
}
}
subscribe(modelId) {
let subscription;
if (modelId) {
subscription = {
close: () => this.deleteSubscription(modelId, subscription),
};
this.addSubscription(modelId, subscription);
}
else {
subscription = {
close: () => this.deleteAllSubscription(subscription),
};
this.addAllSubscription(subscription);
}
return subscription;
}
/**
* Obtain an iterable over all subscriptions pertaining to the given
* model ID.
*
* @param modelId a model ID for which to get subscriptions
* @returns an iterator over all subscriptions specific to the `modelId` (if any)
* followed by the general subscriptions
*/
*subscriptions(modelId) {
const specific = this._subscriptions.get(modelId);
if (specific) {
for (const next of specific) {
yield next;
}
}
for (const next of this._allSubscriptions) {
yield next;
}
}
}
exports.CoreModelManagerImpl = CoreModelManagerImpl;
class ModelStore {
constructor(notify) {
this.notify = notify;
/** Model storage. */
this._models = new Map();
/** Last known ID for each model. */
this._modelIds = new WeakMap();
this._workingCopies = new Map();
this._open = new Map();
}
getModel(modelId) {
return this._models.get(modelId);
}
getModelId(model) {
return this._modelIds.get(model);
}
getModelIds() {
return Array.from(this._models.keys());
}
addModel(modelId, model) {
if (this._models.get(modelId) !== undefined) {
throw new Error(`Model ${modelId} is already registered.`);
}
this._models.set(modelId, model);
this._modelIds.set(model, modelId);
}
removeModel(modelId) {
const removedModel = this.getModel(modelId);
if (removedModel !== undefined) {
this._models.delete(modelId);
// Let the generation info expire naturally with GC
}
return removedModel;
}
//
// WorkingCopyManager protocol
//
open(modelIds) {
if (this.isOpen(modelIds)) {
throw new Error('Already open.');
}
modelIds.forEach((modelId) => {
this._open.set(modelId, true);
this._workingCopies.delete(modelId);
});
}
isOpen(modelIds) {
return modelIds.some((modelId) => this._open.get(modelId));
}
getWorkingCopy(modelId) {
if (!this.isOpen([modelId])) {
throw new Error('Not open.');
}
let result = this._workingCopies.get(modelId);
if (result === undefined) {
const model = this.getModel(modelId);
if (model !== undefined) {
result = (0, lodash_1.cloneDeep)(model);
this._workingCopies.set(modelId, result);
}
}
return result;
}
commit(result, modelIds) {
try {
for (const modelId of modelIds) {
const workingCopy = this._workingCopies.get(modelId);
if (workingCopy !== undefined) {
this._models.set(modelId, workingCopy);
}
this._workingCopies.delete(modelId);
}
this.notify(result);
}
finally {
for (const k of modelIds) {
this._open.delete(k);
}
}
}
cancel(modelIds) {
modelIds.forEach((modelId) => {
this._workingCopies.delete(modelId);
this._open.delete(modelId);
});
}
}
//# sourceMappingURL=core-model-manager-impl.js.map