From 20808eeae538ceeb4f99a219ad93c131581ed37c Mon Sep 17 00:00:00 2001 From: Michael Charfadi Date: Wed, 14 Jun 2023 10:13:17 +0200 Subject: [PATCH] [2080] Add direct edit support on trees elements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: https://github.com/eclipse-sirius/sirius-components/issues/2080 Signed-off-by: Michaƫl Charfadi --- CHANGELOG.adoc | 1 + .../cypress/e2e/project/edit/explorer.cy.js | 19 +- .../NavigationOperationHandlerTests.java | 8 +- .../EditingContextEventProcessorRegistry.java | 4 - .../core/RepresentationMetadata.java | 15 +- ...itingContextRepresentationDataFetcher.java | 21 +-- ...tingContextRepresentationsDataFetcher.java | 11 +- ...ntationMetadataDescriptionDataFetcher.java | 38 +++- .../FlowProjectTemplatesInitializer.java | 4 +- .../StudioProjectTemplatesInitializer.java | 2 +- .../PapayaStudioTemplatesInitializer.java | 2 +- ...nitialDirectEditTreeItemLabelProvider.java | 66 +++++++ .../RepresentationService.java | 25 +-- .../trees/TreeEventProcessorFactory.java | 5 +- ...nitialDirectEditTreeItemLabelProvider.java | 30 ++++ .../trees/api/TreeConfiguration.java | 4 +- .../InitialDirectEditElementLabelInput.java | 25 +++ ...lDirectEditElementLabelSuccessPayload.java | 30 ++++ .../trees/dto/TreeEventInput.java | 2 +- ...alDirectEditTreeItemLabelEventHandler.java | 79 +++++++++ .../src/main/resources/schema/tree.graphqls | 12 +- ...ItemInitialDirectEditLabelDataFetcher.java | 66 +++++++ .../src/treeitems/TreeItem.tsx | 146 ++++------------ .../src/treeitems/TreeItemDirectEditInput.tsx | 164 ++++++++++++++++++ .../TreeItemDirectEditInput.types.ts | 47 +++++ .../src/views/ExplorerView.tsx | 1 + .../src/views/ExplorerView.types.ts | 1 + 27 files changed, 640 insertions(+), 188 deletions(-) create mode 100644 packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/explorer/ExplorerInitialDirectEditTreeItemLabelProvider.java create mode 100644 packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/IInitialDirectEditTreeItemLabelProvider.java create mode 100644 packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelInput.java create mode 100644 packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelSuccessPayload.java create mode 100644 packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/handlers/InitialDirectEditTreeItemLabelEventHandler.java create mode 100644 packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/tree/TreeItemInitialDirectEditLabelDataFetcher.java create mode 100644 packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.tsx create mode 100644 packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.types.ts diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 85c9fc8c28..b38b563ec3 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -663,6 +663,7 @@ When closed, clicking on any of the views' icon will re-open the panel to make t - https://github.com/eclipse-sirius/sirius-components/issues/1201[#1201] [charts] Prepare support for charts in Sirius Components - https://github.com/eclipse-sirius/sirius-components/issues/1180[#1180] [diagram] Add support for the dynamic computation of the connector tools to control the tools displayed in the contextual menu. - https://github.com/eclipse-sirius/sirius-components/issues/1212[#1212] [form] Add support for styling of the View DSL widgets +- https://github.com/eclipse-sirius/sirius-components/issues/2080[#2080] [tree] Add direct edit support on trees items. == v2022.3.0 diff --git a/integration-tests/cypress/e2e/project/edit/explorer.cy.js b/integration-tests/cypress/e2e/project/edit/explorer.cy.js index 8f35ffb2a4..8c567ebf6c 100644 --- a/integration-tests/cypress/e2e/project/edit/explorer.cy.js +++ b/integration-tests/cypress/e2e/project/edit/explorer.cy.js @@ -67,26 +67,31 @@ describe('/projects/:projectId/edit - Explorer', () => { cy.getByTestId('selected').contains('robot'); }); + const option = { + delay: 200, + }; + it('can rename a selected document with simple click (direct edit)', () => { cy.getByTestId('robot').click(); cy.getByTestId('selected').contains('robot'); cy.getByTestId('robot').click(); - cy.getByTestId('robot').type('renamed-robot{enter}'); - cy.getByTestId('renamed-robot').should('exist'); + cy.getByTestId('robot').type('.renamed{enter}', option); + cy.getByTestId('robot.renamed').should('exist'); }); it('can rename a selected document by start typing (direct edit)', () => { cy.getByTestId('robot').click(); cy.getByTestId('selected').contains('robot'); - cy.getByTestId('robot').type('renamed-robot{enter}'); - cy.getByTestId('renamed-robot').should('exist'); + cy.getByTestId('robot').type('.renamed{enter}', option); + cy.getByTestId('robot.renamed').should('exist'); }); it('can cancel a direct edit with Escape', () => { cy.getByTestId('robot').click(); cy.getByTestId('selected').contains('robot'); cy.getByTestId('robot').click(); - cy.getByTestId('robot').type('renamed-robot{esc}'); + cy.getByTestId('robot').type('.renamed{esc}', option); + cy.wait(500); cy.getByTestId('robot').should('exist'); }); @@ -94,9 +99,9 @@ describe('/projects/:projectId/edit - Explorer', () => { cy.getByTestId('robot').dblclick(); cy.getByTestId('Robot').should('exist'); cy.getByTestId('robot').click(); - cy.getByTestId('robot').type('renamed-robot'); + cy.getByTestId('robot').type('.renamed', option); cy.getByTestId('Robot').click(); - cy.getByTestId('renamed-robot').should('exist'); + cy.getByTestId('robot.renamed').should('exist'); }); it('reveals a newly created diagram', () => { diff --git a/packages/compatibility/backend/sirius-components-compatibility-emf/src/test/java/org/eclipse/sirius/components/compatibility/emf/compatibility/operations/NavigationOperationHandlerTests.java b/packages/compatibility/backend/sirius-components-compatibility-emf/src/test/java/org/eclipse/sirius/components/compatibility/emf/compatibility/operations/NavigationOperationHandlerTests.java index f89f2d5893..9dcfa71e5b 100644 --- a/packages/compatibility/backend/sirius-components-compatibility-emf/src/test/java/org/eclipse/sirius/components/compatibility/emf/compatibility/operations/NavigationOperationHandlerTests.java +++ b/packages/compatibility/backend/sirius-components-compatibility-emf/src/test/java/org/eclipse/sirius/components/compatibility/emf/compatibility/operations/NavigationOperationHandlerTests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 Obeo. + * Copyright (c) 2022, 2023 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 @@ -62,9 +62,9 @@ public class NavigationOperationHandlerTests { private final IRepresentationMetadataSearchService representationMetadataSearchService = new IRepresentationMetadataSearchService.NoOp() { @Override public List findAllByTargetObjectId(IEditingContext editingContext, String targetObjectId) { - var firstRepresentationMetadata = new RepresentationMetadata(FIRST_DIAGRAM_ID, Diagram.KIND, FIRST_DIAGRAM_LABEL, FIRST_DIAGRAM_DESCRIPTION_ID, targetObjectId); - var secondRepresentationMetadata = new RepresentationMetadata(SECOND_DIAGRAM_ID, Diagram.KIND, SECOND_DIAGRAM_LABEL, FIRST_DIAGRAM_DESCRIPTION_ID, targetObjectId); - var thirdRepresentationMetadata = new RepresentationMetadata(THIRD_DIAGRAM_ID, Diagram.KIND, THIRD_DIAGRAM_LABEL, SECOND_DIAGRAM_DESCRIPTION_ID, targetObjectId); + var firstRepresentationMetadata = new RepresentationMetadata(FIRST_DIAGRAM_ID, Diagram.KIND, FIRST_DIAGRAM_LABEL, FIRST_DIAGRAM_DESCRIPTION_ID); + var secondRepresentationMetadata = new RepresentationMetadata(SECOND_DIAGRAM_ID, Diagram.KIND, SECOND_DIAGRAM_LABEL, FIRST_DIAGRAM_DESCRIPTION_ID); + var thirdRepresentationMetadata = new RepresentationMetadata(THIRD_DIAGRAM_ID, Diagram.KIND, THIRD_DIAGRAM_LABEL, SECOND_DIAGRAM_DESCRIPTION_ID); return List.of(firstRepresentationMetadata, secondRepresentationMetadata, thirdRepresentationMetadata); } }; diff --git a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorRegistry.java b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorRegistry.java index d71f66a6f7..5c1f74f9a0 100644 --- a/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorRegistry.java +++ b/packages/core/backend/sirius-components-collaborative/src/main/java/org/eclipse/sirius/components/collaborative/editingcontext/EditingContextEventProcessorRegistry.java @@ -20,8 +20,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import jakarta.annotation.PreDestroy; - import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessor; import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessorFactory; import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessorRegistry; @@ -105,7 +103,6 @@ public synchronized Optional getOrCreateEditingCo } } } - return optionalEditingContextEventProcessor; } @@ -116,7 +113,6 @@ public void disposeEditingContextEventProcessor(String editingContextId) { this.logger.trace("Editing context event processors count: {}", this.editingContextEventProcessors.size()); } - @PreDestroy public void dispose() { this.logger.debug("Shutting down all the editing context event processors"); diff --git a/packages/core/backend/sirius-components-core/src/main/java/org/eclipse/sirius/components/core/RepresentationMetadata.java b/packages/core/backend/sirius-components-core/src/main/java/org/eclipse/sirius/components/core/RepresentationMetadata.java index c75d392ac3..d4e660ec28 100644 --- a/packages/core/backend/sirius-components-core/src/main/java/org/eclipse/sirius/components/core/RepresentationMetadata.java +++ b/packages/core/backend/sirius-components-core/src/main/java/org/eclipse/sirius/components/core/RepresentationMetadata.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2022 Obeo. + * Copyright (c) 2022, 2023 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 @@ -29,14 +29,11 @@ public class RepresentationMetadata { private final String descriptionId; - private final String targetObjectId; - - public RepresentationMetadata(String id, String kind, String label, String descriptionId, String targetObjectId) { + public RepresentationMetadata(String id, String kind, String label, String descriptionId) { this.id = Objects.requireNonNull(id); this.kind = Objects.requireNonNull(kind); this.label = Objects.requireNonNull(label); this.descriptionId = Objects.requireNonNull(descriptionId); - this.targetObjectId = targetObjectId; } public String getId() { @@ -55,13 +52,9 @@ public String getDescriptionId() { return this.descriptionId; } - public String getTargetObjectId() { - return this.targetObjectId; - } - @Override public String toString() { - String pattern = "{0} '{'id: {1}, kind: {2}, label: {3}, descriptionId: {4}, targetObjectId: {5}'}'"; - return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.kind, this.label, this.descriptionId, this.targetObjectId); + String pattern = "{0} '{'id: {1}, kind: {2}, label: {3}, descriptionId: {4}'}'"; + return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.kind, this.label, this.descriptionId); } } diff --git a/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationDataFetcher.java b/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationDataFetcher.java index 93523f0d62..f2e54eab7e 100644 --- a/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationDataFetcher.java +++ b/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationDataFetcher.java @@ -15,7 +15,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; -import java.util.Optional; import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher; import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessorRegistry; @@ -24,7 +23,6 @@ import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; import org.eclipse.sirius.components.graphql.api.LocalContextConstants; import org.eclipse.sirius.components.representations.IRepresentation; -import org.eclipse.sirius.components.representations.ISemanticRepresentation; import org.eclipse.sirius.web.services.api.representations.IRepresentationService; import org.eclipse.sirius.web.services.api.representations.RepresentationDescriptor; @@ -66,7 +64,6 @@ public DataFetcherResult get(DataFetchingEnvironment env Map localContext = new HashMap<>(environment.getLocalContext()); localContext.put(LocalContextConstants.REPRESENTATION_ID, representationId); - // Search among the active representations first. They are already loaded in memory and include transient // representations. // @formatter:off @@ -74,14 +71,13 @@ public DataFetcherResult get(DataFetchingEnvironment env .flatMap(editingContextEventProcessor -> editingContextEventProcessor.getRepresentationEventProcessors().stream()) .filter(editingContextEventProcessor -> editingContextEventProcessor.getRepresentation().getId().equals(representationId)) .map(IRepresentationEventProcessor::getRepresentation) - .filter(ISemanticRepresentation.class::isInstance) - .map(ISemanticRepresentation.class::cast) - .map((ISemanticRepresentation representation) -> { + .filter(IRepresentation.class::isInstance) + .map(IRepresentation.class::cast) + .map((IRepresentation representation) -> { return new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), - representation.getDescriptionId(), - representation.getTargetObjectId()); + representation.getDescriptionId()); }) .findFirst(); // @formatter:on @@ -101,14 +97,7 @@ public DataFetcherResult get(DataFetchingEnvironment env } private RepresentationMetadata toRepresentationMetadata(IRepresentation representation) { - // @formatter:off - String targetObjectId = Optional.of(representation) - .filter(ISemanticRepresentation.class::isInstance) - .map(ISemanticRepresentation.class::cast) - .map(ISemanticRepresentation::getTargetObjectId) - .orElse(null); - // @formatter:on - return new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId(), targetObjectId); + return new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId()); } } diff --git a/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationsDataFetcher.java b/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationsDataFetcher.java index 90d5318d1d..516ed3543c 100644 --- a/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationsDataFetcher.java +++ b/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/editingcontext/EditingContextRepresentationsDataFetcher.java @@ -15,13 +15,11 @@ import java.util.Base64; import java.util.List; import java.util.Objects; -import java.util.Optional; import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher; import org.eclipse.sirius.components.core.RepresentationMetadata; import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; import org.eclipse.sirius.components.representations.IRepresentation; -import org.eclipse.sirius.components.representations.ISemanticRepresentation; import org.eclipse.sirius.web.services.api.representations.IRepresentationService; import org.eclipse.sirius.web.services.api.representations.RepresentationDescriptor; @@ -90,14 +88,7 @@ public Connection get(DataFetchingEnvironment environmen } private RepresentationMetadata toRepresentationMetadata(IRepresentation representation) { - // @formatter:off - String targetObjectId = Optional.of(representation) - .filter(ISemanticRepresentation.class::isInstance) - .map(ISemanticRepresentation.class::cast) - .map(ISemanticRepresentation::getTargetObjectId) - .orElse(null); - // @formatter:on - return new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId(), targetObjectId); + return new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId()); } } diff --git a/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/representation/RepresentationMetadataDescriptionDataFetcher.java b/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/representation/RepresentationMetadataDescriptionDataFetcher.java index d1051fe8d3..09144d8a06 100644 --- a/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/representation/RepresentationMetadataDescriptionDataFetcher.java +++ b/packages/sirius-web/backend/sirius-web-graphql/src/main/java/org/eclipse/sirius/web/graphql/datafetchers/representation/RepresentationMetadataDescriptionDataFetcher.java @@ -18,18 +18,24 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.BiFunction; import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher; import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessorRegistry; import org.eclipse.sirius.components.collaborative.dto.GetRepresentationDescriptionInput; import org.eclipse.sirius.components.collaborative.dto.GetRepresentationDescriptionPayload; import org.eclipse.sirius.components.collaborative.forms.PropertiesEventProcessorFactory; +import org.eclipse.sirius.components.collaborative.trees.TreeEventProcessorFactory; import org.eclipse.sirius.components.core.RepresentationMetadata; import org.eclipse.sirius.components.forms.description.FormDescription; import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; import org.eclipse.sirius.components.graphql.api.LocalContextConstants; +import org.eclipse.sirius.components.representations.Failure; import org.eclipse.sirius.components.representations.GetOrCreateRandomIdProvider; import org.eclipse.sirius.components.representations.IRepresentationDescription; +import org.eclipse.sirius.components.representations.IStatus; +import org.eclipse.sirius.components.representations.VariableManager; +import org.eclipse.sirius.components.trees.description.TreeDescription; import graphql.schema.DataFetchingEnvironment; import reactor.core.publisher.Mono; @@ -42,6 +48,8 @@ @QueryDataFetcher(type = "RepresentationMetadata", field = "description") public class RepresentationMetadataDescriptionDataFetcher implements IDataFetcherWithFieldCoordinates> { + static final BiFunction FAKE_RENAME_HANDLER = (a, b) -> new Failure(""); + // @formatter:off private static final IRepresentationDescription FAKE_DETAILS_DESCRIPTION = FormDescription.newFormDescription(PropertiesEventProcessorFactory.DETAILS_VIEW_ID) .label(PropertiesEventProcessorFactory.DETAILS_VIEW_ID) @@ -53,8 +61,28 @@ public class RepresentationMetadataDescriptionDataFetcher implements IDataFetche .build(); // @formatter:on + // @formatter:off + private static final IRepresentationDescription FAKE_DETAILS_TREE = TreeDescription.newTreeDescription(TreeEventProcessorFactory.DETAILS_VIEW_ID) + .label("Explorer") + .idProvider(new GetOrCreateRandomIdProvider()) + .treeItemIdProvider(variableManager -> TreeEventProcessorFactory.DETAILS_VIEW_ID) + .kindProvider(variableManager -> TreeEventProcessorFactory.DETAILS_VIEW_ID) + .labelProvider(variableManager -> TreeEventProcessorFactory.DETAILS_VIEW_ID) + .imageURLProvider(variableManager -> TreeEventProcessorFactory.DETAILS_VIEW_ID) + .editableProvider(variableManager -> null) + .deletableProvider(variableManager -> null) + .elementsProvider(variableManager -> null) + .hasChildrenProvider(variableManager -> null) + .childrenProvider(variableManager -> null) + .canCreatePredicate(variableManager -> true) + .deleteHandler(variableManager -> null) + .renameHandler(FAKE_RENAME_HANDLER) + .build(); + // @formatter:on + private final IEditingContextEventProcessorRegistry editingContextEventProcessorRegistry; + public RepresentationMetadataDescriptionDataFetcher(IEditingContextEventProcessorRegistry editingContextEventProcessorRegistry) { this.editingContextEventProcessorRegistry = Objects.requireNonNull(editingContextEventProcessorRegistry); } @@ -64,7 +92,7 @@ public CompletableFuture get(DataFetchingEnvironment CompletableFuture result = Mono.empty().toFuture(); RepresentationMetadata representationMetadata = environment.getSource(); - if (Objects.equals(PropertiesEventProcessorFactory.DETAILS_VIEW_ID, representationMetadata.getDescriptionId())) { + if (Objects.equals(PropertiesEventProcessorFactory.DETAILS_VIEW_ID, representationMetadata.getDescriptionId()) || (Objects.equals(TreeEventProcessorFactory.DETAILS_VIEW_ID, representationMetadata.getDescriptionId()))) { /* * The FormDescription used for the details view can not be found by * IRepresentationDescriptionSearchService, but we can get away by returning a fake one with the same id as @@ -72,7 +100,12 @@ public CompletableFuture get(DataFetchingEnvironment * only to allow GraphQL resolution to continue on queries like "completionProposals" defined on * FormDescription. */ - result = Mono.just(FAKE_DETAILS_DESCRIPTION).toFuture(); + if (Objects.equals(PropertiesEventProcessorFactory.DETAILS_VIEW_ID, representationMetadata.getDescriptionId())) { + result = Mono.just(FAKE_DETAILS_DESCRIPTION).toFuture(); + } + if (Objects.equals(TreeEventProcessorFactory.DETAILS_VIEW_ID, representationMetadata.getDescriptionId())) { + result = Mono.just(FAKE_DETAILS_TREE).toFuture(); + } } else { Map localContext = environment.getLocalContext(); @@ -80,7 +113,6 @@ public CompletableFuture get(DataFetchingEnvironment String representationId = Optional.ofNullable(localContext.get(LocalContextConstants.REPRESENTATION_ID)).map(Object::toString).orElse(null); if (editingContextId != null && representationId != null) { GetRepresentationDescriptionInput input = new GetRepresentationDescriptionInput(UUID.randomUUID(), editingContextId, representationId); - // @formatter:off result = this.editingContextEventProcessorRegistry.dispatchEvent(input.editingContextId(), input) .filter(GetRepresentationDescriptionPayload.class::isInstance) diff --git a/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/FlowProjectTemplatesInitializer.java b/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/FlowProjectTemplatesInitializer.java index d891604d08..671806e5a5 100644 --- a/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/FlowProjectTemplatesInitializer.java +++ b/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/FlowProjectTemplatesInitializer.java @@ -19,12 +19,14 @@ import fr.obeo.dsl.designer.sample.flow.FlowFactory; import fr.obeo.dsl.designer.sample.flow.Processor; import fr.obeo.dsl.designer.sample.flow.System; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.UUID; + import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain; import org.eclipse.sirius.components.collaborative.api.IRepresentationPersistenceService; @@ -133,7 +135,7 @@ private Optional initializeFlowProject(IEditingContext e Diagram diagram = this.diagramCreationService.create(topographyDiagram.getLabel(), semanticTarget, topographyDiagram, editingContext); this.representationPersistenceService.save(editingContext, diagram); - result = Optional.of(new RepresentationMetadata(diagram.getId(), diagram.getKind(), diagram.getLabel(), diagram.getDescriptionId(), diagram.getTargetObjectId())); + result = Optional.of(new RepresentationMetadata(diagram.getId(), diagram.getKind(), diagram.getLabel(), diagram.getDescriptionId())); } } catch (IOException exception) { this.logger.warn(exception.getMessage(), exception); diff --git a/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/StudioProjectTemplatesInitializer.java b/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/StudioProjectTemplatesInitializer.java index b9354efed2..a77d015d00 100644 --- a/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/StudioProjectTemplatesInitializer.java +++ b/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/configuration/StudioProjectTemplatesInitializer.java @@ -159,7 +159,7 @@ private Optional initializeStudioProject(IEditingContext Diagram diagram = this.diagramCreationService.create(topographyDiagram.getLabel(), semanticTarget, topographyDiagram, editingContext); this.representationPersistenceService.save(editingContext, diagram); - result = Optional.of(new RepresentationMetadata(diagram.getId(), diagram.getKind(), diagram.getLabel(), diagram.getDescriptionId(), diagram.getTargetObjectId())); + result = Optional.of(new RepresentationMetadata(diagram.getId(), diagram.getKind(), diagram.getLabel(), diagram.getDescriptionId())); } } catch (IOException exception) { this.logger.warn(exception.getMessage(), exception); diff --git a/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/papaya/PapayaStudioTemplatesInitializer.java b/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/papaya/PapayaStudioTemplatesInitializer.java index fe7d6fa653..fbe270c284 100644 --- a/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/papaya/PapayaStudioTemplatesInitializer.java +++ b/packages/sirius-web/backend/sirius-web-sample-application/src/main/java/org/eclipse/sirius/web/sample/papaya/PapayaStudioTemplatesInitializer.java @@ -133,7 +133,7 @@ private Optional initializeStudioProject(IEditingContext Diagram diagram = this.diagramCreationService.create(topographyDiagram.getLabel(), semanticTarget, topographyDiagram, editingContext); this.representationPersistenceService.save(editingContext, diagram); - result = Optional.of(new RepresentationMetadata(diagram.getId(), diagram.getKind(), diagram.getLabel(), diagram.getDescriptionId(), diagram.getTargetObjectId())); + result = Optional.of(new RepresentationMetadata(diagram.getId(), diagram.getKind(), diagram.getLabel(), diagram.getDescriptionId())); } } catch (IOException exception) { this.logger.warn(exception.getMessage(), exception); diff --git a/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/explorer/ExplorerInitialDirectEditTreeItemLabelProvider.java b/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/explorer/ExplorerInitialDirectEditTreeItemLabelProvider.java new file mode 100644 index 0000000000..7ec4ad2287 --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/explorer/ExplorerInitialDirectEditTreeItemLabelProvider.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +package org.eclipse.sirius.web.services.explorer; + +import java.util.Optional; + +import org.eclipse.sirius.components.collaborative.trees.api.IInitialDirectEditTreeItemLabelProvider; +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelInput; +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelSuccessPayload; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.trees.Tree; +import org.eclipse.sirius.components.trees.TreeItem; +import org.springframework.stereotype.Service; + +/** + * ExplorerInitialDirectEditTreeItemLabelProvider. + * + * @author mcharfadi + */ +@Service +public class ExplorerInitialDirectEditTreeItemLabelProvider implements IInitialDirectEditTreeItemLabelProvider { + + @Override + public boolean canHandle(Tree tree) { + return tree.getId().startsWith("explorer://"); + } + + @Override + public IPayload handle(IEditingContext editingContext, Tree tree, InitialDirectEditElementLabelInput input) { + String initialLabel = tree.getChildren().stream() + .map(treeItems -> this.searchById(treeItems, input.treeItemId())) + .filter(Optional::isPresent) + .map(Optional::get) + .map(treeItem -> treeItem.getLabel()) + .findFirst() + .orElse(""); + + return new InitialDirectEditElementLabelSuccessPayload(input.id(), initialLabel); + } + + private Optional searchById(TreeItem treeItem, String id) { + Optional optionalTreeItem = Optional.empty(); + if (treeItem.getId().equals(id)) { + optionalTreeItem = Optional.of(treeItem); + } + if (optionalTreeItem.isEmpty() && treeItem.isHasChildren()) { + optionalTreeItem = treeItem.getChildren().stream() + .map(treeItems -> this.searchById(treeItems, id)) + .filter(Optional::isPresent) + .map(Optional::get).findFirst(); + } + return optionalTreeItem; + } + +} diff --git a/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java b/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java index bf4d8f4859..4e53fa3969 100644 --- a/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java +++ b/packages/sirius-web/backend/sirius-web-services/src/main/java/org/eclipse/sirius/web/services/representations/RepresentationService.java @@ -14,6 +14,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; + import java.util.List; import java.util.Objects; import java.util.Optional; @@ -50,6 +51,12 @@ @Service public class RepresentationService implements IRepresentationService, IRepresentationPersistenceService, IDanglingRepresentationDeletionService { + public static final String EXPLORER_DESCRIPTION_ID = UUID.nameUUIDFromBytes("explorer_tree_description".getBytes()).toString(); + + public static final String EXPLORER_DOCUMENT_KIND = "siriusComponents://representation?type=Tree"; + + public static final String EXPLORER_NAME = "Explorer"; + private static final String TIMER_NAME = "siriusweb_representation_save"; private final IObjectService objectService; @@ -182,25 +189,21 @@ public void deleteDanglingRepresentations(String editingContextId) { @Override public Optional findByRepresentationId(String representationId) { + if (representationId.startsWith("explorer://")) { + return Optional.of(new RepresentationMetadata(EXPLORER_DESCRIPTION_ID, EXPLORER_DOCUMENT_KIND, EXPLORER_NAME, representationId)); + } return new IDParser().parse(representationId) .flatMap(this.representationRepository::findById) .map(new RepresentationMapper(this.objectMapper)::toDTO) .map(RepresentationDescriptor::getRepresentation) - .filter(ISemanticRepresentation.class::isInstance) - .map(ISemanticRepresentation.class::cast) - .map(representation -> new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId(), representation.getTargetObjectId())); + .filter(IRepresentation.class::isInstance) + .map(IRepresentation.class::cast) + .map(representation -> new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId())); } @Override public Optional findByRepresentation(IRepresentation representation) { - // @formatter:off - String targetObjectId = Optional.of(representation) - .filter(ISemanticRepresentation.class::isInstance) - .map(ISemanticRepresentation.class::cast) - .map(ISemanticRepresentation::getTargetObjectId) - .orElse(null); - // @formatter:on - return Optional.of(new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId(), targetObjectId)); + return Optional.of(new RepresentationMetadata(representation.getId(), representation.getKind(), representation.getLabel(), representation.getDescriptionId())); } @Override diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java index 3e52b61ab4..6b42654c36 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeEventProcessorFactory.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2022 Obeo. + * Copyright (c) 2019, 2023 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 @@ -15,6 +15,7 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessor; @@ -41,6 +42,8 @@ @Service public class TreeEventProcessorFactory implements IRepresentationEventProcessorFactory { + public static final String DETAILS_VIEW_ID = UUID.nameUUIDFromBytes("explorer_tree_description".getBytes()).toString(); + private final IExplorerDescriptionProvider explorerDescriptionProvider; private final ITreeService treeService; diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/IInitialDirectEditTreeItemLabelProvider.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/IInitialDirectEditTreeItemLabelProvider.java new file mode 100644 index 0000000000..da8267aae2 --- /dev/null +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/IInitialDirectEditTreeItemLabelProvider.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.trees.api; + +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelInput; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.trees.Tree; + +/** + * IInitialDirectEditTreeItemLabelProvider. + * + * @author mcharfadi + */ +public interface IInitialDirectEditTreeItemLabelProvider { + + boolean canHandle(Tree treeDescription); + + IPayload handle(IEditingContext editingContext, Tree tree, InitialDirectEditElementLabelInput input); +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java index b2cedd79ef..312ebba71f 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/TreeConfiguration.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2021 Obeo. + * Copyright (c) 2019, 2023 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 @@ -31,7 +31,7 @@ public class TreeConfiguration implements IRepresentationConfiguration { public TreeConfiguration(String editingContextId, List expanded) { String uniqueId = editingContextId + expanded.toString(); - this.treeId = UUID.nameUUIDFromBytes(uniqueId.getBytes()).toString(); + this.treeId = "explorer://" + UUID.nameUUIDFromBytes(uniqueId.getBytes()).toString(); this.expanded = Objects.requireNonNull(expanded); } diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelInput.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelInput.java new file mode 100644 index 0000000000..9d3f0ec83b --- /dev/null +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelInput.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.trees.dto; + +import java.util.UUID; + +import org.eclipse.sirius.components.collaborative.trees.api.ITreeInput; + +/** + * The "initial direct edit element label" query input. + * + * @author mcharfadi + */ +public record InitialDirectEditElementLabelInput(UUID id, String editingContextId, String representationId, String treeItemId, String initialLabel) implements ITreeInput { +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelSuccessPayload.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelSuccessPayload.java new file mode 100644 index 0000000000..7df4971be2 --- /dev/null +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/InitialDirectEditElementLabelSuccessPayload.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.trees.dto; + +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.sirius.components.core.api.IPayload; + +/** + * The "initial direct edit element label" success payload. + * + * @author mcharfadi + */ +public record InitialDirectEditElementLabelSuccessPayload(UUID id, String initialDirectEditElementLabel) implements IPayload { + public InitialDirectEditElementLabelSuccessPayload { + Objects.requireNonNull(id); + Objects.requireNonNull(initialDirectEditElementLabel); + } +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java index 7e8bbbf24e..f225b2522a 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/dto/TreeEventInput.java @@ -22,5 +22,5 @@ * * @author sbegaudeau */ -public record TreeEventInput(UUID id, String editingContextId, List expanded) implements IInput { +public record TreeEventInput(UUID id, String editingContextId, String treeId, List expanded) implements IInput { } diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/handlers/InitialDirectEditTreeItemLabelEventHandler.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/handlers/InitialDirectEditTreeItemLabelEventHandler.java new file mode 100644 index 0000000000..73a053650a --- /dev/null +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/handlers/InitialDirectEditTreeItemLabelEventHandler.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.trees.handlers; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.sirius.components.collaborative.api.ChangeDescription; +import org.eclipse.sirius.components.collaborative.api.ChangeKind; +import org.eclipse.sirius.components.collaborative.trees.api.IInitialDirectEditTreeItemLabelProvider; +import org.eclipse.sirius.components.collaborative.trees.api.ITreeEventHandler; +import org.eclipse.sirius.components.collaborative.trees.api.ITreeInput; +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelInput; +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelSuccessPayload; +import org.eclipse.sirius.components.core.api.ErrorPayload; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.trees.Tree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import reactor.core.publisher.Sinks.Many; +import reactor.core.publisher.Sinks.One; + +/** + * Handler for InitialDirectEditElementLabelInput which simply forwards to the first available IInitialDirectEditTreeItemLabelProvider which can handle the + * request. + * + * @author mcharfadi + */ +@Service +public class InitialDirectEditTreeItemLabelEventHandler implements ITreeEventHandler { + + private final Logger logger = LoggerFactory.getLogger(InitialDirectEditTreeItemLabelEventHandler.class); + + private final List initialDirectEditTreeItemLabelProvider; + + public InitialDirectEditTreeItemLabelEventHandler(List initialDirectEditTreeItemLabelProvider) { + this.initialDirectEditTreeItemLabelProvider = Objects.requireNonNull(initialDirectEditTreeItemLabelProvider); + } + + @Override + public boolean canHandle(ITreeInput treeInput) { + return treeInput instanceof InitialDirectEditElementLabelInput; + } + + @Override + public void handle(One payloadSink, Many changeDescriptionSink, IEditingContext editingContext, Tree tree, ITreeInput treeInput) { + IPayload payload = new InitialDirectEditElementLabelSuccessPayload(treeInput.id(), ""); + ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, treeInput.representationId(), treeInput); + + if (treeInput instanceof InitialDirectEditElementLabelInput input) { + Optional optionalPathProvider = this.initialDirectEditTreeItemLabelProvider.stream().filter(treePathProvider -> treePathProvider.canHandle(tree)).findFirst(); + if (optionalPathProvider.isPresent()) { + IPayload resultPayload = optionalPathProvider.get().handle(editingContext, tree, input); + if (resultPayload instanceof InitialDirectEditElementLabelSuccessPayload) { + payload = resultPayload; + } else if (resultPayload instanceof ErrorPayload errorPayload) { + this.logger.warn(errorPayload.message()); + } + } + } + changeDescriptionSink.tryEmitNext(changeDescription); + payloadSink.tryEmitValue(payload); + } + +} \ No newline at end of file diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls b/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls index 5c2bf3c915..ec4848e6b3 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/resources/schema/tree.graphqls @@ -14,6 +14,7 @@ type TreePath { input TreeEventInput { id: ID! + treeId: String! editingContextId: ID! expanded: [String!]! } @@ -46,6 +47,7 @@ type TreeItem { type TreeDescription implements RepresentationDescription { id: ID! label: String! + initialDirectEditTreeItemLabel(treeItemId: ID!): String! } extend type Mutation { @@ -63,11 +65,11 @@ input DeleteTreeItemInput { union DeleteTreeItemPayload = SuccessPayload | ErrorPayload input RenameTreeItemInput { - id: ID! - editingContextId: ID! - representationId: ID! - treeItemId: ID! - newLabel: String! + id: ID! + editingContextId: ID! + representationId: ID! + treeItemId: ID! + newLabel: String! } union RenameTreeItemPayload = SuccessPayload | ErrorPayload diff --git a/packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/tree/TreeItemInitialDirectEditLabelDataFetcher.java b/packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/tree/TreeItemInitialDirectEditLabelDataFetcher.java new file mode 100644 index 0000000000..a9639be1cc --- /dev/null +++ b/packages/trees/backend/sirius-components-trees-graphql/src/main/java/org/eclipse/sirius/components/trees/graphql/datafetchers/tree/TreeItemInitialDirectEditLabelDataFetcher.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +package org.eclipse.sirius.components.trees.graphql.datafetchers.tree; + +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher; +import org.eclipse.sirius.components.collaborative.api.IEditingContextEventProcessorRegistry; +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelInput; +import org.eclipse.sirius.components.collaborative.trees.dto.InitialDirectEditElementLabelSuccessPayload; +import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; +import org.eclipse.sirius.components.graphql.api.LocalContextConstants; + +import graphql.schema.DataFetchingEnvironment; +import reactor.core.publisher.Mono; + +/** + * The "initial direct edit element label" query input. + * + * @author mcharfadi + */ +@QueryDataFetcher(type = "TreeDescription", field = "initialDirectEditTreeItemLabel") +public class TreeItemInitialDirectEditLabelDataFetcher implements IDataFetcherWithFieldCoordinates> { + + private static final String INPUT_ARGUMENT = "treeItemId"; + + private final IEditingContextEventProcessorRegistry editingContextEventProcessorRegistry; + + public TreeItemInitialDirectEditLabelDataFetcher(IEditingContextEventProcessorRegistry editingContextEventProcessorRegistry) { + this.editingContextEventProcessorRegistry = Objects.requireNonNull(editingContextEventProcessorRegistry); + } + + @Override + public CompletableFuture get(DataFetchingEnvironment environment) throws Exception { + Map localContext = environment.getLocalContext(); + String editingContextId = Optional.ofNullable(localContext.get(LocalContextConstants.EDITING_CONTEXT_ID)).map(Object::toString).orElse(null); + String representationId = Optional.ofNullable(localContext.get(LocalContextConstants.REPRESENTATION_ID)).map(Object::toString).orElse(null); + String treeItemId = environment.getArgument(INPUT_ARGUMENT); + + if (editingContextId != null && representationId != null && treeItemId != null) { + var input = new InitialDirectEditElementLabelInput(UUID.randomUUID(), editingContextId, representationId, treeItemId, ""); + // @formatter:off + return this.editingContextEventProcessorRegistry.dispatchEvent(editingContextId, input) + .filter(InitialDirectEditElementLabelSuccessPayload.class::isInstance) + .map(InitialDirectEditElementLabelSuccessPayload.class::cast) + .map(InitialDirectEditElementLabelSuccessPayload::initialDirectEditElementLabel) + .toFuture(); + // @formatter:on + } + return Mono. empty().toFuture(); + } +} diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx index ce6cd3839a..5e870ee617 100644 --- a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItem.tsx @@ -10,10 +10,8 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { gql, useMutation } from '@apollo/client'; import { DRAG_SOURCES_TYPE, Selection, SelectionEntry, ServerContext } from '@eclipse-sirius/sirius-components-core'; import IconButton from '@material-ui/core/IconButton'; -import TextField from '@material-ui/core/TextField'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import CropDinIcon from '@material-ui/icons/CropDin'; @@ -22,19 +20,9 @@ import React, { useContext, useEffect, useRef, useState } from 'react'; import { TreeItemProps } from './TreeItem.types'; import { TreeItemArrow } from './TreeItemArrow'; import { TreeItemContextMenu, TreeItemContextMenuContext } from './TreeItemContextMenu'; +import { TreeItemDirectEditInput } from './TreeItemDirectEditInput'; import { isFilterCandidate } from './filterTreeItem'; -const renameTreeItemMutation = gql` - mutation renameTreeItem($input: RenameTreeItemInput!) { - renameTreeItem(input: $input) { - __typename - ... on ErrorPayload { - message - } - } - } -`; - const useTreeItemStyle = makeStyles((theme) => ({ treeItem: { display: 'flex', @@ -137,41 +125,14 @@ export const TreeItem = ({ editingMode: false, label: item.label, prevSelectionId: null, + editingkey: '', }; + const [state, setState] = useState(initialState); - const { showContextMenu, menuAnchor, editingMode, label } = state; + const { showContextMenu, menuAnchor, editingMode } = state; const refDom = useRef() as any; - const [renameTreeItem, { loading: renameTreeItemLoading, data: renameTreeItemData, error: renameTreeItemError }] = - useMutation(renameTreeItemMutation); - useEffect(() => { - if (!renameTreeItemLoading && !renameTreeItemError && renameTreeItemData?.renameTreeItem) { - const { renameTreeItem } = renameTreeItemData; - if (renameTreeItem.__typename === 'SuccessPayload') { - setState((prevState) => { - return { ...prevState, editingMode: false }; - }); - } - } - }, [renameTreeItemData, renameTreeItemError, renameTreeItemLoading]); - - // custom hook for getting previous value - const usePrevious = (value) => { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; - }; - - const prevEditingMode = usePrevious(editingMode); - useEffect(() => { - if (prevEditingMode && !editingMode) { - refDom.current.focus(); - } - }, [editingMode, prevEditingMode]); - // Context menu handling const openContextMenu = (event) => { if (!showContextMenu) { @@ -181,6 +142,7 @@ export const TreeItem = ({ showContextMenu: true, menuAnchor: currentTarget, editingMode: false, + editingkey: '', label: item.label, prevSelectionId: prevState.prevSelectionId, }; @@ -197,6 +159,7 @@ export const TreeItem = ({ showContextMenu: false, menuAnchor: null, editingMode: false, + editingkey: '', label: item.label, prevSelectionId: prevState.prevSelectionId, }; @@ -209,6 +172,7 @@ export const TreeItem = ({ showContextMenu: false, menuAnchor: null, editingMode: true, + editingkey: '', label: item.label, prevSelectionId: prevState.prevSelectionId, }; @@ -286,61 +250,21 @@ export const TreeItem = ({ } const highlightRegExp = new RegExp(`(${textToHighlight})`, 'gi'); let text; + const onCloseEditingMode = () => { + setState((prevState) => { + return { ...prevState, editingMode: false }; + }); + refDom.current.focus(); + }; if (editingMode) { - const handleChange = (event) => { - const newLabel = event.target.value; - setState((prevState) => { - return { ...prevState, editingMode: true, label: newLabel }; - }); - }; - - const doRename = () => { - const isNameValid = label.length >= 1; - if (isNameValid && item) { - renameTreeItem({ - variables: { - input: { - id: crypto.randomUUID(), - editingContextId, - representationId: treeId, - treeItemId: item.id, - newLabel: label, - }, - }, - }); - } else { - setState((prevState) => { - return { ...prevState, editingMode: false, label: item.label }; - }); - } - }; - const onFinishEditing = (event) => { - const { key } = event; - if (key === 'Enter') { - doRename(); - } else if (key === 'Escape') { - setState((prevState) => { - return { ...prevState, editingMode: false, label: item.label }; - }); - } - }; - const onFocusIn = (event) => { - event.target.select(); - }; - const onFocusOut = () => doRename(); text = ( - + ); } else { let itemLabel: JSX.Element; @@ -373,23 +297,25 @@ export const TreeItem = ({ } const onClick: React.MouseEventHandler = (event) => { - refDom.current.focus(); + if (!state.editingMode) { + refDom.current.focus(); - if (event.ctrlKey || event.metaKey) { - event.stopPropagation(); - const isItemInSelection = selection.entries.find((entry) => entry.id === item.id); - if (isItemInSelection) { - const newSelection: Selection = { entries: selection.entries.filter((entry) => entry.id !== item.id) }; - setSelection(newSelection); + if (event.ctrlKey || event.metaKey) { + event.stopPropagation(); + const isItemInSelection = selection.entries.find((entry) => entry.id === item.id); + if (isItemInSelection) { + const newSelection: Selection = { entries: selection.entries.filter((entry) => entry.id !== item.id) }; + setSelection(newSelection); + } else { + const { id, label, kind } = item; + const newEntry = { id, label, kind }; + const newSelection: Selection = { entries: [...selection.entries, newEntry] }; + setSelection(newSelection); + } } else { const { id, label, kind } = item; - const newEntry = { id, label, kind }; - const newSelection: Selection = { entries: [...selection.entries, newEntry] }; - setSelection(newSelection); + setSelection({ entries: [{ id, label, kind }] }); } - } else { - const { id, label, kind } = item; - setSelection({ entries: [{ id, label, kind }] }); } }; @@ -406,7 +332,7 @@ export const TreeItem = ({ !event.metaKey && !event.ctrlKey && key.length === 1 && directEditActivationValidCharacters.test(key); if (validFirstInputChar) { setState((prevState) => { - return { ...prevState, editingMode: true, label: key }; + return { ...prevState, editingMode: true, editingkey: key }; }); } }; diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.tsx b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.tsx new file mode 100644 index 0000000000..b91e4c7110 --- /dev/null +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.tsx @@ -0,0 +1,164 @@ +/******************************************************************************* + * Copyright (c) 2023 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 { gql, useMutation, useQuery } from '@apollo/client'; +import TextField from '@material-ui/core/TextField'; +import { useEffect, useState } from 'react'; +import { + GQLInitialDirectEditElementLabelData, + GQLInitialDirectEditElementLabelInput, + TreeItemDirectEditInputProps, +} from './TreeItemDirectEditInput.types'; + +const renameTreeItemMutation = gql` + mutation renameTreeItem($input: RenameTreeItemInput!) { + renameTreeItem(input: $input) { + __typename + ... on ErrorPayload { + message + } + } + } +`; + +const initialDirectEditElementLabeQuery = gql` + query initialDirectEditElementLabel($editingContextId: ID!, $representationId: ID!, $treeItemId: ID!) { + viewer { + editingContext(editingContextId: $editingContextId) { + representation(representationId: $representationId) { + description { + ... on TreeDescription { + initialDirectEditTreeItemLabel(treeItemId: $treeItemId) + } + } + } + } + } + } +`; + +export const TreeItemDirectEditInput = ({ + editingContextId, + treeId, + treeItemId, + refParent, + editingkey, + onClose, +}: TreeItemDirectEditInputProps) => { + const [state, setState] = useState({ + newLabel: '', + editingkey: editingkey, + }); + + const { + loading: initialLabelTreeItemLoading, + data: initialLabelTreeItemItemData, + error: initialLabelTreeItemItemError, + } = useQuery( + initialDirectEditElementLabeQuery, + { + variables: { + editingContextId: editingContextId, + representationId: treeId, + treeItemId: treeItemId, + }, + } + ); + + useEffect(() => { + if ( + !initialLabelTreeItemLoading && + !initialLabelTreeItemItemError && + initialLabelTreeItemItemData?.viewer.editingContext.representation.description.initialDirectEditTreeItemLabel + ) { + const initialLabel = + initialLabelTreeItemItemData?.viewer.editingContext.representation.description.initialDirectEditTreeItemLabel; + setState((prevState) => { + return { ...prevState, newLabel: initialLabel + state.editingkey }; + }); + } + }, [initialLabelTreeItemLoading, initialLabelTreeItemItemError, initialLabelTreeItemItemData]); + + const [renameTreeItem, { loading: renameTreeItemLoading, data: renameTreeItemData, error: renameTreeItemError }] = + useMutation(renameTreeItemMutation); + + useEffect(() => { + if (!renameTreeItemLoading && !renameTreeItemError && renameTreeItemData?.renameTreeItem) { + const { renameTreeItem } = renameTreeItemData; + if (renameTreeItem.__typename === 'SuccessPayload') { + onClose(); + } + } + }, [renameTreeItemData, renameTreeItemError, renameTreeItemLoading]); + + const doRename = () => { + const isNameValid = state.newLabel.length >= 1; + if (isNameValid) { + renameTreeItem({ + variables: { + input: { + id: crypto.randomUUID(), + editingContextId: editingContextId, + representationId: treeId, + treeItemId: treeItemId, + newLabel: state.newLabel, + }, + }, + }); + } else { + onClose(); + } + }; + + const handleChange = (event) => { + const newLabel = event.target.value; + setState((prevState) => { + return { ...prevState, newLabel: newLabel }; + }); + }; + + const onFinishEditing = (event) => { + const { key } = event; + if (key === 'Enter') { + doRename(); + } else if (key === 'Escape') { + onClose(); + } + }; + + const onFocusIn = (event) => event.target.select(); + + const onFocusOut = (event) => { + //Prevents calling doRename when pressing Escape + if (!event.relatedTarget || event.relatedTarget != refParent) { + doRename(); + } + }; + + return ( + <> + onFocusOut(event)} + onClick={(event) => onFocusOut(event)} + onKeyDown={onFinishEditing} + autoFocus + data-testid="name-edit" + /> + + ); +}; diff --git a/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.types.ts b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.types.ts new file mode 100644 index 0000000000..8cb5ec1433 --- /dev/null +++ b/packages/trees/frontend/sirius-components-trees/src/treeitems/TreeItemDirectEditInput.types.ts @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2023 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 + *******************************************************************************/ +export interface TreeItemDirectEditInputProps { + editingContextId: string; + treeId: string; + treeItemId: string; + editingkey: string; + refParent: any; + onClose: () => void; +} + +export interface GQLInitialDirectEditElementLabelInput { + editingContextId: string; + representationId: string; + treeItemId: string; +} + +export interface GQLInitialDirectEditElementLabelData { + viewer: GQLViewer; +} + +export interface GQLViewer { + editingContext: GQLEditingContext; +} + +export interface GQLEditingContext { + representation: GQLRepresentation; +} + +export interface GQLRepresentation { + description: GQLRepresentationDescription; +} + +export interface GQLRepresentationDescription { + __typename: string; + initialDirectEditTreeItemLabel: string; +} diff --git a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx b/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx index bbf36acf2f..0267dd9877 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx @@ -170,6 +170,7 @@ export const ExplorerView = ({ editingContextId, selection, setSelection, readOn variables: { input: { id, + treeId: 'explorer', editingContextId, expanded, }, diff --git a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts b/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts index aa40b0d5eb..509560b24b 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts @@ -17,6 +17,7 @@ export interface GQLExplorerEventVariables { export interface GQLExplorerEventInput { id: string; + treeId: string; editingContextId: string; expanded: string[]; }