eclipse-sprotty / sprotty

A diagramming framework for the web
https://sprotty.org/
Eclipse Public License 2.0
703 stars 81 forks source link

Fit to screen on model update is unreliable #121

Open tomvdbussche opened 5 years ago

tomvdbussche commented 5 years ago

Whenever the model is updated I want to execute the FitToScreenAction. Currently I do this on the server side by implementing IModelUpdateListener and dispatching a FitToScreenAction (like in the flow example). This does seem to work sometimes, but often nothing happens.

The issue seems to be caused by a race between the FitToScreenAction and the InitializeCanvasBoundsAction. Because the FitToScreenAction depends on the canvas bounds, whenever the client receives the FitToScreenAction before the InitializeCanvasBoundsAction is executed, the model does not get fit to the screen.

spoenemann commented 5 years ago

Why is an InitializeCanvasBoundsAction triggered? That should happen only if the root element of the model is changed (different type or id). Is that the case in your application?

tomvdbussche commented 5 years ago

When the client is initialized, the model is requested from the server. It seems however that when the client receive a response from the server, the model if first set to the default empty model. Only after that it is set to the actual model received from the server.

This is what I get in the browser console (added some extra info):

GraphicsDiagramServer: sending "requestModel"
GraphicsDiagramServer: receiving "requestBounds"
ActionDispatcher: handle SetModelAction
CommandStack: Executing SetModelCommand
ActionDispatcher: Action is postponed due to block condition "requestBounds"
Viewer: rendering SModelRoot { id: "EMPTY", type: "NONE" }
ActionDispatcher: handle InitializeCanvasBoundsAction
CommandStack: Executing InitializeCanvasBoundsCommand
ActionDispatcher: handle "requestBounds"
CommandStack: Executing RequestBoundsCommand
Viewer: rendering hidden
Viewer: rendering SModelRoot { id: "EMPTY", type: "NONE" }
ActionDispatcher: handle ComputedBoundsAction
GraphicsDiagramServer: sending "computedBounds"
GraphicsDiagramServer: receiving "updateModel"
ActionDispatcher: handle "updateModel"
CommandStack: Executing UpdateModelCommand
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
ActionDispatcher: handle InitializeCanvasBoundsAction
CommandStack: Executing InitializeCanvasBoundsCommand
GraphicsDiagramServer: receiving "fit"
ActionDispatcher: handle "fit"
CommandStack: Executing FitToScreenCommand
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
ViewportAnimation: 64 fps
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
ActionDispatcher: handle InitializeCanvasBoundsAction
CommandStack: Executing InitializeCanvasBoundsCommand
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }
ActionDispatcher: handle InitializeCanvasBoundsAction
CommandStack: Executing InitializeCanvasBoundsCommand
Viewer: rendering SGraph { id: "my-diagram", type: "graph:mydiagram" }

In this example the FitToScreen actually worked due to the InitializeCanvasBoundsAction before it. If the FitToScreen was received earlier it would not work

spoenemann commented 5 years ago

I understand. We might use the blockUntil property on UpdateModelCommand to block following actions until the InitializeCanvasBoundsAction is dispatched.

martinber commented 3 months ago

In case someone is searching on Google how to fit the diagram to screen. Here I paste an extract of my code, where I add a 1s timeout due to this bug

import "reflect-metadata";
import { LocalModelSource, TYPES } from 'sprotty';
import { FitToScreenAction } from 'sprotty-protocol';
import { createContainer } from './di.config';
import { createGraph } from './model-source';
import * as package_json from '../package.json';

export default async function run() {

    const data = await (await fetch(
        `${BACKEND_URL}/diagram-data`,
        { headers: HEADERS },
    )).json();

    const container = createContainer("sprotty-container");
    const modelSource = container.get<LocalModelSource>(TYPES.ModelSource);
    modelSource.updateModel(createGraph(data));

    // Sleep 1s due to a bug in Sprotty: https://github.com/eclipse-sprotty/sprotty/issues/121
    await new Promise(r => setTimeout(r, 1000));
    await modelSource.actionDispatcher.dispatch(FitToScreenAction.create([]));
}

document.addEventListener("DOMContentLoaded", () => run());