From ba62d46aaab0723d86ce6a5ed952189b395c9d7e Mon Sep 17 00:00:00 2001 From: Jerome Gout Date: Fri, 10 Jan 2025 18:31:55 +0100 Subject: [PATCH] [4403] Add support of actions in table rows context menu Bug: https://github.com/eclipse-sirius/sirius-web/issues/4403 Signed-off-by: Jerome Gout --- CHANGELOG.adoc | 1 + .../DeleteRowContextMenuEntryExecutor.java | 58 +++++++ .../PackageTableRowContextMenuProvider.java | 48 ++++++ .../resources/icons/full/obj16/row-delete.svg | 4 + ...ayaTableRowControllerIntegrationTests.java | 133 +++++++++++++++- .../tables/TableQueryService.java | 9 +- .../api/IRowContextMenuEntryExecutor.java | 31 ++++ .../api/IRowContextMenuEntryProvider.java | 35 +++++ .../tables/api/ITableQueryService.java | 6 +- .../dto/InvokeRowContextMenuEntryInput.java | 32 ++++ .../dto/RowContextMenuEntriesInput.java | 31 ++++ .../tables/dto/RowContextMenuEntry.java | 30 ++++ .../dto/RowContextMenuSuccessPayload.java | 32 ++++ ...InvokeRowContextMenuEntryEventHandler.java | 102 ++++++++++++ .../ResetTableRowsHeightEventHandler.java | 5 +- .../ResizeTableColumnEventHandler.java | 5 +- .../handlers/RowContextMenuEventHandler.java | 102 ++++++++++++ .../CollaborativeTablesMessageService.java | 7 +- .../ICollaborativeTableMessageService.java | 9 +- .../tables/messages/MessageConstants.java | 3 +- ...components-collaborative-tables.properties | 3 +- .../src/main/resources/schema/table.graphqls | 19 +++ ...owContextMenuEntryIconURLsDataFetcher.java | 48 ++++++ ...eDescriptionRowContextMenuDataFetcher.java | 66 ++++++++ ...nInvokeRowContextMenuEntryDataFetcher.java | 60 +++++++ ...vokeRowContextMenuEntryMutationRunner.java | 60 +++++++ .../graphql/RowContextMenuQueryRunner.java | 63 ++++++++ .../representation/TableRepresentation.tsx | 5 +- .../src/rows/RowAction.tsx | 48 ++++++ .../RowAction.types.ts} | 19 +-- .../src/rows/RowContextMenuContent.tsx | 148 ++++++++++++++++++ .../src/rows/RowContextMenuContent.types.ts | 63 ++++++++ .../src/rows/useInvokeRowContextMenuEntry.ts | 73 +++++++++ .../useInvokeRowContextMenuEntry.types.ts | 36 +++++ .../src/table/TableContent.tsx | 66 +++----- .../src/table/TableContent.types.ts | 2 +- .../src/table/row/ResizeRowHandler.tsx | 73 --------- .../src/table/useTableColumns.tsx | 41 ++++- 38 files changed, 1431 insertions(+), 145 deletions(-) create mode 100644 packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/DeleteRowContextMenuEntryExecutor.java create mode 100644 packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/PackageTableRowContextMenuProvider.java create mode 100644 packages/sirius-web/backend/sirius-web-papaya/src/main/resources/icons/full/obj16/row-delete.svg create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryExecutor.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryProvider.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/InvokeRowContextMenuEntryInput.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntriesInput.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntry.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuSuccessPayload.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/InvokeRowContextMenuEntryEventHandler.java create mode 100644 packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/RowContextMenuEventHandler.java create mode 100644 packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/RowContextMenuEntryIconURLsDataFetcher.java create mode 100644 packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/TableDescriptionRowContextMenuDataFetcher.java create mode 100644 packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/mutation/MutationInvokeRowContextMenuEntryDataFetcher.java create mode 100644 packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/InvokeRowContextMenuEntryMutationRunner.java create mode 100644 packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/RowContextMenuQueryRunner.java create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/RowAction.tsx rename packages/tables/frontend/sirius-components-tables/src/{table/row/ResizeRowHandler.types.ts => rows/RowAction.types.ts} (62%) create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.tsx create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.types.ts create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.ts create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.types.ts delete mode 100644 packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.tsx diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index a3096a1bf2..5217cbf608 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -107,6 +107,7 @@ The expression can return a string (with a prefix of the form `INFO:`, `WARNING: It can be used to add new elements before/after the default UI or to completely replace the UI with another one if needed. Because this new extension point offers complete control of this part of the UI, the `editProjectNavbarSubtitleExtensionPoint` has been removed. See `PapayaExtensionRegistry.tsx` for how the same result as before can be achieved with the new extension point. +- https://github.com/eclipse-sirius/sirius-web/issues/4403[#4403] [table] Add support of actions in table rows context menu === Improvements diff --git a/packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/DeleteRowContextMenuEntryExecutor.java b/packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/DeleteRowContextMenuEntryExecutor.java new file mode 100644 index 0000000000..7a867016aa --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/DeleteRowContextMenuEntryExecutor.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.papaya.representations.table; + +import java.util.Map; +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.api.ChangeKind; +import org.eclipse.sirius.components.collaborative.tables.api.IRowContextMenuEntryExecutor; +import org.eclipse.sirius.components.core.api.IEditService; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IObjectService; +import org.eclipse.sirius.components.representations.IStatus; +import org.eclipse.sirius.components.representations.Success; +import org.eclipse.sirius.components.tables.Line; +import org.eclipse.sirius.components.tables.Table; +import org.eclipse.sirius.components.tables.descriptions.TableDescription; +import org.springframework.stereotype.Service; + +/** + * This class is the implementation of {@link IRowContextMenuEntryExecutor} for the example Delete row action. + * + * @author Jerome Gout + */ +@Service +public class DeleteRowContextMenuEntryExecutor implements IRowContextMenuEntryExecutor { + + private final IEditService editService; + + private final IObjectService objectService; + + public DeleteRowContextMenuEntryExecutor(IEditService editService, IObjectService objectService) { + this.editService = Objects.requireNonNull(editService); + this.objectService = Objects.requireNonNull(objectService); + } + + @Override + public boolean canExecute(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row, String rowMenuContextEntryId) { + return PackageTableRowContextMenuProvider.DELETE_ID.equals(rowMenuContextEntryId); + } + + @Override + public IStatus execute(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row, String rowMenuContextEntryId) { + this.objectService.getObject(editingContext, row.getTargetObjectId()).ifPresent(this.editService::delete); + return new Success(ChangeKind.SEMANTIC_CHANGE, Map.of()); + } +} diff --git a/packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/PackageTableRowContextMenuProvider.java b/packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/PackageTableRowContextMenuProvider.java new file mode 100644 index 0000000000..38b3f3d4ca --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-papaya/src/main/java/org/eclipse/sirius/web/papaya/representations/table/PackageTableRowContextMenuProvider.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.papaya.representations.table; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.tables.api.IRowContextMenuEntryProvider; +import org.eclipse.sirius.components.collaborative.tables.dto.RowContextMenuEntry; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.tables.Line; +import org.eclipse.sirius.components.tables.Table; +import org.eclipse.sirius.components.tables.descriptions.TableDescription; +import org.springframework.stereotype.Service; + +/** + * Example of row context menu entries used inside the papaya package table. + * + * @author Jerome Gout + */ +@Service +public class PackageTableRowContextMenuProvider implements IRowContextMenuEntryProvider { + + public static final String DELETE_ID = "papaya-package-table-delete-row"; + + public static final String DELETE_LABEL = "Delete row"; + + @Override + public boolean canHandle(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row) { + return Objects.equals(tableDescription.getId(), PackageTableRepresentationDescriptionProvider.TABLE_DESCRIPTION_ID); + } + + @Override + public List getRowContextMenuEntries(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row) { + return List.of(new RowContextMenuEntry(DELETE_ID, DELETE_LABEL, List.of("/icons/full/obj16/row-delete.svg"))); + } +} diff --git a/packages/sirius-web/backend/sirius-web-papaya/src/main/resources/icons/full/obj16/row-delete.svg b/packages/sirius-web/backend/sirius-web-papaya/src/main/resources/icons/full/obj16/row-delete.svg new file mode 100644 index 0000000000..c3d65d14ca --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-papaya/src/main/resources/icons/full/obj16/row-delete.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/tables/PapayaTableRowControllerIntegrationTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/tables/PapayaTableRowControllerIntegrationTests.java index 697dd693bc..8422723cec 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/tables/PapayaTableRowControllerIntegrationTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/tables/PapayaTableRowControllerIntegrationTests.java @@ -15,9 +15,12 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import com.jayway.jsonpath.Configuration; import com.jayway.jsonpath.JsonPath; import java.time.Duration; +import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.atomic.AtomicReference; @@ -26,15 +29,19 @@ import org.eclipse.sirius.components.collaborative.dto.CreateRepresentationInput; import org.eclipse.sirius.components.collaborative.tables.TableEventInput; import org.eclipse.sirius.components.collaborative.tables.TableRefreshedEventPayload; +import org.eclipse.sirius.components.collaborative.tables.dto.InvokeRowContextMenuEntryInput; import org.eclipse.sirius.components.collaborative.tables.dto.ResetTableRowsHeightInput; import org.eclipse.sirius.components.collaborative.tables.dto.ResizeTableRowInput; import org.eclipse.sirius.components.core.api.SuccessPayload; import org.eclipse.sirius.components.tables.Line; +import org.eclipse.sirius.components.tables.tests.graphql.InvokeRowContextMenuEntryMutationRunner; import org.eclipse.sirius.components.tables.tests.graphql.ResetTableRowsHeightMutationRunner; import org.eclipse.sirius.components.tables.tests.graphql.ResizeTableRowMutationRunner; +import org.eclipse.sirius.components.tables.tests.graphql.RowContextMenuQueryRunner; import org.eclipse.sirius.components.tables.tests.graphql.TableEventSubscriptionRunner; import org.eclipse.sirius.web.AbstractIntegrationTests; import org.eclipse.sirius.web.data.PapayaIdentifiers; +import org.eclipse.sirius.web.papaya.representations.table.PackageTableRowContextMenuProvider; import org.eclipse.sirius.web.tests.data.GivenSiriusWebServer; import org.eclipse.sirius.web.tests.services.api.IGivenCommittedTransaction; import org.eclipse.sirius.web.tests.services.api.IGivenCreatedTableSubscription; @@ -58,7 +65,7 @@ */ @Transactional @SuppressWarnings("checkstyle:MultipleStringLiterals") -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {"sirius.web.test.enabled=studio"}) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = { "sirius.web.test.enabled=studio" }) public class PapayaTableRowControllerIntegrationTests extends AbstractIntegrationTests { private static final String MISSING_TABLE = "Missing table"; @@ -75,6 +82,12 @@ public class PapayaTableRowControllerIntegrationTests extends AbstractIntegratio @Autowired private ResetTableRowsHeightMutationRunner resetTableRowsHeightMutationRunner; + @Autowired + private InvokeRowContextMenuEntryMutationRunner invokeRowContextMenuEntryMutationRunner; + + @Autowired + private RowContextMenuQueryRunner rowContextMenuQueryRunner; + @Autowired private TableEventSubscriptionRunner tableEventSubscriptionRunner; @@ -208,4 +221,122 @@ public void givenTableWithAResizedRowWhenRowAResetRowsHeightMutationIsTriggeredT .thenCancel() .verify(Duration.ofSeconds(10)); } + + @Test + @GivenSiriusWebServer + @DisplayName("Given a table, when row context menu entries are queried, then the correct entries are returned") + public void giveATableWhenRowContextMenuEntriesAreQueriedThenTheCorrectEntriesAreReturned() { + this.givenCommittedTransaction.commit(); + + var tableEventInput = new TableEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), PapayaIdentifiers.PAPAYA_PACKAGE_TABLE_REPRESENTATION.toString()); + var flux = this.tableEventSubscriptionRunner.run(tableEventInput); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + TestTransaction.start(); + + var tableId = new AtomicReference(); + var rowId = new AtomicReference(); + + Consumer initialTableContentConsumer = payload -> Optional.of(payload) + .filter(DataFetcherResult.class::isInstance) + .map(DataFetcherResult.class::cast) + .map(DataFetcherResult::getData) + .filter(TableRefreshedEventPayload.class::isInstance) + .map(TableRefreshedEventPayload.class::cast) + .map(TableRefreshedEventPayload::table) + .ifPresentOrElse(table -> { + assertThat(table).isNotNull(); + assertThat(table.getLines()).hasSize(2); + tableId.set(table.getId()); + rowId.set(table.getLines().get(0).getId()); + }, () -> fail(MISSING_TABLE)); + + Runnable getContextMenuActions = () -> { + Map variables = Map.of( + "editingContextId", PapayaIdentifiers.PAPAYA_PROJECT.toString(), + "representationId", tableId.get(), + "tableId", tableId.get(), + "rowId", rowId.get().toString() + ); + var result = this.rowContextMenuQueryRunner.run(variables); + Object document = Configuration.defaultConfiguration().jsonProvider().parse(result); + + List actionLabels = JsonPath.read(document, "$.data.viewer.editingContext.representation.description.rowContextMenuEntries[*].label"); + assertThat(actionLabels).isNotEmpty().hasSize(1); + assertThat(actionLabels.get(0)).isEqualTo("Delete row"); + }; + + StepVerifier.create(flux) + .consumeNextWith(initialTableContentConsumer) + .then(getContextMenuActions) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } + + + @Test + @GivenSiriusWebServer + @DisplayName("Given a table, when a row context menu entry is triggered, then the entry is correctly invoked ") + public void giveATableWhenARowContextMenuEntryIsTriggeredThenTheEntryIsCorrectlyInvoked() { + this.givenCommittedTransaction.commit(); + + var tableEventInput = new TableEventInput(UUID.randomUUID(), PapayaIdentifiers.PAPAYA_PROJECT.toString(), PapayaIdentifiers.PAPAYA_PACKAGE_TABLE_REPRESENTATION.toString()); + var flux = this.tableEventSubscriptionRunner.run(tableEventInput); + + TestTransaction.flagForCommit(); + TestTransaction.end(); + TestTransaction.start(); + + var tableId = new AtomicReference(); + var rowId = new AtomicReference(); + + Consumer initialTableContentConsumer = payload -> Optional.of(payload) + .filter(DataFetcherResult.class::isInstance) + .map(DataFetcherResult.class::cast) + .map(DataFetcherResult::getData) + .filter(TableRefreshedEventPayload.class::isInstance) + .map(TableRefreshedEventPayload.class::cast) + .map(TableRefreshedEventPayload::table) + .ifPresentOrElse(table -> { + assertThat(table).isNotNull(); + assertThat(table.getLines()).hasSize(2); + tableId.set(table.getId()); + rowId.set(table.getLines().get(0).getId()); + }, () -> fail(MISSING_TABLE)); + + Runnable invokeDeleteRowAction = () -> { + var invokeRowContextMenuEntryInput = new InvokeRowContextMenuEntryInput( + UUID.randomUUID(), + PapayaIdentifiers.PAPAYA_PROJECT.toString(), + tableId.get(), + tableId.get(), + rowId.get(), + PackageTableRowContextMenuProvider.DELETE_ID + ); + var result = this.invokeRowContextMenuEntryMutationRunner.run(invokeRowContextMenuEntryInput); + + String typename = JsonPath.read(result, "$.data.invokeRowContextMenuEntry.__typename"); + assertThat(typename).isEqualTo(SuccessPayload.class.getSimpleName()); + }; + + Consumer updatedTableContentConsumer = payload -> Optional.of(payload) + .filter(DataFetcherResult.class::isInstance) + .map(DataFetcherResult.class::cast) + .map(DataFetcherResult::getData) + .filter(TableRefreshedEventPayload.class::isInstance) + .map(TableRefreshedEventPayload.class::cast) + .map(TableRefreshedEventPayload::table) + .ifPresentOrElse(table -> { + assertThat(table).isNotNull(); + assertThat(table.getLines()).hasSize(1); + }, () -> fail(MISSING_TABLE)); + + StepVerifier.create(flux) + .consumeNextWith(initialTableContentConsumer) + .then(invokeDeleteRowAction) + .consumeNextWith(updatedTableContentConsumer) + .thenCancel() + .verify(Duration.ofSeconds(10)); + } } diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/TableQueryService.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/TableQueryService.java index 1096c91a8f..53c3270756 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/TableQueryService.java +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/TableQueryService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 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 @@ -43,6 +43,13 @@ public Optional findLineByCellId(Table table, UUID cellId) { .findFirst(); } + @Override + public Optional findLineById(Table table, UUID rowId) { + return table.getLines().stream() + .filter(row -> Objects.equals(rowId, row.getId())) + .findFirst(); + } + @Override public Optional findColumnById(Table table, UUID columnId) { return table.getColumns().stream() diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryExecutor.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryExecutor.java new file mode 100644 index 0000000000..bd6d4937a8 --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryExecutor.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.api; + +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.representations.IStatus; +import org.eclipse.sirius.components.tables.Line; +import org.eclipse.sirius.components.tables.Table; +import org.eclipse.sirius.components.tables.descriptions.TableDescription; + +/** + * Interface allowing to perform row context menu entries. + * + * @author Jerome Gout + */ +public interface IRowContextMenuEntryExecutor { + boolean canExecute(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row, String rowMenuContextEntryId); + + IStatus execute(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row, String rowMenuContextEntryId); +} diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryProvider.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryProvider.java new file mode 100644 index 0000000000..fb80fa873c --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/IRowContextMenuEntryProvider.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.api; + +import java.util.List; + +import org.eclipse.sirius.components.collaborative.tables.dto.RowContextMenuEntry; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.tables.Line; +import org.eclipse.sirius.components.tables.Table; +import org.eclipse.sirius.components.tables.descriptions.TableDescription; + +/** + * Interface allowing to provide context menu entries in a table row. + * + * @author Jerome Gout + */ +public interface IRowContextMenuEntryProvider { + + boolean canHandle(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row); + + List getRowContextMenuEntries(IEditingContext editingContext, TableDescription tableDescription, Table table, Line row); + +} diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/ITableQueryService.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/ITableQueryService.java index bb2eb84fba..322b4a8d1c 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/ITableQueryService.java +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/api/ITableQueryService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 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 @@ -29,7 +29,9 @@ public interface ITableQueryService { Optional findCellById(Table table, UUID cellId); - Optional findLineByCellId(Table table, UUID lineId); + Optional findLineByCellId(Table table, UUID cellId); + + Optional findLineById(Table table, UUID lineId); Optional findColumnById(Table table, UUID columnId); diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/InvokeRowContextMenuEntryInput.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/InvokeRowContextMenuEntryInput.java new file mode 100644 index 0000000000..6bc14667cf --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/InvokeRowContextMenuEntryInput.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.dto; + +import java.util.UUID; + +import org.eclipse.sirius.components.collaborative.tables.api.ITableInput; + +/** + * The input object for the row context menu entry invocation mutation. + * + * @author Jerome Gout + */ +public record InvokeRowContextMenuEntryInput( + UUID id, + String editingContextId, + String representationId, + String tableId, + UUID rowId, + String menuEntryId) implements ITableInput { +} \ No newline at end of file diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntriesInput.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntriesInput.java new file mode 100644 index 0000000000..7b531779b5 --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntriesInput.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.dto; + +import java.util.UUID; + +import org.eclipse.sirius.components.collaborative.tables.api.ITableInput; + +/** + * The input of the table row context menu entries query. + * + * @author Jerome Gout + */ +public record RowContextMenuEntriesInput( + UUID id, + String editingContextId, + String representationId, + String tableId, + UUID rowId) implements ITableInput { +} \ No newline at end of file diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntry.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntry.java new file mode 100644 index 0000000000..fd804a126c --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuEntry.java @@ -0,0 +1,30 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.dto; + +import java.util.List; +import java.util.Objects; + +/** + * Entry in the table row context menu. + * + * @author Jerome Gout + */ +public record RowContextMenuEntry(String id, String label, List iconURLs) { + public RowContextMenuEntry { + Objects.requireNonNull(id); + Objects.requireNonNull(label); + Objects.requireNonNull(iconURLs); + } +} diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuSuccessPayload.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuSuccessPayload.java new file mode 100644 index 0000000000..1d2d02ed9b --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/dto/RowContextMenuSuccessPayload.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.dto; + +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.sirius.components.core.api.IPayload; + +/** + * Payload used to tell the frontend the list of context menu action. + * + * @author Jerome Gout + */ +public record RowContextMenuSuccessPayload(UUID id, List entries) implements IPayload { + public RowContextMenuSuccessPayload { + Objects.requireNonNull(id); + Objects.requireNonNull(entries); + } +} diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/InvokeRowContextMenuEntryEventHandler.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/InvokeRowContextMenuEntryEventHandler.java new file mode 100644 index 0000000000..9b6fc41807 --- /dev/null +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/InvokeRowContextMenuEntryEventHandler.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.handlers; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.api.ChangeDescription; +import org.eclipse.sirius.components.collaborative.api.ChangeKind; +import org.eclipse.sirius.components.collaborative.api.Monitoring; +import org.eclipse.sirius.components.collaborative.tables.api.IRowContextMenuEntryExecutor; +import org.eclipse.sirius.components.collaborative.tables.api.ITableContext; +import org.eclipse.sirius.components.collaborative.tables.api.ITableEventHandler; +import org.eclipse.sirius.components.collaborative.tables.api.ITableInput; +import org.eclipse.sirius.components.collaborative.tables.api.ITableQueryService; +import org.eclipse.sirius.components.collaborative.tables.dto.InvokeRowContextMenuEntryInput; +import org.eclipse.sirius.components.collaborative.tables.messages.ICollaborativeTableMessageService; +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.core.api.SuccessPayload; +import org.eclipse.sirius.components.representations.Failure; +import org.eclipse.sirius.components.representations.Success; +import org.eclipse.sirius.components.tables.Line; +import org.eclipse.sirius.components.tables.descriptions.TableDescription; +import org.springframework.stereotype.Service; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import reactor.core.publisher.Sinks; + +/** + * Handle row context menu entry invocation event. + * + * @author Jerome Gout + */ +@Service +public class InvokeRowContextMenuEntryEventHandler implements ITableEventHandler { + + private final ICollaborativeTableMessageService messageService; + + private final ITableQueryService tableQueryService; + + private final Counter counter; + + private final List rowContextMenuEntryExecutors; + + public InvokeRowContextMenuEntryEventHandler(ICollaborativeTableMessageService messageService, ITableQueryService tableQueryService, MeterRegistry meterRegistry, List rowContextMenuEntryExecutors) { + this.messageService = messageService; + this.tableQueryService = Objects.requireNonNull(tableQueryService); + this.rowContextMenuEntryExecutors = Objects.requireNonNull(rowContextMenuEntryExecutors); + this.counter = Counter.builder(Monitoring.EVENT_HANDLER) + .tag(Monitoring.NAME, this.getClass().getSimpleName()) + .register(meterRegistry); + } + + @Override + public boolean canHandle(ITableInput tableInput) { + return tableInput instanceof InvokeRowContextMenuEntryInput; + } + + @Override + public void handle(Sinks.One payloadSink, Sinks.Many changeDescriptionSink, IEditingContext editingContext, ITableContext tableContext, TableDescription tableDescription, ITableInput tableInput) { + this.counter.increment(); + + ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, tableInput.representationId(), tableInput); + String message = this.messageService.invalidInput(tableInput.getClass().getSimpleName(), InvokeRowContextMenuEntryInput.class.getSimpleName()); + IPayload payload = new ErrorPayload(tableInput.id(), message); + + if (tableInput instanceof InvokeRowContextMenuEntryInput invokeRowContextMenuEntryInput) { + var optionalRow = this.tableQueryService.findLineById(tableContext.getTable(), invokeRowContextMenuEntryInput.rowId()); + if (optionalRow.isPresent()) { + Line row = optionalRow.get(); + var status = this.rowContextMenuEntryExecutors.stream() + .filter(executor -> executor.canExecute(editingContext, tableDescription, tableContext.getTable(), row, invokeRowContextMenuEntryInput.menuEntryId())) + .findFirst() + .map(executor -> executor.execute(editingContext, tableDescription, tableContext.getTable(), row, invokeRowContextMenuEntryInput.menuEntryId())) + .orElseGet(() -> new Failure(this.messageService.noRowContextMenuEntryExecutor())); + + if (status instanceof Success success) { + changeDescription = new ChangeDescription(success.getChangeKind(), tableInput.representationId(), tableInput, success.getParameters()); + payload = new SuccessPayload(tableInput.id()); + } else if (status instanceof Failure failure) { + payload = new ErrorPayload(tableInput.id(), failure.getMessages()); + } + } + } + + payloadSink.tryEmitValue(payload); + changeDescriptionSink.tryEmitNext(changeDescription); + } +} diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/ResetTableRowsHeightEventHandler.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/ResetTableRowsHeightEventHandler.java index 83462200da..be1408a225 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/ResetTableRowsHeightEventHandler.java +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/handlers/ResetTableRowsHeightEventHandler.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 CEA LIST. + * Copyright (c) 2024, 2025 CEA LIST. * 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 @@ -19,7 +19,6 @@ import org.eclipse.sirius.components.collaborative.tables.api.ITableContext; import org.eclipse.sirius.components.collaborative.tables.api.ITableEventHandler; import org.eclipse.sirius.components.collaborative.tables.api.ITableInput; -import org.eclipse.sirius.components.collaborative.tables.dto.EditTextfieldCellInput; import org.eclipse.sirius.components.collaborative.tables.dto.ResetTableRowsHeightInput; import org.eclipse.sirius.components.collaborative.tables.messages.ICollaborativeTableMessageService; import org.eclipse.sirius.components.core.api.ErrorPayload; @@ -63,7 +62,7 @@ public void handle(Sinks.One payloadSink, Sinks.Many payloadSink, Sinks.Many contextMenuEntryProviders; + + public RowContextMenuEventHandler(ICollaborativeTableMessageService messageService, ITableQueryService tableQueryService, MeterRegistry meterRegistry, + List contextMenuEntryProviders) { + this.messageService = Objects.requireNonNull(messageService); + this.tableQueryService = Objects.requireNonNull(tableQueryService); + this.contextMenuEntryProviders = Objects.requireNonNull(contextMenuEntryProviders); + + this.counter = Counter.builder(Monitoring.EVENT_HANDLER) + .tag(Monitoring.NAME, this.getClass().getSimpleName()) + .register(meterRegistry); + } + + @Override + public boolean canHandle(ITableInput tableInput) { + return tableInput instanceof RowContextMenuEntriesInput; + } + + @Override + public void handle(Sinks.One payloadSink, Sinks.Many changeDescriptionSink, IEditingContext editingContext, ITableContext tableContext, + TableDescription tableDescription, ITableInput tableInput) { + this.counter.increment(); + + String message = this.messageService.invalidInput(tableInput.getClass().getSimpleName(), RowContextMenuEntriesInput.class.getSimpleName()); + IPayload payload = new ErrorPayload(tableInput.id(), message); + ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, tableInput.representationId(), tableInput); + + if (tableInput instanceof RowContextMenuEntriesInput input) { + var optionalRow = this.tableQueryService.findLineById(tableContext.getTable(), input.rowId()); + if (optionalRow.isPresent()) { + Line row = optionalRow.get(); + + var entries = this.contextMenuEntryProviders.stream() + .filter(provider -> provider.canHandle(editingContext, tableDescription, tableContext.getTable(), row)) + .flatMap(provider -> provider.getRowContextMenuEntries(editingContext, tableDescription, tableContext.getTable(), row).stream()) + .sorted(Comparator.comparing(RowContextMenuEntry::label)) + .toList(); + + payload = new RowContextMenuSuccessPayload(tableInput.id(), entries); + } + } + + changeDescriptionSink.tryEmitNext(changeDescription); + payloadSink.tryEmitValue(payload); + } +} diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/CollaborativeTablesMessageService.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/CollaborativeTablesMessageService.java index 0e5a1c30e2..832e12c780 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/CollaborativeTablesMessageService.java +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/CollaborativeTablesMessageService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 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 @@ -37,6 +37,11 @@ public String invalidInput(String expectedInputTypeName, String receivedInputTyp return this.messageSourceAccessor.getMessage(MessageConstants.INVALID_INPUT, new Object[] {expectedInputTypeName, receivedInputTypeName}); } + @Override + public String noRowContextMenuEntryExecutor() { + return this.messageSourceAccessor.getMessage(MessageConstants.NO_ROW_CONTEXT_MENU_ENTRY_FOUND); + } + @Override public String noHandlerFound() { return this.messageSourceAccessor.getMessage(MessageConstants.NO_HANDLER_FOUND); diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/ICollaborativeTableMessageService.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/ICollaborativeTableMessageService.java index 74940f6d62..1995bdb923 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/ICollaborativeTableMessageService.java +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/ICollaborativeTableMessageService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 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 @@ -21,6 +21,8 @@ public interface ICollaborativeTableMessageService { String invalidInput(String expectedInputTypeName, String receivedInputTypeName); + String noRowContextMenuEntryExecutor(); + String noHandlerFound(); /** @@ -35,6 +37,11 @@ public String invalidInput(String expectedInputTypeName, String receivedInputTyp return "invalidInput"; } + @Override + public String noRowContextMenuEntryExecutor() { + return "noRowContextMenuEntryFound"; + } + @Override public String noHandlerFound() { return "noHandlerFound"; diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/MessageConstants.java b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/MessageConstants.java index 20ce98cbd9..748008b32b 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/MessageConstants.java +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/java/org/eclipse/sirius/components/collaborative/tables/messages/MessageConstants.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 Obeo. + * Copyright (c) 2024, 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 @@ -21,6 +21,7 @@ public final class MessageConstants { public static final String INVALID_INPUT = "INVALID_INPUT"; public static final String NO_HANDLER_FOUND = "NO_HANDLER_FOUND"; + public static final String NO_ROW_CONTEXT_MENU_ENTRY_FOUND = "NO_ROW_CONTEXT_MENU_ENTRY_FOUND"; private MessageConstants() { // Prevent instantiation diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/messages/sirius-components-collaborative-tables.properties b/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/messages/sirius-components-collaborative-tables.properties index ac5dc3fb21..baadb2df60 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/messages/sirius-components-collaborative-tables.properties +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/messages/sirius-components-collaborative-tables.properties @@ -1,5 +1,5 @@ ################################################################################################ -# Copyright (c) 2021 Obeo. All Rights Reserved. +# Copyright (c) 2021, 2025 Obeo. All Rights Reserved. # This software and the attached documentation are the exclusive ownership # of its authors and was conceded to the profit of Obeo SARL. # This software and the attached documentation are protected under the rights @@ -15,3 +15,4 @@ ################################################################################################ INVALID_INPUT=Invalid input type, "{0}" has been received while "{1}" was expected NO_HANDLER_FOUND=No handler found +NO_ROW_CONTEXT_MENU_ENTRY_FOUND=No existing action for this menu entry diff --git a/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/schema/table.graphqls b/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/schema/table.graphqls index b868249d1e..58dcfe9394 100644 --- a/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/schema/table.graphqls +++ b/packages/tables/backend/sirius-components-collaborative-tables/src/main/resources/schema/table.graphqls @@ -140,6 +140,13 @@ type IconLabelCell implements Cell { type TableDescription implements RepresentationDescription { id: ID! label: String! + rowContextMenuEntries(tableId: ID!, rowId: ID!): [RowContextMenuEntry!]! +} + +type RowContextMenuEntry { + id: ID! + label: String! + iconURLs: [String!]! } extend type Mutation { @@ -155,6 +162,7 @@ extend type Mutation { changeGlobalFilterValue(input: ChangeGlobalFilterValueInput!): ChangeGlobalFilterValuePayload! changeColumnFilter(input: ChangeColumnFilterInput!): ChangeColumnFilterPayload! reorderTableColumns(input: ReorderTableColumnsInput!): ReorderTableColumnsPayload! + invokeRowContextMenuEntry(input: InvokeRowContextMenuEntryInput!): InvokeRowContextMenuEntryPayload! } input EditCheckboxCellInput { @@ -292,3 +300,14 @@ input ReorderTableColumnsInput { } union ReorderTableColumnsPayload = ErrorPayload | SuccessPayload + +input InvokeRowContextMenuEntryInput { + id: ID! + editingContextId: ID! + representationId: ID! + tableId: ID! + rowId: ID! + menuEntryId: ID! +} + +union InvokeRowContextMenuEntryPayload = ErrorPayload | SuccessPayload diff --git a/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/RowContextMenuEntryIconURLsDataFetcher.java b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/RowContextMenuEntryIconURLsDataFetcher.java new file mode 100644 index 0000000000..bc3ce8d383 --- /dev/null +++ b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/RowContextMenuEntryIconURLsDataFetcher.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.graphql.datafetchers; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.components.annotations.spring.graphql.QueryDataFetcher; +import org.eclipse.sirius.components.collaborative.tables.dto.RowContextMenuEntry; +import org.eclipse.sirius.components.core.api.IImageURLSanitizer; +import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; +import org.eclipse.sirius.components.graphql.api.URLConstants; + +import graphql.schema.DataFetchingEnvironment; + +/** + * The data fetcher used to concatenate the server image URL to the icon of context menu entry of row. + * + * @author Jerome Gout + */ +@QueryDataFetcher(type = "RowContextMenuEntry", field = "iconURLs") +public class RowContextMenuEntryIconURLsDataFetcher implements IDataFetcherWithFieldCoordinates> { + + private final IImageURLSanitizer imageURLSanitizer; + + public RowContextMenuEntryIconURLsDataFetcher(IImageURLSanitizer imageURLSanitizer) { + this.imageURLSanitizer = Objects.requireNonNull(imageURLSanitizer); + } + + @Override + public List get(DataFetchingEnvironment environment) throws Exception { + RowContextMenuEntry source = environment.getSource(); + + return source.iconURLs().stream() + .map(url -> this.imageURLSanitizer.sanitize(URLConstants.IMAGE_BASE_PATH, url)) + .toList(); + } +} diff --git a/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/TableDescriptionRowContextMenuDataFetcher.java b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/TableDescriptionRowContextMenuDataFetcher.java new file mode 100644 index 0000000000..7ffe60d360 --- /dev/null +++ b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/TableDescriptionRowContextMenuDataFetcher.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.graphql.datafetchers; + +import java.util.List; +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.tables.dto.RowContextMenuEntriesInput; +import org.eclipse.sirius.components.collaborative.tables.dto.RowContextMenuEntry; +import org.eclipse.sirius.components.collaborative.tables.dto.RowContextMenuSuccessPayload; +import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; +import org.eclipse.sirius.components.graphql.api.LocalContextConstants; + +import graphql.schema.DataFetchingEnvironment; + +/** + * Used to retrieve the table description context menu. + * + * @author Jerome Gout + */ +@QueryDataFetcher(type = "TableDescription", field = "rowContextMenuEntries") +public class TableDescriptionRowContextMenuDataFetcher implements IDataFetcherWithFieldCoordinates>> { + + private static final String TABLE_ID_ARGUMENT = "tableId"; + + private static final String ROW_ID_ARGUMENT = "rowId"; + + private final IEditingContextEventProcessorRegistry editingContextEventProcessorRegistry; + + public TableDescriptionRowContextMenuDataFetcher(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 tableId = environment.getArgument(TABLE_ID_ARGUMENT); + String rowId = environment.getArgument(ROW_ID_ARGUMENT); + + var input = new RowContextMenuEntriesInput(UUID.randomUUID(), editingContextId, representationId, tableId, UUID.fromString(rowId)); + return this.editingContextEventProcessorRegistry.dispatchEvent(editingContextId, input) + .filter(RowContextMenuSuccessPayload.class::isInstance) + .map(RowContextMenuSuccessPayload.class::cast) + .map(RowContextMenuSuccessPayload::entries) + .toFuture(); + } +} diff --git a/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/mutation/MutationInvokeRowContextMenuEntryDataFetcher.java b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/mutation/MutationInvokeRowContextMenuEntryDataFetcher.java new file mode 100644 index 0000000000..a2bbab4a27 --- /dev/null +++ b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/mutation/MutationInvokeRowContextMenuEntryDataFetcher.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.graphql.datafetchers.mutation; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.sirius.components.annotations.spring.graphql.MutationDataFetcher; +import org.eclipse.sirius.components.collaborative.tables.dto.InvokeRowContextMenuEntryInput; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; +import org.eclipse.sirius.components.graphql.api.IEditingContextDispatcher; +import org.eclipse.sirius.components.graphql.api.IExceptionWrapper; + +import graphql.schema.DataFetchingEnvironment; + +/** + * Data fetcher used to invoke an entry of the context menu of a table row. + * + * @author Jerome Gout + */ +@MutationDataFetcher(type = "Mutation", field = "invokeRowContextMenuEntry") +public class MutationInvokeRowContextMenuEntryDataFetcher implements IDataFetcherWithFieldCoordinates> { + + private static final String INPUT_ARGUMENT = "input"; + + private final ObjectMapper objectMapper; + + private final IExceptionWrapper exceptionWrapper; + + private final IEditingContextDispatcher editingContextDispatcher; + + public MutationInvokeRowContextMenuEntryDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEditingContextDispatcher editingContextDispatcher) { + this.objectMapper = Objects.requireNonNull(objectMapper); + this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper); + this.editingContextDispatcher = Objects.requireNonNull(editingContextDispatcher); + } + + @Override + public CompletableFuture get(DataFetchingEnvironment environment) throws Exception { + Object argument = environment.getArgument(INPUT_ARGUMENT); + var input = this.objectMapper.convertValue(argument, InvokeRowContextMenuEntryInput.class); + + return this.exceptionWrapper.wrapMono(() -> this.editingContextDispatcher.dispatchMutation(input.editingContextId(), input), input).toFuture(); + } + +} diff --git a/packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/InvokeRowContextMenuEntryMutationRunner.java b/packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/InvokeRowContextMenuEntryMutationRunner.java new file mode 100644 index 0000000000..4bd35783e6 --- /dev/null +++ b/packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/InvokeRowContextMenuEntryMutationRunner.java @@ -0,0 +1,60 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.tests.graphql; + +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.tables.dto.InvokeRowContextMenuEntryInput; +import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; +import org.eclipse.sirius.components.graphql.tests.api.IMutationRunner; +import org.springframework.stereotype.Service; + +/** + * Used to invoke a row context menu entry with the GraphQL API. + * + * @author Jerome Gout + */ +@Service +public class InvokeRowContextMenuEntryMutationRunner implements IMutationRunner { + + private static final String INVOKE_ROW_CONTEXT_MENU_ENTRY_MUTATION = """ + mutation invokeRowContextMenuEntry($input: InvokeRowContextMenuEntryInput!) { + invokeRowContextMenuEntry(input: $input) { + __typename + ... on ErrorPayload { + messages { + body + level + } + } + ... on SuccessPayload { + messages { + body + level + } + } + } + } + """; + + private final IGraphQLRequestor graphQLRequestor; + + public InvokeRowContextMenuEntryMutationRunner(IGraphQLRequestor graphQLRequestor) { + this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor); + } + + @Override + public String run(InvokeRowContextMenuEntryInput input) { + return this.graphQLRequestor.execute(INVOKE_ROW_CONTEXT_MENU_ENTRY_MUTATION, input); + } +} diff --git a/packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/RowContextMenuQueryRunner.java b/packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/RowContextMenuQueryRunner.java new file mode 100644 index 0000000000..2d1761408e --- /dev/null +++ b/packages/tables/backend/sirius-components-tables-tests/src/main/java/org/eclipse/sirius/components/tables/tests/graphql/RowContextMenuQueryRunner.java @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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.tables.tests.graphql; + +import java.util.Map; +import java.util.Objects; + +import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; +import org.eclipse.sirius.components.graphql.tests.api.IQueryRunner; +import org.springframework.stereotype.Service; + +/** + * Used to call a row context menu graphQL query for testing purpose. + * + * @author Jerome Gout + */ +@Service +public class RowContextMenuQueryRunner implements IQueryRunner { + + private static final String ROW_CONTEXT_MENU_QUERY = """ + query rowContextMenuQuery($editingContextId: ID!, $representationId: ID!, $tableId: ID!, $rowId: ID!) { + viewer { + editingContext(editingContextId: $editingContextId) { + representation(representationId: $representationId) { + description { + ... on TableDescription { + rowContextMenuEntries(tableId: $tableId, rowId: $rowId) { + __typename + id + label + iconURLs + } + } + } + } + } + } + } + """; + + private final IGraphQLRequestor graphQLRequestor; + + public RowContextMenuQueryRunner(IGraphQLRequestor graphQLRequestor) { + this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor); + } + + @Override + public String run(Map variables) { + return this.graphQLRequestor.execute(ROW_CONTEXT_MENU_QUERY, variables); + + } +} diff --git a/packages/tables/frontend/sirius-components-tables/src/representation/TableRepresentation.tsx b/packages/tables/frontend/sirius-components-tables/src/representation/TableRepresentation.tsx index cb78d6df7f..5a47b1ba5b 100644 --- a/packages/tables/frontend/sirius-components-tables/src/representation/TableRepresentation.tsx +++ b/packages/tables/frontend/sirius-components-tables/src/representation/TableRepresentation.tsx @@ -27,6 +27,9 @@ const useTableRepresentationStyles = makeStyles()((theme) => ({ justifyContent: 'center', paddingTop: theme.spacing(8), }, + representation: { + overflowX: 'auto', + }, })); export const TableRepresentation = ({ editingContextId, representationId, readOnly }: RepresentationComponentProps) => { @@ -73,7 +76,7 @@ export const TableRepresentation = ({ editingContextId, representationId, readOn } return ( -
+
{table !== null && !complete ? ( { + const [anchorEl, setAnchorEl] = useState(null); + + const handleOpenRowActionMenu = (event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + return ( + <> + + + + + + {anchorEl != null ? ( + setAnchorEl(null)} + /> + ) : null} + + ); +}; diff --git a/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.types.ts b/packages/tables/frontend/sirius-components-tables/src/rows/RowAction.types.ts similarity index 62% rename from packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.types.ts rename to packages/tables/frontend/sirius-components-tables/src/rows/RowAction.types.ts index 7647205d50..55854b9267 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.types.ts +++ b/packages/tables/frontend/sirius-components-tables/src/rows/RowAction.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2024 CEA LIST. + * Copyright (c) 2025 CEA LIST. * 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 @@ -10,19 +10,14 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { GQLLine, GQLTable } from '../TableContent.types'; -export interface ResizeRowHandlerProps { +import { MRT_Row } from 'material-react-table'; +import { GQLLine } from '../table/TableContent.types'; + +export interface RowActionProps { editingContextId: string; representationId: string; - table: GQLTable; + tableId: string; + row: MRT_Row; readOnly: boolean; - row: GQLLine; - onRowHeightChanged: (rowId: string, height: number) => void; -} - -export interface DragState { - isDragging: boolean; - height: number; - trElement: HTMLElement | undefined; } diff --git a/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.tsx b/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.tsx new file mode 100644 index 0000000000..fa38979cd1 --- /dev/null +++ b/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.tsx @@ -0,0 +1,148 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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, useQuery } from '@apollo/client'; +import { IconOverlay, useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import ListItemIcon from '@mui/material/ListItemIcon'; +import ListItemText from '@mui/material/ListItemText'; +import Menu from '@mui/material/Menu'; +import MenuItem from '@mui/material/MenuItem'; +import { SxProps, Theme } from '@mui/material/styles'; +import Typography from '@mui/material/Typography'; +import { useEffect } from 'react'; +import { + GQLGetAllRowContextMenuEntriesData, + GQLGetAllRowContextMenuEntriesVariables, + GQLRepresentationDescription, + GQLRowContextMenuEntry, + GQLTableDescription, + RowContextMenuContentProps, +} from './RowContextMenuContent.types'; +import { useInvokeRowContextMenuEntry } from './useInvokeRowContextMenuEntry'; + +const getRowContextMenuEntriesQuery = gql` + query getAllRowContextMenuEntries($editingContextId: ID!, $representationId: ID!, $tableId: ID!, $rowId: ID!) { + viewer { + editingContext(editingContextId: $editingContextId) { + representation(representationId: $representationId) { + description { + __typename + ... on TableDescription { + rowContextMenuEntries(tableId: $tableId, rowId: $rowId) { + __typename + id + label + iconURLs + } + } + } + } + } + } + } +`; + +const isTableDescription = ( + gqlRepresentationDescription: GQLRepresentationDescription +): gqlRepresentationDescription is GQLTableDescription => + gqlRepresentationDescription.__typename === 'TableDescription'; + +export const RowContextMenuContent = ({ + editingContextId, + representationId, + tableId, + row, + readOnly, + anchorEl, + onClose, +}: RowContextMenuContentProps) => { + const { loading, data, error } = useQuery< + GQLGetAllRowContextMenuEntriesData, + GQLGetAllRowContextMenuEntriesVariables + >(getRowContextMenuEntriesQuery, { + variables: { + editingContextId, + representationId: tableId, + tableId, + rowId: row.original.id, + }, + }); + const { invokeRowContextMenuEntry } = useInvokeRowContextMenuEntry( + editingContextId, + representationId, + tableId, + row.original.id + ); + + const { addErrorMessage } = useMultiToast(); + + useEffect(() => { + if (error) { + const { message } = error; + addErrorMessage(message); + } + }, [error]); + + const handleClickContextMenuEntry = (menuEntry: GQLRowContextMenuEntry) => { + invokeRowContextMenuEntry(menuEntry.id); + onClose(); + }; + + const representationDescription: GQLRepresentationDescription | null = + data?.viewer.editingContext.representation?.description ?? null; + + const rowContextMenuEntries: GQLRowContextMenuEntry[] = + representationDescription && isTableDescription(representationDescription) + ? representationDescription.rowContextMenuEntries + : []; + + if (loading) { + return null; + } + + const noEntryFoundStyle: SxProps = (theme: Theme) => ({ + paddingX: theme.spacing(1), + }); + + return ( + event.stopPropagation()} + onClose={onClose} + open + data-testid={`row-context_menu-${row.original.headerLabel}`}> + {rowContextMenuEntries.length > 0 ? ( + rowContextMenuEntries.map((entry) => ( + handleClickContextMenuEntry(entry)} + data-testid={`context-menu-entry-${entry.label}`} + disabled={readOnly} + aria-disabled> + + {entry.iconURLs.length > 0 ? ( + + ) : ( +
+ )} + + + + )) + ) : ( + No entries found + )} +
+ ); +}; diff --git a/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.types.ts b/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.types.ts new file mode 100644 index 0000000000..aa38a25eb5 --- /dev/null +++ b/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenuContent.types.ts @@ -0,0 +1,63 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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 { MRT_Row } from 'material-react-table'; +import { GQLLine } from '../table/TableContent.types'; + +export interface RowContextMenuContentProps { + editingContextId: string; + representationId: string; + tableId: string; + row: MRT_Row; + readOnly: boolean; + anchorEl: HTMLElement | null; + onClose: () => void; +} + +export interface GQLGetAllRowContextMenuEntriesVariables { + editingContextId: string; + representationId: string; + tableId: string; + rowId: string; +} + +export interface GQLGetAllRowContextMenuEntriesData { + viewer: GQLViewer; +} + +export interface GQLViewer { + editingContext: GQLEditingContext; +} + +export interface GQLEditingContext { + representation: GQLRepresentationMetadata | null; +} + +export interface GQLRepresentationMetadata { + description: GQLRepresentationDescription; +} + +export interface GQLRepresentationDescription { + __typename: string; +} + +export interface GQLTableDescription extends GQLRepresentationDescription { + rowContextMenuEntries: GQLRowContextMenuEntry[]; +} + +export interface GQLRowContextMenuEntry { + __typename: string; + id: string; + label: string; + iconURLs: string[]; +} diff --git a/packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.ts b/packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.ts new file mode 100644 index 0000000000..7c349d7bec --- /dev/null +++ b/packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.ts @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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 } from '@apollo/client'; +import { useReporting } from '@eclipse-sirius/sirius-components-core'; +import { + GQLInvokeRowContextMenuEntryData, + GQLInvokeRowContextMenuEntryInput, + GQLInvokeRowContextMenuEntryVariables, + UseInvokeRowContextMenuEntryMutationValue, +} from './useInvokeRowContextMenuEntry.types'; + +export const invokeRowContextMenuEntryMutation = gql` + mutation invokeRowContextMenuEntry($input: InvokeRowContextMenuEntryInput!) { + invokeRowContextMenuEntry(input: $input) { + __typename + ... on ErrorPayload { + messages { + body + level + } + } + ... on SuccessPayload { + messages { + body + level + } + } + } + } +`; + +export const useInvokeRowContextMenuEntry = ( + editingContextId: string, + representationId: string, + tableId: string, + rowId: string +): UseInvokeRowContextMenuEntryMutationValue => { + const [mutationInvokeRowContextMenuEntry, mutationInvokeRowContextMenuEntryResult] = useMutation< + GQLInvokeRowContextMenuEntryData, + GQLInvokeRowContextMenuEntryVariables + >(invokeRowContextMenuEntryMutation); + useReporting( + mutationInvokeRowContextMenuEntryResult, + (data: GQLInvokeRowContextMenuEntryData) => data.invokeRowContextMenuEntry + ); + + const invokeRowContextMenuEntry = (menuEntryId: string) => { + const input: GQLInvokeRowContextMenuEntryInput = { + id: crypto.randomUUID(), + editingContextId, + representationId, + tableId, + rowId, + menuEntryId, + }; + + mutationInvokeRowContextMenuEntry({ variables: { input } }); + }; + + return { + invokeRowContextMenuEntry, + }; +}; diff --git a/packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.types.ts b/packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.types.ts new file mode 100644 index 0000000000..8cd7a205e3 --- /dev/null +++ b/packages/tables/frontend/sirius-components-tables/src/rows/useInvokeRowContextMenuEntry.types.ts @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright (c) 2025 CEA LIST. + * 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 { GQLErrorPayload, GQLSuccessPayload } from '@eclipse-sirius/sirius-components-core'; + +export interface UseInvokeRowContextMenuEntryMutationValue { + invokeRowContextMenuEntry: (menuEntryId: string) => void; +} + +export interface GQLInvokeRowContextMenuEntryInput { + id: string; + editingContextId: string; + representationId: string; + tableId: string; + rowId: string; + menuEntryId: string; +} + +export interface GQLInvokeRowContextMenuEntryVariables { + input: GQLInvokeRowContextMenuEntryInput; +} + +export interface GQLInvokeRowContextMenuEntryData { + invokeRowContextMenuEntry: GQLInvokeRowContextMenuEntryPayload; +} + +export type GQLInvokeRowContextMenuEntryPayload = GQLErrorPayload | GQLSuccessPayload; diff --git a/packages/tables/frontend/sirius-components-tables/src/table/TableContent.tsx b/packages/tables/frontend/sirius-components-tables/src/table/TableContent.tsx index 6f705a030e..5918b00df3 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/TableContent.tsx +++ b/packages/tables/frontend/sirius-components-tables/src/table/TableContent.tsx @@ -10,7 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { Selection, useSelection } from '@eclipse-sirius/sirius-components-core'; +import { useSelection } from '@eclipse-sirius/sirius-components-core'; import Box from '@mui/material/Box'; import { Theme, useTheme } from '@mui/material/styles'; import { MaterialReactTable, MRT_DensityState, MRT_TableOptions, useMaterialReactTable } from 'material-react-table'; @@ -20,11 +20,10 @@ import { useTableColumnFiltering } from '../columns/useTableColumnFiltering'; import { useTableColumnOrdering } from '../columns/useTableColumnOrdering'; import { useTableColumnSizing } from '../columns/useTableColumnSizing'; import { useTableColumnVisibility } from '../columns/useTableColumnVisibility'; -import { ResizeRowHandler } from '../rows/ResizeRowHandler'; -import { RowHeader } from '../rows/RowHeader'; +import { RowAction } from '../rows/RowAction'; import { useResetRowsMutation } from '../rows/useResetRows'; import { CursorBasedPagination } from './CursorBasedPagination'; -import { GQLLine, TablePaginationState, TableProps } from './TableContent.types'; +import { GQLLine, TableContentProps, TablePaginationState } from './TableContent.types'; import { useGlobalFilter } from './useGlobalFilter'; import { useTableColumns } from './useTableColumns'; @@ -44,10 +43,14 @@ export const TableContent = memo( enableGlobalFilter, enablePagination, enableColumnOrdering, - }: TableProps) => { - const { selection, setSelection } = useSelection(); + }: TableContentProps) => { + const { selection } = useSelection(); const theme: Theme = useTheme(); + const handleRowHeightChange = (rowId: string, height: number) => { + setLinesState((prev) => prev.map((line) => (line.id === rowId ? { ...line, height } : line))); + }; + const { columns } = useTableColumns( editingContextId, representationId, @@ -56,7 +59,9 @@ export const TableContent = memo( enableColumnVisibility, enableColumnResizing, enableColumnFilters, - enableColumnOrdering + enableColumnOrdering, + enableRowSizing, + handleRowHeightChange ); const { columnSizing, setColumnSizing } = useTableColumnSizing( editingContextId, @@ -132,10 +137,6 @@ export const TableContent = memo( onPaginationChange(pagination.cursor, pagination.direction, pagination.size); }, [pagination.cursor, pagination.size, pagination.direction]); - const handleRowHeightChange = (rowId, height) => { - setLinesState((prev) => prev.map((line) => (line.id === rowId ? { ...line, height } : line))); - }; - useEffect(() => { setLinesState([...table.lines]); }, [table]); @@ -164,7 +165,11 @@ export const TableContent = memo( enableGlobalFilter, manualFiltering: true, onGlobalFilterChange: setGlobalFilter, - initialState: { showGlobalFilter: enableGlobalFilter }, + enableColumnPinning: false, + initialState: { + showGlobalFilter: enableGlobalFilter, + columnPinning: { left: ['mrt-row-header'], right: ['mrt-row-actions'] }, + }, onColumnSizingChange: setColumnSizing, onColumnVisibilityChange: setColumnVisibility, onDensityChange: setDensity, @@ -211,37 +216,14 @@ export const TableContent = memo( onPageSizeChange={handlePageSize} /> ), - displayColumnDefOptions: { - 'mrt-row-actions': { - header: '', - size: 120, - }, - }, renderRowActions: ({ row }) => ( -
{ - const newSelection: Selection = { - entries: [ - { - id: row.original.targetObjectId, - kind: row.original.targetObjectKind, - }, - ], - }; - setSelection(newSelection); - }}> - - {enableRowSizing ? ( - - ) : null} -
+ ), }; diff --git a/packages/tables/frontend/sirius-components-tables/src/table/TableContent.types.ts b/packages/tables/frontend/sirius-components-tables/src/table/TableContent.types.ts index ffdbf2ba4b..3c400670ed 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/TableContent.types.ts +++ b/packages/tables/frontend/sirius-components-tables/src/table/TableContent.types.ts @@ -12,7 +12,7 @@ *******************************************************************************/ import { GQLColumnFilter } from '../columns/useTableColumnFiltering.types'; -export interface TableProps { +export interface TableContentProps { editingContextId: string; representationId: string; table: GQLTable; diff --git a/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.tsx b/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.tsx deleted file mode 100644 index 1e8866fc68..0000000000 --- a/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2024 CEA LIST. - * 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 { memo, useRef } from 'react'; -import { makeStyles } from 'tss-react/mui'; -import { useTableMutations } from '../../graphql/mutation/useTableMutation'; -import { DragState, ResizeRowHandlerProps } from './ResizeRowHandler.types'; - -const useStyles = makeStyles()(() => ({ - handler: { - position: 'absolute', - margin: 0, - backgroundColor: '#B3BFC5', - borderColor: '#B3BFC5', - borderRadius: '2px', - width: '24px', - height: '4px', - bottom: 0, - left: '15px', - cursor: 'row-resize', - }, -})); - -export const ResizeRowHandler = memo( - ({ editingContextId, representationId, table, readOnly, row, onRowHeightChanged }: ResizeRowHandlerProps) => { - const { classes } = useStyles(); - const { resizeRow } = useTableMutations(editingContextId, representationId, table.id); - - const dragState = useRef({ - isDragging: false, - height: 0, - trElement: undefined, - }); - - const handleMouseDown = (e) => { - e.preventDefault(); - - dragState.current = { - isDragging: true, - height: parseInt(window.getComputedStyle(e.target.parentElement.parentElement).height, 10), - trElement: e.target.parentElement.parentElement, - }; - - const handleMouseMove = (e: MouseEvent) => { - if (dragState.current.isDragging) { - dragState.current.height += e.movementY; - onRowHeightChanged(row.id, dragState.current.height); - } - }; - - const handleMouseUp = (_) => { - dragState.current.isDragging = false; - resizeRow(row.id, dragState.current.height); - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - }; - - document.addEventListener('mousemove', handleMouseMove); - document.addEventListener('mouseup', handleMouseUp); - }; - - return !readOnly && row.isResizable ?
: null; - } -); diff --git a/packages/tables/frontend/sirius-components-tables/src/table/useTableColumns.tsx b/packages/tables/frontend/sirius-components-tables/src/table/useTableColumns.tsx index d1c44f239f..b4bb7984c2 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/useTableColumns.tsx +++ b/packages/tables/frontend/sirius-components-tables/src/table/useTableColumns.tsx @@ -10,10 +10,13 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +import { Selection, useSelection } from '@eclipse-sirius/sirius-components-core'; import { MRT_ColumnDef } from 'material-react-table'; import { useMemo } from 'react'; import { Cell } from '../cells/Cell'; import { ColumnHeader } from '../columns/ColumnHeader'; +import { ResizeRowHandler } from '../rows/ResizeRowHandler'; +import { RowHeader } from '../rows/RowHeader'; import { GQLCell, GQLLine, GQLTable } from './TableContent.types'; import { UseTableColumnsValue } from './useTableColumns.types'; @@ -25,8 +28,11 @@ export const useTableColumns = ( enableColumnVisibility: boolean, enableColumnSizing: boolean, enableColumnFilters: boolean, - enableColumnOrdering: boolean + enableColumnOrdering: boolean, + enableRowSizing: boolean, + handleRowHeightChange: (rowId: string, height: number) => void ): UseTableColumnsValue => { + const { setSelection } = useSelection(); const columns = useMemo[]>(() => { const columnDefs: MRT_ColumnDef[] = table.columns.map((column) => { return { @@ -57,7 +63,38 @@ export const useTableColumns = ( }; }); - return columnDefs; + const rowHeaderColumn: MRT_ColumnDef = { + id: 'mrt-row-header', + header: '', + columnDefType: 'display', + Cell: ({ row }) => ( +
{ + const newSelection: Selection = { + entries: [ + { + id: row.original.targetObjectId, + kind: row.original.targetObjectKind, + }, + ], + }; + setSelection(newSelection); + }}> + + {enableRowSizing ? ( + + ) : null} +
+ ), + }; + return [rowHeaderColumn, ...columnDefs]; }, [table]); return {