From 833d0454d08d4e4b264905be807878e546718975 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 | 2 +- .../DeleteRowContextMenuEntryExecutor.java | 58 ++++++++ .../PackageTableRowContextMenuProvider.java | 48 +++++++ ...ayaTableRowControllerIntegrationTests.java | 135 +++++++++++++++++- .../tables/TableQueryService.java | 9 +- .../api/IRowContextMenuEntryExecutor.java | 31 ++++ .../api/IRowContextMenuEntryProvider.java | 35 +++++ .../tables/api/ITableQueryService.java | 6 +- .../dto/InvokeRowContextMenuEntryInput.java | 26 ++++ .../dto/RowContextMenuEntriesInput.java | 25 ++++ .../tables/dto/RowContextMenuEntry.java | 23 +++ .../dto/RowContextMenuSuccessPayload.java | 27 ++++ ...InvokeRowContextMenuEntryEventHandler.java | 103 +++++++++++++ .../ResetTableRowsHeightEventHandler.java | 5 +- .../ResizeTableColumnEventHandler.java | 5 +- .../handlers/RowContextMenuEventHandler.java | 101 +++++++++++++ .../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 +++++++ ...ableDescriptionContextMenuDataFetcher.java | 65 +++++++++ ...nInvokeRowContextMenuEntryDataFetcher.java | 60 ++++++++ ...vokeRowContextMenuEntryMutationRunner.java | 60 ++++++++ .../graphql/RowContextMenuQueryRunner.java | 63 ++++++++ .../representation/TableRepresentation.tsx | 7 +- .../src/rows/RowContextMenu.tsx | 125 ++++++++++++++++ .../RowContextMenu.types.ts} | 20 ++- .../src/rows/useInvokeRowContextMenuEntry.ts | 73 ++++++++++ .../useInvokeRowContextMenuEntry.types.ts | 36 +++++ .../src/rows/useRowContextMenuEntries.ts | 64 +++++++++ .../rows/useRowContextMenuEntries.types.ts | 56 ++++++++ .../src/table/TableContent.tsx | 48 +++---- .../src/table/row/ResizeRowHandler.tsx | 73 ---------- .../src/table/useTableColumns.tsx | 77 ++++++---- 36 files changed, 1399 insertions(+), 156 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/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/TableDescriptionContextMenuDataFetcher.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/RowContextMenu.tsx rename packages/tables/frontend/sirius-components-tables/src/{table/row/ResizeRowHandler.types.ts => rows/RowContextMenu.types.ts} (62%) 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 create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.ts create mode 100644 packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.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 7b5a7bc598..03a8e52219 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -64,7 +64,7 @@ Some log messages have been updated in order to provide more information and mak The configuration property `sirius.web.graphql.tracing` has also been added to active the tracing mode of the GraphQL API. It can be activated using `sirius.web.graphql.tracing=true` since it is not enabled by default to not have any impact on the performance of the application. Some additional log has also been contributed on the frontend in order to view more easily the order and time of the GraphQL requests and responses. - +- 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..6b50ad7a0a --- /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(TableDescription tableDescription, String tableId, String rowId, 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..daff23e0bf --- /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/DeleteTool.svg"))); + } +} 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 a6084eb099..d275a4dfd7 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 @@ -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 @@ -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.services.api.IGivenCommittedTransaction; import org.eclipse.sirius.web.tests.services.api.IGivenCreatedTableSubscription; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; @@ -76,6 +83,12 @@ public class PapayaTableRowControllerIntegrationTests extends AbstractIntegratio @Autowired private ResetTableRowsHeightMutationRunner resetTableRowsHeightMutationRunner; + @Autowired + private InvokeRowContextMenuEntryMutationRunner invokeRowContextMenuEntryMutationRunner; + + @Autowired + private RowContextMenuQueryRunner rowContextMenuQueryRunner; + @Autowired private TableEventSubscriptionRunner tableEventSubscriptionRunner; @@ -211,4 +224,124 @@ public void givenTableWithAResizedRowWhenRowAResetRowsHeightMutationIsTriggeredT .thenCancel() .verify(Duration.ofSeconds(10)); } + + @Test + @DisplayName("Given a table, when row context menu entries are queried, then the correct entries are returned") + @Sql(scripts = {"/scripts/papaya.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = {"/scripts/cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + 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.contextMenuEntries[*].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 + @DisplayName("Given a table, when a row context menu entry is triggered, then the entry is correctly invoked ") + @Sql(scripts = {"/scripts/papaya.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = {"/scripts/cleanup.sql"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD, config = @SqlConfig(transactionMode = SqlConfig.TransactionMode.ISOLATED)) + 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..31a3b01842 --- /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(TableDescription tableDescription, String tableId, String rowId, 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..f8b58310cb --- /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,26 @@ +/******************************************************************************* + * 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..aea76e3905 --- /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,25 @@ +/******************************************************************************* + * 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..8e9a44642e --- /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,23 @@ +/******************************************************************************* + * 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; + +/** + * Entry in the table row context menu. + * + * @author Jerome Gout + */ +public record RowContextMenuEntry(String id, String label, List 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..04e6bd7472 --- /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,27 @@ +/******************************************************************************* + * 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.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 { +} 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..a8b7c60c03 --- /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,103 @@ +/******************************************************************************* + * 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(tableDescription, tableContext.getTable().getId(), invokeRowContextMenuEntryInput.rowId().toString(), + 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()) + .filter(Objects::nonNull) + .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..3099ffb5dc 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 entry menu 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 31b5f52384..384172fa3e 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 @@ -132,6 +132,13 @@ type IconLabelCell implements Cell { type TableDescription implements RepresentationDescription { id: ID! label: String! + contextMenuEntries(tableId: ID!, rowId: ID!): [RowContextMenuEntry!]! +} + +type RowContextMenuEntry { + id: ID! + label: String! + iconURLs: [String!]! } extend type Mutation { @@ -145,6 +152,7 @@ extend type Mutation { resetTableRowsHeight(input: ResetTableRowsHeightInput!): ResetTableRowsHeightPayload! changeGlobalFilterValue(input: ChangeGlobalFilterValueInput!): ChangeGlobalFilterValuePayload! changeColumnFilter(input: ChangeColumnFilterInput!): ChangeColumnFilterPayload! + invokeRowContextMenuEntry(input: InvokeRowContextMenuEntryInput!): InvokeRowContextMenuEntryPayload! } input EditCheckboxCellInput { @@ -261,3 +269,14 @@ input ResetTableRowsHeightInput { } union ResetTableRowsHeightPayload = 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/TableDescriptionContextMenuDataFetcher.java b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/TableDescriptionContextMenuDataFetcher.java new file mode 100644 index 0000000000..9eb30470f1 --- /dev/null +++ b/packages/tables/backend/sirius-components-tables-graphql/src/main/java/org/eclipse/sirius/components/tables/graphql/datafetchers/TableDescriptionContextMenuDataFetcher.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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 = "contextMenuEntries") +public class TableDescriptionContextMenuDataFetcher implements IDataFetcherWithFieldCoordinates>> { + + private static final String TABLE_ID_ARGUMENT = "tableId"; + private static final String ROW_ID_ARGUMENT = "rowId"; + + private final IEditingContextEventProcessorRegistry editingContextEventProcessorRegistry; + + public TableDescriptionContextMenuDataFetcher(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..1e6a874717 --- /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 { + contextMenuEntries(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 3a297085c9..6f53bce75f 100644 --- a/packages/tables/frontend/sirius-components-tables/src/representation/TableRepresentation.tsx +++ b/packages/tables/frontend/sirius-components-tables/src/representation/TableRepresentation.tsx @@ -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 @@ -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 [contextMenuEntries, setContextMenuEntries] = useState([]); + const { getRowContextMenuEntries, loading, contextMenuEntriesData } = useRowContextMenuEntries(); + const { invokeRowContextMenuEntry } = useInvokeRowContextMenuEntry( + editingContextId, + representationId, + tableId, + row.original.id + ); + + useEffect(() => { + if (!loading && contextMenuEntriesData) { + const allContextEntries = + contextMenuEntriesData.viewer.editingContext.representation?.description.contextMenuEntries; + setContextMenuEntries(allContextEntries); + } + }, [loading, contextMenuEntriesData]); + + const handleOpenRowActionMenu = (event: MouseEvent) => { + event.preventDefault(); + setAnchorEl(event.currentTarget); + + const variables = { + variables: { + editingContextId, + representationId: tableId, + tableId, + rowId: row.original.id, + }, + }; + getRowContextMenuEntries(variables); + }; + + const handleClickContextMenuEntry = (menuEntry: GQLRowContextMenuEntry) => { + invokeRowContextMenuEntry(menuEntry.id); + setAnchorEl(null); + }; + + return ( + <> + + + + + + event.stopPropagation()} + onClose={() => setAnchorEl(null)} + open={!!anchorEl} + data-testid={`row-context_menu-${row.original.headerLabel}`}> + {contextMenuEntries.map((entry) => ( + handleClickContextMenuEntry(entry)} + data-testid={`context-menu-entry-${entry.label}`} + disabled={readOnly} + aria-disabled> + + {entry.iconURLs.length > 0 ? ( + + ) : ( +
+ )} + + + + ))} +
+ + ); +}; diff --git a/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.types.ts b/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenu.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/RowContextMenu.types.ts index 7647205d50..34bb6e079c 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/row/ResizeRowHandler.types.ts +++ b/packages/tables/frontend/sirius-components-tables/src/rows/RowContextMenu.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,15 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { GQLLine, GQLTable } from '../TableContent.types'; -export interface ResizeRowHandlerProps { +import { MRT_Row, MRT_TableInstance } from 'material-react-table'; +import { GQLLine } from '../table/TableContent.types'; + +export interface RowContextMenuProps { editingContextId: string; representationId: string; - table: GQLTable; + tableId: string; + row: MRT_Row; + table: MRT_TableInstance; 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/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/rows/useRowContextMenuEntries.ts b/packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.ts new file mode 100644 index 0000000000..b27a0866bd --- /dev/null +++ b/packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.ts @@ -0,0 +1,64 @@ +/******************************************************************************* + * 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, useLazyQuery } from '@apollo/client'; +import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import { useEffect } from 'react'; +import { + GQLGetAllRowContextMenuEntriesData, + GQLGetAllRowContextMenuEntriesVariables, + UseRowContextMenuEntriesValue, +} from './useRowContextMenuEntries.types'; + +const getRowContextMenuEntriesQuery = gql` + query getAllRowContextMenuEntries($editingContextId: ID!, $representationId: ID!, $tableId: ID!, $rowId: ID!) { + viewer { + editingContext(editingContextId: $editingContextId) { + representation(representationId: $representationId) { + description { + ... on TableDescription { + contextMenuEntries(tableId: $tableId, rowId: $rowId) { + __typename + id + label + iconURLs + } + } + } + } + } + } + } +`; + +export const useRowContextMenuEntries = (): UseRowContextMenuEntriesValue => { + const [getRowContextMenuEntries, { loading, data, error }] = useLazyQuery< + GQLGetAllRowContextMenuEntriesData, + GQLGetAllRowContextMenuEntriesVariables + >(getRowContextMenuEntriesQuery); + + const { addErrorMessage } = useMultiToast(); + + useEffect(() => { + if (error) { + const { message } = error; + addErrorMessage(message); + } + }, [error]); + + return { + getRowContextMenuEntries, + loading, + contextMenuEntriesData: data ?? null, + }; +}; diff --git a/packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.types.ts b/packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.types.ts new file mode 100644 index 0000000000..326ea4bc41 --- /dev/null +++ b/packages/tables/frontend/sirius-components-tables/src/rows/useRowContextMenuEntries.types.ts @@ -0,0 +1,56 @@ +/******************************************************************************* + * 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 { LazyQueryExecFunction } from '@apollo/client'; + +export interface UseRowContextMenuEntriesValue { + getRowContextMenuEntries: LazyQueryExecFunction< + GQLGetAllRowContextMenuEntriesData, + GQLGetAllRowContextMenuEntriesVariables + >; + loading: boolean; + contextMenuEntriesData: GQLGetAllRowContextMenuEntriesData | null; +} + +export interface GQLGetAllRowContextMenuEntriesVariables { + editingContextId: string; + representationId: string; + tableId: string; + rowId: string; +} + +export interface GQLGetAllRowContextMenuEntriesData { + viewer: GQLGetAllRowContextMenuEntriesViewer; +} + +export interface GQLGetAllRowContextMenuEntriesViewer { + editingContext: GQLGetAllRowContextMenuEntriesEditingContext; +} + +export interface GQLGetAllRowContextMenuEntriesEditingContext { + representation: GQLGetAllRowContextMenuEntriesRepresentationMetadata; +} + +export interface GQLGetAllRowContextMenuEntriesRepresentationMetadata { + description: GQLGetAllRowContextMenuEntriesRepresentationDescription; +} + +export interface GQLGetAllRowContextMenuEntriesRepresentationDescription { + contextMenuEntries: GQLRowContextMenuEntry[]; +} + +export interface GQLRowContextMenuEntry { + __typename: string; + id: string; + label: string; + iconURLs: string[]; +} 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 c85bf29492..f4fa6235d6 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/TableContent.tsx +++ b/packages/tables/frontend/sirius-components-tables/src/table/TableContent.tsx @@ -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 @@ -18,8 +18,7 @@ import { SettingsButton } from '../actions/SettingsButton'; import { useTableColumnFiltering } from '../columns/useTableColumnFiltering'; import { useTableColumnSizing } from '../columns/useTableColumnSizing'; import { useTableColumnVisibility } from '../columns/useTableColumnVisibility'; -import { ResizeRowHandler } from '../rows/ResizeRowHandler'; -import { RowHeader } from '../rows/RowHeader'; +import { RowContextMenu } from '../rows/RowContextMenu'; import { useResetRowsMutation } from '../rows/useResetRows'; import { CursorBasedPagination } from './CursorBasedPagination'; import { GQLLine, TablePaginationState, TableProps } from './TableContent.types'; @@ -51,7 +50,9 @@ export const TableContent = memo( readOnly, enableColumnVisibility, enableColumnResizing, - enableColumnFilters + enableColumnFilters, + enableRowSizing, + handleRowHeightChange ); const { columnSizing, setColumnSizing } = useTableColumnSizing( editingContextId, @@ -126,9 +127,9 @@ export const TableContent = memo( onPaginationChange(pagination.cursor, pagination.direction, pagination.size); }, [pagination.cursor, pagination.size, pagination.direction]); - const handleRowHeightChange = (rowId, height) => { + function handleRowHeightChange(rowId, height) { setLinesState((prev) => prev.map((line) => (line.id === rowId ? { ...line, height } : line))); - }; + } useEffect(() => { setLinesState([...table.lines]); @@ -158,7 +159,11 @@ export const TableContent = memo( enableGlobalFilter, manualFiltering: true, onGlobalFilterChange: setGlobalFilter, - initialState: { showGlobalFilter: enableGlobalFilter }, + enableColumnPinning: true, + initialState: { + showGlobalFilter: enableGlobalFilter, + columnPinning: { left: ['mrt-row-header'], right: ['mrt-row-actions'] }, + }, onColumnSizingChange: setColumnSizing, onColumnVisibilityChange: setColumnVisibility, onDensityChange: setDensity, @@ -193,26 +198,15 @@ export const TableContent = memo( onPageSizeChange={handlePageSize} /> ), - displayColumnDefOptions: { - 'mrt-row-actions': { - header: '', - size: 120, - }, - }, - renderRowActions: ({ row }) => ( - <> - - {enableRowSizing ? ( - - ) : null} - + renderRowActions: ({ row, table: MRT_table }) => ( + ), }; 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 f1b2e09a1e..a69cedc728 100644 --- a/packages/tables/frontend/sirius-components-tables/src/table/useTableColumns.tsx +++ b/packages/tables/frontend/sirius-components-tables/src/table/useTableColumns.tsx @@ -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 @@ -14,6 +14,8 @@ 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'; @@ -24,37 +26,58 @@ export const useTableColumns = ( readOnly: boolean, enableColumnVisibility: boolean, enableColumnSizing: boolean, - enableColumnFilters: boolean + enableColumnFilters: boolean, + enableRowSizing: boolean, + handleRowHeightChange: (rowId: string, height: number) => void ): UseTableColumnsValue => { const columns = useMemo[]>(() => { - const columnDefs: MRT_ColumnDef[] = table.columns.map((column) => { - return { - id: column.id, - accessorKey: column.id, - header: column.headerLabel, - Header: ({}) => { - return ; - }, - filterVariant: enableColumnFilters ? column.filterVariant : undefined, - size: enableColumnSizing && column.width > 0 ? column.width : undefined, - enableResizing: enableColumnSizing && column.isResizable, - visibleInShowHideMenu: enableColumnVisibility, - Cell: ({ row }) => { - const cell: GQLCell | null = row.original.cells.find((cell) => column.id === cell.columnId) ?? null; - return ( - [] = table.columns.map((column) => ({ + id: column.id, + accessorKey: column.id, + header: column.headerLabel, + Header: ({}) => { + return ; + }, + filterVariant: enableColumnFilters ? column.filterVariant : undefined, + size: enableColumnSizing && column.width > 0 ? column.width : undefined, + enableResizing: enableColumnSizing && column.isResizable, + visibleInShowHideMenu: enableColumnVisibility, + Cell: ({ row }) => { + const cell: GQLCell | null = row.original.cells.find((cell) => column.id === cell.columnId) ?? null; + return ( + + ); + }, + })); + + const rowHeaderColumn: MRT_ColumnDef = { + id: 'mrt-row-header', + header: '', + size: 200, + columnDefType: 'display', + Cell: ({ row }) => ( + <> + + {enableRowSizing ? ( + - ); - }, - }; - }); - - return columnDefs; + ) : null} + + ), + }; + return [rowHeaderColumn, ...columnDefs]; }, [table]); return {