UNPKG

@eclipse-glsp/client

Version:

A sprotty-based client for GLSP

153 lines (134 loc) 5.58 kB
/******************************************************************************** * Copyright (c) 2025 EclipseSource 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 { BoundsAwareViewportCommand, Disposable, DisposableCollection, Emitter, Event, GModelRoot, ICommand, ICommandStack, LazyInjector, SetModelCommand, SetViewportCommand, TYPES, UpdateModelCommand, Viewport, almostEquals, isViewport } from '@eclipse-glsp/sprotty'; import { inject, injectable, postConstruct, preDestroy } from 'inversify'; /** * Service that tracks changes to the model root and the viewport. * Allows to register listeners that are notified when the model root or the viewport changes. * The current model root can be queried at any time. */ export interface IModelChangeService { /** The current model root */ readonly currentRoot: Readonly<GModelRoot> | undefined; /** * Event that is fired when the model root of the diagram changes i.e. after the `CommandStack` has processed a model update. */ onModelRootChanged: Event<Readonly<GModelRoot>>; /** * Event that is fired when the viewport of the diagram changes i.e. after the `CommandStack` has processed a viewport update. * By default, this event is only fired if the viewport was changed via a `SetViewportCommand` or `BoundsAwareViewportCommand` */ onViewportChanged: Event<ViewportChange>; } /** * Event data for the {@link IModelChangeService.onViewportChanged} event. */ export interface ViewportChange { /** The new viewport */ newViewport: Readonly<Viewport>; /** The old viewport */ oldViewport?: Readonly<Viewport>; } @injectable() export class ModelChangeService implements IModelChangeService, Disposable { @inject(LazyInjector) protected lazyInjector: LazyInjector; protected _currentRoot?: Readonly<GModelRoot>; protected lastViewport?: Readonly<Viewport>; protected toDispose = new DisposableCollection(); get currentRoot(): Readonly<GModelRoot> | undefined { return this._currentRoot; } protected get commandStack(): ICommandStack { return this.lazyInjector.get<ICommandStack>(TYPES.ICommandStack); } protected onModelRootChangedEmitter = new Emitter<Readonly<GModelRoot>>(); get onModelRootChanged(): Event<Readonly<GModelRoot>> { return this.onModelRootChangedEmitter.event; } protected onViewportChangedEmitter = new Emitter<ViewportChange>(); get onViewportChanged(): Event<ViewportChange> { return this.onViewportChangedEmitter.event; } @postConstruct() protected initialize(): void { this.toDispose.push(this.onModelRootChangedEmitter, this.onViewportChangedEmitter); this.commandStack.onCommandExecuted(data => this.handleCommandExecution(data.command, data.newRoot)); } @preDestroy() dispose(): void { this.toDispose.dispose(); } protected handleCommandExecution(command: ICommand, newRoot: GModelRoot): void { if (this.isModelRootChangeCommand(command)) { this.handleModelRootChangeCommand(command, newRoot); } if (this.isViewportChangeCommand(command)) { this.handleViewportChangeCommand(command, newRoot); } } protected isModelRootChangeCommand(command: ICommand): boolean { return command instanceof SetModelCommand || command instanceof UpdateModelCommand; } protected isViewportChangeCommand(command: ICommand): boolean { return command instanceof SetViewportCommand || command instanceof BoundsAwareViewportCommand; } protected handleModelRootChangeCommand(command: ICommand, newRoot: GModelRoot): void { this._currentRoot = newRoot; this.lastViewport = this.toViewport(newRoot); this.onModelRootChangedEmitter.fire(newRoot); } protected handleViewportChangeCommand(command: ICommand, newRoot: GModelRoot): void { const viewport = this.toViewport(newRoot); if (!viewport) { return; } if (this.hasViewportChanged(viewport)) { this.onViewportChangedEmitter.fire({ newViewport: viewport, oldViewport: this.lastViewport }); this.lastViewport = viewport; } } protected hasViewportChanged(newViewport: Readonly<Viewport>): boolean { if (!this.lastViewport) { return true; } return !( almostEquals(newViewport.zoom, this.lastViewport.zoom) && almostEquals(newViewport.scroll.x, this.lastViewport.scroll.x) && almostEquals(newViewport.scroll.y, this.lastViewport.scroll.y) ); } protected toViewport(root: Readonly<GModelRoot>): Readonly<Viewport> | undefined { return isViewport(root) ? { scroll: root.scroll, zoom: root.zoom } : undefined; } }