diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 21bb1cb3e6b..c2124bf133e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -84,6 +84,8 @@ As a result, the following maven modules have been deleted: `sirius-web-sample-a - https://github.com/eclipse-sirius/sirius-web/issues/3437[#3437] [core] Migrate frontend to MUI 5, if you contributed React componenent that use MUI, you should upgrade them to use MUI 5. - https://github.com/eclipse-sirius/sirius-web/issues/2204[#2204] [core] Added `getStyledLabel` in `IDefaultLabelService` and `IObjectService`, you must implement this method if you have custom implementation of these interface. - https://github.com/eclipse-sirius/sirius-web/issues/3815[#3815] [core] Make child creation descriptions locale independent +- https://github.com/eclipse-sirius/sirius-web/issues/2204[#2204] [core, tree] Added `getStyledLabel` in `IDefaultLabelService` and `IObjectService`, you must implement this method if you have custom implementation of these interface. +- https://github.com/eclipse-sirius/sirius-web/issues/3875[#3875] [sirius-web] `ExplorerView` component has been moved from components-trees to sirius-web-application module === Dependency update diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java new file mode 100644 index 00000000000..a4466ac5da8 --- /dev/null +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowserEventProcessorFactory.java @@ -0,0 +1,100 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.widget.reference.browser; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; +import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessor; +import org.eclipse.sirius.components.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.components.collaborative.api.IRepresentationRefreshPolicyRegistry; +import org.eclipse.sirius.components.collaborative.api.ISubscriptionManagerFactory; +import org.eclipse.sirius.components.collaborative.trees.TreeEventProcessor; +import org.eclipse.sirius.components.collaborative.trees.api.ITreeEventHandler; +import org.eclipse.sirius.components.collaborative.trees.api.ITreeService; +import org.eclipse.sirius.components.collaborative.trees.api.TreeCreationParameters; +import org.eclipse.sirius.components.collaborative.widget.reference.configurations.ModelBrowserConfiguration; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; +import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.springframework.stereotype.Service; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; + +/** + * Used to create the tree event processors in the context of model browser. + * + * @author Jerome Gout + */ +@Service +public class ModelBrowserEventProcessorFactory implements IRepresentationEventProcessorFactory { + + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + private final ITreeService treeService; + + private final List treeEventHandlers; + + private final ISubscriptionManagerFactory subscriptionManagerFactory; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + + public ModelBrowserEventProcessorFactory(IRepresentationDescriptionSearchService representationDescriptionSearchService, List treeEventHandlers, ITreeService treeService, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry, ISubscriptionManagerFactory subscriptionManagerFactory) { + this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); + this.treeService = Objects.requireNonNull(treeService); + this.treeEventHandlers = Objects.requireNonNull(treeEventHandlers); + this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); + } + + @Override + public boolean canHandle(IRepresentationConfiguration configuration) { + return configuration instanceof ModelBrowserConfiguration; + } + + @Override + public Optional createRepresentationEventProcessor(IRepresentationConfiguration configuration, IEditingContext editingContext) { + if (configuration instanceof ModelBrowserConfiguration modelBrowserConfiguration) { + + String descriptionId; + if (modelBrowserConfiguration.getId().startsWith(ModelBrowsersDescriptionProvider.MODEL_BROWSER_CONTAINER_PREFIX)) { + descriptionId = ModelBrowsersDescriptionProvider.CONTAINER_DESCRIPTION_ID; + } else { + descriptionId = ModelBrowsersDescriptionProvider.REFERENCE_DESCRIPTION_ID; + } + + Optional optionalTreeDescription = this.representationDescriptionSearchService + .findById(editingContext, descriptionId) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); + if (optionalTreeDescription.isPresent()) { + var treeDescription = optionalTreeDescription.get(); + + TreeCreationParameters treeCreationParameters = TreeCreationParameters.newTreeCreationParameters(modelBrowserConfiguration.getId()) + .treeDescription(treeDescription) + .activeFilterIds(List.of()) + .expanded(modelBrowserConfiguration.getExpanded()) + .editingContext(editingContext) + .build(); + + IRepresentationEventProcessor treeEventProcessor = new TreeEventProcessor(editingContext, this.treeService, treeCreationParameters, this.treeEventHandlers, + this.subscriptionManagerFactory.create(), new SimpleMeterRegistry(), this.representationRefreshPolicyRegistry); + return Optional.of(treeEventProcessor); + } + } + return Optional.empty(); + } + +} diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java index 200e9e45a36..a880fd7896d 100644 --- a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/browser/ModelBrowsersDescriptionProvider.java @@ -22,7 +22,6 @@ import java.util.Optional; import java.util.UUID; import java.util.function.Function; -import java.util.function.Predicate; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; @@ -34,7 +33,6 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.edit.command.CommandParameter; import org.eclipse.emf.edit.domain.EditingDomain; -import org.eclipse.sirius.components.collaborative.trees.api.ITreeConfiguration; import org.eclipse.sirius.components.collaborative.widget.reference.api.IReferenceWidgetRootCandidateSearchProvider; import org.eclipse.sirius.components.core.CoreImageConstants; import org.eclipse.sirius.components.core.URLParser; @@ -99,34 +97,28 @@ public ModelBrowsersDescriptionProvider(IObjectService objectService, IURLParser @Override public List getRepresentationDescriptions(IEditingContext editingContext) { - Predicate containerDescriptionCanCreatePredicate = variableManager -> variableManager.get(ITreeConfiguration.TREE_ID, String.class) - .map(treeId -> treeId.startsWith(MODEL_BROWSER_CONTAINER_PREFIX)) - .orElse(false); Function containerDescriptionIsSelectableProvider = variableManager -> { EClass referenceKind = this.resolveReferenceEClass(variableManager).orElse(null); return this.isContainerSelectable(variableManager, referenceKind); }; - var containerDescription = this.getModelBrowserDescription(CONTAINER_DESCRIPTION_ID, containerDescriptionCanCreatePredicate, containerDescriptionIsSelectableProvider, this::getCreationScopeElements); + var containerDescription = this.getModelBrowserDescription(CONTAINER_DESCRIPTION_ID, containerDescriptionIsSelectableProvider, this::getCreationScopeElements, MODEL_BROWSER_CONTAINER_PREFIX); - Predicate referenceDescriptionCanCreatePredicate = variableManager -> variableManager.get(ITreeConfiguration.TREE_ID, String.class) - .map(treeId -> treeId.startsWith(MODEL_BROWSER_REFERENCE_PREFIX)) - .orElse(false); Function referenceDescriptionIsSelectableProvider = variableManager -> { EClass targetType = this.resolveTargetType(variableManager).orElse(null); boolean isContainment = this.resolveIsContainment(variableManager); return this.isTypeSelectable(variableManager, targetType, isContainment); }; - var referenceDescription = this.getModelBrowserDescription(REFERENCE_DESCRIPTION_ID, referenceDescriptionCanCreatePredicate, referenceDescriptionIsSelectableProvider, this::getSearchScopeElements); + var referenceDescription = this.getModelBrowserDescription(REFERENCE_DESCRIPTION_ID, referenceDescriptionIsSelectableProvider, this::getSearchScopeElements, MODEL_BROWSER_REFERENCE_PREFIX); return List.of(containerDescription, referenceDescription); } - private TreeDescription getModelBrowserDescription(String descriptionId, Predicate canCreatePredicate, Function isSelectableProvider, - Function> elementsProvider) { + private TreeDescription getModelBrowserDescription(String descriptionId, Function isSelectableProvider, + Function> elementsProvider, String treeId) { return TreeDescription.newTreeDescription(descriptionId) .label(REPRESENTATION_NAME) - .idProvider(variableManager -> variableManager.get(ITreeConfiguration.TREE_ID, String.class).orElse(null)) + .idProvider(variableManager -> variableManager.get(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, String.class).orElse(treeId)) .treeItemIdProvider(this::getTreeItemId) .kindProvider(this::getKind) .labelProvider(this::getLabel) @@ -140,7 +132,7 @@ private TreeDescription getModelBrowserDescription(String descriptionId, Predica .childrenProvider(variableManager -> this.getChildren(variableManager, isSelectableProvider)) // This predicate will NOT be used while creating the model browser, but we don't want to see the description of the // model browser in the list of representations that can be created. Thus, we will return false all the time. - .canCreatePredicate(canCreatePredicate) + .canCreatePredicate(variableManager -> false) .deleteHandler(this::getDeleteHandler) .renameHandler(this::getRenameHandler) .treeItemObjectProvider(this::getTreeItemObject) diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java new file mode 100644 index 00000000000..c35af506c83 --- /dev/null +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/configurations/ModelBrowserConfiguration.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.components.collaborative.widget.reference.configurations; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; +import org.eclipse.sirius.components.collaborative.widget.reference.browser.ModelBrowsersDescriptionProvider; + +/** + * The configuration of the model browser event processor. + * + * @author Jerome Gout + */ +public class ModelBrowserConfiguration implements IRepresentationConfiguration { + + private final String treeId; + + private final List expanded; + + public ModelBrowserConfiguration(String editingContextId, String treeId, List expanded) { + this.expanded = Objects.requireNonNull(expanded); + + StringBuilder idBuilder = new StringBuilder(treeId); + if (treeId.endsWith(ModelBrowsersDescriptionProvider.MODEL_BROWSER_CONTAINER_PREFIX) || treeId.endsWith(ModelBrowsersDescriptionProvider.MODEL_BROWSER_REFERENCE_PREFIX)) { + idBuilder.append("?"); + } else { + idBuilder.append("&"); + } + + List expandedObjectIds = expanded.stream().map(id -> URLEncoder.encode(id, StandardCharsets.UTF_8)).toList(); + idBuilder.append("expandedIds=[").append(String.join(",", expandedObjectIds)).append("]"); + + this.treeId = idBuilder.toString(); + } + + @Override + public String getId() { + return this.treeId; + } + + public List getExpanded() { + return this.expanded; + } + +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/ITreeConfiguration.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java similarity index 60% rename from packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/ITreeConfiguration.java rename to packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java index 097288244e4..78a781e72b7 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/api/ITreeConfiguration.java +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/main/java/org/eclipse/sirius/components/collaborative/widget/reference/dto/ModelBrowserEventInput.java @@ -10,16 +10,17 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -package org.eclipse.sirius.components.collaborative.trees.api; +package org.eclipse.sirius.components.collaborative.widget.reference.dto; -import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; +import java.util.List; +import java.util.UUID; + +import org.eclipse.sirius.components.core.api.IInput; /** - * The configuration of the tree event processor. + * The input of the model browser event subscription. * * @author Jerome Gout */ -public interface ITreeConfiguration extends IRepresentationConfiguration { - - String TREE_ID = "treeId"; +public record ModelBrowserEventInput(UUID id, String editingContextId, String treeId, List expanded) implements IInput { } diff --git a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java index 1fc5a9e0f78..cb3d76cc609 100644 --- a/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java +++ b/packages/forms/backend/sirius-components-collaborative-widget-reference/src/test/java/org/eclipse/sirius/components/collaborative/widget/reference/provider/ModelBrowserTests.java @@ -16,7 +16,6 @@ import java.util.List; -import org.eclipse.sirius.components.collaborative.trees.api.ITreeConfiguration; import org.eclipse.sirius.components.collaborative.widget.reference.browser.ModelBrowsersDescriptionProvider; import org.eclipse.sirius.components.core.URLParser; import org.eclipse.sirius.components.core.api.IEditingContext; @@ -55,7 +54,6 @@ public void testModelBrowserDescriptionProvider() { variableManager.put(IEditingContext.EDITING_CONTEXT, editingContext); var referenceTreeId = "modelBrowser://reference"; - variableManager.put(ITreeConfiguration.TREE_ID, referenceTreeId); var tree = new TreeRenderer(variableManager, referenceBrowserDescription.get()).render(); assertThat(tree).isNotNull(); assertThat(tree.getId()).isEqualTo(referenceTreeId); @@ -63,7 +61,6 @@ public void testModelBrowserDescriptionProvider() { assertThat(tree.getKind()).isEqualTo(Tree.KIND); var containerTreeId = "modelBrowser://container"; - variableManager.put(ITreeConfiguration.TREE_ID, containerTreeId); tree = new TreeRenderer(variableManager, containerBrowserDescription.get()).render(); assertThat(tree).isNotNull(); assertThat(tree.getId()).isEqualTo(containerTreeId); diff --git a/packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java b/packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java new file mode 100644 index 00000000000..0554d810d7c --- /dev/null +++ b/packages/forms/backend/sirius-components-widget-reference-graphql/src/main/java/org/eclipse/sirius/components/widget/reference/graphql/datafetchers/subscription/SubscriptionModelBrowserEventDataFetcher.java @@ -0,0 +1,72 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.components.widget.reference.graphql.datafetchers.subscription; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.sirius.components.annotations.spring.graphql.SubscriptionDataFetcher; +import org.eclipse.sirius.components.collaborative.widget.reference.configurations.ModelBrowserConfiguration; +import org.eclipse.sirius.components.collaborative.widget.reference.dto.ModelBrowserEventInput; +import org.eclipse.sirius.components.core.api.IPayload; +import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates; +import org.eclipse.sirius.components.graphql.api.IEventProcessorSubscriptionProvider; +import org.eclipse.sirius.components.graphql.api.IExceptionWrapper; +import org.eclipse.sirius.components.graphql.api.LocalContextConstants; +import org.reactivestreams.Publisher; + +import graphql.execution.DataFetcherResult; +import graphql.schema.DataFetchingEnvironment; + +/** + * The data fetcher used to send the refreshed tree to a model browser subscription . + * + * @author Jerome Gout + */ +@SubscriptionDataFetcher(type = "Subscription", field = "modelBrowserEvent") +public class SubscriptionModelBrowserEventDataFetcher implements IDataFetcherWithFieldCoordinates>> { + + private static final String INPUT_ARGUMENT = "input"; + + private final ObjectMapper objectMapper; + + private final IExceptionWrapper exceptionWrapper; + + private final IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider; + + public SubscriptionModelBrowserEventDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEventProcessorSubscriptionProvider eventProcessorSubscriptionProvider) { + this.objectMapper = Objects.requireNonNull(objectMapper); + this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper); + this.eventProcessorSubscriptionProvider = Objects.requireNonNull(eventProcessorSubscriptionProvider); + } + + @Override + public Publisher> get(DataFetchingEnvironment environment) throws Exception { + Object argument = environment.getArgument(INPUT_ARGUMENT); + var input = this.objectMapper.convertValue(argument, ModelBrowserEventInput.class); + var modelBrowserConfiguration = new ModelBrowserConfiguration(input.editingContextId(), input.treeId(), input.expanded()); + + Map localContext = new HashMap<>(); + localContext.put(LocalContextConstants.EDITING_CONTEXT_ID, input.editingContextId()); + localContext.put(LocalContextConstants.REPRESENTATION_ID, modelBrowserConfiguration.getId()); + + return this.exceptionWrapper.wrapFlux(() -> this.eventProcessorSubscriptionProvider.getSubscription(input.editingContextId(), modelBrowserConfiguration, input), input) + .map(payload -> DataFetcherResult.newResult() + .data(payload) + .localContext(localContext) + .build()); + } +} diff --git a/packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls b/packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls new file mode 100644 index 00000000000..7ef36e3356f --- /dev/null +++ b/packages/forms/backend/sirius-components-widget-reference/src/main/resources/schema/modelbrowser.graphqls @@ -0,0 +1,10 @@ +extend type Subscription { + modelBrowserEvent(input: ModelBrowserEventInput!): TreeEventPayload! +} + +input ModelBrowserEventInput { + id: ID! + treeId: String! + editingContextId: ID! + expanded: [String!]! +} \ No newline at end of file diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx index 66622afdbfc..76fe368e763 100644 --- a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.tsx @@ -18,6 +18,7 @@ import { useState } from 'react'; import { makeStyles } from 'tss-react/mui'; import { ModelBrowserFilterBar } from './ModelBrowserFilterBar'; import { ModelBrowserTreeViewProps, ModelBrowserTreeViewState } from './ModelBrowserTreeView.types'; +import { useModelBrowserSubscription } from './useModelBrowserSubscription'; const useTreeStyle = makeStyles()((theme) => ({ title: { @@ -43,35 +44,47 @@ export const ModelBrowserTreeView = ({ }: ModelBrowserTreeViewProps) => { const { classes } = useTreeStyle(); - const [state, setState] = useState({ filterBarText: '' }); + const [state, setState] = useState({ + filterBarText: '', + expanded: [], + maxDepth: 1, + }); + + const treeId: string = `modelBrowser://${leafType}?ownerKind=${encodeURIComponent( + ownerKind + )}&targetType=${encodeURIComponent(widget.reference.referenceKind)}&ownerId=${ + widget.ownerId + }&descriptionId=${encodeURIComponent(widget.descriptionId)}&isContainment=${widget.reference.containment}`; + const { tree } = useModelBrowserSubscription(editingContextId, treeId, state.expanded, state.maxDepth); + + const onExpandedElementChange = (expanded: string[], maxDepth: number) => { + setState((prevState) => ({ ...prevState, expanded, maxDepth })); + }; return ( <> setState({ filterBarText: event.target.value })} - onTextClear={() => setState({ filterBarText: '' })} + onTextChange={(event) => setState((prevState) => ({ ...prevState, filterBarText: event.target.value }))} + onTextClear={() => setState((prevState) => ({ ...prevState, filterBarText: '' }))} text={state.filterBarText} /> {title}
- } - eventType="explorerEvent" - eventInputType="ExplorerEventInput" - /> + {tree && ( + } + onExpandedElementChange={onExpandedElementChange} + /> + )}
); diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts index 84510c4df5d..1a7c9478733 100644 --- a/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/ModelBrowserTreeView.types.ts @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2023 Obeo. + * Copyright (c) 2023, 2024 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 @@ -24,4 +24,6 @@ export interface ModelBrowserTreeViewProps { export interface ModelBrowserTreeViewState { filterBarText: string; + expanded: string[]; + maxDepth: number; } diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx new file mode 100644 index 00000000000..5d3229767f8 --- /dev/null +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.tsx @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { gql, OnDataOptions, useSubscription } from '@apollo/client'; +import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import { + getTreeEventSubscription, + GQLTreeEventPayload, + GQLTreeRefreshedEventPayload, + UseTreeSubscriptionState, + UseTreeSubscriptionValue, +} from '@eclipse-sirius/sirius-components-trees'; +import { useEffect, useState } from 'react'; +import { + GQLModelBrowserEventData, + GQLModelBrowserEventInput, + GQLModelBrowserEventVariables, +} from './useModelBrowserSubscription.types'; + +const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => + payload.__typename === 'TreeRefreshedEventPayload'; + +export const useModelBrowserSubscription = ( + editingContextId: string, + treeId: string, + expanded: string[], + maxDepth: number +): UseTreeSubscriptionValue => { + const [state, setState] = useState({ + id: crypto.randomUUID(), + tree: null, + complete: false, + }); + + const input: GQLModelBrowserEventInput = { + id: state.id, + editingContextId, + treeId, + expanded, + }; + + const variables: GQLModelBrowserEventVariables = { input }; + + const onData = ({ data }: OnDataOptions) => { + const { data: gqlTreeData } = data; + if (gqlTreeData) { + const { modelBrowserEvent: payload } = gqlTreeData; + if (isTreeRefreshedEventPayload(payload)) { + const { tree } = payload; + setState((prevState) => ({ ...prevState, tree })); + } + } + }; + + const onComplete = () => setState((prevState) => ({ ...prevState, complete: true })); + + const { error, loading } = useSubscription( + gql(getTreeEventSubscription(maxDepth, 'modelBrowserEvent', 'ModelBrowserEventInput')), + { + variables, + fetchPolicy: 'no-cache', + onData, + onComplete, + } + ); + + const { addErrorMessage } = useMultiToast(); + useEffect(() => { + if (error) { + addErrorMessage('An unexpected error has occurred, please refresh the page'); + } + }, [error]); + + return { + loading, + tree: state.tree, + complete: state.complete, + }; +}; diff --git a/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts new file mode 100644 index 00000000000..47f752e5c13 --- /dev/null +++ b/packages/forms/frontend/sirius-components-widget-reference/src/components/useModelBrowserSubscription.types.ts @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { GQLTreeEventPayload } from '@eclipse-sirius/sirius-components-trees'; + +export interface GQLModelBrowserEventInput { + id: string; + treeId: string; + editingContextId: string; + expanded: string[]; +} + +export interface GQLModelBrowserEventVariables { + input: GQLModelBrowserEventInput; +} + +export interface GQLModelBrowserEventData { + modelBrowserEvent: GQLTreeEventPayload; +} diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java index 750005e96d1..44f6bceb923 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerConfiguration.java @@ -17,14 +17,14 @@ import java.util.List; import java.util.Objects; -import org.eclipse.sirius.components.collaborative.trees.api.ITreeConfiguration; +import org.eclipse.sirius.components.collaborative.api.IRepresentationConfiguration; /** * The configuration of the explorer event processor. * * @author sbegaudeau */ -public class ExplorerConfiguration implements ITreeConfiguration { +public class ExplorerConfiguration implements IRepresentationConfiguration { private final String treeId; diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java index 10131037d8f..99c991df867 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/ExplorerEventProcessorFactory.java @@ -28,8 +28,8 @@ import org.eclipse.sirius.components.collaborative.trees.api.TreeCreationParameters; import org.eclipse.sirius.components.core.api.IEditingContext; import org.eclipse.sirius.components.core.api.IRepresentationDescriptionSearchService; -import org.eclipse.sirius.components.representations.VariableManager; import org.eclipse.sirius.components.trees.description.TreeDescription; +import org.eclipse.sirius.web.application.views.explorer.services.ExplorerDescriptionProvider; import org.springframework.stereotype.Service; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; @@ -72,7 +72,10 @@ public boolean canHandle(IRepresentationConfiguration configuration) { public Optional createRepresentationEventProcessor(IRepresentationConfiguration configuration, IEditingContext editingContext) { if (configuration instanceof ExplorerConfiguration treeConfiguration) { - Optional optionalTreeDescription = this.findTreeDescription(editingContext, treeConfiguration); + Optional optionalTreeDescription = this.representationDescriptionSearchService + .findById(editingContext, ExplorerDescriptionProvider.DESCRIPTION_ID) + .filter(TreeDescription.class::isInstance) + .map(TreeDescription.class::cast); if (optionalTreeDescription.isPresent()) { var treeDescription = optionalTreeDescription.get(); @@ -90,16 +93,4 @@ public Optional createRepresentationEventProcesso } return Optional.empty(); } - - private Optional findTreeDescription(IEditingContext editingContext, ExplorerConfiguration treeConfiguration) { - VariableManager variableManager = new VariableManager(); - variableManager.put(ExplorerConfiguration.TREE_ID, treeConfiguration.getId()); - return this.representationDescriptionSearchService - .findAll(editingContext).values().stream() - .filter(TreeDescription.class::isInstance) - .map(TreeDescription.class::cast) - .filter(treeDescription -> treeDescription.getCanCreatePredicate().test(variableManager)) - .findFirst(); - } - } diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java index 4e6ce924cf9..68c8bec8eb3 100644 --- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java +++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/views/explorer/services/ExplorerDescriptionProvider.java @@ -17,7 +17,6 @@ import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.function.Predicate; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; @@ -104,10 +103,6 @@ public ExplorerDescriptionProvider(ExplorerDescriptionProviderConfiguration expl @Override public List getRepresentationDescriptions(IEditingContext editingContext) { - Predicate canCreatePredicate = variableManager -> variableManager.get("treeId", String.class) - .map(treeId -> treeId.startsWith(PREFIX)) - .orElse(false); - var explorerTreeDescription = TreeDescription.newTreeDescription(DESCRIPTION_ID) .label(REPRESENTATION_NAME) .idProvider(this::getTreeId) @@ -123,7 +118,9 @@ public List getRepresentationDescriptions(IEditingCo .elementsProvider(this.explorerElementsProvider::getElements) .hasChildrenProvider(this.explorerChildrenProvider::hasChildren) .childrenProvider(this.explorerChildrenProvider::getChildren) - .canCreatePredicate(canCreatePredicate) + // This predicate will NOT be used while creating the explorer, but we don't want to see the description of the + // explorer in the list of representations that can be created. Thus, we will return false all the time. + .canCreatePredicate(variableManager -> false) .deleteHandler(this::getDeleteHandler) .renameHandler(this::getRenameHandler) .treeItemObjectProvider(this::getTreeItemObject) diff --git a/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java index 5607de109e7..920e51c3c3e 100644 --- a/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java +++ b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/explorer/ExplorerEventSubscriptionRunner.java @@ -22,14 +22,14 @@ import reactor.core.publisher.Flux; /** - * Used to get the tree event subscription with the GraphQL API. + * Used to get the explorer event subscription with the GraphQL API. * * @author gdaniel */ @Service public class ExplorerEventSubscriptionRunner implements ISubscriptionRunner { - private static final String TREE_EVENT_SUBSCRIPTION = """ + private static final String EXPLORER_EVENT_SUBSCRIPTION = """ subscription explorerEvent($input: ExplorerEventInput!) { explorerEvent(input: $input) { __typename @@ -45,7 +45,7 @@ public ExplorerEventSubscriptionRunner(IGraphQLRequestor graphQLRequestor) { @Override public Flux run(ExplorerEventInput input) { - return this.graphQLRequestor.subscribe(TREE_EVENT_SUBSCRIPTION, input); + return this.graphQLRequestor.subscribe(EXPLORER_EVENT_SUBSCRIPTION, input); } } diff --git a/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java new file mode 100644 index 00000000000..2c568f472cb --- /dev/null +++ b/packages/sirius-web/backend/sirius-web-tests/src/main/java/org/eclipse/sirius/web/tests/services/modelbrowser/ModelBrowserEventSubscriptionRunner.java @@ -0,0 +1,51 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.sirius.web.tests.services.modelbrowser; + +import java.util.Objects; + +import org.eclipse.sirius.components.collaborative.widget.reference.dto.ModelBrowserEventInput; +import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor; +import org.eclipse.sirius.components.graphql.tests.api.ISubscriptionRunner; +import org.springframework.stereotype.Service; + +import reactor.core.publisher.Flux; + +/** + * Used to get the model browser event subscription with the GraphQL API. + * + * @author Jerome Gout + */ +@Service +public class ModelBrowserEventSubscriptionRunner implements ISubscriptionRunner { + + private static final String MODEL_BROWSER_EVENT_SUBSCRIPTION = """ + subscription modelBrowserEvent($input: ModelBrowserEventInput!) { + modelBrowserEvent(input: $input) { + __typename + } + } + """; + + private final IGraphQLRequestor graphQLRequestor; + + public ModelBrowserEventSubscriptionRunner(IGraphQLRequestor graphQLRequestor) { + this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor); + } + + @Override + public Flux run(ModelBrowserEventInput input) { + return this.graphQLRequestor.subscribe(MODEL_BROWSER_EVENT_SUBSCRIPTION, input); + } + +} diff --git a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java index 08df05038f7..583124e75ab 100644 --- a/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java +++ b/packages/sirius-web/backend/sirius-web/src/test/java/org/eclipse/sirius/web/application/controllers/trees/ModelBrowserExpandAllControllerTests.java @@ -26,14 +26,13 @@ import java.util.function.Consumer; import org.eclipse.sirius.components.collaborative.trees.dto.TreeRefreshedEventPayload; +import org.eclipse.sirius.components.collaborative.widget.reference.dto.ModelBrowserEventInput; import org.eclipse.sirius.components.trees.Tree; import org.eclipse.sirius.components.trees.tests.graphql.ExpandAllTreePathQueryRunner; import org.eclipse.sirius.web.AbstractIntegrationTests; -import org.eclipse.sirius.web.application.views.explorer.ExplorerEventInput; -import org.eclipse.sirius.web.application.views.explorer.services.ExplorerDescriptionProvider; import org.eclipse.sirius.web.data.StudioIdentifiers; import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState; -import org.eclipse.sirius.web.tests.services.explorer.ExplorerEventSubscriptionRunner; +import org.eclipse.sirius.web.tests.services.modelbrowser.ModelBrowserEventSubscriptionRunner; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -56,11 +55,14 @@ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class ModelBrowserExpandAllControllerTests extends AbstractIntegrationTests { + private static final String TREE_ID = "modelBrowser://container?ownerKind=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor&targetType=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor" + + "&ownerId=7ec7c9bf-96a1-4f69-8441-176161c08877&descriptionId=rectangular.nodestyle.background&isContainment=false"; + @Autowired private IGivenInitialServerState givenInitialServerState; @Autowired - private ExplorerEventSubscriptionRunner treeEventSubscriptionRunner; + private ModelBrowserEventSubscriptionRunner treeEventSubscriptionRunner; @Autowired private ExpandAllTreePathQueryRunner expandAllTreePathQueryRunner; @@ -70,14 +72,12 @@ public void beforeEach() { this.givenInitialServerState.initialize(); } - @Test @DisplayName("Given a reference widget, when we ask for the tree path to expand all a document, then its path in the explorer is returned") @Sql(scripts = { "/scripts/studio.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 givenReferenceWidgetWhenWeAskForTheTreePathToExpandAllThenItsPathInTheExplorerIsReturned() { - var input = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), "modelBrowser://container?ownerKind=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor&targetType=siriusComponents%3A%2F%2Fsemantic%3Fdomain%3Dview%26entity%3DUserColor" + - "&ownerId=7ec7c9bf-96a1-4f69-8441-176161c08877&descriptionId=rectangular.nodestyle.background&isContainment=false", List.of(), List.of()); + var input = new ModelBrowserEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), TREE_ID, List.of()); var flux = this.treeEventSubscriptionRunner.run(input); var treeId = new AtomicReference(); @@ -115,7 +115,7 @@ public void givenReferenceWidgetWhenWeAskForTheTreePathToExpandAllThenItsPathInT .thenCancel() .verify(Duration.ofSeconds(10)); - var expandedTreeInput = new ExplorerEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), ExplorerDescriptionProvider.PREFIX, treeItemIds.get(), List.of()); + var expandedTreeInput = new ModelBrowserEventInput(UUID.randomUUID(), StudioIdentifiers.EMPTY_STUDIO_PROJECT.toString(), TREE_ID, treeItemIds.get()); var expandedTreeFlux = this.treeEventSubscriptionRunner.run(expandedTreeInput); Consumer initialExpandedTreeContentConsumer = this.getTreeSubscriptionConsumer(tree -> { diff --git a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx index 8f21737f8d7..42d9ba17173 100644 --- a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx @@ -35,8 +35,9 @@ import { } from '@eclipse-sirius/sirius-components-forms'; import { GanttRepresentation } from '@eclipse-sirius/sirius-components-gantt'; import { PortalRepresentation } from '@eclipse-sirius/sirius-components-portals'; -import { ExplorerView, treeItemContextMenuEntryExtensionPoint } from '@eclipse-sirius/sirius-components-trees'; +import { treeItemContextMenuEntryExtensionPoint } from '@eclipse-sirius/sirius-components-trees'; import { ValidationView } from '@eclipse-sirius/sirius-components-validation'; +import { SelectionDialog } from '@eclipse-sirius/sirius-components-selection'; import { GQLReferenceWidget, ReferenceIcon, @@ -59,17 +60,17 @@ import { ObjectTreeItemContextMenuContribution } from '../views/edit-project/Obj import { DetailsView } from '../views/edit-project/workbench-views/DetailsView'; import { RelatedElementsView } from '../views/edit-project/workbench-views/RelatedElementsView'; import { RepresentationsView } from '../views/edit-project/workbench-views/RepresentationsView'; +import { ExplorerView } from '../views/explorer/ExplorerView'; import { createProjectAreaCardExtensionPoint } from '../views/project-browser/create-projects-area/CreateProjectAreaExtensionPoints'; import { NewProjectCard } from '../views/project-browser/create-projects-area/NewProjectCard'; import { ShowAllProjectTemplatesCard } from '../views/project-browser/create-projects-area/ShowAllProjectTemplatesCard'; import { UploadProjectCard } from '../views/project-browser/create-projects-area/UploadProjectCard'; +import { ProjectSettingTabContribution } from '../views/project-settings/ProjectSettingsView.types'; +import { projectSettingsTabExtensionPoint } from '../views/project-settings/ProjectSettingsViewExtensionPoints'; import { ProjectImagesSettings } from '../views/project-settings/images/ProjectImagesSettings'; import { ellipseNodeStyleDocumentTransform } from './ElipseNodeDocumentTransform'; import { referenceWidgetDocumentTransform } from './ReferenceWidgetDocumentTransform'; -import { SelectionDialog } from '@eclipse-sirius/sirius-components-selection'; -import { ProjectSettingTabContribution } from '../views/project-settings/ProjectSettingsView.types'; -import { projectSettingsTabExtensionPoint } from '../views/project-settings/ProjectSettingsViewExtensionPoints'; const getType = (representation: RepresentationMetadata): string | null => { const query = representation.kind.substring(representation.kind.indexOf('?') + 1, representation.kind.length); const params = new URLSearchParams(query); diff --git a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.tsx similarity index 84% rename from packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx rename to packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.tsx index 8b6a1888ce2..cbbec7b2f81 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.tsx +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.tsx @@ -11,16 +11,20 @@ * Obeo - initial API and implementation *******************************************************************************/ import { WorkbenchViewComponentProps } from '@eclipse-sirius/sirius-components-core'; +import { + TreeToolBar, + TreeToolBarContext, + TreeToolBarContextValue, + TreeView, + TreeFilter, + FilterBar, + useTreeFilters, +} from '@eclipse-sirius/sirius-components-trees'; import { Theme } from '@mui/material/styles'; import { useContext, useEffect, useRef, useState } from 'react'; import { makeStyles } from 'tss-react/mui'; -import { TreeToolBar } from '../toolbar/TreeToolBar'; -import { TreeToolBarContext } from '../toolbar/TreeToolBarContext'; -import { TreeToolBarContextValue } from '../toolbar/TreeToolBarContext.types'; -import { FilterBar } from '../trees/FilterBar'; -import { ExplorerViewState, TreeFilter } from './ExplorerView.types'; -import { TreeView } from './TreeView'; -import { useTreeFilters } from './useTreeFilters'; +import { ExplorerViewState } from './ExplorerView.types'; +import { useExplorerSubscription } from './useExplorerSubscription'; const useStyles = makeStyles()((theme: Theme) => ({ treeView: { @@ -43,11 +47,16 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon filterBarText: '', filterBarTreeFiltering: false, treeFilters: [], + expanded: [], + maxDepth: 1, }; const [state, setState] = useState(initialState); const treeToolBarContributionComponents = useContext(TreeToolBarContext).map( (contribution) => contribution.props.component ); + const activeTreeFilterIds = state.treeFilters.filter((filter) => filter.state).map((filter) => filter.id); + + const { tree } = useExplorerSubscription(editingContextId, activeTreeFilterIds, state.expanded, state.maxDepth); const { loading, treeFilters } = useTreeFilters(editingContextId, 'explorer://'); @@ -96,9 +105,10 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon }); }} onFilterButtonClick={(enabled) => - setState((prevState) => { - return { ...prevState, filterBarTreeFiltering: enabled }; - }) + setState((prevState) => ({ + ...prevState, + filterBarTreeFiltering: enabled, + })) } onClose={() => setState((prevState) => { @@ -108,8 +118,9 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon /> ); } - - const activeTreeFilterIds = state.treeFilters.filter((filter) => filter.state).map((filter) => filter.id); + const onExpandedElementChange = (expanded: string[], maxDepth: number) => { + setState((prevState) => ({ ...prevState, expanded, maxDepth })); + }; return (
@@ -136,13 +147,12 @@ export const ExplorerView = ({ editingContextId, readOnly }: WorkbenchViewCompon editingContextId={editingContextId} readOnly={readOnly} treeId={'explorer://'} + tree={tree} enableMultiSelection={true} synchronizedWithSelection={state.synchronizedWithSelection} - activeFilterIds={activeTreeFilterIds} textToHighlight={state.filterBarText} textToFilter={state.filterBarTreeFiltering ? state.filterBarText : null} - eventType="explorerEvent" - eventInputType="ExplorerEventInput" + onExpandedElementChange={onExpandedElementChange} />
diff --git a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.types.ts similarity index 86% rename from packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts rename to packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.types.ts index 8436ee962a9..e3418decbc7 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/ExplorerView.types.ts +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/ExplorerView.types.ts @@ -10,6 +10,7 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +import { TreeFilter } from '@eclipse-sirius/sirius-components-trees'; export interface ExplorerViewState { synchronizedWithSelection: boolean; @@ -17,10 +18,6 @@ export interface ExplorerViewState { filterBarText: string | null; filterBarTreeFiltering: boolean; treeFilters: TreeFilter[]; -} - -export interface TreeFilter { - id: string; - label: string; - state: boolean; + expanded: string[]; + maxDepth: number; } diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx new file mode 100644 index 00000000000..cf1e5be32fe --- /dev/null +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.tsx @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ + +import { gql, OnDataOptions, useSubscription } from '@apollo/client'; +import { useMultiToast } from '@eclipse-sirius/sirius-components-core'; +import { useEffect, useState } from 'react'; +import { + GQLTreeEventPayload, + GQLTreeRefreshedEventPayload, + UseTreeSubscriptionValue, + UseTreeSubscriptionState, + getTreeEventSubscription, +} from '@eclipse-sirius/sirius-components-trees'; +import { + GQLExplorerEventInput, + GQLExplorerEventVariables, + GQLExplorerEventData, +} from './useExplorerSubscription.types'; + +const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => + payload.__typename === 'TreeRefreshedEventPayload'; + +export const useExplorerSubscription = ( + editingContextId: string, + activeFilterIds: string[], + expanded: string[], + maxDepth: number +): UseTreeSubscriptionValue => { + const [state, setState] = useState({ + id: crypto.randomUUID(), + tree: null, + complete: false, + }); + + const input: GQLExplorerEventInput = { + id: state.id, + editingContextId, + treeId: 'explorer://', + activeFilterIds, + expanded, + }; + + const variables: GQLExplorerEventVariables = { input }; + + const onData = ({ data }: OnDataOptions) => { + const { data: gqlTreeData } = data; + if (gqlTreeData) { + const { explorerEvent: payload } = gqlTreeData; + if (isTreeRefreshedEventPayload(payload)) { + const { tree } = payload; + setState((prevState) => ({ ...prevState, tree })); + } + } + }; + + const onComplete = () => setState((prevState) => ({ ...prevState, complete: true })); + + const { error, loading } = useSubscription( + gql(getTreeEventSubscription(maxDepth, 'explorerEvent', 'ExplorerEventInput')), + { + variables, + fetchPolicy: 'no-cache', + onData, + onComplete, + } + ); + + const { addErrorMessage } = useMultiToast(); + useEffect(() => { + if (error) { + addErrorMessage('An unexpected error has occurred, please refresh the page'); + } + }, [error]); + + return { + loading, + tree: state.tree, + complete: state.complete, + }; +}; diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts new file mode 100644 index 00000000000..f9315b9973c --- /dev/null +++ b/packages/sirius-web/frontend/sirius-web-application/src/views/explorer/useExplorerSubscription.types.ts @@ -0,0 +1,29 @@ +/******************************************************************************* + * Copyright (c) 2024 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +import { GQLTreeEventPayload } from '@eclipse-sirius/sirius-components-trees'; + +export interface GQLExplorerEventInput { + id: string; + treeId: string; + editingContextId: string; + expanded: string[]; + activeFilterIds: string[]; +} + +export interface GQLExplorerEventVariables { + input: GQLExplorerEventInput; +} + +export interface GQLExplorerEventData { + explorerEvent: GQLTreeEventPayload; +} diff --git a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java index 25ba1f6d6c9..1a056dec475 100644 --- a/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java +++ b/packages/trees/backend/sirius-components-collaborative-trees/src/main/java/org/eclipse/sirius/components/collaborative/trees/TreeService.java @@ -12,7 +12,6 @@ *******************************************************************************/ package org.eclipse.sirius.components.collaborative.trees; -import org.eclipse.sirius.components.collaborative.trees.api.ITreeConfiguration; import org.eclipse.sirius.components.collaborative.trees.api.ITreeService; import org.eclipse.sirius.components.collaborative.trees.api.TreeCreationParameters; import org.eclipse.sirius.components.core.api.IEditingContext; @@ -34,7 +33,6 @@ public class TreeService implements ITreeService { public Tree create(TreeCreationParameters treeCreationParameters) { VariableManager variableManager = new VariableManager(); variableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, treeCreationParameters.getId()); - variableManager.put(ITreeConfiguration.TREE_ID, treeCreationParameters.getId()); variableManager.put(IEditingContext.EDITING_CONTEXT, treeCreationParameters.getEditingContext()); variableManager.put(TreeRenderer.EXPANDED, treeCreationParameters.getExpanded()); variableManager.put(TreeRenderer.ACTIVE_FILTER_IDS, treeCreationParameters.getActiveFilterIds()); diff --git a/packages/trees/frontend/sirius-components-trees/src/index.ts b/packages/trees/frontend/sirius-components-trees/src/index.ts index 89bc33b5751..19fe55a5f6d 100644 --- a/packages/trees/frontend/sirius-components-trees/src/index.ts +++ b/packages/trees/frontend/sirius-components-trees/src/index.ts @@ -10,17 +10,22 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +export * from './toolbar/TreeToolBar'; export * from './toolbar/TreeToolBarContext'; export * from './toolbar/TreeToolBarContext.types'; export * from './toolbar/TreeToolBarContribution'; export * from './toolbar/TreeToolBarContribution.types'; +export * from './treeitems/filterTreeItem'; export * from './treeitems/TreeItemAction.types'; export * from './treeitems/TreeItemContextMenu'; export * from './treeitems/TreeItemContextMenu.types'; export * from './treeitems/TreeItemContextMenuEntry.types'; export * from './treeitems/TreeItemContextMenuEntryExtensionPoints'; -export * from './treeitems/filterTreeItem'; -export * from './views/ExplorerView'; +export * from './trees/FilterBar'; +export * from './views/getTreeEventSubscription'; +export * from './views/TreeFiltersMenu'; +export * from './views/TreeFiltersMenu.types'; export * from './views/TreeView'; export * from './views/TreeView.types'; export * from './views/TreeViewExtensionPoints'; +export * from './views/useTreeFilters'; diff --git a/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts b/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts index bb5f3c47d8f..6837b611c61 100644 --- a/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/toolbar/TreeToolBar.types.ts @@ -10,8 +10,8 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { TreeFilter } from '../views/ExplorerView.types'; import { TreeToolBarContributionComponentProps } from './TreeToolBarContribution.types'; +import { TreeFilter } from '../views/TreeFiltersMenu.types'; export interface TreeToolBarProps { editingContextId: string; diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx index 74e7ccf2ab8..c739283b224 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.tsx @@ -22,8 +22,7 @@ import Paper from '@mui/material/Paper'; import Popper from '@mui/material/Popper'; import { useEffect, useRef, useState } from 'react'; import { makeStyles } from 'tss-react/mui'; -import { TreeFilter } from './ExplorerView.types'; -import { TreeFilterMenuProps } from './TreeFiltersMenu.types'; +import { TreeFilterMenuProps, TreeFilter } from './TreeFiltersMenu.types'; const useTreeFiltersMenuStyles = makeStyles()((_) => ({ root: { diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts index 0b8c08f991b..4ff55da1566 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeFiltersMenu.types.ts @@ -11,9 +11,13 @@ * Obeo - initial API and implementation *******************************************************************************/ -import { TreeFilter } from './ExplorerView.types'; - export interface TreeFilterMenuProps { filters: TreeFilter[]; onTreeFilterMenuItemClick: (filters: TreeFilter[]) => void; } + +export interface TreeFilter { + id: string; + label: string; + state: boolean; +} diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx index bdc4d471304..0c06aba45bc 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.tsx @@ -10,10 +10,9 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ -import { gql, useLazyQuery, useSubscription } from '@apollo/client'; -import { DataExtension, Toast, useData, useSelection } from '@eclipse-sirius/sirius-components-core'; -import { useMachine } from '@xstate/react'; -import { useEffect } from 'react'; +import { gql, useLazyQuery } from '@apollo/client'; +import { DataExtension, useData, useMultiToast, useSelection } from '@eclipse-sirius/sirius-components-core'; +import { useEffect, useState } from 'react'; import { Tree } from '../trees/Tree'; import { GQLGetExpandAllTreePathData, @@ -21,30 +20,12 @@ import { GQLGetTreePathData, GQLGetTreePathVariables, GQLTree, - GQLTreeEventData, - GQLTreeEventVariables, GQLTreeItem, TreeConverter, TreeViewComponentProps, + TreeViewComponentState, } from './TreeView.types'; import { treeViewTreeConverterExtensionPoint } from './TreeViewExtensionPoints'; -import { - AutoExpandToRevealSelectionEvent, - HandleCompleteEvent, - HandleExpandAllTreePathEvent, - HandleExpandedEvent, - HandleOnExpandAllEvent, - HandleSubscriptionResultEvent, - HandleTreePathEvent, - HideToastEvent, - SchemaValue, - ShowToastEvent, - SynchronizeWithSelectionEvent, - TreeViewContext, - TreeViewEvent, - treeViewMachine, -} from './TreeViewMachine'; -import { getTreeEventSubscription } from './getTreeEventSubscription'; const getTreePathQuery = gql` query getTreePath($editingContextId: ID!, $treeId: ID!, $selectionEntryIds: [ID!]!) { @@ -75,27 +56,23 @@ const getExpandAllTreePathQuery = gql` export const TreeView = ({ editingContextId, readOnly, + tree, treeId, enableMultiSelection, synchronizedWithSelection, - activeFilterIds, textToHighlight, textToFilter, markedItemIds = [], treeItemActionRender, - eventType, - eventInputType, + onExpandedElementChange, }: TreeViewComponentProps) => { - const [{ value, context }, dispatch] = useMachine(treeViewMachine, { - context: { - synchronizedWithSelection: synchronizedWithSelection, - }, + const [state, setState] = useState({ + autoExpandToRevealSelection: synchronizedWithSelection, + expanded: [], + maxDepth: 1, }); const { selection } = useSelection(); - const { toast, treeView } = value as SchemaValue; - const { id, tree, expanded, maxDepth, autoExpandToRevealSelection, treeItemToExpandAll, message } = context; - const [getTreePath, { loading: treePathLoading, data: treePathData, error: treePathError }] = useLazyQuery< GQLGetTreePathData, GQLGetTreePathVariables @@ -112,7 +89,7 @@ export const TreeView = ({ .sort() .join(':'); useEffect(() => { - if (tree && autoExpandToRevealSelection) { + if (tree && state.autoExpandToRevealSelection) { const variables: GQLGetTreePathVariables = { editingContextId, treeId: tree.id, @@ -120,109 +97,97 @@ export const TreeView = ({ }; getTreePath({ variables }); } - }, [editingContextId, tree, selectionKey, autoExpandToRevealSelection, getTreePath]); + }, [editingContextId, tree, selectionKey, state.autoExpandToRevealSelection, getTreePath]); useEffect(() => { if (!treePathLoading) { if (treePathData) { - const handleTreePathEvent: HandleTreePathEvent = { type: 'HANDLE_TREE_PATH', treePathData }; - dispatch(handleTreePathEvent); - } - if (treePathError) { - const { message } = treePathError; - const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; - dispatch(showToastEvent); + const { expanded, maxDepth } = state; + if (treePathData.viewer?.editingContext?.treePath) { + const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = treePathData.viewer.editingContext.treePath; + const newExpanded: string[] = [...expanded]; + + treeItemIdsToExpand?.forEach((itemToExpand) => { + if (!expanded.includes(itemToExpand)) { + newExpanded.push(itemToExpand); + } + }); + setState((prevState) => ({ + ...prevState, + expanded: newExpanded, + maxDepth: Math.max(expandedMaxDepth, maxDepth), + })); + } } } - }, [treePathLoading, treePathData, treePathError]); - - useEffect(() => { - if (tree && treeItemToExpandAll) { - const variables: GQLGetExpandAllTreePathVariables = { - editingContextId, - treeId: tree.id, - treeItemId: treeItemToExpandAll, - }; - getExpandAllTreePath({ variables }); - } - }, [editingContextId, tree, treeItemToExpandAll, getExpandAllTreePathQuery]); + }, [treePathLoading, treePathData]); useEffect(() => { if (!expandAllTreePathLoading) { if (expandAllTreePathData) { - const handleExpandAllTreePathEvent: HandleExpandAllTreePathEvent = { - type: 'HANDLE_EXPAND_ALL_TREE_PATH', - expandAllTreePathData, - }; - dispatch(handleExpandAllTreePathEvent); - } - if (expandAllTreePathError) { - const { message } = expandAllTreePathError; - const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; - dispatch(showToastEvent); + const { expanded, maxDepth } = state; + if (expandAllTreePathData.viewer?.editingContext?.expandAllTreePath) { + const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = + expandAllTreePathData.viewer.editingContext.expandAllTreePath; + const newExpanded: string[] = [...expanded]; + + treeItemIdsToExpand?.forEach((itemToExpand) => { + if (!expanded.includes(itemToExpand)) { + newExpanded.push(itemToExpand); + } + }); + setState((prevState) => ({ + ...prevState, + expanded: newExpanded, + maxDepth: Math.max(expandedMaxDepth, maxDepth), + })); + } } } - }, [expandAllTreePathLoading, expandAllTreePathData, expandAllTreePathError]); + }, [expandAllTreePathLoading, expandAllTreePathData]); - const { error } = useSubscription( - gql(getTreeEventSubscription(maxDepth, eventType, eventInputType)), - { - variables: { - input: { - id, - treeId, - editingContextId, - expanded, - activeFilterIds, - }, - }, - fetchPolicy: 'no-cache', - skip: treeView === 'complete', - onData: ({ data }) => { - const handleDataEvent: HandleSubscriptionResultEvent = { - type: 'HANDLE_SUBSCRIPTION_RESULT', - result: data, - }; - dispatch(handleDataEvent); - }, - onComplete: () => { - const completeEvent: HandleCompleteEvent = { type: 'HANDLE_COMPLETE' }; - dispatch(completeEvent); - }, - } - ); + const { addMessages } = useMultiToast(); useEffect(() => { - if (error) { - const { message } = error; - const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; - dispatch(showToastEvent); + if (expandAllTreePathError) { + addMessages(expandAllTreePathError.message); } - }, [error, dispatch]); - - useEffect(() => { - const autoExpandToRevealSelectionEvent: AutoExpandToRevealSelectionEvent = { - type: 'AUTO_EXPAND_TO_REVEAL_SELECTION', - autoExpandToRevealSelection: true, - }; - dispatch(autoExpandToRevealSelectionEvent); - }, [selection]); - + }, [expandAllTreePathError]); useEffect(() => { - const synchronizeWithSelectionEvent: SynchronizeWithSelectionEvent = { - type: 'SYNCHRONIZE_WITH_SELECTION', - synchronizedWithSelection: synchronizedWithSelection, - }; - dispatch(synchronizeWithSelectionEvent); - }, [synchronizedWithSelection]); + if (treePathError) { + addMessages(treePathError.message); + } + }, [treePathError]); const onExpand = (id: string, depth: number) => { - const handleExpandedEvent: HandleExpandedEvent = { type: 'HANDLE_EXPANDED', id, depth }; - dispatch(handleExpandedEvent); + const { expanded, maxDepth } = state; + + if (expanded.includes(id)) { + const newExpanded = [...expanded]; + newExpanded.splice(newExpanded.indexOf(id), 1); + + // Disable synchronize mode on collapse + setState((prevState) => ({ + ...prevState, + autoExpandToRevealSelection: false, + expanded: newExpanded, + maxDepth: Math.max(maxDepth, depth), + })); + } else { + setState((prevState) => ({ ...prevState, expanded: [...expanded, id], maxDepth: Math.max(maxDepth, depth) })); + } }; + useEffect(() => { + onExpandedElementChange(state.expanded, state.maxDepth); + }, [state.expanded, state.maxDepth]); + const onExpandAll = (treeItem: GQLTreeItem) => { - const handleOnExpandAllEvent: HandleOnExpandAllEvent = { type: 'HANDLE_ON_EXPAND_ALL', treeItemId: treeItem.id }; - dispatch(handleOnExpandAllEvent); + const variables: GQLGetExpandAllTreePathVariables = { + editingContextId, + treeId: tree.id, + treeItemId: treeItem.id, + }; + getExpandAllTreePath({ variables }); }; const { data: treeConverters }: DataExtension = useData(treeViewTreeConverterExtensionPoint); @@ -233,28 +198,21 @@ export const TreeView = ({ }); return ( - <> -
- {tree ? ( - - ) : null} -
- dispatch({ type: 'HIDE_TOAST' } as HideToastEvent)} - /> - +
+ {tree ? ( + + ) : null} +
); }; diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts index 562f24f2bf6..1d9f1979faa 100644 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts +++ b/packages/trees/frontend/sirius-components-trees/src/views/TreeView.types.ts @@ -16,48 +16,24 @@ import { TreeItemActionProps } from '../treeitems/TreeItemAction.types'; export interface TreeViewComponentProps extends WorkbenchViewComponentProps { treeId: string; + tree: GQLTree; enableMultiSelection: boolean; synchronizedWithSelection: boolean; - activeFilterIds: string[]; textToHighlight: string | null; textToFilter: string | null; markedItemIds?: string[]; treeItemActionRender?: (props: TreeItemActionProps) => React.ReactNode; - eventType: string; - eventInputType: string; -} - -export interface TreeConverter { - convert(editingContextId: string, tree: GQLTree): GQLTree; + onExpandedElementChange: (expanded: string[], maxDepth: number) => void; } -export interface GQLTreeEventVariables { - input: GQLTreeEventInput; -} - -export interface GQLTreeEventInput { - id: string; - treeId: string; - editingContextId: string; +export interface TreeViewComponentState { + autoExpandToRevealSelection: boolean; expanded: string[]; - activeFilterIds: string[]; -} - -export interface GQLTreeEventData { - treeEvent: GQLTreeEventPayload; -} - -export interface GQLExplorerEventData { - explorerEvent: GQLTreeEventPayload; -} - -export interface GQLTreeEventPayload { - __typename: string; + maxDepth: number; } -export interface GQLTreeRefreshedEventPayload extends GQLTreeEventPayload { - id: string; - tree: GQLTree; +export interface TreeConverter { + convert(editingContextId: string, tree: GQLTree): GQLTree; } export interface GQLTree { @@ -118,3 +94,24 @@ export interface GQLGetExpandAllTreePathViewer { export interface GQLGetExpandAllTreePathEditingContext { expandAllTreePath: GQLTreePath; } + +export interface GQLTreeEventPayload { + __typename: string; +} + +export interface GQLTreeRefreshedEventPayload extends GQLTreeEventPayload { + id: string; + tree: GQLTree; +} + +export interface UseTreeSubscriptionValue { + loading: boolean; + tree: GQLTree | null; + complete: boolean; +} + +export interface UseTreeSubscriptionState { + id: string; + tree: GQLTree | null; + complete: boolean; +} diff --git a/packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts b/packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts deleted file mode 100644 index 99e016f8412..00000000000 --- a/packages/trees/frontend/sirius-components-trees/src/views/TreeViewMachine.ts +++ /dev/null @@ -1,301 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2022, 2024 Obeo. - * This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v2.0 - * which accompanies this distribution, and is available at - * https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Obeo - initial API and implementation - *******************************************************************************/ -import { SubscriptionResult } from '@apollo/client'; -import { assign, Machine } from 'xstate'; -import { - GQLExplorerEventData, - GQLGetExpandAllTreePathData, - GQLGetTreePathData, - GQLTree, - GQLTreeEventData, - GQLTreeEventPayload, - GQLTreeRefreshedEventPayload, -} from './TreeView.types'; - -export interface TreeViewStateSchema { - states: { - toast: { - states: { - visible: {}; - hidden: {}; - }; - }; - treeView: { - states: { - idle: {}; - ready: {}; - complete: {}; - }; - }; - }; -} - -export type SchemaValue = { - toast: 'visible' | 'hidden'; - treeView: 'idle' | 'ready' | 'complete'; -}; - -export interface TreeViewContext { - id: string; - tree: GQLTree | null; - expanded: string[]; - maxDepth: number; - autoExpandToRevealSelection: boolean; - treeItemToExpandAll: string | null; - synchronizedWithSelection: boolean; - message: string | null; -} - -export type ShowToastEvent = { type: 'SHOW_TOAST'; message: string }; -export type HideToastEvent = { type: 'HIDE_TOAST' }; -export type HandleSubscriptionResultEvent = { - type: 'HANDLE_SUBSCRIPTION_RESULT'; - result: SubscriptionResult; -}; -export type HandleCompleteEvent = { type: 'HANDLE_COMPLETE' }; -export type AutoExpandToRevealSelectionEvent = { - type: 'AUTO_EXPAND_TO_REVEAL_SELECTION'; - autoExpandToRevealSelection: boolean; -}; -export type SynchronizeWithSelectionEvent = { - type: 'SYNCHRONIZE_WITH_SELECTION'; - synchronizedWithSelection: boolean; -}; -export type HandleExpandedEvent = { type: 'HANDLE_EXPANDED'; id: string; depth: number }; -export type HandleOnExpandAllEvent = { - type: 'HANDLE_ON_EXPAND_ALL'; - treeItemId: string; -}; -export type HandleExpandAllTreePathEvent = { - type: 'HANDLE_EXPAND_ALL_TREE_PATH'; - expandAllTreePathData: GQLGetExpandAllTreePathData; -}; -export type HandleTreePathEvent = { type: 'HANDLE_TREE_PATH'; treePathData: GQLGetTreePathData }; -export type TreeViewEvent = - | HandleSubscriptionResultEvent - | HandleCompleteEvent - | ShowToastEvent - | HideToastEvent - | AutoExpandToRevealSelectionEvent - | HandleExpandedEvent - | HandleOnExpandAllEvent - | HandleExpandAllTreePathEvent - | HandleTreePathEvent - | SynchronizeWithSelectionEvent; - -const isTreeRefreshedEventPayload = (payload: GQLTreeEventPayload): payload is GQLTreeRefreshedEventPayload => - payload.__typename === 'TreeRefreshedEventPayload'; - -const isATreeEventData = (data: GQLTreeEventData | GQLExplorerEventData): data is GQLTreeEventData => { - return 'treeEvent' in data; -}; - -export const treeViewMachine = Machine( - { - type: 'parallel', - context: { - id: crypto.randomUUID(), - tree: null, - expanded: [], - maxDepth: 1, - autoExpandToRevealSelection: true, - treeItemToExpandAll: null, - synchronizedWithSelection: true, - message: null, - }, - states: { - toast: { - initial: 'hidden', - states: { - hidden: { - on: { - SHOW_TOAST: { - target: 'visible', - actions: 'setMessage', - }, - }, - }, - visible: { - on: { - HIDE_TOAST: { - target: 'hidden', - actions: 'clearMessage', - }, - }, - }, - }, - }, - treeView: { - initial: 'idle', - states: { - idle: { - on: { - HANDLE_SUBSCRIPTION_RESULT: [ - { - cond: 'isTreeRefreshedEventPayload', - target: 'ready', - actions: 'handleSubscriptionResult', - }, - { - target: 'idle', - actions: 'handleSubscriptionResult', - }, - ], - }, - }, - ready: { - on: { - HANDLE_SUBSCRIPTION_RESULT: { - target: 'ready', - actions: 'handleSubscriptionResult', - }, - AUTO_EXPAND_TO_REVEAL_SELECTION: { - actions: 'autoExpandToRevealSelection', - }, - HANDLE_EXPANDED: { - actions: 'expand', - }, - HANDLE_ON_EXPAND_ALL: { - actions: 'onExpandAll', - }, - HANDLE_EXPAND_ALL_TREE_PATH: { - actions: 'expandAllTreePath', - }, - HANDLE_TREE_PATH: { - actions: 'handleTreePath', - }, - HANDLE_COMPLETE: { - target: 'complete', - }, - SYNCHRONIZE_WITH_SELECTION: { - actions: 'synchronizeWithSelection', - }, - }, - }, - complete: { - type: 'final', - }, - }, - }, - }, - }, - { - guards: { - isTreeRefreshedEventPayload: (_, event) => { - const { result } = event as HandleSubscriptionResultEvent; - const { data } = result; - if (isATreeEventData(data)) { - return isTreeRefreshedEventPayload(data.treeEvent); - } else { - return isTreeRefreshedEventPayload(data.explorerEvent); - } - }, - }, - actions: { - handleSubscriptionResult: assign((_, event) => { - const { result } = event as HandleSubscriptionResultEvent; - const { data } = result; - if (isATreeEventData(data)) { - if (isTreeRefreshedEventPayload(data.treeEvent)) { - const { tree } = data.treeEvent; - return { tree }; - } - } else { - if (isTreeRefreshedEventPayload(data.explorerEvent)) { - const { tree } = data.explorerEvent; - return { tree }; - } - } - return {}; - }), - synchronizeWithSelection: assign((_, event) => { - const { synchronizedWithSelection } = event as SynchronizeWithSelectionEvent; - if (synchronizedWithSelection) { - return { synchronizedWithSelection, autoExpandToRevealSelection: true }; - } - return { synchronizedWithSelection, autoExpandToRevealSelection: false }; - }), - autoExpandToRevealSelection: assign((context, event) => { - const { autoExpandToRevealSelection } = event as AutoExpandToRevealSelectionEvent; - const { synchronizedWithSelection } = context; - if (synchronizedWithSelection) { - return { autoExpandToRevealSelection }; - } - return {}; - }), - expand: assign((context, event) => { - const { expanded, maxDepth } = context; - const { id, depth } = event as HandleExpandedEvent; - - if (expanded.includes(id)) { - const newExpanded = [...expanded]; - newExpanded.splice(newExpanded.indexOf(id), 1); - - // Disable synchronize mode on collapse - return { expanded: newExpanded, autoExpandToRevealSelection: false, maxDepth: Math.max(maxDepth, depth) }; - } - return { expanded: [...expanded, id], maxDepth: Math.max(maxDepth, depth) }; - }), - onExpandAll: assign((_, event) => { - const { treeItemId } = event as HandleOnExpandAllEvent; - return { treeItemToExpandAll: treeItemId }; - }), - expandAllTreePath: assign((context, event) => { - const { expanded, maxDepth } = context; - const { expandAllTreePathData } = event as HandleExpandAllTreePathEvent; - if (expandAllTreePathData.viewer?.editingContext?.expandAllTreePath) { - const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = - expandAllTreePathData.viewer.editingContext.expandAllTreePath; - const newExpanded: string[] = [...expanded]; - - treeItemIdsToExpand?.forEach((itemToExpand) => { - if (!expanded.includes(itemToExpand)) { - newExpanded.push(itemToExpand); - } - }); - - return { - expanded: newExpanded, - maxDepth: Math.max(expandedMaxDepth, maxDepth), - treeItemToExpandAll: null, - }; - } - return { treeItemToExpandAll: null }; - }), - handleTreePath: assign((context, event) => { - const { expanded, maxDepth } = context; - const { treePathData } = event as HandleTreePathEvent; - if (treePathData.viewer?.editingContext?.treePath) { - const { treeItemIdsToExpand, maxDepth: expandedMaxDepth } = treePathData.viewer.editingContext.treePath; - const newExpanded: string[] = [...expanded]; - - treeItemIdsToExpand?.forEach((itemToExpand) => { - if (!expanded.includes(itemToExpand)) { - newExpanded.push(itemToExpand); - } - }); - - return { expanded: newExpanded, maxDepth: Math.max(expandedMaxDepth, maxDepth) }; - } - return {}; - }), - setMessage: assign((_, event) => { - const { message } = event as ShowToastEvent; - return { message }; - }), - clearMessage: assign((_) => { - return { message: null }; - }), - }, - } -);