diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index c8158677d..f6f55d60e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -46,6 +46,7 @@ This fix ensure that imported models containing, for example, a top-level `Libra - https://github.com/eclipse-syson/syson/issues/1565[#1565] Provide a way to duplicate semantic element in the _Explorer_ view. - https://github.com/eclipse-syson/syson/issues/1737[#1737] [diagrams] Add creation tools to InterconnectionCompartmentNode - https://github.com/eclipse-syson/syson/issues/1395[#1395] Provide a way to duplicate a semantic element ans its representation node in the _General View_ diagram. +- https://github.com/eclipse-syson/syson/issues/1747[#1747] [diagrams] Most new elements created by invoking a tool on a diagram are now automatically selected == v2025.12.0 diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AbstractCompartmentNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AbstractCompartmentNodeToolProvider.java index 852297ce9..359378601 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AbstractCompartmentNodeToolProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/AbstractCompartmentNodeToolProvider.java @@ -12,8 +12,6 @@ *******************************************************************************/ package org.eclipse.syson.diagram.common.view.tools; -import java.util.List; - import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.diagrams.Node; @@ -25,9 +23,9 @@ import org.eclipse.sirius.components.view.diagram.NodeTool; import org.eclipse.sirius.components.view.diagram.SelectionDialogDescription; import org.eclipse.sirius.components.view.emf.diagram.ViewDiagramDescriptionConverter; +import org.eclipse.syson.diagram.common.view.services.ViewNodeService; import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService; import org.eclipse.syson.util.AQLConstants; -import org.eclipse.syson.util.AQLUtils; import org.eclipse.syson.util.ServiceMethod; /** @@ -37,6 +35,8 @@ */ public abstract class AbstractCompartmentNodeToolProvider implements INodeToolProvider { + private static final String NEW_INSTANCE = "newInstance"; + protected final DiagramBuilders diagramBuilderHelper = new DiagramBuilders(); protected final ViewBuilders viewBuilderHelper = new ViewBuilders(); @@ -77,8 +77,8 @@ public NodeTool create(IViewDiagramElementFinder cache) { ChangeContextBuilder revealOperation; if (this.revealOnCreate()) { revealOperation = this.viewBuilderHelper.newChangeContext() - .expression(AQLUtils.getServiceCallExpression(Node.SELECTED_NODE, "revealCompartment", - List.of("self", DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))); + .expression(ServiceMethod.of4(ViewNodeService::revealCompartment).aql(Node.SELECTED_NODE, AQLConstants.SELF, DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT, + ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)); } else { revealOperation = this.viewBuilderHelper.newChangeContext().expression(AQLConstants.AQL_SELF); } @@ -87,19 +87,25 @@ public NodeTool create(IViewDiagramElementFinder cache) { .expression(ServiceMethod.of4(DiagramMutationAQLService::expose).aqlSelf(IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)); - var creationCompartmentItemServiceCall = this.viewBuilderHelper.newChangeContext() - .expression(this.getServiceCallExpression()) + var exposeAndRevealNewInstance = this.viewBuilderHelper.newChangeContext() + .expression(AQLConstants.AQL + NEW_INSTANCE) .children(addToExposedElements.build(), revealOperation.build()); + var letNewInstance = this.viewBuilderHelper.newLet() + .variableName(NEW_INSTANCE) + .valueExpression(this.getServiceCallExpression()) + .children(exposeAndRevealNewInstance.build()); + var rootChangContext = this.viewBuilderHelper.newChangeContext() .expression(AQLConstants.AQL_SELF) - .children(creationCompartmentItemServiceCall.build()) + .children(letNewInstance.build()) .build(); return builder.name(this.getNodeToolName()) .iconURLsExpression(this.getNodeToolIconURLsExpression()) .body(rootChangContext) .preconditionExpression(this.getPreconditionExpression()) + .elementsToSelectExpression(AQLConstants.AQL + NEW_INSTANCE) .build(); } diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActionFlowCompartmentNodeToolProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActionFlowCompartmentNodeToolProvider.java index 33c9ddcfc..ed5b4a2b0 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActionFlowCompartmentNodeToolProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/tools/ActionFlowCompartmentNodeToolProvider.java @@ -12,8 +12,6 @@ *******************************************************************************/ package org.eclipse.syson.diagram.common.view.tools; -import java.util.List; - import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.diagrams.Node; @@ -23,8 +21,10 @@ import org.eclipse.sirius.components.view.builder.providers.INodeToolProvider; import org.eclipse.sirius.components.view.diagram.NodeTool; import org.eclipse.sirius.components.view.emf.diagram.ViewDiagramDescriptionConverter; +import org.eclipse.syson.diagram.common.view.services.ViewCreateService; +import org.eclipse.syson.diagram.common.view.services.ViewNodeService; import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService; -import org.eclipse.syson.util.AQLUtils; +import org.eclipse.syson.util.AQLConstants; import org.eclipse.syson.util.ServiceMethod; /** @@ -34,6 +34,8 @@ */ public class ActionFlowCompartmentNodeToolProvider implements INodeToolProvider { + private static final String NEW_INSTANCE = "newInstance"; + private final DiagramBuilders diagramBuilderHelper = new DiagramBuilders(); private final ViewBuilders viewBuilderHelper = new ViewBuilders(); @@ -47,18 +49,19 @@ public NodeTool create(IViewDiagramElementFinder cache) { ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)); var revealOperation = this.viewBuilderHelper.newChangeContext() - .expression( - AQLUtils.getServiceCallExpression(Node.SELECTED_NODE, "revealCompartment", - List.of("self", DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT, ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))); + .expression(ServiceMethod.of4(ViewNodeService::revealCompartment).aql(Node.SELECTED_NODE, AQLConstants.SELF, DiagramContext.DIAGRAM_CONTEXT, IEditingContext.EDITING_CONTEXT, + ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE)); - var creationServiceCall = this.viewBuilderHelper.newChangeContext() - .expression(AQLUtils.getSelfServiceCallExpression("createSubActionUsage")) + var letNewInstance = this.viewBuilderHelper.newLet() + .variableName(NEW_INSTANCE) + .valueExpression(ServiceMethod.of0(ViewCreateService::createSubActionUsage).aqlSelf()) .children(addToExposedElements.build(), revealOperation.build()) .build(); return builder.name("New Action") .iconURLsExpression("/icons/full/obj16/ActionUsage.svg") - .body(creationServiceCall) + .body(letNewInstance) + .elementsToSelectExpression(AQLConstants.AQL + NEW_INSTANCE) .build(); } } diff --git a/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc b/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc index 27fc1dfce..2b3398f89 100644 --- a/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc +++ b/doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc @@ -62,6 +62,8 @@ This applies to: `FeatureTyping`, `Subsetting`, `Redefinition` and `Subclassific image::release-notes-redundant-feature-typing-message.png[Message displayed when attempting to create a redundant feature typing] - In diagrams, it is now possible to create a `SatisfyRequirement` from a `PartDefinition` or `PartUsage` graphical node. +- In diagrams, many new tools now automatically select the `Elements` they create or expose on a diagram. +This makes it easy to start editing newly created elements to give them proper names by starting to type directly when the new element appears. == Technical details diff --git a/integration-tests-playwright/playwright/e2e/diagram-selection.spec.ts b/integration-tests-playwright/playwright/e2e/diagram-selection.spec.ts new file mode 100644 index 000000000..b73affa12 --- /dev/null +++ b/integration-tests-playwright/playwright/e2e/diagram-selection.spec.ts @@ -0,0 +1,114 @@ +/******************************************************************************* + * 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 + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { expect, test } from '@playwright/test'; +import { PlaywrightExplorer } from '../helpers/PlaywrightExplorer'; +import { PlaywrightNode } from '../helpers/PlaywrightNode'; +import { PlaywrightProject } from '../helpers/PlaywrightProject'; + +test.describe('diagram - general view', () => { + let projectId; + test.beforeEach(async ({ page, request }) => { + await page.addInitScript(() => { + // @ts-expect-error: we use a variable in the DOM to disable `fitView` functionality for Cypress tests. + window.document.DEACTIVATE_FIT_VIEW_FOR_CYPRESS_TESTS = true; + }); + const project = await new PlaywrightProject(request).createSysMLV2Project('general'); + projectId = project.projectId; + + await page.goto(`/projects/${projectId}/edit`); + const playwrightExplorer = new PlaywrightExplorer(page); + await playwrightExplorer.uploadDocument('SysMLv2WithGeneralView.sysml'); + await playwrightExplorer.expand('SysMLv2WithGeneralView.sysml'); + await playwrightExplorer.expand('Package1'); + await playwrightExplorer.createRepresentation('view1 [GeneralView]', 'General View', 'view1'); + + // Make sure all the elements are visible and at well-defined locations + await page.getByTestId('arrange-all-menu').click(); + await page.getByTestId('arrange-all-elk-layered').click(); + await page.getByTestId('zoom-out').click(); + }); + + test.afterEach(async ({ request }) => { + await new PlaywrightProject(request).deleteProject(projectId); + }); + + test('WHEN creating an attribute inside a part, THEN the new attribute is automatically selected and can be renamed immediately', async ({ + page, + }) => { + const partNode = new PlaywrightNode(page, 'part1', 'List'); + await expect(partNode.nodeLocator).toBeAttached(); + + // Create a new attribute + await partNode.click(); + await partNode.openPalette(); + await page.getByTestId('toolSection-Structure').click(); + await page.getByTestId('tool-New Attribute').click(); + + // It should be selected + const newNode = new PlaywrightNode(page, 'attribute1', 'IconLabel'); + await expect(newNode.nodeLocator).toBeAttached(); + await expect(newNode.nodeLocator).toContainClass('selected'); + + // Start typing a new name immediately + const newName = 'editedAttribute'; + await page.keyboard.type(newName); + await page.keyboard.press('Enter'); + await expect(newNode.nodeLocator).not.toBeAttached(); + + // The newly created node has changed its label and is still selected + const editedNode = new PlaywrightNode(page, newName, 'IconLabel'); + await expect(editedNode.nodeLocator).toBeAttached(); + await expect(editedNode.nodeLocator).toContainClass('selected'); + + // The selection can be seen from the rest of the workbench, e.g. in the Explorer + const explorer = new PlaywrightExplorer(page); + const treeItem = await explorer.getTreeItemLabel(newName); + await expect(treeItem).not.toBeVisible(); + await explorer.revealGlobalSelection(); + await expect(treeItem).toBeVisible(); + }); + + test('WHEN creating a port on a part, THEN the new port is automatically selected', async ({ page }) => { + const partNode = new PlaywrightNode(page, 'part1', 'List'); + await expect(partNode.nodeLocator).toBeAttached(); + + // Create a new port + await partNode.click(); + await partNode.openPalette(); + await page.getByTestId('toolSection-Structure').click(); + await page.getByTestId('tool-New Port').click(); + + // It should be selected + const newNode = new PlaywrightNode(page, 'port1', 'FreeForm'); + await expect(newNode.nodeLocator).toBeAttached(); + await expect(newNode.nodeLocator).toContainClass('selected'); + + // Start typing a new name immediately + const newName = 'editedPort'; + await page.keyboard.type(newName); + await page.keyboard.press('Enter'); + await expect(newNode.nodeLocator).not.toBeAttached(); + + // The newly created node has changed its label and is still selected + const editedNode = new PlaywrightNode(page, newName, 'FreeForm'); + await expect(editedNode.nodeLocator).toBeAttached(); + await expect(editedNode.nodeLocator).toContainClass('selected'); + + // The selection can be seen from the rest of the workbench, e.g. in the Explorer + const explorer = new PlaywrightExplorer(page); + const treeItem = await explorer.getTreeItemLabel(newName); + await expect(treeItem).not.toBeVisible(); + await explorer.revealGlobalSelection(); + await expect(treeItem).toBeVisible(); + }); +}); diff --git a/integration-tests-playwright/playwright/resources/SysMLv2WithGeneralView.sysml b/integration-tests-playwright/playwright/resources/SysMLv2WithGeneralView.sysml new file mode 100644 index 000000000..23b1151b8 --- /dev/null +++ b/integration-tests-playwright/playwright/resources/SysMLv2WithGeneralView.sysml @@ -0,0 +1,6 @@ +package Package1 { + view view1 : StandardViewDefinitions::GeneralView { + expose part1; + } + part part1; +} \ No newline at end of file