Skip to content

Commit

Permalink
[4285] Fix direct edit when the palette is closed
Browse files Browse the repository at this point in the history
Bug: #4285
Signed-off-by: Michaël Charfadi <[email protected]>
  • Loading branch information
mcharfadi committed Feb 12, 2025
1 parent e8c7f27 commit 207d8b6
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 160 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ Now they are executed for some data if the data was coming from a studio too.
- https://github.com/eclipse-sirius/sirius-web/issues/4390[#4390] [form] Fix an issue displaying irrelevant help text and diagnostics
- [diagram] Replace `requestIdleCallback` with `setTimeout` for browser compatibility given the fact that `requestIdleCallback` is still a working draft
- [sirius-web] Fix a recently introduced bug in which the semantic data of a project could remain after the deletion of the project
- https://github.com/eclipse-sirius/sirius-web/issues/4285[#4285] [diagram] Fix direct edit after closing the palette


=== New Features
Expand Down
204 changes: 103 additions & 101 deletions integration-tests/cypress/e2e/project/diagrams/direct-edit-label.cy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Obeo.
* Copyright (c) 2025 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
Expand All @@ -12,116 +12,118 @@
*******************************************************************************/

import { Project } from '../../../pages/Project';
import { isCreateProjectFromTemplateSuccessPayload } from '../../../support/server/createProjectFromTemplateCommand';
import { Studio } from '../../../usecases/Studio';
import { Details } from '../../../workbench/Details';
import { Flow } from '../../../usecases/Flow';
import { Diagram } from '../../../workbench/Diagram';
import { Explorer } from '../../../workbench/Explorer';

