diff --git a/examples/circlegraph/src/di.config.ts b/examples/circlegraph/src/di.config.ts index ced7dcbc..0fac292d 100644 --- a/examples/circlegraph/src/di.config.ts +++ b/examples/circlegraph/src/di.config.ts @@ -19,7 +19,7 @@ import { defaultModule, TYPES, configureViewerOptions, SGraphView, PolylineEdgeView, ConsoleLogger, LogLevel, WebSocketDiagramServer, boundsModule, moveModule, selectModule, undoRedoModule, viewportModule, LocalModelSource, exportModule, CircularNode, configureModelElement, SGraph, SEdge, updateModule, - graphModule, routingModule + graphModule, routingModule, modelSourceModule } from "../../../src"; import { CircleNodeView } from "./views"; @@ -44,6 +44,6 @@ export default (useWebsocket: boolean) => { const container = new Container(); container.load(defaultModule, selectModule, moveModule, boundsModule, undoRedoModule, viewportModule, - exportModule, updateModule, graphModule, routingModule, circlegraphModule); + exportModule, updateModule, graphModule, routingModule, modelSourceModule, circlegraphModule); return container; }; diff --git a/examples/classdiagram/src/di.config.ts b/examples/classdiagram/src/di.config.ts index 94d1d7c2..54066fdc 100644 --- a/examples/classdiagram/src/di.config.ts +++ b/examples/classdiagram/src/di.config.ts @@ -21,7 +21,7 @@ import { viewportModule, hoverModule, LocalModelSource, HtmlRootView, PreRenderedView, exportModule, expandModule, fadeModule, ExpandButtonView, buttonModule, edgeEditModule, SRoutingHandleView, PreRenderedElement, HtmlRoot, SGraph, configureModelElement, SLabel, SCompartment, SEdge, SButton, SRoutingHandle, - edgeLayoutModule, updateModule, graphModule, routingModule + edgeLayoutModule, updateModule, graphModule, routingModule, modelSourceModule } from "../../../src"; import { ClassNodeView, IconView} from "./views"; import { PopupModelProvider } from "./popup"; @@ -64,6 +64,7 @@ export default (useWebsocket: boolean, containerId: string) => { const container = new Container(); container.load(defaultModule, selectModule, moveModule, boundsModule, undoRedoModule, viewportModule, fadeModule, hoverModule, exportModule, expandModule, buttonModule, - updateModule, graphModule, routingModule, edgeEditModule, edgeLayoutModule, classDiagramModule); + updateModule, graphModule, routingModule, edgeEditModule, edgeLayoutModule, + modelSourceModule, classDiagramModule); return container; }; diff --git a/examples/mindmap/src/di.config.ts b/examples/mindmap/src/di.config.ts index df184412..ebbeb555 100644 --- a/examples/mindmap/src/di.config.ts +++ b/examples/mindmap/src/di.config.ts @@ -20,7 +20,7 @@ import { LogLevel, WebSocketDiagramServer, boundsModule, moveModule, selectModule, undoRedoModule, viewportModule, hoverModule, LocalModelSource, HtmlRootView, PreRenderedView, exportModule, expandModule, fadeModule, buttonModule, PreRenderedElement, SNode, SLabel, HtmlRoot, - configureModelElement, configureCommand, graphModule, updateModule, routingModule + configureModelElement, configureCommand, graphModule, updateModule, routingModule, modelSourceModule } from "../../../src"; import { MindmapNodeView, PopupButtonView } from "./views"; import { PopupButtonMouseListener, AddElementCommand, PopupModelProvider } from "./popup"; @@ -56,6 +56,6 @@ export default (useWebsocket: boolean, containerId: string) => { const container = new Container(); container.load(defaultModule, selectModule, moveModule, boundsModule, undoRedoModule, viewportModule, fadeModule, hoverModule, exportModule, expandModule, buttonModule, - updateModule, graphModule, routingModule, mindmapModule); + updateModule, graphModule, routingModule, modelSourceModule, mindmapModule); return container; }; diff --git a/examples/multicore/src/di.config.ts b/examples/multicore/src/di.config.ts index 555669e4..c8cbe828 100644 --- a/examples/multicore/src/di.config.ts +++ b/examples/multicore/src/di.config.ts @@ -19,7 +19,7 @@ import { SCompartmentView, SLabelView, defaultModule, TYPES, configureViewerOptions, ConsoleLogger, LogLevel, WebSocketDiagramServer, boundsModule, selectModule, viewportModule, moveModule, fadeModule, hoverModule, LocalModelSource, HtmlRootView, PreRenderedView, - exportModule, SvgExporter, configureView, graphModule, updateModule + exportModule, SvgExporter, configureView, graphModule, updateModule, modelSourceModule } from '../../../src'; import { ChipModelFactory } from "./chipmodel-factory"; import { ProcessorView, CoreView, CrossbarView, ChannelView, SimpleCoreView } from "./views"; @@ -70,7 +70,7 @@ export default (useWebsocket: boolean) => { const container = new Container(); container.load(defaultModule, boundsModule, selectModule, moveModule, viewportModule, fadeModule, - exportModule, hoverModule, graphModule, updateModule, multicoreModule); + exportModule, hoverModule, graphModule, updateModule, modelSourceModule, multicoreModule); return container; }; diff --git a/examples/svg/src/di.config.ts b/examples/svg/src/di.config.ts index 5779268e..369ca494 100644 --- a/examples/svg/src/di.config.ts +++ b/examples/svg/src/di.config.ts @@ -18,7 +18,8 @@ import { Container, ContainerModule } from "inversify"; import { defaultModule, TYPES, ConsoleLogger, LogLevel, boundsModule, moveModule, selectModule, undoRedoModule, viewportModule, hoverModule, LocalModelSource, PreRenderedView, SvgViewportView, - exportModule, ViewportRootElement, ShapedPreRenderedElement, configureModelElement + exportModule, ViewportRootElement, ShapedPreRenderedElement, configureModelElement, + modelSourceModule } from "../../../src"; export default () => { @@ -35,6 +36,6 @@ export default () => { const container = new Container(); container.load(defaultModule, selectModule, moveModule, boundsModule, undoRedoModule, viewportModule, - hoverModule, exportModule, svgModule); + hoverModule, exportModule, modelSourceModule, svgModule); return container; }; diff --git a/src/features/move/move.ts b/src/features/move/move.ts index 4599b174..ec75e9e0 100644 --- a/src/features/move/move.ts +++ b/src/features/move/move.ts @@ -37,6 +37,7 @@ import { isSelectable } from "../select/model"; import { SelectAction, SelectAllAction } from "../select/select"; import { isViewport } from "../viewport/model"; import { isLocateable, isMoveable, Locateable } from './model'; +import { CommitModelAction } from "../../model-source/commit-model"; export class MoveAction implements Action { kind = MoveCommand.KIND; @@ -497,6 +498,7 @@ export class MoveMouseListener extends MouseListener { result.push(new DeleteElementAction(deleteIds)); } } + result.push(new CommitModelAction()); this.hasDragged = false; this.lastDragPosition = undefined; return result; diff --git a/src/model-source/commit-model.ts b/src/model-source/commit-model.ts new file mode 100644 index 00000000..27ce55b5 --- /dev/null +++ b/src/model-source/commit-model.ts @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2019 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 + ********************************************************************************/ + +import { injectable, inject } from "inversify"; +import { Command, CommandExecutionContext, CommandResult } from "../base/commands/command"; +import { TYPES } from "../base/types"; +import { ModelSource } from "./model-source"; +import { SModelRootSchema } from "../base/model/smodel"; +import { IModelFactory } from "../base/model/smodel-factory"; + +/** + * Commit the current SModel back to the model source. + * + * The SModel (AKA internal model) contains a lot of dirty/transitional state, such + * as intermediate move postions or handles. When a user interaction that spans multiple + * commands finishes, it fires a CommitModelAction to write the final changes back to + * the model source. + */ +export class CommitModelAction { + kind = CommitModelCommand.KIND; +} + +@injectable() +export class CommitModelCommand extends Command { + static KIND = 'commitModel'; + + @inject(TYPES.IModelFactory) modelFactory: IModelFactory; + @inject(TYPES.ModelSource) modelSource: ModelSource; + + originalModel: SModelRootSchema; + newModel: SModelRootSchema; + + constructor(@inject(TYPES.Action) action: CommitModelAction) { + super(); + } + + execute(context: CommandExecutionContext): CommandResult { + this.newModel = this.modelFactory.createSchema(context.root); + this.originalModel = this.modelSource.commitModel(this.newModel); + return context.root; + } + + undo(context: CommandExecutionContext): CommandResult { + this.modelSource.commitModel(this.originalModel); + return context.root; + } + + redo(context: CommandExecutionContext): CommandResult { + this.modelSource.commitModel(this.newModel); + return context.root; + } +} diff --git a/src/model-source/di.config.ts b/src/model-source/di.config.ts index 58f75872..15d3241d 100644 --- a/src/model-source/di.config.ts +++ b/src/model-source/di.config.ts @@ -17,6 +17,8 @@ import { ContainerModule } from "inversify"; import { TYPES } from "../base/types"; import { ModelSource } from "./model-source"; +import { configureCommand } from "../base/commands/command-registration"; +import { CommitModelCommand } from "./commit-model"; /** * This container module does NOT provide any binding for TYPES.ModelSource because that needs to be @@ -31,6 +33,7 @@ const modelSourceModule = new ContainerModule((bind, _unbind, isBound) => { }); }; }); + configureCommand({bind, isBound}, CommitModelCommand); }); export default modelSourceModule; diff --git a/src/model-source/diagram-server.ts b/src/model-source/diagram-server.ts index d73be574..3d2f3528 100644 --- a/src/model-source/diagram-server.ts +++ b/src/model-source/diagram-server.ts @@ -219,4 +219,10 @@ export abstract class DiagramServer extends ModelSource { protected handleServerStateAction(action: ServerStatusAction): boolean { return false; } + + commitModel(newRoot: SModelRootSchema): SModelRootSchema { + const previousRoot = this.currentRoot; + this.currentRoot = newRoot; + return previousRoot; + } } diff --git a/src/model-source/local-model-source.ts b/src/model-source/local-model-source.ts index ee7b0e6a..5a5980c6 100644 --- a/src/model-source/local-model-source.ts +++ b/src/model-source/local-model-source.ts @@ -107,6 +107,12 @@ export class LocalModelSource extends ModelSource { return this.submitModel(newRoot, false); } + commitModel(newRoot: SModelRootSchema): SModelRootSchema { + 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 diff --git a/src/model-source/model-source.ts b/src/model-source/model-source.ts index 80032150..5d059770 100644 --- a/src/model-source/model-source.ts +++ b/src/model-source/model-source.ts @@ -23,6 +23,7 @@ import { RequestModelAction } from "../base/features/set-model"; import { TYPES } from "../base/types"; import { ViewerOptions } from "../base/views/viewer-options"; import { ExportSvgAction } from '../features/export/svg-exporter'; +import { SModelRootSchema } from "../base/model/smodel"; /** * A model source is serving the model to the event cycle. It represents @@ -58,4 +59,15 @@ export abstract class ModelSource implements IActionHandler { } abstract handle(action: Action): ICommand | Action | void; + + /** + * Commit changes from the internal SModel back to the currentModel. + * + * Does not have any side effects such as triggering layout or bounds computation, + * as the internal model is already current. See CommitModelAction + * for details. + * @param newRoot the new model. + * @return the previous model. + */ + abstract commitModel(newRoot: SModelRootSchema): SModelRootSchema; }