Skip to content

Commit

Permalink
[997] Prevent the cursor mouse listener to trigger unwanted update model
Browse files Browse the repository at this point in the history
When the model is updated after a new version of the diagram has been
sent by the backend, the next time the cursor mouse listener is
triggered (at the next mouse move), it updates the model and thus,
forces sprotty to render the diagram again. Sometime it may look like
the diagram is blinking especially when an edge is selected and one of
its routing points has been modified.

Bug: #997
Signed-off-by: Guillaume Coutable <guillaume.coutable@obeo.fr>
  • Loading branch information
gcoutable authored and sbegaudeau committed Oct 4, 2022
1 parent b619827 commit 65f3986
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import {
import { edgeCreationFeedback } from '../sprotty/edgeCreationFeedback';
import { Toolbar } from '../toolbar/Toolbar';
import {
CursorValue,
GQLDeletionPolicy,
GQLDiagramEventPayload,
GQLDiagramEventSubscription,
Expand Down Expand Up @@ -632,8 +633,8 @@ export const DiagramRepresentation = ({
diagramServer.actionDispatcher.dispatch(selectSprottyAction);
};

const getCursorOn = (element, diagramServer: DiagramServer) => {
let cursor = 'pointer';
const getCursorOn = (element, diagramServer: DiagramServer): CursorValue => {
let cursor: CursorValue = 'pointer';
if (diagramServer.diagramSource) {
if (diagramServer.activeConnectorTools.length > 0) {
const cursorAllowed = atLeastOneSingleClickOnTwoDiagramElementsTool(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*******************************************************************************/
import { Node } from '../sprotty/Diagram.types';

export type CursorValue = 'pointer' | 'copy' | 'not-allowed';

export interface GQLDiagramEventSubscription {
diagramEvent: GQLDiagramEventPayload;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { assign, Machine } from 'xstate';
import { createDependencyInjectionContainer } from '../sprotty/DependencyInjection';
import { DiagramServer } from '../sprotty/DiagramServer';
import {
CursorValue,
GQLDiagram,
Menu,
Palette,
Expand Down Expand Up @@ -118,7 +119,7 @@ export type InitializeRepresentationEvent = {
position: Position,
event: MouseEvent
) => void;
getCursorOn: any;
getCursorOn: (element, diagramServer: DiagramServer) => CursorValue;
setActiveTool: (tool: Tool) => void;
toolSections: ToolSection[];
setContextualPalette: (contextualPalette: Palette) => void;
Expand Down Expand Up @@ -381,7 +382,7 @@ export const diagramRepresentationMachine = Machine<
httpOrigin,
} = event as InitializeRepresentationEvent;

const container = createDependencyInjectionContainer(diagramDomElement.current.id, getCursorOn);
const container = createDependencyInjectionContainer(diagramDomElement.current.id);
const diagramServer = <DiagramServer>container.get(TYPES.ModelSource);

/**
Expand All @@ -403,6 +404,7 @@ export const diagramRepresentationMachine = Machine<
diagramServer.setActiveToolListener(setActiveTool);
diagramServer.setOnSelectElementListener(onSelectElement);
diagramServer.setUpdateRoutingPointsListener(updateRoutingPointsListener);
diagramServer.setGetCursorOnListener(getCursorOn);

return {
diagramServer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import {
ZoomMouseListener,
zorderModule,
} from 'sprotty';
import { Action, Point, RequestPopupModelAction, SetPopupModelAction, UpdateModelAction } from 'sprotty-protocol';
import { Action, Point, RequestPopupModelAction, SetPopupModelAction } from 'sprotty-protocol';
import { siriusCommonModule } from './common/siriusCommonModule';
import { BorderNode, Label, Node } from './Diagram.types';
import { DiagramServer, HIDE_CONTEXTUAL_TOOLBAR_ACTION, SPROTTY_DELETE_ACTION } from './DiagramServer';
Expand Down Expand Up @@ -128,7 +128,7 @@ const siriusWebContainerModule = new ContainerModule((bind, unbind, isBound, reb
* @param containerId The identifier of the container
* @param onSelectElement The selection call back
*/
export const createDependencyInjectionContainer = (containerId: string, getCursorOn) => {
export const createDependencyInjectionContainer = (containerId: string) => {
const container = new Container();
container.load(
defaultModule,
Expand All @@ -152,16 +152,6 @@ export const createDependencyInjectionContainer = (containerId: string, getCurso
labelEditUiModule
);

const findElementWithTarget = (element) => {
if (element.targetObjectId) {
return element;
} else if (element.parent) {
return findElementWithTarget(element.parent);
}
// Otherwise, use the diagram as element with target.
return element.root;
};

class DiagramMouseListener extends MouseListener {
diagramServer: DiagramServer;
previousCoordinates: Point | null;
Expand Down Expand Up @@ -246,34 +236,6 @@ export const createDependencyInjectionContainer = (containerId: string, getCurso
}
container.bind(TYPES.MouseListener).to(DiagramZoomMouseListener).inSingletonScope();

class CursorMouseListener extends MouseListener {
diagramServer: any;
constructor(diagramServer) {
super();
this.diagramServer = diagramServer;
}

override mouseMove(element, event) {
const root = element.root;
const elementWithTarget = findElementWithTarget(element);
const expectedCursor = getCursorOn(elementWithTarget, this.diagramServer);
if (root.cursor !== expectedCursor) {
root.cursor = expectedCursor;
return [
{
kind: UpdateModelAction.KIND,
input: root,
},
];
}

return [];
}
}
decorate(inject(TYPES.ModelSource) as ParameterDecorator, CursorMouseListener, 0);

container.bind(TYPES.MouseListener).to(CursorMouseListener).inSingletonScope();

// The list of characters that will enable the direct edit mechanism.
const directEditActivationValidCharacters = /[\w&é§èàùçÔØÁÛÊË"«»’”„´$¥€£\\¿?!=+-,;:%/{}[\]–#@*.]/;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
} from 'sprotty-protocol';
import {
Bounds,
CursorValue,
GQLDeletionPolicy,
Menu,
Palette,
Expand Down Expand Up @@ -140,6 +141,7 @@ export class DiagramServer extends ModelSource {
httpOrigin;

firstOpen: boolean = true;
getCursorOn: (element: any, diagramServer: DiagramServer) => CursorValue;

override initialize(registry: ActionHandlerRegistry) {
super.initialize(registry);
Expand Down Expand Up @@ -368,6 +370,7 @@ export class DiagramServer extends ModelSource {
const convertedDiagram = convertDiagram(diagram, this.httpOrigin, readOnly);
const sprottyModel = this.modelFactory.createRoot(convertedDiagram);
this.actionDispatcher.request<SelectionResult>(GetSelectionAction.create()).then((selectionResult) => {
(sprottyModel as any).cursor = 'pointer';
const actionsToDispatch: Action[] = [UpdateModelAction.create(sprottyModel as any)];

if (selectionResult.selectedElementsIDs.length > 0) {
Expand Down Expand Up @@ -673,4 +676,8 @@ export class DiagramServer extends ModelSource {
setUpdateRoutingPointsListener(updateRoutingPointsListener: (routingPoints: Point[], edgeId: string) => void) {
this.updateRoutingPointsListener = updateRoutingPointsListener;
}

setGetCursorOnListener(getCursorOn: (element, diagramServer: DiagramServer) => CursorValue) {
this.getCursorOn = getCursorOn;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*******************************************************************************
* Copyright (c) 2022 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Obeo - initial API and implementation
*******************************************************************************/

import { decorate, inject } from 'inversify';
import { MouseListener, TYPES } from 'sprotty';
import { UpdateModelAction } from 'sprotty-protocol';
import { DiagramServer } from '../DiagramServer';

/**
* Update the cursor if need.
*
* It triggers a model update every time the cursor has to change. This will trigger a rendering that will use the new cursor value.
*/
export class CursorMouseListener extends MouseListener {
constructor(protected readonly diagramServer: DiagramServer) {
super();
}

override mouseMove(element, _event) {
const root = element.root;
const elementWithTarget = findElementWithTarget(element);
const expectedCursor = this.diagramServer.getCursorOn(elementWithTarget, this.diagramServer);
if (root.cursor !== expectedCursor) {
root.cursor = expectedCursor;
return [UpdateModelAction.create(root)];
}

return [];
}
}
decorate(inject(TYPES.ModelSource) as ParameterDecorator, CursorMouseListener, 0);

const findElementWithTarget = (element) => {
if (element.targetObjectId) {
return element;
} else if (element.parent) {
return findElementWithTarget(element.parent);
}
// Otherwise, use the diagram as element with target.
return element.root;
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
* Obeo - initial API and implementation
*******************************************************************************/
import { ContainerModule } from 'inversify';
import { configureCommand } from 'sprotty';
import { configureCommand, TYPES } from 'sprotty';
import { CursorMouseListener } from './CursorMouseListener';
import { IsSiriusModelElementCommand } from './isSiriusModelElementRequest';

export const siriusCommonModule = new ContainerModule((bind, unbind, isBound, rebind) => {
export const siriusCommonModule = new ContainerModule((bind, _unbind, isBound) => {
configureCommand({ bind, isBound }, IsSiriusModelElementCommand);
bind(TYPES.MouseListener).to(CursorMouseListener).inSingletonScope();
});

0 comments on commit 65f3986

Please sign in to comment.