diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index f07ce3e1bfb..687e0e0c98c 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,10 @@ - https://github.com/eclipse-sirius/sirius-components/issues/818[#818] [workbench] The concept of `Selection` will be restructured, as described in the ADR-036. Every part of the code involved in the manipulation of the selection of the workbench will be impacted. This includes concepts as remote as the representation descriptions which are used to computed fields like `kind`. For example, the behavior of the `TreeDescription#getKindProvider` and `NodeDescription#getTargetObjectKindProvider` will have to be updated for all the providers. Failure to update to the new behavior will make the selection fail in the workbench +=== Deprecation warning + +[core] The Success parameterless contructor will be removed soon. + === Breaking Changes - https://github.com/eclipse-sirius/sirius-components/issues/804[#804] [core] Update the name of our configuration properties. The configuration property `sirius.web.graphql.websocket.allowed.origins` will now be `sirius.components.cors.allowedOriginPatterns` and it will support complex patterns on top of regular origins. The default value will be restored to nothing since it has temporarily been set to `*`. In a development environment, the recommended value would be both patterns `https://localhost:[*]` and `http://localhost:[*]` in order to accept requests from any application hosted on the same machine. The configuration property `org.eclipse.sirius.web.editingContextEventProcessorRegistry.disposeDelay` will now be `sirius.components.editingContext.disposeDelay`. Its default value will be 1s since it is the only realistic option with domain and view support. @@ -19,12 +23,14 @@ === New features - https://github.com/eclipse-sirius/sirius-components/issues/773[#773] [compatibility] Add support for both `createView` and `deleteView` model operations which can be used to support unsynchronized diagrams from Sirius Desktop. +- https://github.com/eclipse-sirius/sirius-components/issues/694[#694] [core] Add `IRepresentationRefreshPolicyRegistry` to contribute `IRepresentationRefreshPolicyProvider`s in order to customize on which kind of change description, representations will be refreshed. === Improvements - https://github.com/eclipse-sirius/sirius-components/issues/799[#799] [diagram] The buttons in the diagram's toolbar now have proper tooltips - [core] Add a task to display TypeScript errors in the VS Code problems view - https://github.com/eclipse-sirius/sirius-components/issues/773[#773] [compatibility] The synchronization policy of the node descriptions is now properly computed from the `AbstractNodeMapping` +- https://github.com/eclipse-sirius/sirius-components/issues/694[#694] [core] Data can be provided to Success in order to notify changment made by operation made on the editing context. == v0.5.0 diff --git a/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/ListItem.java b/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/ListItem.java index 5679f067675..0b6411ff739 100644 --- a/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/ListItem.java +++ b/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/ListItem.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Obeo. + * Copyright (c) 2019, 2021 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,12 +14,14 @@ import java.text.MessageFormat; import java.util.Objects; +import java.util.function.Supplier; import org.eclipse.sirius.web.annotations.Immutable; import org.eclipse.sirius.web.annotations.graphql.GraphQLField; import org.eclipse.sirius.web.annotations.graphql.GraphQLID; import org.eclipse.sirius.web.annotations.graphql.GraphQLNonNull; import org.eclipse.sirius.web.annotations.graphql.GraphQLObjectType; +import org.eclipse.sirius.web.representations.IStatus; /** * The list item. @@ -33,8 +35,14 @@ public final class ListItem { private String label; + private String kind; + private String imageURL; + private boolean deletable; + + private Supplier deleteHandler; + private ListItem() { // Prevent instantiation } @@ -52,20 +60,36 @@ public String getLabel() { return this.label; } + @GraphQLField + @GraphQLNonNull + public String getKind() { + return this.kind; + } + @GraphQLField @GraphQLNonNull public String getImageURL() { return this.imageURL; } + @GraphQLField + @GraphQLNonNull + public boolean isDeletable() { + return this.deletable; + } + + public Supplier getDeleteHandler() { + return this.deleteHandler; + } + public static Builder newListItem(String id) { return new Builder(id); } @Override public String toString() { - String pattern = "{0} '{'id: {1}, label: {2}, imageURL: {3}'}'"; //$NON-NLS-1$ - return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.label, this.imageURL); + String pattern = "{0} '{'id: {1}, label: {2}, kind: {3}, deletable: {4}, imageURL: {5}'}'"; //$NON-NLS-1$ + return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.label, this.kind, this.deletable, this.imageURL); } /** @@ -79,8 +103,14 @@ public static final class Builder { private String label; + private String kind; + private String imageURL; + private boolean deletable; + + private Supplier deleteHandler; + private Builder(String id) { this.id = Objects.requireNonNull(id); } @@ -90,15 +120,33 @@ public Builder label(String label) { return this; } + public Builder kind(String kind) { + this.kind = Objects.requireNonNull(kind); + return this; + } + public Builder imageURL(String imageURL) { this.imageURL = Objects.requireNonNull(imageURL); return this; } + public Builder deletable(boolean deletable) { + this.deletable = Objects.requireNonNull(deletable); + return this; + } + + public Builder deleteHandler(Supplier deleteHandler) { + this.deleteHandler = Objects.requireNonNull(deleteHandler); + return this; + } + public ListItem build() { ListItem listItem = new ListItem(); listItem.id = Objects.requireNonNull(this.id); listItem.label = Objects.requireNonNull(this.label); + listItem.kind = Objects.requireNonNull(this.kind); + listItem.deletable = Objects.requireNonNull(this.deletable); + listItem.deleteHandler = Objects.requireNonNull(this.deleteHandler); listItem.imageURL = Objects.requireNonNull(this.imageURL); return listItem; } diff --git a/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/components/ListComponent.java b/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/components/ListComponent.java index 17339209ad9..8a087f48339 100644 --- a/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/components/ListComponent.java +++ b/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/components/ListComponent.java @@ -15,6 +15,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; import org.eclipse.sirius.web.components.Element; import org.eclipse.sirius.web.components.IComponent; @@ -23,6 +25,7 @@ import org.eclipse.sirius.web.forms.elements.ListElementProps; import org.eclipse.sirius.web.forms.validation.DiagnosticComponent; import org.eclipse.sirius.web.forms.validation.DiagnosticComponentProps; +import org.eclipse.sirius.web.representations.IStatus; import org.eclipse.sirius.web.representations.VariableManager; /** @@ -58,12 +61,21 @@ public Element render() { String itemId = listDescription.getItemIdProvider().apply(itemVariableManager); String itemLabel = listDescription.getItemLabelProvider().apply(itemVariableManager); + String itemKind = listDescription.getItemKindProvider().apply(itemVariableManager); String itemImageURL = listDescription.getItemImageURLProvider().apply(itemVariableManager); + boolean isItemDeletable = listDescription.getItemDeletableProvider().apply(itemVariableManager); + Function genericHandler = listDescription.getItemDeleteHandlerProvider(); + Supplier specializedHandler = () -> { + return genericHandler.apply(itemVariableManager); + }; // @formatter:off ListItem item = ListItem.newListItem(itemId) .label(itemLabel) + .kind(itemKind) .imageURL(itemImageURL) + .deletable(isItemDeletable) + .deleteHandler(specializedHandler) .build(); // @formatter:on diff --git a/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/description/ListDescription.java b/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/description/ListDescription.java index cc88b494008..ed6a41be420 100644 --- a/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/description/ListDescription.java +++ b/backend/sirius-web-forms/src/main/java/org/eclipse/sirius/web/forms/description/ListDescription.java @@ -18,6 +18,7 @@ import java.util.function.Function; import org.eclipse.sirius.web.annotations.Immutable; +import org.eclipse.sirius.web.representations.IStatus; import org.eclipse.sirius.web.representations.VariableManager; /** @@ -38,8 +39,14 @@ public final class ListDescription extends AbstractWidgetDescription { private Function itemLabelProvider; + private Function itemKindProvider; + private Function itemImageURLProvider; + private Function itemDeletableProvider; + + private Function itemDeleteHandlerProvider; + private ListDescription() { // Prevent instantiation } @@ -64,10 +71,22 @@ public Function getItemLabelProvider() { return this.itemLabelProvider; } + public Function getItemKindProvider() { + return this.itemKindProvider; + } + public Function getItemImageURLProvider() { return this.itemImageURLProvider; } + public Function getItemDeletableProvider() { + return this.itemDeletableProvider; + } + + public Function getItemDeleteHandlerProvider() { + return this.itemDeleteHandlerProvider; + } + public static Builder newListDescription(String id) { return new Builder(id); } @@ -97,8 +116,14 @@ public static final class Builder { private Function itemLabelProvider; + private Function itemKindProvider; + private Function itemImageURLProvider; + private Function itemDeletableProvider; + + private Function itemDeleteHandlerProvider; + private Function> diagnosticsProvider; private Function kindProvider; @@ -134,11 +159,26 @@ public Builder itemLabelProvider(Function itemLabelProv return this; } + public Builder itemKindProvider(Function itemKindProvider) { + this.itemKindProvider = Objects.requireNonNull(itemKindProvider); + return this; + } + public Builder itemImageURLProvider(Function itemImageURLProvider) { this.itemImageURLProvider = Objects.requireNonNull(itemImageURLProvider); return this; } + public Builder itemDeletableProvider(Function itemDeletableProvider) { + this.itemDeletableProvider = Objects.requireNonNull(itemDeletableProvider); + return this; + } + + public Builder itemDeleteHandlerProvider(Function itemDeleteHandlerProvider) { + this.itemDeleteHandlerProvider = Objects.requireNonNull(itemDeleteHandlerProvider); + return this; + } + public Builder diagnosticsProvider(Function> diagnosticsProvider) { this.diagnosticsProvider = Objects.requireNonNull(diagnosticsProvider); return this; @@ -162,7 +202,10 @@ public ListDescription build() { listDescription.itemsProvider = Objects.requireNonNull(this.itemsProvider); listDescription.itemIdProvider = Objects.requireNonNull(this.itemIdProvider); listDescription.itemLabelProvider = Objects.requireNonNull(this.itemLabelProvider); + listDescription.itemKindProvider = Objects.requireNonNull(this.itemKindProvider); listDescription.itemImageURLProvider = Objects.requireNonNull(this.itemImageURLProvider); + listDescription.itemDeletableProvider = Objects.requireNonNull(this.itemDeletableProvider); + listDescription.itemDeleteHandlerProvider = Objects.requireNonNull(this.itemDeleteHandlerProvider); listDescription.diagnosticsProvider = Objects.requireNonNull(this.diagnosticsProvider); listDescription.kindProvider = Objects.requireNonNull(this.kindProvider); listDescription.messageProvider = Objects.requireNonNull(this.messageProvider); diff --git a/backend/sirius-web-representations/src/main/java/org/eclipse/sirius/web/representations/Success.java b/backend/sirius-web-representations/src/main/java/org/eclipse/sirius/web/representations/Success.java index b69cd055cce..4ed5905605e 100644 --- a/backend/sirius-web-representations/src/main/java/org/eclipse/sirius/web/representations/Success.java +++ b/backend/sirius-web-representations/src/main/java/org/eclipse/sirius/web/representations/Success.java @@ -12,6 +12,10 @@ *******************************************************************************/ package org.eclipse.sirius.web.representations; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + /** * The status to return when the representation description handler success. * @@ -19,4 +23,25 @@ */ public class Success implements IStatus { + private final Map parameters; + + private final String changeKind; + + public Success() { + this("", new HashMap<>()); //$NON-NLS-1$ + } + + public Success(String changeKind, Map parameters) { + this.changeKind = Objects.requireNonNull(changeKind); + this.parameters = Objects.requireNonNull(parameters); + } + + public String getChangeKind() { + return this.changeKind; + } + + public Map getParameters() { + return this.parameters; + } + } diff --git a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramCreationService.java b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramCreationService.java index 78947baabe8..aae8227b901 100644 --- a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramCreationService.java +++ b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramCreationService.java @@ -82,6 +82,7 @@ public Diagram create(String label, Object targetObject, DiagramDescription diag @Override public Optional refresh(IEditingContext editingContext, IDiagramContext diagramContext) { Diagram previousDiagram = diagramContext.getDiagram(); + var optionalObject = this.objectService.getObject(editingContext, previousDiagram.getTargetObjectId()); // @formatter:off var optionalDiagramDescription = this.representationDescriptionSearchService.findById(editingContext, previousDiagram.getDescriptionId()) diff --git a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessor.java b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessor.java index 492ad174be6..37b0c42e635 100644 --- a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessor.java +++ b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessor.java @@ -20,11 +20,15 @@ import org.eclipse.sirius.web.core.api.IEditingContext; import org.eclipse.sirius.web.core.api.IInput; import org.eclipse.sirius.web.core.api.IPayload; +import org.eclipse.sirius.web.core.api.IRepresentationDescriptionSearchService; import org.eclipse.sirius.web.core.api.IRepresentationInput; import org.eclipse.sirius.web.diagrams.Diagram; +import org.eclipse.sirius.web.diagrams.description.DiagramDescription; import org.eclipse.sirius.web.representations.IRepresentation; import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; import org.eclipse.sirius.web.spring.collaborative.api.ChangeKind; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicy; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManager; import org.eclipse.sirius.web.spring.collaborative.diagrams.api.IDiagramContext; import org.eclipse.sirius.web.spring.collaborative.diagrams.api.IDiagramCreationService; @@ -64,11 +68,16 @@ public class DiagramEventProcessor implements IDiagramEventProcessor { private final IDiagramCreationService diagramCreationService; + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + private final Many canBeDisposedSink = Sinks.many().unicast().onBackpressureBuffer(); private final DiagramEventFlux diagramEventFlux; public DiagramEventProcessor(IEditingContext editingContext, IDiagramContext diagramContext, List diagramEventHandlers, ISubscriptionManager subscriptionManager, + IRepresentationDescriptionSearchService representationDescriptionSearchService, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry, IDiagramCreationService diagramCreationService) { this.logger.trace("Creating the diagram event processor {}", diagramContext.getDiagram().getId()); //$NON-NLS-1$ @@ -76,6 +85,8 @@ public DiagramEventProcessor(IEditingContext editingContext, IDiagramContext dia this.diagramContext = Objects.requireNonNull(diagramContext); this.diagramEventHandlers = Objects.requireNonNull(diagramEventHandlers); this.subscriptionManager = Objects.requireNonNull(subscriptionManager); + this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); this.diagramCreationService = Objects.requireNonNull(diagramCreationService); // We automatically refresh the representation before using it since things may have changed since the moment it @@ -137,15 +148,32 @@ public void refresh(ChangeDescription changeDescription) { /** * A diagram is refresh if there is a semantic change or if there is a diagram layout change coming from this very - * diagram (not other diagrams) + * diagram (not other diagrams). * * @param changeDescription * The change description * @return true if the diagram should be refreshed, false otherwise */ - private boolean shouldRefresh(ChangeDescription changeDescription) { - return ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind()) - || (DiagramChangeKind.DIAGRAM_LAYOUT_CHANGE.equals(changeDescription.getKind()) && changeDescription.getSourceId().equals(this.diagramContext.getDiagram().getId())); + public boolean shouldRefresh(ChangeDescription changeDescription) { + Diagram diagram = this.diagramContext.getDiagram(); + // @formatter:off + var optionalDiagramDescription = this.representationDescriptionSearchService.findById(this.editingContext, diagram.getDescriptionId()) + .filter(DiagramDescription.class::isInstance) + .map(DiagramDescription.class::cast); + // @formatter:on + + // @formatter:off + return optionalDiagramDescription.flatMap(this.representationRefreshPolicyRegistry::getRepresentationRefreshPolicy) + .orElseGet(this::getDefaultRefreshPolicy) + .shouldRefresh(changeDescription); + // @formatter:on + } + + private IRepresentationRefreshPolicy getDefaultRefreshPolicy() { + return changeDescription -> { + return ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind()) + || (DiagramChangeKind.DIAGRAM_LAYOUT_CHANGE.equals(changeDescription.getKind()) && changeDescription.getSourceId().equals(this.diagramContext.getDiagram().getId())); + }; } @Override diff --git a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessorFactory.java index d9964b241f0..d4f95fa90a6 100644 --- a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessorFactory.java +++ b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/DiagramEventProcessorFactory.java @@ -17,10 +17,12 @@ import java.util.Optional; import org.eclipse.sirius.web.core.api.IEditingContext; +import org.eclipse.sirius.web.core.api.IRepresentationDescriptionSearchService; import org.eclipse.sirius.web.diagrams.Diagram; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationSearchService; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; import org.eclipse.sirius.web.spring.collaborative.diagrams.api.DiagramConfiguration; @@ -45,12 +47,19 @@ public class DiagramEventProcessorFactory implements IRepresentationEventProcess private final ISubscriptionManagerFactory subscriptionManagerFactory; + private final IRepresentationDescriptionSearchService representationDescriptionSearchService; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + public DiagramEventProcessorFactory(IRepresentationSearchService representationSearchService, IDiagramCreationService diagramCreationService, List diagramEventHandlers, - ISubscriptionManagerFactory subscriptionManagerFactory) { + ISubscriptionManagerFactory subscriptionManagerFactory, IRepresentationDescriptionSearchService representationDescriptionSearchService, + IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.representationSearchService = Objects.requireNonNull(representationSearchService); this.diagramCreationService = Objects.requireNonNull(diagramCreationService); this.diagramEventHandlers = Objects.requireNonNull(diagramEventHandlers); this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); } @Override @@ -70,7 +79,7 @@ public Optional createRepresentatio // @formatter:off DiagramContext diagramContext = new DiagramContext(diagram); IRepresentationEventProcessor diagramEventProcessor = new DiagramEventProcessor(editingContext, diagramContext, - this.diagramEventHandlers, this.subscriptionManagerFactory.create(), this.diagramCreationService); + this.diagramEventHandlers, this.subscriptionManagerFactory.create(), this.representationDescriptionSearchService, this.representationRefreshPolicyRegistry, this.diagramCreationService); return Optional.of(diagramEventProcessor) .filter(representationEventProcessorClass::isInstance) diff --git a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/api/IDiagramCreationService.java b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/api/IDiagramCreationService.java index f2c5f2009b3..0768b6e675e 100644 --- a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/api/IDiagramCreationService.java +++ b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/api/IDiagramCreationService.java @@ -73,7 +73,6 @@ public Diagram create(String label, Object targetObject, DiagramDescription diag public Optional refresh(IEditingContext editingContext, IDiagramContext diagramContext) { return Optional.empty(); } - } } diff --git a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeEdgeToolOnDiagramEventHandler.java b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeEdgeToolOnDiagramEventHandler.java index ea674b009de..759833654ea 100644 --- a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeEdgeToolOnDiagramEventHandler.java +++ b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeEdgeToolOnDiagramEventHandler.java @@ -103,7 +103,6 @@ public void handle(One payloadSink, Many changeDesc if (optionalTool.isPresent()) { IStatus status = this.executeTool(editingContext, diagramContext, input.getDiagramSourceElementId(), input.getDiagramTargetElementId(), optionalTool.get()); if (status instanceof Success) { - payload = new InvokeEdgeToolOnDiagramSuccessPayload(diagramInput.getId(), diagram); changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, diagramInput.getRepresentationId(), diagramInput); } diff --git a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeNodeToolOnDiagramEventHandler.java b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeNodeToolOnDiagramEventHandler.java index 18902fc0b3b..445e8991f65 100644 --- a/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeNodeToolOnDiagramEventHandler.java +++ b/backend/sirius-web-spring-collaborative-diagrams/src/main/java/org/eclipse/sirius/web/spring/collaborative/diagrams/handlers/InvokeNodeToolOnDiagramEventHandler.java @@ -109,7 +109,6 @@ public void handle(One payloadSink, Many changeDesc IStatus status = this.executeTool(editingContext, diagramContext, input.getDiagramElementId(), optionalTool.get(), input.getStartingPositionX(), input.getStartingPositionY(), input.getSelectedObjectId()); if (status instanceof Success) { - payload = new InvokeNodeToolOnDiagramSuccessPayload(diagramInput.getId(), diagram); changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, diagramInput.getRepresentationId(), diagramInput); } diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessor.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessor.java index 13402b9259d..0ce807ddbb8 100644 --- a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessor.java +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessor.java @@ -26,14 +26,16 @@ import org.eclipse.sirius.web.forms.Form; import org.eclipse.sirius.web.forms.components.FormComponent; import org.eclipse.sirius.web.forms.components.FormComponentProps; -import org.eclipse.sirius.web.forms.description.FormDescription; import org.eclipse.sirius.web.forms.renderer.FormRenderer; import org.eclipse.sirius.web.representations.GetOrCreateRandomIdProvider; import org.eclipse.sirius.web.representations.IRepresentation; import org.eclipse.sirius.web.representations.VariableManager; import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; import org.eclipse.sirius.web.spring.collaborative.api.ChangeKind; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicy; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManager; +import org.eclipse.sirius.web.spring.collaborative.forms.api.FormCreationParameters; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventHandler; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventProcessor; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormInput; @@ -62,13 +64,7 @@ public class FormEventProcessor implements IFormEventProcessor { private final Logger logger = LoggerFactory.getLogger(FormEventProcessor.class); - private final IEditingContext editingContext; - - private final FormDescription formDescription; - - private final UUID formId; - - private final Object object; + private final FormCreationParameters formCreationParameters; private final List formEventHandlers; @@ -76,23 +72,23 @@ public class FormEventProcessor implements IFormEventProcessor { private final IWidgetSubscriptionManager widgetSubscriptionManager; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + private final Many sink = Sinks.many().multicast().directBestEffort(); private final Many canBeDisposedSink = Sinks.many().unicast().onBackpressureBuffer(); private final AtomicReference
currentForm = new AtomicReference<>(); - public FormEventProcessor(IEditingContext editingContext, FormDescription formDescription, UUID formId, Object object, List formEventHandlers, - ISubscriptionManager subscriptionManager, IWidgetSubscriptionManager widgetSubscriptionManager) { - this.logger.trace("Creating the form event processor {}", formId); //$NON-NLS-1$ + public FormEventProcessor(FormCreationParameters formCreationParameters, List formEventHandlers, ISubscriptionManager subscriptionManager, + IWidgetSubscriptionManager widgetSubscriptionManager, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { + this.logger.trace("Creating the form event processor {}", formCreationParameters.getId()); //$NON-NLS-1$ - this.formDescription = Objects.requireNonNull(formDescription); - this.editingContext = Objects.requireNonNull(editingContext); - this.formId = Objects.requireNonNull(formId); - this.object = Objects.requireNonNull(object); + this.formCreationParameters = Objects.requireNonNull(formCreationParameters); this.formEventHandlers = Objects.requireNonNull(formEventHandlers); this.subscriptionManager = Objects.requireNonNull(subscriptionManager); this.widgetSubscriptionManager = Objects.requireNonNull(widgetSubscriptionManager); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); Form form = this.refreshForm(); this.currentForm.set(form); @@ -135,7 +131,7 @@ public void handle(One payloadSink, Many changeDesc @Override public void refresh(ChangeDescription changeDescription) { - if (ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind())) { + if (this.shouldRefresh(changeDescription)) { Form form = this.refreshForm(); this.currentForm.set(form); @@ -147,13 +143,26 @@ public void refresh(ChangeDescription changeDescription) { } } + private boolean shouldRefresh(ChangeDescription changeDescription) { + // @formatter:off + return this.representationRefreshPolicyRegistry.getRepresentationRefreshPolicy(this.formCreationParameters.getFormDescription()) + .orElseGet(this::getDefaultRefreshPolicy) + .shouldRefresh(changeDescription); + // @formatter:on + + } + + private IRepresentationRefreshPolicy getDefaultRefreshPolicy() { + return (changeDescription) -> ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind()); + } + private Form refreshForm() { VariableManager variableManager = new VariableManager(); - variableManager.put(VariableManager.SELF, this.object); - variableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, this.formId); - variableManager.put(IEditingContext.EDITING_CONTEXT, this.editingContext); + variableManager.put(VariableManager.SELF, this.formCreationParameters.getObject()); + variableManager.put(GetOrCreateRandomIdProvider.PREVIOUS_REPRESENTATION_ID, this.formCreationParameters.getId()); + variableManager.put(IEditingContext.EDITING_CONTEXT, this.formCreationParameters.getEditingContext()); - FormComponentProps formComponentProps = new FormComponentProps(variableManager, this.formDescription); + FormComponentProps formComponentProps = new FormComponentProps(variableManager, this.formCreationParameters.getFormDescription()); Element element = new Element(FormComponent.class, formComponentProps); Form form = new FormRenderer().render(element); @@ -176,12 +185,12 @@ public Flux getOutputEvents(IInput input) { .doOnSubscribe(subscription -> { String username = SecurityContextHolder.getContext().getAuthentication().getName(); this.subscriptionManager.add(input, username); - this.logger.trace("{} has subscribed to the form {} {}", username, this.formId, this.subscriptionManager); //$NON-NLS-1$ + this.logger.trace("{} has subscribed to the form {} {}", username, this.formCreationParameters.getId(), this.subscriptionManager); //$NON-NLS-1$ }) .doOnCancel(() -> { String username = SecurityContextHolder.getContext().getAuthentication().getName(); this.subscriptionManager.remove(UUID.randomUUID(), username); - this.logger.trace("{} has unsubscribed from the form {} {}", username, this.formId, this.subscriptionManager); //$NON-NLS-1$ + this.logger.trace("{} has unsubscribed from the form {} {}", username, this.formCreationParameters.getId(), this.subscriptionManager); //$NON-NLS-1$ if (this.subscriptionManager.isEmpty()) { EmitResult emitResult = this.canBeDisposedSink.tryEmitNext(Boolean.TRUE); @@ -201,7 +210,7 @@ public Flux canBeDisposed() { @Override public void dispose() { - this.logger.trace("Disposing the form event processor {}", this.formId); //$NON-NLS-1$ + this.logger.trace("Disposing the form event processor {}", this.formCreationParameters.getId()); //$NON-NLS-1$ this.subscriptionManager.dispose(); this.widgetSubscriptionManager.dispose(); diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessorFactory.java index 5e6743232c2..82b98ffa73e 100644 --- a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessorFactory.java +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/FormEventProcessorFactory.java @@ -24,9 +24,11 @@ import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationSearchService; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; import org.eclipse.sirius.web.spring.collaborative.forms.api.FormConfiguration; +import org.eclipse.sirius.web.spring.collaborative.forms.api.FormCreationParameters; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventHandler; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventProcessor; import org.eclipse.sirius.web.spring.collaborative.forms.api.IWidgetSubscriptionManagerFactory; @@ -53,15 +55,18 @@ public class FormEventProcessorFactory implements IRepresentationEventProcessorF private final IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + public FormEventProcessorFactory(IRepresentationDescriptionSearchService representationDescriptionSearchService, IObjectService objectService, IRepresentationSearchService representationSearchService, List formEventHandlers, ISubscriptionManagerFactory subscriptionManagerFactory, - IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory) { + IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); this.objectService = Objects.requireNonNull(objectService); this.representationSearchService = Objects.requireNonNull(representationSearchService); this.formEventHandlers = Objects.requireNonNull(formEventHandlers); this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); this.widgetSubscriptionManagerFactory = Objects.requireNonNull(widgetSubscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); } @Override @@ -88,8 +93,16 @@ public Optional createRepresentatio FormDescription formDescription = optionalFormDescription.get(); Object object = optionalObject.get(); - IRepresentationEventProcessor formEventProcessor = new FormEventProcessor(editingContext, formDescription, formConfiguration.getId(), object, this.formEventHandlers, - this.subscriptionManagerFactory.create(), this.widgetSubscriptionManagerFactory.create()); + // @formatter:off + FormCreationParameters formCreationParameters = FormCreationParameters.newFormCreationParameters(formConfiguration.getId()) + .editingContext(editingContext) + .formDescription(formDescription) + .object(object) + .build(); + // @formatter:on + + IRepresentationEventProcessor formEventProcessor = new FormEventProcessor(formCreationParameters, this.formEventHandlers, this.subscriptionManagerFactory.create(), + this.widgetSubscriptionManagerFactory.create(), this.representationRefreshPolicyRegistry); // @formatter:off return Optional.of(formEventProcessor) diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/PropertiesEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/PropertiesEventProcessorFactory.java index afdbfb8be02..2aae9b96774 100644 --- a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/PropertiesEventProcessorFactory.java +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/PropertiesEventProcessorFactory.java @@ -22,7 +22,9 @@ import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; +import org.eclipse.sirius.web.spring.collaborative.forms.api.FormCreationParameters; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventHandler; import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventProcessor; import org.eclipse.sirius.web.spring.collaborative.forms.api.IPropertiesDefaultDescriptionProvider; @@ -51,15 +53,18 @@ public class PropertiesEventProcessorFactory implements IRepresentationEventProc private final IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + public PropertiesEventProcessorFactory(IPropertiesDescriptionService propertiesDescriptionService, IPropertiesDefaultDescriptionProvider propertiesDefaultDescriptionProvider, IObjectService objectService, List formEventHandlers, ISubscriptionManagerFactory subscriptionManagerFactory, - IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory) { + IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.propertiesDescriptionService = Objects.requireNonNull(propertiesDescriptionService); this.propertiesDefaultDescriptionProvider = Objects.requireNonNull(propertiesDefaultDescriptionProvider); this.objectService = Objects.requireNonNull(objectService); this.formEventHandlers = Objects.requireNonNull(formEventHandlers); this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); this.widgetSubscriptionManagerFactory = Objects.requireNonNull(widgetSubscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); } @Override @@ -82,8 +87,17 @@ public Optional createRepresentatio optionalFormDescription = new FormDescriptionAggregator().aggregate(formDescriptions, object, this.objectService); } FormDescription formDescription = optionalFormDescription.orElse(this.propertiesDefaultDescriptionProvider.getFormDescription()); - IRepresentationEventProcessor formEventProcessor = new FormEventProcessor(editingContext, formDescription, propertiesConfiguration.getId(), object, this.formEventHandlers, - this.subscriptionManagerFactory.create(), this.widgetSubscriptionManagerFactory.create()); + + // @formatter:off + FormCreationParameters formCreationParameters = FormCreationParameters.newFormCreationParameters(propertiesConfiguration.getId()) + .editingContext(editingContext) + .formDescription(formDescription) + .object(object) + .build(); + // @formatter:on + + IRepresentationEventProcessor formEventProcessor = new FormEventProcessor(formCreationParameters, this.formEventHandlers, this.subscriptionManagerFactory.create(), + this.widgetSubscriptionManagerFactory.create(), this.representationRefreshPolicyRegistry); // @formatter:off return Optional.of(formEventProcessor) diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/RepresentationsEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/RepresentationsEventProcessorFactory.java new file mode 100644 index 00000000000..80dbcb09448 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/RepresentationsEventProcessorFactory.java @@ -0,0 +1,103 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import org.eclipse.sirius.web.core.api.IEditingContext; +import org.eclipse.sirius.web.core.api.IObjectService; +import org.eclipse.sirius.web.forms.description.FormDescription; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; +import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; +import org.eclipse.sirius.web.spring.collaborative.forms.api.FormCreationParameters; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventHandler; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventProcessor; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IRepresentationsDescriptionProvider; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IWidgetSubscriptionManagerFactory; +import org.eclipse.sirius.web.spring.collaborative.forms.api.RepresentationsConfiguration; +import org.springframework.stereotype.Service; + +/** + * Used to create the representations event processors. + * + * @author gcoutable + */ +@Service +public class RepresentationsEventProcessorFactory implements IRepresentationEventProcessorFactory { + + private final IRepresentationsDescriptionProvider representationsDescriptionProvider; + + private final IObjectService objectService; + + private final List formEventHandlers; + + private final ISubscriptionManagerFactory subscriptionManagerFactory; + + private final IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + + public RepresentationsEventProcessorFactory(IRepresentationsDescriptionProvider representationsDescriptionProvider, IObjectService objectService, List formEventHandlers, + ISubscriptionManagerFactory subscriptionManagerFactory, IWidgetSubscriptionManagerFactory widgetSubscriptionManagerFactory, + IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { + this.representationsDescriptionProvider = Objects.requireNonNull(representationsDescriptionProvider); + this.objectService = Objects.requireNonNull(objectService); + this.formEventHandlers = Objects.requireNonNull(formEventHandlers); + this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.widgetSubscriptionManagerFactory = Objects.requireNonNull(widgetSubscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); + } + + @Override + public boolean canHandle(Class representationEventProcessorClass, IRepresentationConfiguration configuration) { + return IFormEventProcessor.class.isAssignableFrom(representationEventProcessorClass) && configuration instanceof RepresentationsConfiguration; + } + + @Override + public Optional createRepresentationEventProcessor(Class representationEventProcessorClass, IRepresentationConfiguration configuration, + IEditingContext editingContext) { + if (IFormEventProcessor.class.isAssignableFrom(representationEventProcessorClass) && configuration instanceof RepresentationsConfiguration) { + RepresentationsConfiguration representationsConfiguration = (RepresentationsConfiguration) configuration; + + FormDescription formDescription = this.representationsDescriptionProvider.getRepresentationsDescription(); + Optional optionalObject = this.objectService.getObject(editingContext, representationsConfiguration.getObjectId()); + if (optionalObject.isPresent()) { + Object object = optionalObject.get(); + // @formatter:off + FormCreationParameters formCreationParameters = FormCreationParameters.newFormCreationParameters(representationsConfiguration.getId()) + .editingContext(editingContext) + .formDescription(formDescription) + .object(object) + .build(); + // @formatter:on + + FormEventProcessor formEventProcessor = new FormEventProcessor(formCreationParameters, this.formEventHandlers, this.subscriptionManagerFactory.create(), + this.widgetSubscriptionManagerFactory.create(), this.representationRefreshPolicyRegistry); + + // @formatter:off + return Optional.of(formEventProcessor) + .filter(representationEventProcessorClass::isInstance) + .map(representationEventProcessorClass::cast); + // @formatter:on + } + } + + return Optional.empty(); + } + +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/FormCreationParameters.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/FormCreationParameters.java index 5c2d82b4f1a..e8ce4fd6bbd 100644 --- a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/FormCreationParameters.java +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/FormCreationParameters.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019, 2020 Obeo. + * Copyright (c) 2019, 2021 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 @@ -33,8 +33,6 @@ public final class FormCreationParameters { private UUID id; - private String label; - private Object object; private FormDescription formDescription; @@ -49,10 +47,6 @@ public UUID getId() { return this.id; } - public String getLabel() { - return this.label; - } - public Object getObject() { return this.object; } @@ -74,15 +68,14 @@ public static Builder newFormCreationParameters(FormCreationParameters formCreat return new Builder(formCreationParameters.getId()) .formDescription(formCreationParameters.getFormDescription()) .editingContext(formCreationParameters.getEditingContext()) - .label(formCreationParameters.getLabel()) .object(formCreationParameters.getObject()); // @formatter:on } @Override public String toString() { - String pattern = "{0} '{'id: {1}, label: {2}, formDescriptionId: {3}'}'"; //$NON-NLS-1$ - return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.label, this.formDescription.getId()); + String pattern = "{0} '{'id: {1}, formDescriptionId: {2}'}'"; //$NON-NLS-1$ + return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.formDescription.getId()); } /** @@ -94,8 +87,6 @@ public String toString() { public static final class Builder { private UUID id; - private String label; - private Object object; private FormDescription formDescription; @@ -106,11 +97,6 @@ private Builder(UUID id) { this.id = id; } - public Builder label(String label) { - this.label = Objects.requireNonNull(label); - return this; - } - public Builder object(Object object) { this.object = Objects.requireNonNull(object); return this; @@ -129,7 +115,6 @@ public Builder editingContext(IEditingContext editingContext) { public FormCreationParameters build() { FormCreationParameters formCreationParameters = new FormCreationParameters(); formCreationParameters.id = this.id; - formCreationParameters.label = Objects.requireNonNull(this.label); formCreationParameters.object = Objects.requireNonNull(this.object); formCreationParameters.formDescription = Objects.requireNonNull(this.formDescription); formCreationParameters.editingContext = Objects.requireNonNull(this.editingContext); diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/IRepresentationsDescriptionProvider.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/IRepresentationsDescriptionProvider.java new file mode 100644 index 00000000000..fbb875041ac --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/IRepresentationsDescriptionProvider.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.api; + +import org.eclipse.sirius.web.forms.description.FormDescription; + +/** + * Interface used to contribute the form description that contains all OCP representations. + * + * @author gcoutable + */ +public interface IRepresentationsDescriptionProvider { + FormDescription getRepresentationsDescription(); +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/RepresentationsConfiguration.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/RepresentationsConfiguration.java new file mode 100644 index 00000000000..f2b414c8122 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/api/RepresentationsConfiguration.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.api; + +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; + +/** + * The configuration used to create the representations event processor. + * + * @author gcoutable + */ +public class RepresentationsConfiguration implements IRepresentationConfiguration { + + private static final String REPRESENTATIONS_PREFIX = "representations:"; //$NON-NLS-1$ + + private final String objectId; + + private UUID formId; + + public RepresentationsConfiguration(String objectId) { + this.objectId = Objects.requireNonNull(objectId); + this.formId = UUID.nameUUIDFromBytes((REPRESENTATIONS_PREFIX + objectId).getBytes()); + } + + @Override + public UUID getId() { + return this.formId; + } + + public String getObjectId() { + return this.objectId; + } + +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/DeleteListItemInput.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/DeleteListItemInput.java new file mode 100644 index 00000000000..5097d149ed2 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/DeleteListItemInput.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.dto; + +import java.text.MessageFormat; +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.sirius.web.annotations.graphql.GraphQLField; +import org.eclipse.sirius.web.annotations.graphql.GraphQLID; +import org.eclipse.sirius.web.annotations.graphql.GraphQLInputObjectType; +import org.eclipse.sirius.web.annotations.graphql.GraphQLNonNull; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormInput; + +/** + * The input object for the list item deletion mutation. + * + * @author gcoutable + */ +@GraphQLInputObjectType +public final class DeleteListItemInput implements IFormInput { + private UUID id; + + private UUID representationId; + + private UUID editingContextId; + + private String listId; + + private String listItemId; + + public DeleteListItemInput() { + // Used by Jackson + } + + public DeleteListItemInput(UUID id, UUID representationId, UUID editingContextId, String listId, String listItemId) { + this.id = Objects.requireNonNull(id); + this.representationId = Objects.requireNonNull(representationId); + this.editingContextId = Objects.requireNonNull(editingContextId); + this.listId = Objects.requireNonNull(listId); + this.listItemId = Objects.requireNonNull(listItemId); + } + + @Override + @GraphQLID + @GraphQLField + @GraphQLNonNull + public UUID getId() { + return this.id; + } + + @Override + @GraphQLID + @GraphQLField + @GraphQLNonNull + public UUID getRepresentationId() { + return this.representationId; + } + + @GraphQLID + @GraphQLField + @GraphQLNonNull + public UUID getEditingContextId() { + return this.editingContextId; + } + + @GraphQLID + @GraphQLField + @GraphQLNonNull + public String getListId() { + return this.listId; + } + + @GraphQLID + @GraphQLField + @GraphQLNonNull + public String getListItemId() { + return this.listItemId; + } + + @Override + public String toString() { + String pattern = "{0} '{'id: {1}, representationId: {2}, editingContextId: {3}, listId: {5}, listItemId: {4}'}'"; //$NON-NLS-1$ + return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.representationId, this.editingContextId, this.listId, this.listItemId); + } + +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/DeleteListItemSuccessPayload.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/DeleteListItemSuccessPayload.java new file mode 100644 index 00000000000..7333adcfbb5 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/DeleteListItemSuccessPayload.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.dto; + +import java.text.MessageFormat; +import java.util.Objects; +import java.util.UUID; + +import org.eclipse.sirius.web.annotations.graphql.GraphQLID; +import org.eclipse.sirius.web.annotations.graphql.GraphQLNonNull; +import org.eclipse.sirius.web.annotations.graphql.GraphQLObjectType; +import org.eclipse.sirius.web.core.api.IPayload; + +/** + * The payload for the list item deletion mutation. + * + * @author gcoutable + */ +@GraphQLObjectType +public final class DeleteListItemSuccessPayload implements IPayload { + private final UUID id; + + public DeleteListItemSuccessPayload(UUID id) { + this.id = Objects.requireNonNull(id); + } + + @Override + @GraphQLID + @GraphQLNonNull + public UUID getId() { + return this.id; + } + + @Override + public String toString() { + String pattern = "{0} '{'id: {1}'}'"; //$NON-NLS-1$ + return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id); + } + +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/RepresentationsEventInput.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/RepresentationsEventInput.java new file mode 100644 index 00000000000..713ac00f6d7 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/dto/RepresentationsEventInput.java @@ -0,0 +1,66 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.dto; + +import java.text.MessageFormat; +import java.util.UUID; + +import org.eclipse.sirius.web.annotations.graphql.GraphQLField; +import org.eclipse.sirius.web.annotations.graphql.GraphQLID; +import org.eclipse.sirius.web.annotations.graphql.GraphQLInputObjectType; +import org.eclipse.sirius.web.annotations.graphql.GraphQLNonNull; +import org.eclipse.sirius.web.core.api.IInput; + +/** + * The input of the representations event subscription. + * + * @author gcoutable + */ +@GraphQLInputObjectType +public class RepresentationsEventInput implements IInput { + + private UUID id; + + private UUID editingContextId; + + private String objectId; + + @Override + @GraphQLID + @GraphQLField + @GraphQLNonNull + public UUID getId() { + return this.id; + } + + @GraphQLID + @GraphQLField + @GraphQLNonNull + public UUID getEditingContextId() { + return this.editingContextId; + } + + @GraphQLID + @GraphQLField + @GraphQLNonNull + public String getObjectId() { + return this.objectId; + } + + @Override + public String toString() { + String pattern = "{0} '{'id: {1}, editingContextId: {2}, objectId: {3}'}'"; //$NON-NLS-1$ + return MessageFormat.format(pattern, this.getClass().getSimpleName(), this.id, this.editingContextId, this.objectId); + } + +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/handlers/DeleteListItemEventHandler.java b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/handlers/DeleteListItemEventHandler.java new file mode 100644 index 00000000000..0e79cfb4da0 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/main/java/org/eclipse/sirius/web/spring/collaborative/forms/handlers/DeleteListItemEventHandler.java @@ -0,0 +1,108 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.handlers; + +import java.util.Collection; +import java.util.Objects; + +import org.eclipse.sirius.web.core.api.ErrorPayload; +import org.eclipse.sirius.web.core.api.IPayload; +import org.eclipse.sirius.web.forms.Form; +import org.eclipse.sirius.web.forms.List; +import org.eclipse.sirius.web.forms.ListItem; +import org.eclipse.sirius.web.representations.Failure; +import org.eclipse.sirius.web.representations.Success; +import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; +import org.eclipse.sirius.web.spring.collaborative.api.ChangeKind; +import org.eclipse.sirius.web.spring.collaborative.api.Monitoring; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormEventHandler; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormInput; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormQueryService; +import org.eclipse.sirius.web.spring.collaborative.forms.dto.DeleteListItemInput; +import org.eclipse.sirius.web.spring.collaborative.forms.dto.DeleteListItemSuccessPayload; +import org.eclipse.sirius.web.spring.collaborative.forms.messages.ICollaborativeFormMessageService; +import org.springframework.stereotype.Service; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import reactor.core.publisher.Sinks.Many; +import reactor.core.publisher.Sinks.One; + +/** + * The handler of the delete list item event. + * + * @author gcoutable + */ +@Service +public class DeleteListItemEventHandler implements IFormEventHandler { + + private final ICollaborativeFormMessageService messageService; + + private final Counter counter; + + private final IFormQueryService formQueryService; + + public DeleteListItemEventHandler(IFormQueryService formQueryService, ICollaborativeFormMessageService messageService, MeterRegistry meterRegistry) { + this.formQueryService = Objects.requireNonNull(formQueryService); + this.messageService = Objects.requireNonNull(messageService); + + // @formatter:off + this.counter = Counter.builder(Monitoring.EVENT_HANDLER) + .tag(Monitoring.NAME, this.getClass().getSimpleName()) + .register(meterRegistry); + // @formatter:on + } + + @Override + public boolean canHandle(IFormInput formInput) { + return formInput instanceof DeleteListItemInput; + } + + @Override + public void handle(One payloadSink, Many changeDescriptionSink, Form form, IFormInput formInput) { + this.counter.increment(); + + String message = this.messageService.invalidInput(formInput.getClass().getSimpleName(), DeleteListItemInput.class.getSimpleName()); + IPayload payload = new ErrorPayload(formInput.getId(), message); + ChangeDescription changeDescription = new ChangeDescription(ChangeKind.NOTHING, formInput.getRepresentationId(), formInput); + + if (formInput instanceof DeleteListItemInput) { + DeleteListItemInput input = (DeleteListItemInput) formInput; + + // @formatter:off + var optionalListItem = this.formQueryService.findWidget(form, input.getListId()) + .filter(List.class::isInstance) + .map(List.class::cast) + .stream() + .map(List::getItems) + .flatMap(Collection::stream) + .filter(item -> item.getId().toString().equals(input.getListItemId())) + .findFirst(); + + var status = optionalListItem.map(ListItem::getDeleteHandler) + .map(handler -> handler.get()) + .orElse(new Failure("")); //$NON-NLS-1$ + // @formatter:on + + if (status instanceof Success) { + Success success = (Success) status; + changeDescription = new ChangeDescription(success.getChangeKind(), formInput.getRepresentationId(), formInput, success.getParameters()); + payload = new DeleteListItemSuccessPayload(formInput.getId()); + } + } + + changeDescriptionSink.tryEmitNext(changeDescription); + payloadSink.tryEmitValue(payload); + } + +} diff --git a/backend/sirius-web-spring-collaborative-forms/src/main/resources/schema/form.graphqls b/backend/sirius-web-spring-collaborative-forms/src/main/resources/schema/form.graphqls index 2b347ddcada..f19879bec4e 100644 --- a/backend/sirius-web-spring-collaborative-forms/src/main/resources/schema/form.graphqls +++ b/backend/sirius-web-spring-collaborative-forms/src/main/resources/schema/form.graphqls @@ -1,6 +1,7 @@ extend type Subscription { formEvent(input: FormEventInput!): FormEventPayload! propertiesEvent(input: PropertiesEventInput!): PropertiesEventPayload! + representationsEvent(input: RepresentationsEventInput!): RepresentationsEventPayload! } input FormEventInput { @@ -15,10 +16,18 @@ input PropertiesEventInput { objectId: ID! } +input RepresentationsEventInput { + id: ID! + editingContextId: ID! + objectId: ID! +} + union FormEventPayload = ErrorPayload | FormRefreshedEventPayload | SubscribersUpdatedEventPayload | WidgetSubscriptionsUpdatedEventPayload union PropertiesEventPayload = ErrorPayload | FormRefreshedEventPayload | SubscribersUpdatedEventPayload | WidgetSubscriptionsUpdatedEventPayload +union RepresentationsEventPayload = ErrorPayload | FormRefreshedEventPayload | SubscribersUpdatedEventPayload | WidgetSubscriptionsUpdatedEventPayload + type WidgetSubscriptionsUpdatedEventPayload { id: ID! widgetSubscriptions: [WidgetSubscription]! @@ -77,7 +86,9 @@ type List implements Widget { type ListItem { id: ID! label: String! + kind: String! imageURL: String! + deletable: Boolean! } type MultiSelect implements Widget { @@ -141,6 +152,7 @@ extend type Mutation { editSelect(input: EditSelectInput!): EditSelectPayload! editTextfield(input: EditTextfieldInput!): EditTextfieldPayload! updateWidgetFocus(input: UpdateWidgetFocusInput!): UpdateWidgetFocusPayload! + deleteListItem(input: DeleteListItemInput!): DeleteListItemPayload! } input EditCheckboxInput { @@ -151,7 +163,7 @@ input EditCheckboxInput { newValue: Boolean! } -union EditCheckboxPayload = ErrorPayload|EditCheckboxSuccessPayload +union EditCheckboxPayload = ErrorPayload | EditCheckboxSuccessPayload type EditCheckboxSuccessPayload { id: ID! @@ -226,4 +238,18 @@ union UpdateWidgetFocusPayload = ErrorPayload | UpdateWidgetFocusSuccessPayload type UpdateWidgetFocusSuccessPayload { id: ID! updateFocusWidgetId: String! -} \ No newline at end of file +} + +input DeleteListItemInput { + id: ID! + editingContextId: ID! + representationId: ID! + listId: ID! + listItemId: ID! +} + +type DeleteListItemSuccessPayload { + id: ID! +} + +union DeleteListItemPayload = ErrorPayload | DeleteListItemSuccessPayload \ No newline at end of file diff --git a/backend/sirius-web-spring-collaborative-forms/src/test/java/org/eclipse/sirius/web/spring/collaborative/forms/handlers/DeleteListItemEventHandlerTests.java b/backend/sirius-web-spring-collaborative-forms/src/test/java/org/eclipse/sirius/web/spring/collaborative/forms/handlers/DeleteListItemEventHandlerTests.java new file mode 100644 index 00000000000..877b5546a01 --- /dev/null +++ b/backend/sirius-web-spring-collaborative-forms/src/test/java/org/eclipse/sirius/web/spring/collaborative/forms/handlers/DeleteListItemEventHandlerTests.java @@ -0,0 +1,130 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.forms.handlers; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; + +import org.eclipse.sirius.web.core.api.IPayload; +import org.eclipse.sirius.web.forms.AbstractWidget; +import org.eclipse.sirius.web.forms.Form; +import org.eclipse.sirius.web.forms.Group; +import org.eclipse.sirius.web.forms.List; +import org.eclipse.sirius.web.forms.ListItem; +import org.eclipse.sirius.web.forms.Page; +import org.eclipse.sirius.web.representations.IStatus; +import org.eclipse.sirius.web.representations.Success; +import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; +import org.eclipse.sirius.web.spring.collaborative.forms.api.IFormQueryService; +import org.eclipse.sirius.web.spring.collaborative.forms.dto.DeleteListItemInput; +import org.eclipse.sirius.web.spring.collaborative.forms.dto.DeleteListItemSuccessPayload; +import org.eclipse.sirius.web.spring.collaborative.forms.messages.ICollaborativeFormMessageService; +import org.junit.jupiter.api.Test; + +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import reactor.core.publisher.Sinks; +import reactor.core.publisher.Sinks.Many; +import reactor.core.publisher.Sinks.One; + +/** + * Unit tests of the delete list item event handler. + * + * @author gcoutable + */ +public class DeleteListItemEventHandlerTests { + private static final UUID FORM_ID = UUID.randomUUID(); + + @Test + public void testListItemDeletion() { + String listId = "List id"; //$NON-NLS-1$ + String listItemId = "element id to delete"; //$NON-NLS-1$ + String changeKind = "delete something"; //$NON-NLS-1$ + String changeDescriptionParameterKey = "change_description_parameter_key"; //$NON-NLS-1$ + + Map parameters = new HashMap<>(); + parameters.put(changeDescriptionParameterKey, listItemId); + var input = new DeleteListItemInput(UUID.randomUUID(), FORM_ID, UUID.randomUUID(), listId, listItemId); + + AtomicBoolean hasBeenExecuted = new AtomicBoolean(); + Supplier deleteHandler = () -> { + hasBeenExecuted.set(true); + return new Success(changeKind, parameters); + }; + + // @formatter:off + ListItem listItem = ListItem.newListItem(listItemId) + .deletable(true) + .deleteHandler(deleteHandler) + .imageURL("") //$NON-NLS-1$ + .kind("Diagram") //$NON-NLS-1$ + .label("empty representation") //$NON-NLS-1$ + .build(); + + List list = List.newList(listId) + .diagnostics(Collections.emptyList()) + .items(Collections.singletonList(listItem)) + .label("") //$NON-NLS-1$ + .build(); + + Group group = Group.newGroup("groupId") //$NON-NLS-1$ + .label("group label") //$NON-NLS-1$ + .widgets(Collections.singletonList(list)) + .build(); + + Page page = Page.newPage("pageId") //$NON-NLS-1$ + .label("page label") //$NON-NLS-1$ + .groups(Collections.singletonList(group)) + .build(); + + Form form = Form.newForm(FORM_ID) + .targetObjectId("targetObjectId") //$NON-NLS-1$ + .descriptionId(UUID.randomUUID()) + .label("form label") //$NON-NLS-1$ + .pages(Collections.singletonList(page)) + .build(); + // @formatter:on + + IFormQueryService formQueryService = new IFormQueryService.NoOp() { + @Override + public Optional findWidget(Form form, String widgetId) { + return Optional.of(list); + } + }; + + DeleteListItemEventHandler handler = new DeleteListItemEventHandler(formQueryService, new ICollaborativeFormMessageService.NoOp(), new SimpleMeterRegistry()); + assertThat(handler.canHandle(input)).isTrue(); + + Many changeDescriptionSink = Sinks.many().unicast().onBackpressureBuffer(); + One payloadSink = Sinks.one(); + + handler.handle(payloadSink, changeDescriptionSink, form, input); + + ChangeDescription changeDescription = changeDescriptionSink.asFlux().blockFirst(); + assertThat(changeDescription.getKind()).isEqualTo(changeKind); + Map changeDescriptionParameters = changeDescription.getParameters(); + assertThat(changeDescriptionParameters.get(changeDescriptionParameterKey)).isEqualTo(listItemId); + + IPayload payload = payloadSink.asMono().block(); + assertThat(payload).isInstanceOf(DeleteListItemSuccessPayload.class); + + assertThat(hasBeenExecuted.get()).isTrue(); + } + +} diff --git a/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessor.java b/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessor.java index f8e5bfecf99..0636aa66706 100644 --- a/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessor.java +++ b/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessor.java @@ -28,6 +28,8 @@ import org.eclipse.sirius.web.selection.renderer.SelectionRenderer; import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; import org.eclipse.sirius.web.spring.collaborative.api.ChangeKind; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicy; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManager; import org.eclipse.sirius.web.spring.collaborative.selection.api.ISelectionEventProcessor; import org.eclipse.sirius.web.spring.collaborative.selection.dto.SelectionRefreshedEventPayload; @@ -60,6 +62,8 @@ public class SelectionEventProcessor implements ISelectionEventProcessor { private final Object object; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + private final ISubscriptionManager subscriptionManager; private final Many sink = Sinks.many().multicast().directBestEffort(); @@ -68,7 +72,8 @@ public class SelectionEventProcessor implements ISelectionEventProcessor { private final AtomicReference currentSelection = new AtomicReference<>(); - public SelectionEventProcessor(IEditingContext editingContext, SelectionDescription selectionDescription, UUID id, Object object, ISubscriptionManager subscriptionManager) { + public SelectionEventProcessor(IEditingContext editingContext, SelectionDescription selectionDescription, UUID id, Object object, ISubscriptionManager subscriptionManager, + IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.logger.trace("Creating the selection event processor {}", id); //$NON-NLS-1$ this.selectionDescription = Objects.requireNonNull(selectionDescription); @@ -76,6 +81,7 @@ public SelectionEventProcessor(IEditingContext editingContext, SelectionDescript this.id = Objects.requireNonNull(id); this.object = Objects.requireNonNull(object); this.subscriptionManager = Objects.requireNonNull(subscriptionManager); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); Selection selection = this.refreshSelection(); this.currentSelection.set(selection); @@ -99,7 +105,7 @@ public void handle(One payloadSink, Many changeDesc @Override public void refresh(ChangeDescription changeDescription) { - if (ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind())) { + if (this.shouldRefresh(changeDescription)) { Selection selection = this.refreshSelection(); this.currentSelection.set(selection); @@ -111,6 +117,18 @@ public void refresh(ChangeDescription changeDescription) { } } + private boolean shouldRefresh(ChangeDescription changeDescription) { + // @formatter:off + return this.representationRefreshPolicyRegistry.getRepresentationRefreshPolicy(this.selectionDescription) + .orElseGet(this::getDefaultRefreshPolicy) + .shouldRefresh(changeDescription); + // @formatter:on + } + + private IRepresentationRefreshPolicy getDefaultRefreshPolicy() { + return (changeDescription) -> ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind()); + } + private Selection refreshSelection() { VariableManager variableManager = new VariableManager(); variableManager.put(VariableManager.SELF, this.object); diff --git a/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessorFactory.java index b65e32e676d..f67e9f7a3ec 100644 --- a/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessorFactory.java +++ b/backend/sirius-web-spring-collaborative-selection/src/main/java/org/eclipse/sirius/web/spring/collaborative/selection/SelectionEventProcessorFactory.java @@ -22,6 +22,7 @@ import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; import org.eclipse.sirius.web.spring.collaborative.selection.api.ISelectionEventProcessor; import org.eclipse.sirius.web.spring.collaborative.selection.api.SelectionConfiguration; @@ -41,11 +42,14 @@ public class SelectionEventProcessorFactory implements IRepresentationEventProce private final ISubscriptionManagerFactory subscriptionManagerFactory; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + public SelectionEventProcessorFactory(IRepresentationDescriptionSearchService representationDescriptionSearchService, IObjectService objectService, - ISubscriptionManagerFactory subscriptionManagerFactory) { + ISubscriptionManagerFactory subscriptionManagerFactory, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.representationDescriptionSearchService = Objects.requireNonNull(representationDescriptionSearchService); this.objectService = Objects.requireNonNull(objectService); this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); } @Override @@ -72,7 +76,7 @@ public Optional createRepresentatio Object object = optionalObject.get(); IRepresentationEventProcessor selectionEventProcessor = new SelectionEventProcessor(editingContext, selectionDescription, selectionConfiguration.getId(), object, - this.subscriptionManagerFactory.create()); + this.subscriptionManagerFactory.create(), this.representationRefreshPolicyRegistry); // @formatter:off return Optional.of(selectionEventProcessor) diff --git a/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessor.java b/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessor.java index f404dd347a5..8dad7646788 100644 --- a/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessor.java +++ b/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessor.java @@ -25,6 +25,8 @@ import org.eclipse.sirius.web.representations.IRepresentation; import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; import org.eclipse.sirius.web.spring.collaborative.api.ChangeKind; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicy; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManager; import org.eclipse.sirius.web.spring.collaborative.api.Monitoring; import org.eclipse.sirius.web.spring.collaborative.trees.api.ITreeEventHandler; @@ -65,6 +67,8 @@ public class TreeEventProcessor implements ITreeEventProcessor { private final ISubscriptionManager subscriptionManager; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + private final Many sink = Sinks.many().multicast().directBestEffort(); private final Many canBeDisposedSink = Sinks.many().unicast().onBackpressureBuffer(); @@ -74,13 +78,14 @@ public class TreeEventProcessor implements ITreeEventProcessor { private final Timer timer; public TreeEventProcessor(ITreeService treeService, TreeCreationParameters treeCreationParameters, List treeEventHandlers, ISubscriptionManager subscriptionManager, - MeterRegistry meterRegistry) { + MeterRegistry meterRegistry, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.logger.trace("Creating the tree event processor {}", treeCreationParameters.getEditingContext().getId()); //$NON-NLS-1$ this.treeService = Objects.requireNonNull(treeService); this.treeCreationParameters = Objects.requireNonNull(treeCreationParameters); this.treeEventHandlers = Objects.requireNonNull(treeEventHandlers); this.subscriptionManager = Objects.requireNonNull(subscriptionManager); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); // @formatter:off this.timer = Timer.builder(Monitoring.REPRESENTATION_EVENT_PROCESSOR_REFRESH) @@ -119,7 +124,7 @@ public void handle(One payloadSink, Many changeDesc @Override public void refresh(ChangeDescription changeDescription) { - if (this.shouldRefresh(changeDescription.getKind())) { + if (this.shouldRefresh(changeDescription)) { long start = System.currentTimeMillis(); Tree tree = this.refreshTree(); @@ -136,27 +141,37 @@ public void refresh(ChangeDescription changeDescription) { } } - private boolean shouldRefresh(String changeKind) { - boolean shouldRefresh = false; - - switch (changeKind) { - case ChangeKind.SEMANTIC_CHANGE: - shouldRefresh = true; - break; - case ChangeKind.REPRESENTATION_CREATION: - shouldRefresh = true; - break; - case ChangeKind.REPRESENTATION_DELETION: - shouldRefresh = true; - break; - case ChangeKind.REPRESENTATION_RENAMING: - shouldRefresh = true; - break; - default: - shouldRefresh = false; - } + private boolean shouldRefresh(ChangeDescription changeDescription) { + // @formatter:off + return this.representationRefreshPolicyRegistry.getRepresentationRefreshPolicy(this.treeCreationParameters.getTreeDescription()) + .orElseGet(this::getDefaultRefreshPolicy) + .shouldRefresh(changeDescription); + // @formatter:on + } + + private IRepresentationRefreshPolicy getDefaultRefreshPolicy() { + return changeDescription -> { + boolean shouldRefresh = false; + + switch (changeDescription.getKind()) { + case ChangeKind.SEMANTIC_CHANGE: + shouldRefresh = true; + break; + case ChangeKind.REPRESENTATION_CREATION: + shouldRefresh = true; + break; + case ChangeKind.REPRESENTATION_DELETION: + shouldRefresh = true; + break; + case ChangeKind.REPRESENTATION_RENAMING: + shouldRefresh = true; + break; + default: + shouldRefresh = false; + } - return shouldRefresh; + return shouldRefresh; + }; } private Tree refreshTree() { diff --git a/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessorFactory.java index bc19c86cdf6..3fea07fc38e 100644 --- a/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessorFactory.java +++ b/backend/sirius-web-spring-collaborative-trees/src/main/java/org/eclipse/sirius/web/spring/collaborative/trees/TreeEventProcessorFactory.java @@ -20,6 +20,7 @@ import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; import org.eclipse.sirius.web.spring.collaborative.trees.api.IExplorerDescriptionProvider; import org.eclipse.sirius.web.spring.collaborative.trees.api.ITreeEventHandler; @@ -48,12 +49,15 @@ public class TreeEventProcessorFactory implements IRepresentationEventProcessorF private final ISubscriptionManagerFactory subscriptionManagerFactory; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + public TreeEventProcessorFactory(IExplorerDescriptionProvider explorerDescriptionProvider, ITreeService treeService, List treeEventHandlers, - ISubscriptionManagerFactory subscriptionManagerFactory) { + ISubscriptionManagerFactory subscriptionManagerFactory, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.explorerDescriptionProvider = Objects.requireNonNull(explorerDescriptionProvider); this.treeService = Objects.requireNonNull(treeService); this.treeEventHandlers = Objects.requireNonNull(treeEventHandlers); this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); } @Override @@ -78,7 +82,7 @@ public Optional createRepresentatio // @formatter:on IRepresentationEventProcessor treeEventProcessor = new TreeEventProcessor(this.treeService, treeCreationParameters, this.treeEventHandlers, this.subscriptionManagerFactory.create(), - new SimpleMeterRegistry()); + new SimpleMeterRegistry(), this.representationRefreshPolicyRegistry); // @formatter:off return Optional.of(treeEventProcessor) .filter(representationEventProcessorClass::isInstance) diff --git a/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessor.java b/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessor.java index 373f5672950..dc5f8a36735 100644 --- a/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessor.java +++ b/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessor.java @@ -27,6 +27,8 @@ import org.eclipse.sirius.web.representations.VariableManager; import org.eclipse.sirius.web.spring.collaborative.api.ChangeDescription; import org.eclipse.sirius.web.spring.collaborative.api.ChangeKind; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicy; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManager; import org.eclipse.sirius.web.spring.collaborative.api.Monitoring; import org.eclipse.sirius.web.spring.collaborative.validation.api.IValidationEventHandler; @@ -71,6 +73,8 @@ public class ValidationEventProcessor implements IValidationEventProcessor { private final ISubscriptionManager subscriptionManager; + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; + private final Many sink = Sinks.many().multicast().directBestEffort(); private final Many canBeDisposedSink = Sinks.many().unicast().onBackpressureBuffer(); @@ -78,12 +82,14 @@ public class ValidationEventProcessor implements IValidationEventProcessor { private final Timer timer; public ValidationEventProcessor(IEditingContext editingContext, ValidationDescription validationDescription, ValidationContext validationContext, - List validationEventHandlers, ISubscriptionManager subscriptionManager, MeterRegistry meterRegistry) { + List validationEventHandlers, ISubscriptionManager subscriptionManager, MeterRegistry meterRegistry, + IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.editingContext = Objects.requireNonNull(editingContext); this.validationDescription = Objects.requireNonNull(validationDescription); this.validationContext = Objects.requireNonNull(validationContext); this.validationEventHandlers = Objects.requireNonNull(validationEventHandlers); this.subscriptionManager = Objects.requireNonNull(subscriptionManager); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); // @formatter:off this.timer = Timer.builder(Monitoring.REPRESENTATION_EVENT_PROCESSOR_REFRESH) @@ -121,7 +127,7 @@ public void handle(One payloadSink, Many changeDesc @Override public void refresh(ChangeDescription changeDescription) { - if (this.shouldRefresh(changeDescription.getKind())) { + if (this.shouldRefresh(changeDescription)) { long start = System.currentTimeMillis(); Validation validation = this.refreshValidation(); @@ -138,8 +144,16 @@ public void refresh(ChangeDescription changeDescription) { } } - private boolean shouldRefresh(String changeKind) { - return ChangeKind.SEMANTIC_CHANGE.equals(changeKind); + private boolean shouldRefresh(ChangeDescription changeDescription) { + // @formatter:off + return this.representationRefreshPolicyRegistry.getRepresentationRefreshPolicy(this.validationDescription) + .orElseGet(this::getDefaultRefreshPolicy) + .shouldRefresh(changeDescription); + // @formatter:on + } + + private IRepresentationRefreshPolicy getDefaultRefreshPolicy() { + return (changeDescription) -> ChangeKind.SEMANTIC_CHANGE.equals(changeDescription.getKind()); } @Override diff --git a/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessorFactory.java b/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessorFactory.java index 70b2dd3dc8d..f674763b330 100644 --- a/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessorFactory.java +++ b/backend/sirius-web-spring-collaborative-validation/src/main/java/org/eclipse/sirius/web/spring/collaborative/validation/ValidationEventProcessorFactory.java @@ -20,6 +20,7 @@ import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationConfiguration; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessor; import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationEventProcessorFactory; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; import org.eclipse.sirius.web.spring.collaborative.api.ISubscriptionManagerFactory; import org.eclipse.sirius.web.spring.collaborative.validation.api.IValidationDescriptionProvider; import org.eclipse.sirius.web.spring.collaborative.validation.api.IValidationEventHandler; @@ -40,15 +41,18 @@ public class ValidationEventProcessorFactory implements IRepresentationEventProc private final IValidationDescriptionProvider validationDescriptionProvider; - private List validationEventHandlers; + private final List validationEventHandlers; - private ISubscriptionManagerFactory subscriptionManagerFactory; + private final ISubscriptionManagerFactory subscriptionManagerFactory; + + private final IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry; public ValidationEventProcessorFactory(IValidationDescriptionProvider validationDescriptionProvider, List validationEventHandlers, - ISubscriptionManagerFactory subscriptionManagerFactory) { + ISubscriptionManagerFactory subscriptionManagerFactory, IRepresentationRefreshPolicyRegistry representationRefreshPolicyRegistry) { this.validationDescriptionProvider = Objects.requireNonNull(validationDescriptionProvider); this.validationEventHandlers = Objects.requireNonNull(validationEventHandlers); this.subscriptionManagerFactory = Objects.requireNonNull(subscriptionManagerFactory); + this.representationRefreshPolicyRegistry = Objects.requireNonNull(representationRefreshPolicyRegistry); } @Override @@ -64,7 +68,7 @@ public Optional createRepresentatio ValidationContext validationContext = new ValidationContext(null); IRepresentationEventProcessor validationEventProcessor = new ValidationEventProcessor(editingContext, validationDescription, validationContext, this.validationEventHandlers, - this.subscriptionManagerFactory.create(), new SimpleMeterRegistry()); + this.subscriptionManagerFactory.create(), new SimpleMeterRegistry(), this.representationRefreshPolicyRegistry); // @formatter:off return Optional.of(validationEventProcessor) diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeDescription.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeDescription.java index 8fc965e06f3..8ad2dc35fd8 100644 --- a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeDescription.java +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeDescription.java @@ -13,6 +13,8 @@ package org.eclipse.sirius.web.spring.collaborative.api; import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -33,10 +35,17 @@ public class ChangeDescription { private final IInput input; + private final Map parameters; + public ChangeDescription(String kind, UUID sourceId, IInput input) { + this(kind, sourceId, input, new HashMap<>()); + } + + public ChangeDescription(String kind, UUID sourceId, IInput input, Map parameters) { this.kind = Objects.requireNonNull(kind); this.sourceId = Objects.requireNonNull(sourceId); this.input = Objects.requireNonNull(input); + this.parameters = Objects.requireNonNull(parameters); } public String getKind() { @@ -51,6 +60,10 @@ public IInput getInput() { return this.input; } + public Map getParameters() { + return this.parameters; + } + @Override public String toString() { String pattern = "{0} '{'kind: {1}, sourceId: {2}'}'"; //$NON-NLS-1$ diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeKind.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeKind.java index 7532da66b4e..219825fc63d 100644 --- a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeKind.java +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/ChangeKind.java @@ -33,6 +33,8 @@ public final class ChangeKind { public static final String FOCUS_CHANGE = "FOCUS_CHANGE"; //$NON-NLS-1$ + public static final String REPRESENTATION_TO_DELETE = "REPRESENTATION_TO_DELETE"; //$NON-NLS-1$ + private ChangeKind() { // Prevent instantiation } diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicy.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicy.java new file mode 100644 index 00000000000..f36f968e47b --- /dev/null +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicy.java @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.api; + +/** + * The interface used to describe the refresh policy of a representation regarding the given change description. + * + * @author gcoutable + */ +public interface IRepresentationRefreshPolicy { + boolean shouldRefresh(ChangeDescription changeDescription); +} diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicyProvider.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicyProvider.java new file mode 100644 index 00000000000..3c945e787d6 --- /dev/null +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicyProvider.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.api; + +import org.eclipse.sirius.web.representations.IRepresentationDescription; + +/** + * The provider interface to contribute a representation refresh policy to the representation refresh policy registry. + * + * @author gcoutable + */ +public interface IRepresentationRefreshPolicyProvider { + boolean canHandle(IRepresentationDescription representationDescription); + + IRepresentationRefreshPolicy getRepresentationRefreshPolicy(IRepresentationDescription representationDescription); +} diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicyRegistry.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicyRegistry.java new file mode 100644 index 00000000000..1afc6216f72 --- /dev/null +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/api/IRepresentationRefreshPolicyRegistry.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.api; + +import java.util.Optional; + +import org.eclipse.sirius.web.representations.IRepresentationDescription; + +/** + * Registry of contributed representation refresh policy. + * + * @author gcoutable + */ +public interface IRepresentationRefreshPolicyRegistry { + + Optional getRepresentationRefreshPolicy(IRepresentationDescription representationDescription); + + void add(IRepresentationRefreshPolicyProvider representationRefreshPolicyProvider); + + /** + * Implementation which does nothing, used for mocks in unit tests. + * + * @author gcoutable + */ + class NoOp implements IRepresentationRefreshPolicyRegistry { + + @Override + public Optional getRepresentationRefreshPolicy(IRepresentationDescription representationDescription) { + return Optional.empty(); + } + + @Override + public void add(IRepresentationRefreshPolicyProvider representationRefreshPolicyProvider) { + } + + } +} diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/projects/EditingContextEventProcessor.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/projects/EditingContextEventProcessor.java index 47f2ccce364..a48368c44be 100644 --- a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/projects/EditingContextEventProcessor.java +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/projects/EditingContextEventProcessor.java @@ -70,6 +70,8 @@ */ public class EditingContextEventProcessor implements IEditingContextEventProcessor { + public static final String REPRESENTATION_ID = "representationId"; //$NON-NLS-1$ + private final Logger logger = LoggerFactory.getLogger(EditingContextEventProcessor.class); private final ICollaborativeMessageService messageService; @@ -120,6 +122,15 @@ public EditingContextEventProcessor(ICollaborativeMessageService messageService, private Disposable setupChangeDescriptionSinkConsumer() { Consumer consumer = changeDescription -> { + + if (ChangeKind.REPRESENTATION_TO_DELETE.equals(changeDescription.getKind())) { + Object representationId = changeDescription.getParameters().get(REPRESENTATION_ID); + if (representationId instanceof UUID) { + DeleteRepresentationInput deleteRepresentationInput = new DeleteRepresentationInput(UUID.randomUUID(), (UUID) representationId); + this.doHandle(Sinks.one(), deleteRepresentationInput); + } + } + this.publishEvent(changeDescription); this.disposeRepresentationIfNeeded(); diff --git a/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/representations/RepresentationRefreshPolicyRegistry.java b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/representations/RepresentationRefreshPolicyRegistry.java new file mode 100644 index 00000000000..c61fc777a8a --- /dev/null +++ b/backend/sirius-web-spring-collaborative/src/main/java/org/eclipse/sirius/web/spring/collaborative/representations/RepresentationRefreshPolicyRegistry.java @@ -0,0 +1,50 @@ +/******************************************************************************* + * Copyright (c) 2021 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.spring.collaborative.representations; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.eclipse.sirius.web.representations.IRepresentationDescription; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicy; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyProvider; +import org.eclipse.sirius.web.spring.collaborative.api.IRepresentationRefreshPolicyRegistry; +import org.springframework.stereotype.Service; + +/** + * Registry of the representation refresh policies. + * + * @author gcoutable + */ +@Service +public class RepresentationRefreshPolicyRegistry implements IRepresentationRefreshPolicyRegistry { + + private List representationRefreshPolicyProviders = new ArrayList<>(); + + @Override + public Optional getRepresentationRefreshPolicy(IRepresentationDescription representationDescription) { + // @formatter:off + return this.representationRefreshPolicyProviders.stream() + .filter(representationRefreshPolicyProvider -> representationRefreshPolicyProvider.canHandle(representationDescription)) + .findFirst() + .map(representationRefreshPolicyProvider -> representationRefreshPolicyProvider.getRepresentationRefreshPolicy(representationDescription)); + // @formatter:on + } + + @Override + public void add(IRepresentationRefreshPolicyProvider representationRefreshPolicyProvider) { + this.representationRefreshPolicyProviders.add(representationRefreshPolicyProvider); + } + +} diff --git a/frontend/src/form/Form.types.ts b/frontend/src/form/Form.types.ts index e92a97c091e..bb4891f5c1f 100644 --- a/frontend/src/form/Form.types.ts +++ b/frontend/src/form/Form.types.ts @@ -98,5 +98,7 @@ export interface List extends Widget { export interface ListItem { id: string; label: string; + kind: string; imageURL: string; + deletable: boolean; } diff --git a/frontend/src/form/FormEventFragments.ts b/frontend/src/form/FormEventFragments.ts index 9427e0adb55..08ffa8a7c2b 100644 --- a/frontend/src/form/FormEventFragments.ts +++ b/frontend/src/form/FormEventFragments.ts @@ -94,7 +94,9 @@ export const formRefreshedEventPayloadFragment = gql` items { id label + kind imageURL + deletable } } } diff --git a/frontend/src/form/FormEventFragments.types.ts b/frontend/src/form/FormEventFragments.types.ts index 08acd26bc8c..78ef219c6db 100644 --- a/frontend/src/form/FormEventFragments.types.ts +++ b/frontend/src/form/FormEventFragments.types.ts @@ -26,12 +26,26 @@ export interface GQLPropertiesEventPayload { __typename: string; } -export interface GQLSubscribersUpdatedEventPayload extends GQLFormEventPayload, GQLPropertiesEventPayload { +export interface GQLRepresentationsEventSubscription { + representationsEvent: GQLRepresentationsEventPayload; +} + +export interface GQLRepresentationsEventPayload { + __typename: string; +} + +export interface GQLSubscribersUpdatedEventPayload + extends GQLFormEventPayload, + GQLPropertiesEventPayload, + GQLRepresentationsEventPayload { id: string; subscribers: GQLSubscriber[]; } -export interface GQLWidgetSubscriptionsUpdatedEventPayload extends GQLFormEventPayload, GQLPropertiesEventPayload { +export interface GQLWidgetSubscriptionsUpdatedEventPayload + extends GQLFormEventPayload, + GQLPropertiesEventPayload, + GQLRepresentationsEventPayload { id: string; widgetSubscriptions: GQLWidgetSubscription[]; } @@ -45,7 +59,10 @@ export interface GQLSubscriber { username: string; } -export interface GQLFormRefreshedEventPayload extends GQLFormEventPayload, GQLPropertiesEventPayload { +export interface GQLFormRefreshedEventPayload + extends GQLFormEventPayload, + GQLPropertiesEventPayload, + GQLRepresentationsEventPayload { id: string; form: GQLForm; } @@ -129,4 +146,5 @@ export interface GQLListItem { id: string; label: string; imageURL: string; + deletable: Boolean; } diff --git a/frontend/src/form/FormWebSocketContainer.tsx b/frontend/src/form/FormWebSocketContainer.tsx index 417a443ec48..9d1b243270e 100644 --- a/frontend/src/form/FormWebSocketContainer.tsx +++ b/frontend/src/form/FormWebSocketContainer.tsx @@ -73,6 +73,7 @@ const useFormWebSocketContainerStyles = makeStyles((theme) => ({ export const FormWebSocketContainer = ({ editingContextId, representationId, + setSelection, readOnly, }: RepresentationComponentProps) => { const classes = useFormWebSocketContainerStyles(); @@ -135,6 +136,7 @@ export const FormWebSocketContainer = ({ form={form} subscribers={subscribers} widgetSubscriptions={widgetSubscriptions} + setSelection={setSelection} readOnly={readOnly} /> ); diff --git a/frontend/src/properties/Group.tsx b/frontend/src/properties/Group.tsx index 142e20ac7a5..ca9ac8fecb3 100644 --- a/frontend/src/properties/Group.tsx +++ b/frontend/src/properties/Group.tsx @@ -31,6 +31,7 @@ import { RadioPropertySection } from 'properties/propertysections/RadioPropertyS import { SelectPropertySection } from 'properties/propertysections/SelectPropertySection'; import { TextfieldPropertySection } from 'properties/propertysections/TextfieldPropertySection'; import React from 'react'; +import { Selection } from 'workbench/Workbench.types'; const useGroupStyles = makeStyles((theme) => ({ group: { @@ -51,11 +52,11 @@ const useGroupStyles = makeStyles((theme) => ({ }, })); -export const Group = ({ editingContextId, formId, group, widgetSubscriptions, readOnly }: GroupProps) => { +export const Group = ({ editingContextId, formId, group, widgetSubscriptions, setSelection, readOnly }: GroupProps) => { const classes = useGroupStyles(); let propertySections = group.widgets.map((widget) => - widgetToPropertySection(editingContextId, formId, widget, widgetSubscriptions, readOnly) + widgetToPropertySection(editingContextId, formId, widget, widgetSubscriptions, setSelection, readOnly) ); return ( @@ -81,6 +82,7 @@ const widgetToPropertySection = ( formId: string, widget: Widget, widgetSubscriptions: WidgetSubscription[], + setSelection: (selection: Selection) => void, readOnly: boolean ) => { let subscribers = []; @@ -146,7 +148,15 @@ const widgetToPropertySection = ( ); } else if (isList(widget)) { propertySection = ( - + ); } else { console.error(`Unsupported widget type ${widget.__typename}`); diff --git a/frontend/src/properties/Group.types.ts b/frontend/src/properties/Group.types.ts index 85ccfef73d9..a35122cfef1 100644 --- a/frontend/src/properties/Group.types.ts +++ b/frontend/src/properties/Group.types.ts @@ -11,11 +11,13 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Group, WidgetSubscription } from 'form/Form.types'; +import { Selection } from 'workbench/Workbench.types'; export interface GroupProps { editingContextId: string; formId: string; group: Group; widgetSubscriptions: WidgetSubscription[]; + setSelection: (selection: Selection) => void; readOnly: boolean; } diff --git a/frontend/src/properties/Page.tsx b/frontend/src/properties/Page.tsx index 39586c33daa..4bbd80da831 100644 --- a/frontend/src/properties/Page.tsx +++ b/frontend/src/properties/Page.tsx @@ -25,7 +25,7 @@ const usePageStyles = makeStyles((theme) => ({ }, })); -export const Page = ({ editingContextId, formId, page, widgetSubscriptions, readOnly }: PageProps) => { +export const Page = ({ editingContextId, formId, page, widgetSubscriptions, setSelection, readOnly }: PageProps) => { const classes = usePageStyles(); return (
@@ -37,6 +37,7 @@ export const Page = ({ editingContextId, formId, page, widgetSubscriptions, read group={group} widgetSubscriptions={widgetSubscriptions} key={group.id} + setSelection={setSelection} readOnly={readOnly} /> ); diff --git a/frontend/src/properties/Page.types.ts b/frontend/src/properties/Page.types.ts index 831b1b2ac61..fcb89879c6e 100644 --- a/frontend/src/properties/Page.types.ts +++ b/frontend/src/properties/Page.types.ts @@ -11,11 +11,13 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Page, WidgetSubscription } from 'form/Form.types'; +import { Selection } from 'workbench/Workbench.types'; export interface PageProps { editingContextId: string; formId: string; page: Page; widgetSubscriptions: WidgetSubscription[]; + setSelection: (selection: Selection) => void; readOnly: boolean; } diff --git a/frontend/src/properties/Properties.tsx b/frontend/src/properties/Properties.tsx index ae5a80f9644..d8e42b29ddb 100644 --- a/frontend/src/properties/Properties.tsx +++ b/frontend/src/properties/Properties.tsx @@ -53,7 +53,14 @@ const usePropertiesStyles = makeStyles((theme) => ({ }, })); -export const Properties = ({ editingContextId, form, subscribers, widgetSubscriptions, readOnly }: FormProps) => { +export const Properties = ({ + editingContextId, + form, + subscribers, + widgetSubscriptions, + setSelection, + readOnly, +}: FormProps) => { const classes = usePropertiesStyles(); const { id, label, pages } = form; @@ -67,6 +74,7 @@ export const Properties = ({ editingContextId, form, subscribers, widgetSubscrip formId={id} page={pages[0]} widgetSubscriptions={widgetSubscriptions} + setSelection={setSelection} readOnly={readOnly} />
@@ -78,6 +86,7 @@ export const Properties = ({ editingContextId, form, subscribers, widgetSubscrip formId={id} page={pages[0]} widgetSubscriptions={widgetSubscriptions} + setSelection={setSelection} readOnly={readOnly} /> ); diff --git a/frontend/src/properties/Properties.types.ts b/frontend/src/properties/Properties.types.ts index d193f432ec1..4e4c756aced 100644 --- a/frontend/src/properties/Properties.types.ts +++ b/frontend/src/properties/Properties.types.ts @@ -11,6 +11,7 @@ * Obeo - initial API and implementation *******************************************************************************/ import { Form, Subscriber, WidgetSubscription } from 'form/Form.types'; +import { Selection } from 'workbench/Workbench.types'; export interface FormProps { editingContextId: string; @@ -18,4 +19,5 @@ export interface FormProps { subscribers: Subscriber[]; widgetSubscriptions: WidgetSubscription[]; readOnly: boolean; + setSelection: (selection: Selection) => void; } diff --git a/frontend/src/properties/PropertiesWebSocketContainer.tsx b/frontend/src/properties/PropertiesWebSocketContainer.tsx index 297530f2d79..4f9dc9bec3e 100644 --- a/frontend/src/properties/PropertiesWebSocketContainer.tsx +++ b/frontend/src/properties/PropertiesWebSocketContainer.tsx @@ -72,6 +72,7 @@ const usePropertiesWebSocketContainerStyles = makeStyles((theme) => ({ export const PropertiesWebSocketContainer = ({ editingContextId, selection, + setSelection, readOnly, }: PropertiesWebSocketContainerProps) => { const classes = usePropertiesWebSocketContainerStyles(); @@ -140,6 +141,7 @@ export const PropertiesWebSocketContainer = ({ form={form} subscribers={subscribers} widgetSubscriptions={widgetSubscriptions} + setSelection={setSelection} readOnly={readOnly} /> ); diff --git a/frontend/src/properties/PropertiesWebSocketContainer.types.ts b/frontend/src/properties/PropertiesWebSocketContainer.types.ts index 1ca3b7ed008..e90772003c0 100644 --- a/frontend/src/properties/PropertiesWebSocketContainer.types.ts +++ b/frontend/src/properties/PropertiesWebSocketContainer.types.ts @@ -15,6 +15,6 @@ import { Selection } from 'workbench/Workbench.types'; export interface PropertiesWebSocketContainerProps { editingContextId: string; selection: Selection; - + setSelection: (selection: Selection) => void; readOnly: boolean; } diff --git a/frontend/src/properties/propertysections/ListPropertySection.tsx b/frontend/src/properties/propertysections/ListPropertySection.tsx index cdb12d8bda6..4ed6cfbedd9 100644 --- a/frontend/src/properties/propertysections/ListPropertySection.tsx +++ b/frontend/src/properties/propertysections/ListPropertySection.tsx @@ -10,17 +10,42 @@ * Contributors: * Obeo - initial API and implementation *******************************************************************************/ +import { useMutation } from '@apollo/client'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; +import IconButton from '@material-ui/core/IconButton'; +import Snackbar from '@material-ui/core/Snackbar'; import { makeStyles } from '@material-ui/core/styles'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableCell from '@material-ui/core/TableCell'; import TableRow from '@material-ui/core/TableRow'; +import Typography from '@material-ui/core/Typography'; +import CloseIcon from '@material-ui/icons/Close'; +import DeleteIcon from '@material-ui/icons/Delete'; import { httpOrigin } from 'common/URL'; -import { ListPropertySectionProps } from 'properties/propertysections/ListPropertySection.types'; +import { ListItem } from 'form/Form.types'; +import gql from 'graphql-tag'; +import { + GQLDeleteListItemMutationData, + GQLDeleteListItemPayload, + GQLErrorPayload, + ListPropertySectionProps, +} from 'properties/propertysections/ListPropertySection.types'; import { PropertySectionLabel } from 'properties/propertysections/PropertySectionLabel'; -import React from 'react'; +import React, { MouseEvent, useEffect, useState } from 'react'; +import { v4 as uuid } from 'uuid'; + +const deleteListItemMutation = gql` + mutation deleteListItem($input: DeleteListItemInput!) { + deleteListItem(input: $input) { + __typename + ... on ErrorPayload { + message + } + } + } +`; const useListPropertySectionStyles = makeStyles((theme) => ({ table: { @@ -38,44 +63,128 @@ const useListPropertySectionStyles = makeStyles((theme) => ({ height: '16px', marginRight: theme.spacing(2), }, + canBeSelectedItem: { + '&:hover': { + textDecoration: 'underline', + cursor: 'pointer', + }, + }, })); -export const ListPropertySection = ({ widget, subscribers }: ListPropertySectionProps) => { +const NONE_WIDGET_ITEM_ID = 'none'; + +const isErrorPayload = (payload: GQLDeleteListItemPayload): payload is GQLErrorPayload => + payload.__typename === 'ErrorPayload'; +export const ListPropertySection = ({ + editingContextId, + formId, + widget, + subscribers, + readOnly, + setSelection, +}: ListPropertySectionProps) => { const classes = useListPropertySectionStyles(); + const [message, setMessage] = useState(null); - let items = widget.items; + let items = [...widget.items]; if (items.length === 0) { items.push({ - id: 'none', + id: NONE_WIDGET_ITEM_ID, imageURL: '', label: 'None', + kind: 'Unknown', + deletable: false, }); } + const [deleteListItem, { loading: deleteLoading, error: deleteError, data: deleteData }] = + useMutation(deleteListItemMutation); + + const onDelete = (event: MouseEvent, item: ListItem) => { + const variables = { + input: { + id: uuid(), + editingContextId, + representationId: formId, + listId: widget.id, + listItemId: item.id, + }, + }; + deleteListItem({ variables }); + }; + + useEffect(() => { + if (!deleteLoading) { + if (deleteError) { + setMessage('An unexpected error has occurred, please refresh the page'); + } + if (deleteData) { + const { deleteListItem } = deleteData; + if (isErrorPayload(deleteListItem)) { + setMessage(deleteListItem.message); + } + } + } + }, [deleteLoading, deleteError, deleteData]); + + const onRowClick = (item: ListItem) => { + const { id, label, kind } = item; + setSelection({ id, label, kind }); + }; + + const getTableCellContent = (item: ListItem): JSX.Element => { + return ( + onRowClick(item)} + color="textPrimary" + data-testid={`representation-${item.id}`}> + {item.imageURL ? ( + {item.label} + ) : null} + {item.label} + + ); + }; + return ( - 0}> + 0} fullWidth> - +
- {widget.items.map((item) => ( + {items.map((item) => ( - - {item.imageURL ? ( - {item.label} - ) : null} - {item.label} + {getTableCellContent(item)} + + onDelete(event, item)} + disabled={readOnly || !item.deletable} + data-testid={`delete-representation-${item.id}`}> + + ))}
{widget.diagnostics[0]?.message} + setMessage(null)} + message={message} + action={ + setMessage(null)}> + + + } + data-testid="error" + />
); }; diff --git a/frontend/src/properties/propertysections/ListPropertySection.types.ts b/frontend/src/properties/propertysections/ListPropertySection.types.ts index a688d669048..abd413d43fe 100644 --- a/frontend/src/properties/propertysections/ListPropertySection.types.ts +++ b/frontend/src/properties/propertysections/ListPropertySection.types.ts @@ -11,9 +11,25 @@ * Obeo - initial API and implementation *******************************************************************************/ import { List, Subscriber } from 'form/Form.types'; +import { Selection } from 'workbench/Workbench.types'; export interface ListPropertySectionProps { + editingContextId: string; + formId: string; widget: List; subscribers: Subscriber[]; - readonly: boolean; + readOnly: boolean; + setSelection: (selection: Selection) => void; +} + +export interface GQLDeleteListItemMutationData { + deleteListItem: GQLDeleteListItemPayload; +} + +export interface GQLDeleteListItemPayload { + __typename: string; +} + +export interface GQLErrorPayload extends GQLDeleteListItemPayload { + message: string; } diff --git a/frontend/src/representations/RepresentationsWebSocketContainer.tsx b/frontend/src/representations/RepresentationsWebSocketContainer.tsx new file mode 100644 index 00000000000..e1515779ca9 --- /dev/null +++ b/frontend/src/representations/RepresentationsWebSocketContainer.tsx @@ -0,0 +1,171 @@ +/******************************************************************************* + * Copyright (c) 2021 Obeo and others. + * 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 { useSubscription } from '@apollo/client'; +import IconButton from '@material-ui/core/IconButton'; +import Snackbar from '@material-ui/core/Snackbar'; +import makeStyles from '@material-ui/core/styles/makeStyles'; +import Typography from '@material-ui/core/Typography'; +import CloseIcon from '@material-ui/icons/Close'; +import { useMachine } from '@xstate/react'; +import { + formRefreshedEventPayloadFragment, + subscribersUpdatedEventPayloadFragment, + widgetSubscriptionsUpdatedEventPayloadFragment, +} from 'form/FormEventFragments'; +import gql from 'graphql-tag'; +import { ListPropertySection } from 'properties/propertysections/ListPropertySection'; +import React, { useContext, useEffect } from 'react'; +import { RepresentationContext } from 'workbench/RepresentationContext'; +import { RepresentationsWebSocketContainerProps } from './RepresentationsWebSocketContainer.types'; +import { + HandleCompleteEvent, + HandleSubscriptionResultEvent, + HideToastEvent, + RepresentationsWebSocketContainerContext, + RepresentationsWebSocketContainerEvent, + representationsWebSocketContainerMachine, + SchemaValue, + ShowToastEvent, + SwitchSelectionEvent, +} from './RepresentationsWebSocketContainerMachine'; + +const representationsEventSubscription = gql` + subscription representationsEvent($input: RepresentationsEventInput!) { + representationsEvent(input: $input) { + __typename + ... on SubscribersUpdatedEventPayload { + ...subscribersUpdatedEventPayloadFragment + } + ... on WidgetSubscriptionsUpdatedEventPayload { + ...widgetSubscriptionsUpdatedEventPayloadFragment + } + ... on FormRefreshedEventPayload { + ...formRefreshedEventPayloadFragment + } + } + } + ${subscribersUpdatedEventPayloadFragment} + ${widgetSubscriptionsUpdatedEventPayloadFragment} + ${formRefreshedEventPayloadFragment} +`; + +const useRepresentationsWebSocketContainerStyles = makeStyles((theme) => ({ + idle: { + padding: theme.spacing(1), + }, +})); + +export const RepresentationsWebSocketContainer = ({ + editingContextId, + selection, + setSelection, + readOnly, +}: RepresentationsWebSocketContainerProps) => { + const classes = useRepresentationsWebSocketContainerStyles(); + + const [{ value, context }, dispatch] = useMachine< + RepresentationsWebSocketContainerContext, + RepresentationsWebSocketContainerEvent + >(representationsWebSocketContainerMachine); + + const { toast, representationsWebSocketContainer } = value as SchemaValue; + const { id, currentSelection, formId, widget, subscribers, message } = context; + const { registry } = useContext(RepresentationContext); + + useEffect(() => { + if (currentSelection?.id !== selection?.id) { + const isRepresentation = registry.isRepresentation(selection.kind); + const switchSelectionEvent: SwitchSelectionEvent = { type: 'SWITCH_SELECTION', selection, isRepresentation }; + dispatch(switchSelectionEvent); + } + }, [currentSelection, registry, selection, dispatch]); + + const { error } = useSubscription(representationsEventSubscription, { + variables: { + input: { + id, + editingContextId, + objectId: currentSelection?.id, + }, + }, + fetchPolicy: 'no-cache', + skip: representationsWebSocketContainer === 'empty' || representationsWebSocketContainer === 'unsupportedSelection', + onSubscriptionData: ({ subscriptionData }) => { + const handleDataEvent: HandleSubscriptionResultEvent = { + type: 'HANDLE_SUBSCRIPTION_RESULT', + result: subscriptionData, + }; + dispatch(handleDataEvent); + }, + onSubscriptionComplete: () => { + const completeEvent: HandleCompleteEvent = { type: 'HANDLE_COMPLETE' }; + dispatch(completeEvent); + }, + }); + + useEffect(() => { + if (error) { + const { message } = error; + const showToastEvent: ShowToastEvent = { type: 'SHOW_TOAST', message }; + dispatch(showToastEvent); + } + }, [error, dispatch]); + + let content = null; + if (!selection || representationsWebSocketContainer === 'unsupportedSelection') { + content = ( +
+ No object selected +
+ ); + } + if ((representationsWebSocketContainer === 'idle' && widget) || representationsWebSocketContainer === 'ready') { + content = ( + + ); + } + + return ( + <> + {content} + dispatch({ type: 'HIDE_TOAST' } as HideToastEvent)} + message={message} + action={ + dispatch({ type: 'HIDE_TOAST' } as HideToastEvent)}> + + + } + data-testid="error" + /> + + ); +}; diff --git a/frontend/src/representations/RepresentationsWebSocketContainer.types.ts b/frontend/src/representations/RepresentationsWebSocketContainer.types.ts new file mode 100644 index 00000000000..4a666c33c7d --- /dev/null +++ b/frontend/src/representations/RepresentationsWebSocketContainer.types.ts @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright (c) 2021 Obeo and others. + * 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 { Selection } from 'workbench/Workbench.types'; + +export interface RepresentationsWebSocketContainerProps { + editingContextId: string; + selection: Selection; + setSelection: (selection: Selection) => void; + readOnly: boolean; +} diff --git a/frontend/src/representations/RepresentationsWebSocketContainerMachine.ts b/frontend/src/representations/RepresentationsWebSocketContainerMachine.ts new file mode 100644 index 00000000000..84e875d4dcb --- /dev/null +++ b/frontend/src/representations/RepresentationsWebSocketContainerMachine.ts @@ -0,0 +1,274 @@ +/******************************************************************************* + * Copyright (c) 2021 Obeo and others. + * 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 { List, Subscriber, WidgetSubscription } from 'form/Form.types'; +import { + GQLFormRefreshedEventPayload, + GQLPropertiesEventPayload, + GQLRepresentationsEventSubscription, + GQLSubscribersUpdatedEventPayload, + GQLWidget, + GQLWidgetSubscriptionsUpdatedEventPayload, +} from 'form/FormEventFragments.types'; +import { v4 as uuid } from 'uuid'; +import { Selection } from 'workbench/Workbench.types'; +import { assign, Machine } from 'xstate'; + +export interface RepresentationsWebSocketContainerStateSchema { + states: { + toast: { + states: { + visible: {}; + hidden: {}; + }; + }; + representationsWebSocketContainer: { + states: { + empty: {}; + unsupportedSelection: {}; + idle: {}; + ready: {}; + complete: {}; + }; + }; + }; +} + +export type SchemaValue = { + toast: 'visible' | 'hidden'; + representationsWebSocketContainer: 'empty' | 'unsupportedSelection' | 'idle' | 'ready' | 'complete'; +}; + +export interface RepresentationsWebSocketContainerContext { + id: string; + currentSelection: Selection | null; + formId: string | null; + widget: List | null; + subscribers: Subscriber[]; + widgetSubscriptions: WidgetSubscription[]; + message: string | null; +} + +export type ShowToastEvent = { type: 'SHOW_TOAST'; message: string }; +export type HideToastEvent = { type: 'HIDE_TOAST' }; +export type SwitchSelectionEvent = { type: 'SWITCH_SELECTION'; selection: Selection; isRepresentation: boolean }; +export type HandleSubscriptionResultEvent = { + type: 'HANDLE_SUBSCRIPTION_RESULT'; + result: SubscriptionResult; +}; +export type HandleCompleteEvent = { type: 'HANDLE_COMPLETE' }; +export type RepresentationsWebSocketContainerEvent = + | SwitchSelectionEvent + | HandleSubscriptionResultEvent + | HandleCompleteEvent + | ShowToastEvent + | HideToastEvent; + +const isFormRefreshedEventPayload = (payload: GQLPropertiesEventPayload): payload is GQLFormRefreshedEventPayload => + payload.__typename === 'FormRefreshedEventPayload'; +const isSubscribersUpdatedEventPayload = ( + payload: GQLPropertiesEventPayload +): payload is GQLSubscribersUpdatedEventPayload => payload.__typename === 'SubscribersUpdatedEventPayload'; +const isWidgetSubscriptionsUpdatedEventPayload = ( + payload: GQLPropertiesEventPayload +): payload is GQLWidgetSubscriptionsUpdatedEventPayload => + payload.__typename == 'WidgetSubscriptionsUpdatedEventPayload'; +const isList = (widget: GQLWidget): widget is List => widget.__typename === 'List'; + +export const representationsWebSocketContainerMachine = Machine< + RepresentationsWebSocketContainerContext, + RepresentationsWebSocketContainerStateSchema, + RepresentationsWebSocketContainerEvent +>( + { + type: 'parallel', + context: { + id: uuid(), + currentSelection: null, + widget: null, + formId: null, + subscribers: [], + widgetSubscriptions: [], + message: null, + }, + states: { + toast: { + initial: 'hidden', + states: { + hidden: { + on: { + SHOW_TOAST: { + target: 'visible', + actions: 'setMessage', + }, + }, + }, + visible: { + on: { + HIDE_TOAST: { + target: 'hidden', + actions: 'clearMessage', + }, + }, + }, + }, + }, + representationsWebSocketContainer: { + initial: 'empty', + states: { + empty: { + on: { + SWITCH_SELECTION: [ + { + cond: 'isSelectionUnsupported', + target: 'unsupportedSelection', + actions: ['switchSelection', 'clearForm'], + }, + { + target: 'idle', + actions: 'switchSelection', + }, + ], + }, + }, + unsupportedSelection: { + on: { + SWITCH_SELECTION: [ + { + cond: 'isSelectionUnsupported', + target: 'unsupportedSelection', + actions: ['switchSelection', 'clearForm'], + }, + { + target: 'idle', + actions: 'switchSelection', + }, + ], + }, + }, + idle: { + on: { + SWITCH_SELECTION: [ + { + cond: 'isSelectionUnsupported', + target: 'unsupportedSelection', + actions: ['switchSelection', 'clearForm'], + }, + { + target: 'idle', + actions: 'switchSelection', + }, + ], + HANDLE_SUBSCRIPTION_RESULT: [ + { + cond: 'isFormRefreshedEventPayload', + target: 'ready', + actions: 'handleSubscriptionResult', + }, + { + target: 'idle', + actions: 'handleSubscriptionResult', + }, + ], + }, + }, + ready: { + on: { + SWITCH_SELECTION: [ + { + cond: 'isSelectionUnsupported', + target: 'unsupportedSelection', + actions: ['switchSelection', 'clearForm'], + }, + { + target: 'idle', + actions: 'switchSelection', + }, + ], + HANDLE_SUBSCRIPTION_RESULT: { + target: 'ready', + actions: 'handleSubscriptionResult', + }, + HANDLE_COMPLETE: { + target: 'complete', + }, + }, + }, + complete: { + on: { + SWITCH_SELECTION: [ + { + cond: 'isSelectionUnsupported', + target: 'unsupportedSelection', + actions: ['switchSelection', 'clearForm'], + }, + { + target: 'idle', + actions: 'switchSelection', + }, + ], + }, + }, + }, + }, + }, + }, + { + guards: { + isFormRefreshedEventPayload: (_, event) => { + const { result } = event as HandleSubscriptionResultEvent; + const { data } = result; + return isFormRefreshedEventPayload(data.representationsEvent); + }, + isSelectionUnsupported: (_, event) => { + const { selection, isRepresentation } = event as SwitchSelectionEvent; + return !selection || isRepresentation || selection.kind === 'Unknown' || selection.kind === 'Document'; + }, + }, + actions: { + switchSelection: assign((_, event) => { + const { selection } = event as SwitchSelectionEvent; + return { id: uuid(), currentSelection: selection }; + }), + clearForm: assign((_, event) => { + return { widget: null }; + }), + handleSubscriptionResult: assign((_, event) => { + const { result } = event as HandleSubscriptionResultEvent; + const { data } = result; + if (isFormRefreshedEventPayload(data.representationsEvent)) { + const { form } = data.representationsEvent; + const widget = form.pages[0]?.groups[0]?.widgets[0]; + if (isList(widget)) { + return { widget, formId: form.id }; + } + } else if (isSubscribersUpdatedEventPayload(data.representationsEvent)) { + const { subscribers } = data.representationsEvent; + return { subscribers }; + } else if (isWidgetSubscriptionsUpdatedEventPayload(data.representationsEvent)) { + const { widgetSubscriptions } = data.representationsEvent; + return { widgetSubscriptions }; + } + return {}; + }), + setMessage: assign((_, event) => { + const { message } = event as ShowToastEvent; + return { message }; + }), + clearMessage: assign((_) => { + return { message: null }; + }), + }, + } +); diff --git a/frontend/src/workbench/RightSite.tsx b/frontend/src/workbench/RightSite.tsx index f84c6842df5..5e02de2d372 100644 --- a/frontend/src/workbench/RightSite.tsx +++ b/frontend/src/workbench/RightSite.tsx @@ -14,17 +14,24 @@ import MuiAccordion from '@material-ui/core/Accordion'; import AccordionDetails from '@material-ui/core/AccordionDetails'; import MuiAccordionSummary from '@material-ui/core/AccordionSummary'; -import MuiCollapse from '@material-ui/core/Collapse'; +import MuiCollapse, { CollapseProps } from '@material-ui/core/Collapse'; import { makeStyles, withStyles } from '@material-ui/core/styles'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { PropertiesWebSocketContainer } from 'properties/PropertiesWebSocketContainer'; -import React from 'react'; +import React, { useState } from 'react'; +import { RepresentationsWebSocketContainer } from 'representations/RepresentationsWebSocketContainer'; import { RightSiteProps } from './RightSite.types'; const useSiteStyles = makeStyles((theme) => ({ site: { display: 'grid', gridTemplateColumns: 'minmax(0,1fr)', - gridTemplateRows: 'minmax(0,1fr)', + }, + detailsExpanded: { + gridTemplateRows: 'minmax(0,1fr) min-content', + }, + representationsExpanded: { + gridTemplateRows: 'min-content minmax(0,1fr)', }, accordionDetailsRoot: { display: 'block', @@ -67,21 +74,83 @@ const AccordionSummary = withStyles({ expanded: {}, })(MuiAccordionSummary); -const CustomCollapse = withStyles({ +const StyledCollapse = withStyles({ entered: { overflow: 'auto', }, })(MuiCollapse); -export const RightSite = ({ editingContextId, selection, readOnly }: RightSiteProps) => { +const CustomCollapse = (props: CollapseProps) => { + const { children, ...collapseProps } = props; + + const handleEntering = (node: HTMLElement, isAppearing: boolean) => { + node.style.height = 'auto'; + }; + + const handleExit = (node: HTMLElement) => { + node.style.height = 'auto'; + }; + + return ( + + {children} + + ); +}; + +const DETAILS_PANEL_NAME = 'details'; +const REPRESENTATIONS_PANEL_NAME = 'representations'; + +export const RightSite = ({ editingContextId, selection, setSelection, readOnly }: RightSiteProps) => { const classes = useSiteStyles(); + const [expanded, setExpanded] = useState(DETAILS_PANEL_NAME); + + const handleChange = (panel: string) => () => { + setExpanded(panel); + }; + + let classSite = classes.site; + if (expanded === DETAILS_PANEL_NAME) { + classSite = `${classSite} ${classes.detailsExpanded}`; + } else if (expanded === REPRESENTATIONS_PANEL_NAME) { + classSite = `${classSite} ${classes.representationsExpanded}`; + } + return ( -
- - Details +
+ + } IconButtonProps={{ size: 'small' }}> + Details + - + + + + + } IconButtonProps={{ size: 'small' }}> + Representations + + +
diff --git a/frontend/src/workbench/RightSite.types.ts b/frontend/src/workbench/RightSite.types.ts index 61849b02a6d..c508bae6146 100644 --- a/frontend/src/workbench/RightSite.types.ts +++ b/frontend/src/workbench/RightSite.types.ts @@ -14,6 +14,7 @@ import { Selection } from 'workbench/Workbench.types'; export interface RightSiteProps { editingContextId: string; - selection?: Selection; + selection: Selection; + setSelection: (selection: Selection) => void; readOnly: boolean; } diff --git a/frontend/src/workbench/Workbench.tsx b/frontend/src/workbench/Workbench.tsx index 9fc82a69538..2a602e239aa 100644 --- a/frontend/src/workbench/Workbench.tsx +++ b/frontend/src/workbench/Workbench.tsx @@ -91,7 +91,14 @@ export const Workbench = ({ /> ); - const rightSite = ; + const rightSite = ( + + ); let main = (