From 65f3986549873fc6c59a1fe1fd2a32cc1a403bb8 Mon Sep 17 00:00:00 2001 From: Guillaume Coutable Date: Mon, 21 Feb 2022 18:38:03 +0100 Subject: [PATCH] [997] Prevent the cursor mouse listener to trigger unwanted update model 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: https://github.com/eclipse-sirius/sirius-components/issues/997 Signed-off-by: Guillaume Coutable --- .../representation/DiagramRepresentation.tsx | 5 +- .../DiagramRepresentation.types.ts | 2 + .../DiagramRepresentationMachine.ts | 6 ++- .../src/sprotty/DependencyInjection.ts | 42 +-------------- .../src/sprotty/DiagramServer.tsx | 7 +++ .../src/sprotty/common/CursorMouseListener.ts | 51 +++++++++++++++++++ .../src/sprotty/common/siriusCommonModule.ts | 6 ++- 7 files changed, 73 insertions(+), 46 deletions(-) create mode 100644 packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/CursorMouseListener.ts diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx index c5447009280..7d96353408a 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.tsx @@ -55,6 +55,7 @@ import { import { edgeCreationFeedback } from '../sprotty/edgeCreationFeedback'; import { Toolbar } from '../toolbar/Toolbar'; import { + CursorValue, GQLDeletionPolicy, GQLDiagramEventPayload, GQLDiagramEventSubscription, @@ -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( diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.types.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.types.ts index 6d8ae4a1529..f6de838e5a9 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.types.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentation.types.ts @@ -12,6 +12,8 @@ *******************************************************************************/ import { Node } from '../sprotty/Diagram.types'; +export type CursorValue = 'pointer' | 'copy' | 'not-allowed'; + export interface GQLDiagramEventSubscription { diagramEvent: GQLDiagramEventPayload; } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentationMachine.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentationMachine.ts index 3999eb7971b..96e82784d7b 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentationMachine.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/representation/DiagramRepresentationMachine.ts @@ -19,6 +19,7 @@ import { assign, Machine } from 'xstate'; import { createDependencyInjectionContainer } from '../sprotty/DependencyInjection'; import { DiagramServer } from '../sprotty/DiagramServer'; import { + CursorValue, GQLDiagram, Menu, Palette, @@ -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; @@ -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 = container.get(TYPES.ModelSource); /** @@ -403,6 +404,7 @@ export const diagramRepresentationMachine = Machine< diagramServer.setActiveToolListener(setActiveTool); diagramServer.setOnSelectElementListener(onSelectElement); diagramServer.setUpdateRoutingPointsListener(updateRoutingPointsListener); + diagramServer.setGetCursorOnListener(getCursorOn); return { diagramServer, diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DependencyInjection.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DependencyInjection.ts index f0164aac3fa..7e99dcea604 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DependencyInjection.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DependencyInjection.ts @@ -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'; @@ -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, @@ -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; @@ -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&é§èàùçÔØÁÛÊË"«»’”„´$¥€£\\¿?!=+-,;:%/{}[\]–#@*.]/; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DiagramServer.tsx b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DiagramServer.tsx index 0765b62d30a..2ad42a3dbe4 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DiagramServer.tsx +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/DiagramServer.tsx @@ -44,6 +44,7 @@ import { } from 'sprotty-protocol'; import { Bounds, + CursorValue, GQLDeletionPolicy, Menu, Palette, @@ -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); @@ -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(GetSelectionAction.create()).then((selectionResult) => { + (sprottyModel as any).cursor = 'pointer'; const actionsToDispatch: Action[] = [UpdateModelAction.create(sprottyModel as any)]; if (selectionResult.selectedElementsIDs.length > 0) { @@ -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; + } } diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/CursorMouseListener.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/CursorMouseListener.ts new file mode 100644 index 00000000000..47da6219c88 --- /dev/null +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/CursorMouseListener.ts @@ -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; +}; diff --git a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/siriusCommonModule.ts b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/siriusCommonModule.ts index 9c28eb22271..de5226b33ed 100644 --- a/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/siriusCommonModule.ts +++ b/packages/diagrams/frontend/sirius-components-diagrams/src/sprotty/common/siriusCommonModule.ts @@ -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(); });