describe('Diagram - Direct edit label', () => {
context.skip('Given a view with only one node with a direct edit tool and one edge with direct edit tool', () => {
let studioProjectId: string = '';
let domainName: string = '';

before(() => {
cy.createProjectFromTemplate('studio-template').then((res) => {
const payload = res.body.data.createProjectFromTemplate;
if (isCreateProjectFromTemplateSuccessPayload(payload)) {
const projectId = payload.project.id;
studioProjectId = projectId;

const project = new Project();
project.visit(projectId);
project.disableDeletionConfirmationDialog();

const explorer = new Explorer();
explorer.getTreeItemByLabel('DomainNewModel').dblclick();
cy.get('[title="domain::Domain"]').then(($div) => {
domainName = $div.data().testid;
explorer.expandWithDoubleClick('ViewNewModel');
explorer.expandWithDoubleClick('View');
explorer.expandWithDoubleClick(`${domainName} Diagram Description`);
explorer.expandWithDoubleClick('Entity1 Node');
explorer.expandWithDoubleClick('NodePalette');
explorer.delete('Edit Label');
explorer.select('LinkedTo Edge');
new Details().getTextField('Center Label Expression').type('Edge center{enter}');
});
}
});
});

after(() => cy.deleteProject(studioProjectId));

context('When we create a new instance project', () => {
let instanceProjectId: string = '';

beforeEach(() => {
const studio = new Studio();
studio.createProjectFromDomain('Cypress - Studio Instance', domainName, 'Root').then((res) => {
instanceProjectId = res.projectId;

new Explorer().createRepresentation('Root', `${domainName} Diagram Description`, 'diagram');
});
});

afterEach(() => cy.deleteProject(instanceProjectId));

it.skip('Then we cannot perform the direct edition of the node without the direct edit tool', () => {
describe('Diagram - Fork Representation', () => {
context('Given a flow project', () => {
let projectId: string = '';
beforeEach(() => {
new Flow().createFlowProject().then((createdProjectData) => {
projectId = createdProjectData.projectId;
const project = new Project();
project.visit(projectId);
project.disableDeletionConfirmationDialog();
const explorer = new Explorer();
explorer.createObject('Root', 'entity1s-Entity1');
explorer.getTreeItemByLabel('Entity1').click();

const details = new Details();
details.getTextField('Name').type('Entity1{Enter}');

const diagram = new Diagram();
diagram.fitToScreen();
diagram.getNodes('diagram', 'Entity1').click();
diagram.getPalette().should('exist');
cy.getByTestId('Edit - Tool').should('not.exist');

diagram.getNodes('diagram', 'Entity1').trigger('keydown', { altKey: true, keyCode: 113, which: 113 }); // key code for F2
cy.getByTestId('name-edit').should('not.exist');
explorer.expandWithDoubleClick('Flow');
explorer.expandWithDoubleClick('NewSystem');
explorer.selectRepresentation('Topography');
});
});

it('Then during edit triggering escape cancelled the current edition', () => {
const explorer = new Explorer();
explorer.createObject('Root', 'entity2s-Entity2');
explorer.getTreeItemByLabel('Entity2').click();

const details = new Details();
details.getTextField('Name').type('Entity2{Enter}');

const diagram = new Diagram();
diagram.fitToScreen();
diagram.getNodes('diagram', 'Entity2').click();
diagram.getPalette().should('exist');
it('Then we can use direct edit on a node', () => {
const diagram = new Diagram();
//Wait for the fit to screen
cy.get('@consoleDebug').should('be.calledWith', 'fit-to-screen has been performed:true');
//Select a node
diagram.getDiagram('Topography').should('exist');
diagram.getNodes('Topography', 'DataSource1').should('exist');
diagram.getNodes('Topography', 'DataSource1').click();
cy.focused().should('have.class', 'react-flow__node');
//Trigger direct edit with f2
diagram.getNodes('Topography', 'DataSource1').trigger('keydown', { altKey: true, keyCode: 113, which: 113 }); // key code for F2
cy.getByTestId('name-edit').should('exist');
diagram.getPalette().should('not.exist');
//Edit
cy.wait(100);
cy.getByTestId('name-edit').type('Edited{enter}');
cy.getByTestId('name-edit').should('not.exist');
diagram.getNodes('Topography', 'Edited').should('exist');
});

cy.getByTestId('Edit - Tool').click();
cy.getByTestId('name-edit').should('exist').type('test{esc}');
diagram.getNodes('diagram', 'Entity2').should('exist');
diagram.getNodes('diagram', 'test').should('not.exist');
});
it('Then during edit triggering escape cancelled the current edition', () => {
const diagram = new Diagram();
//Wait for the fit to screen
cy.get('@consoleDebug').should('be.calledWith', 'fit-to-screen has been performed:true');
//Select a node
diagram.getDiagram('Topography').should('exist');
diagram.getNodes('Topography', 'DataSource1').should('exist');
diagram.getNodes('Topography', 'DataSource1').click();
cy.focused().should('have.class', 'react-flow__node');
//Trigger direct edit with f2
diagram.getNodes('Topography', 'DataSource1').trigger('keydown', { altKey: true, keyCode: 113, which: 113 }); // key code for F2
cy.getByTestId('name-edit').should('exist');
diagram.getPalette().should('not.exist');
//Cancel direct edit with escp
cy.wait(100);
cy.getByTestId('name-edit').type('test{esc}');
cy.getByTestId('name-edit').should('not.exist');
diagram.getNodes('Topography', 'DataSource1').should('exist');
});

it('Then during the direct edition, the palette is hidden', () => {
const explorer = new Explorer();
explorer.createObject('Root', 'entity2s-Entity2');
explorer.getTreeItemByLabel('Entity2').click();
it('Then we can use direct edit on a node even if the palette is opened', () => {
const diagram = new Diagram();
//Wait for the fit to screen
cy.get('@consoleDebug').should('be.calledWith', 'fit-to-screen has been performed:true');
//Select a node
diagram.getDiagram('Topography').should('exist');
diagram.getNodes('Topography', 'DataSource1').should('exist');
diagram.getNodes('Topography', 'DataSource1').click();
//Open the palette
diagram.getNodes('Topography', 'DataSource1').rightclick();
diagram.getPalette().should('exist');
//Trigger direct edit with f2
diagram.getNodes('Topography', 'DataSource1').trigger('keydown', { altKey: true, keyCode: 113, which: 113 }); // key code for F2
cy.getByTestId('name-edit').should('exist');
diagram.getPalette().should('not.exist');
//Edit
cy.wait(100);
cy.getByTestId('name-edit').type('Edited{enter}');
cy.getByTestId('name-edit').should('not.exist');
diagram.getNodes('Topography', 'Edited').should('exist');
});

const details = new Details();
details.getTextField('Name').type('Entity2{Enter}');
it('Then we can use direct after creating a new node', () => {
const diagram = new Diagram();
// wait for the fit to screen
cy.get('@consoleDebug').should('be.calledWith', 'fit-to-screen has been performed:true');
//Open the palette
cy.getByTestId('rf__wrapper').should('exist').rightclick(100, 800).rightclick(100, 800);
diagram.getPalette().should('exist');
//Create a new node
diagram.getPalette().getByTestId('toolSection-Creation Tools').should('exist');
diagram.getPalette().getByTestId('toolSection-Creation Tools').click();
diagram.getPalette().getByTestId('tool-Composite Processor').should('exist');
diagram.getPalette().getByTestId('tool-Composite Processor').click();
diagram.getNodes('Topography', 'CompositeProcessor2').should('exist');
cy.get('@consoleDebug').should('be.calledWith', 'fit-to-screen has been performed:true');
//Trigger direct edit with f2
cy.wait(100);
diagram
.getNodes('Topography', 'CompositeProcessor2')
.trigger('keydown', { altKey: true, keyCode: 113, which: 113 }); // key code for F2
cy.getByTestId('name-edit').should('exist');
diagram.getPalette().should('not.exist');
//Edit
cy.wait(100);
cy.getByTestId('name-edit').type('Edited2{enter}');
cy.getByTestId('name-edit').should('not.exist');
diagram.getNodes('Topography', 'Edited2').should('exist');
});

const diagram = new Diagram();
diagram.fitToScreen();
diagram.getNodes('diagram', 'Entity2').click();
diagram.getPalette().should('exist');
cy.getByTestId('Edit - Tool').should('exist').click();
cy.getByTestId('name-edit').should('exist');
diagram.getPalette().should('not.exist');
cy.getByTestId('name-edit').type('Entity Entity2{enter}');
cy.getByTestId('name-edit').should('not.exist');
});
afterEach(() => {
cy.deleteProject(projectId);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Obeo.
* Copyright (c) 2023, 2025 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
Expand Down Expand Up @@ -30,19 +30,26 @@ export const useDiagramDirectEdit = (): UseDiagramDirectEditValue => {
const onDirectEdit = useCallback(
(event: React.KeyboardEvent<Element>) => {
const { key } = event;
/*If a modifier key is hit alone, do nothing*/

const isTextField = event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement;
if ((event.altKey && key === 'Alt') || (event.shiftKey && key === 'Shift') || isTextField || readOnly) {

/*If a modifier key is hit alone, do nothing*/
if ((event.altKey && key === 'Alt') || (event.shiftKey && key === 'Shift') || readOnly) {
return;
}

if (key !== 'F2' && isTextField) {
return;
}
event.preventDefault();

const validFirstInputChar =
!event.metaKey && !event.ctrlKey && key.length === 1 && directEditActivationValidCharacters.test(key);

let currentlyEditedLabelId: string | undefined | null;
let isLabelEditable: boolean = false;
const nodeData: NodeData | undefined = getNodes().find((node) => node.selected)?.data;

if (nodeData) {
if (nodeData.insideLabel) {
currentlyEditedLabelId = nodeData.insideLabel.id;
Expand All @@ -51,6 +58,7 @@ export const useDiagramDirectEdit = (): UseDiagramDirectEditValue => {
}
isLabelEditable = nodeData.labelEditable;
}

if (!currentlyEditedLabelId) {
currentlyEditedLabelId = getEdges().find((edge) => edge.selected)?.data?.label?.id;
isLabelEditable = getEdges().find((edge) => edge.selected)?.data?.centerLabelEditable || false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Obeo.
* Copyright (c) 2023, 2025 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
Expand All @@ -23,14 +23,9 @@ import { useDiagramElementPalette } from './useDiagramElementPalette';
export const DiagramElementPalette = memo(
({ diagramElementId, targetObjectId, labelId }: DiagramElementPaletteProps) => {
const { readOnly } = useContext<DiagramContextValue>(DiagramContext);
const { isOpened, x, y, hideDiagramElementPalette } = useDiagramElementPalette();
const { isOpened, x, y } = useDiagramElementPalette();
const { setCurrentlyEditedLabelId, currentlyEditedLabelId } = useDiagramDirectEdit();

//If the Palette search field has the focus on, the useKeyPress from reactflow ignore the key pressed event.
const onEscape = () => {
hideDiagramElementPalette();
};

if (readOnly) {
return null;
}
Expand All @@ -49,7 +44,6 @@ export const DiagramElementPalette = memo(
diagramElementId={diagramElementId}
targetObjectId={targetObjectId}
onDirectEditClick={handleDirectEditClick}
onEscape={onEscape}
/>
</PalettePortal>
) : null;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2023, 2024 Obeo.
* Copyright (c) 2023, 2025 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
Expand All @@ -11,8 +11,7 @@
* Obeo - initial API and implementation
*******************************************************************************/

import { useKeyPress } from '@xyflow/react';
import { memo, useContext, useEffect } from 'react';
import { memo, useContext } from 'react';
import { DiagramContext } from '../../contexts/DiagramContext';
import { DiagramContextValue } from '../../contexts/DiagramContext.types';
import { DiagramPaletteProps } from './DiagramPalette.types';
Expand All @@ -22,24 +21,12 @@ import { useDiagramPalette } from './useDiagramPalette';

export const DiagramPalette = memo(({ diagramElementId, targetObjectId }: DiagramPaletteProps) => {
const { readOnly } = useContext<DiagramContextValue>(DiagramContext);
const { isOpened, x, y, hideDiagramPalette } = useDiagramPalette();

const escapePressed = useKeyPress('Escape');
useEffect(() => {
if (escapePressed) {
hideDiagramPalette();
}
}, [escapePressed, hideDiagramPalette]);
const { isOpened, x, y } = useDiagramPalette();

if (readOnly) {
return null;
}

//If the Palette search field has the focus on, the useKeyPress from reactflow ignore the key pressed event.
const onEscape = () => {
hideDiagramPalette();
};

return isOpened && x && y ? (
<PalettePortal>
<Palette
Expand All @@ -48,7 +35,6 @@ export const DiagramPalette = memo(({ diagramElementId, targetObjectId }: Diagra
diagramElementId={diagramElementId}
targetObjectId={targetObjectId}
onDirectEditClick={() => {}}
onEscape={onEscape}
/>
</PalettePortal>
) : null;
Expand Down
Loading

0 comments on commit 207d8b6

Please sign in to comment.