diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index d66ad0b53c..b2b0792dd3 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -67,6 +67,7 @@ The new endpoints are:
description (optional).
** deleteProject (`POST /api/rest/projects/{projectId}`): Delete the project with the given id (projectId).
** updateProject (`PUT /projects/{projectId}`): Update the project with the given id (projectId).
+- https://github.com/eclipse-sirius/sirius-web/issues/4048[#4048] [core] Add the ability to undo/redo a mutation on the the edit project view with ctrl+z or ctrl+y (restoring a deleted resource is not yet supported).
=== Improvements
diff --git a/packages/core/backend/sirius-components-collaborative/src/main/resources/schema/core.graphqls b/packages/core/backend/sirius-components-collaborative/src/main/resources/schema/core.graphqls
index 5cfc39e512..458e414d30 100644
--- a/packages/core/backend/sirius-components-collaborative/src/main/resources/schema/core.graphqls
+++ b/packages/core/backend/sirius-components-collaborative/src/main/resources/schema/core.graphqls
@@ -165,6 +165,8 @@ type Mutation {
deleteObject(input: DeleteObjectInput!): DeleteObjectPayload!
renameObject(input: RenameObjectInput!): RenameObjectPayload!
invokeEditingContextAction(input: InvokeEditingContextActionInput!): InvokeEditingContextActionPayload!
+ undo(input : UndoRedoInput!) : UndoPayload!
+ redo(input : UndoRedoInput!) : UndoPayload!
}
type Object {
@@ -229,6 +231,14 @@ input DeleteObjectInput {
objectId: ID!
}
+input UndoRedoInput {
+ id: ID!
+ editingContextId: ID!
+ mutationId: ID!
+}
+
+union UndoPayload = ErrorPayload | SuccessPayload
+
union DeleteObjectPayload = ErrorPayload | SuccessPayload
input RenameObjectInput {
diff --git a/packages/sirius-web/backend/sirius-web-application/pom.xml b/packages/sirius-web/backend/sirius-web-application/pom.xml
index b39e588dd4..3906148bd2 100644
--- a/packages/sirius-web/backend/sirius-web-application/pom.xml
+++ b/packages/sirius-web/backend/sirius-web-application/pom.xml
@@ -204,6 +204,12 @@
sirius-components-view-emf
2024.9.4
+
+ org.eclipse.emf
+ org.eclipse.emf.ecore.change
+ 2.17.0
+ compile
+
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/EditingContext.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/EditingContext.java
index 6ebef30f0c..9fc721b85d 100644
--- a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/EditingContext.java
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/editingcontext/EditingContext.java
@@ -12,10 +12,13 @@
*******************************************************************************/
package org.eclipse.sirius.web.application.editingcontext;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import org.eclipse.emf.ecore.change.ChangeDescription;
+import org.eclipse.emf.ecore.change.util.ChangeRecorder;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.sirius.components.emf.services.api.IEMFEditingContext;
import org.eclipse.sirius.components.representations.IRepresentationDescription;
@@ -36,9 +39,14 @@ public class EditingContext implements IEMFEditingContext {
private final List views;
+ private final Map changesDescription = new HashMap<>();
+
+ private final ChangeRecorder changeRecorder;
+
public EditingContext(String id, AdapterFactoryEditingDomain editingDomain, Map representationDescriptions, List views) {
this.id = Objects.requireNonNull(id);
this.editingDomain = Objects.requireNonNull(editingDomain);
+ this.changeRecorder = new ChangeRecorder(this.editingDomain.getResourceSet());
this.representationDescriptions = Objects.requireNonNull(representationDescriptions);
this.views = Objects.requireNonNull(views);
}
@@ -61,4 +69,12 @@ public List getViews() {
return this.views;
}
+ public ChangeRecorder getChangeRecorder() {
+ return changeRecorder;
+ }
+
+ public Map getChangesDescription() {
+ return changesDescription;
+ }
+
}
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/controller/RedoDataFetcher.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/controller/RedoDataFetcher.java
new file mode 100644
index 0000000000..10b9379f7c
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/controller/RedoDataFetcher.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import graphql.schema.DataFetchingEnvironment;
+import org.eclipse.sirius.components.annotations.spring.graphql.MutationDataFetcher;
+import org.eclipse.sirius.components.core.api.IPayload;
+
+import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
+import org.eclipse.sirius.components.graphql.api.IEditingContextDispatcher;
+import org.eclipse.sirius.components.graphql.api.IExceptionWrapper;
+import org.eclipse.sirius.web.application.undo.dto.RedoInput;
+
+
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Data fetcher for the field Mutation#redo.
+ *
+ * @author mcharfadi
+ */
+@MutationDataFetcher(type = "Mutation", field = "redo")
+public class RedoDataFetcher implements IDataFetcherWithFieldCoordinates> {
+
+ private static final String INPUT_ARGUMENT = "input";
+
+ private final ObjectMapper objectMapper;
+
+ private final IExceptionWrapper exceptionWrapper;
+
+ private final IEditingContextDispatcher editingContextDispatcher;
+
+ public RedoDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEditingContextDispatcher editingContextDispatcher) {
+ this.objectMapper = Objects.requireNonNull(objectMapper);
+ this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper);
+ this.editingContextDispatcher = Objects.requireNonNull(editingContextDispatcher);
+ }
+
+ @Override
+ public CompletableFuture get(DataFetchingEnvironment environment) throws Exception {
+ Object argument = environment.getArgument(INPUT_ARGUMENT);
+ var input = this.objectMapper.convertValue(argument, RedoInput.class);
+
+ return this.exceptionWrapper.wrapMono(() -> this.editingContextDispatcher.dispatchMutation(input.editingContextId(), input), input).toFuture();
+ }
+}
\ No newline at end of file
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/controller/UndoDataFetcher.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/controller/UndoDataFetcher.java
new file mode 100644
index 0000000000..c76566d4d0
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/controller/UndoDataFetcher.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.controller;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import graphql.schema.DataFetchingEnvironment;
+import org.eclipse.sirius.components.annotations.spring.graphql.MutationDataFetcher;
+import org.eclipse.sirius.components.core.api.IPayload;
+
+import org.eclipse.sirius.components.graphql.api.IDataFetcherWithFieldCoordinates;
+import org.eclipse.sirius.components.graphql.api.IEditingContextDispatcher;
+import org.eclipse.sirius.components.graphql.api.IExceptionWrapper;
+import org.eclipse.sirius.web.application.undo.dto.UndoInput;
+
+
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Data fetcher for the field Mutation#undo.
+ *
+ * @author mcharfadi
+ */
+@MutationDataFetcher(type = "Mutation", field = "undo")
+public class UndoDataFetcher implements IDataFetcherWithFieldCoordinates> {
+
+ private static final String INPUT_ARGUMENT = "input";
+
+ private final ObjectMapper objectMapper;
+
+ private final IExceptionWrapper exceptionWrapper;
+
+ private final IEditingContextDispatcher editingContextDispatcher;
+
+ public UndoDataFetcher(ObjectMapper objectMapper, IExceptionWrapper exceptionWrapper, IEditingContextDispatcher editingContextDispatcher) {
+ this.objectMapper = Objects.requireNonNull(objectMapper);
+ this.exceptionWrapper = Objects.requireNonNull(exceptionWrapper);
+ this.editingContextDispatcher = Objects.requireNonNull(editingContextDispatcher);
+ }
+
+ @Override
+ public CompletableFuture get(DataFetchingEnvironment environment) throws Exception {
+ Object argument = environment.getArgument(INPUT_ARGUMENT);
+ var input = this.objectMapper.convertValue(argument, UndoInput.class);
+
+ return this.exceptionWrapper.wrapMono(() -> this.editingContextDispatcher.dispatchMutation(input.editingContextId(), input), input).toFuture();
+ }
+}
\ No newline at end of file
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/dto/RedoInput.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/dto/RedoInput.java
new file mode 100644
index 0000000000..26d8c9ddad
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/dto/RedoInput.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.dto;
+
+import org.eclipse.sirius.components.core.api.IInput;
+
+import java.util.UUID;
+
+/**
+ * The input for redo mutation.
+ *
+ * @author mcharfadi
+ */
+public record RedoInput(UUID id, String editingContextId, String mutationId) implements IInput {
+}
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/dto/UndoInput.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/dto/UndoInput.java
new file mode 100644
index 0000000000..990c0f3974
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/dto/UndoInput.java
@@ -0,0 +1,25 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.dto;
+
+import org.eclipse.sirius.components.core.api.IInput;
+
+import java.util.UUID;
+
+/**
+ * The input for undo mutation.
+ *
+ * @author mcharfadi
+ */
+public record UndoInput(UUID id, String editingContextId, String mutationId) implements IInput {
+}
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/handlers/RedoEventHandler.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/handlers/RedoEventHandler.java
new file mode 100644
index 0000000000..da278577d6
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/handlers/RedoEventHandler.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.handlers;
+
+import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
+import org.eclipse.sirius.components.collaborative.api.ChangeKind;
+import org.eclipse.sirius.components.collaborative.api.IEditingContextEventHandler;
+import org.eclipse.sirius.components.core.api.ErrorPayload;
+import org.eclipse.sirius.components.core.api.IEditingContext;
+import org.eclipse.sirius.components.core.api.IInput;
+import org.eclipse.sirius.components.core.api.IPayload;
+import org.eclipse.sirius.components.core.api.SuccessPayload;
+import org.eclipse.sirius.web.application.editingcontext.EditingContext;
+import org.eclipse.sirius.web.application.undo.dto.RedoInput;
+import org.springframework.stereotype.Service;
+
+import reactor.core.publisher.Sinks.Many;
+import reactor.core.publisher.Sinks.One;
+
+/**
+ * Handler used to redo mutations.
+ *
+ * @author mcharfadi
+ */
+@Service
+public class RedoEventHandler implements IEditingContextEventHandler {
+
+ @Override
+ public boolean canHandle(IEditingContext editingContext, IInput input) {
+ return input instanceof RedoInput;
+ }
+
+ @Override
+ public void handle(One payloadSink, Many changeDescriptionSink, IEditingContext editingContext, IInput input) {
+ IPayload payload = new ErrorPayload(input.id(), "Error ");
+ ChangeDescription changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, editingContext.getId(), input);
+ if (editingContext instanceof EditingContext siriusEditingContext && input instanceof RedoInput redoInput) {
+ var emfChangeDescription = siriusEditingContext.getChangesDescription().get(redoInput.mutationId());
+ if (emfChangeDescription != null) {
+ emfChangeDescription.applyAndReverse();
+ }
+ payload = new SuccessPayload(input.id());
+ }
+ payloadSink.tryEmitValue(payload);
+ changeDescriptionSink.tryEmitNext(changeDescription);
+ }
+
+}
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/handlers/UndoEventHandler.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/handlers/UndoEventHandler.java
new file mode 100644
index 0000000000..f33d3c3b7b
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/handlers/UndoEventHandler.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.handlers;
+
+import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
+import org.eclipse.sirius.components.collaborative.api.ChangeKind;
+import org.eclipse.sirius.components.collaborative.api.IEditingContextEventHandler;
+import org.eclipse.sirius.components.core.api.ErrorPayload;
+import org.eclipse.sirius.components.core.api.IEditingContext;
+import org.eclipse.sirius.components.core.api.IInput;
+import org.eclipse.sirius.components.core.api.IPayload;
+import org.eclipse.sirius.components.core.api.SuccessPayload;
+import org.eclipse.sirius.web.application.editingcontext.EditingContext;
+import org.eclipse.sirius.web.application.undo.dto.UndoInput;
+import org.springframework.stereotype.Service;
+
+import reactor.core.publisher.Sinks.Many;
+import reactor.core.publisher.Sinks.One;
+
+/**
+ * Handler used to undo mutations.
+ *
+ * @author mcharfadi
+ */
+@Service
+public class UndoEventHandler implements IEditingContextEventHandler {
+
+ @Override
+ public boolean canHandle(IEditingContext editingContext, IInput input) {
+ return input instanceof UndoInput;
+ }
+
+ @Override
+ public void handle(One payloadSink, Many changeDescriptionSink, IEditingContext editingContext, IInput input) {
+ IPayload payload = new ErrorPayload(input.id(), "Error ");
+ ChangeDescription changeDescription = new ChangeDescription(ChangeKind.SEMANTIC_CHANGE, editingContext.getId(), input);
+ if (editingContext instanceof EditingContext siriusEditingContext && input instanceof UndoInput undoInput) {
+ var emfChangeDescription = siriusEditingContext.getChangesDescription().get(undoInput.mutationId());
+ if (emfChangeDescription != null) {
+ emfChangeDescription.applyAndReverse();
+ }
+ payload = new SuccessPayload(input.id());
+ }
+ payloadSink.tryEmitValue(payload);
+ changeDescriptionSink.tryEmitNext(changeDescription);
+ }
+
+}
diff --git a/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/services/UndoRedoMutationsPrePostProcess.java b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/services/UndoRedoMutationsPrePostProcess.java
new file mode 100644
index 0000000000..df4d5fa831
--- /dev/null
+++ b/packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/services/UndoRedoMutationsPrePostProcess.java
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.sirius.web.application.undo.services;
+
+import org.eclipse.sirius.components.collaborative.api.ChangeDescription;
+import org.eclipse.sirius.components.collaborative.api.IInputPostProcessor;
+import org.eclipse.sirius.components.collaborative.api.IInputPreProcessor;
+import org.eclipse.sirius.components.collaborative.diagrams.dto.LayoutDiagramInput;
+import org.eclipse.sirius.components.core.api.IEditingContext;
+import org.eclipse.sirius.components.core.api.IInput;
+import org.eclipse.sirius.web.application.editingcontext.EditingContext;
+import org.eclipse.sirius.web.application.undo.dto.RedoInput;
+import org.eclipse.sirius.web.application.undo.dto.UndoInput;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Sinks;
+
+/**
+ * Used to save mutations id.
+ *
+ * @author mcharfadi
+ */
+@Service
+public class UndoRedoMutationsPrePostProcess implements IInputPreProcessor, IInputPostProcessor {
+
+ private boolean canHandle(IInput input) {
+ return !(input instanceof UndoInput || input instanceof RedoInput || input instanceof LayoutDiagramInput);
+ }
+
+ @Override
+ public IInput preProcess(IEditingContext editingContext, IInput input, Sinks.Many changeDescriptionSink) {
+ if (editingContext instanceof EditingContext siriusEditingContext && canHandle(input)) {
+ siriusEditingContext.getChangeRecorder().beginRecording(siriusEditingContext.getDomain().getResourceSet().getResources());
+ }
+ return input;
+ }
+
+ @Override
+ public void postProcess(IEditingContext editingContext, IInput input, Sinks.Many changeDescriptionSink) {
+ if (editingContext instanceof EditingContext siriusEditingContext && canHandle(input)) {
+ var changeDescription = siriusEditingContext.getChangeRecorder().summarize();
+ siriusEditingContext.getChangesDescription().put(input.id().toString(), changeDescription);
+ siriusEditingContext.getChangeRecorder().endRecording();
+ }
+
+ }
+}
diff --git a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx
index 7c0c7a9ee1..5319822cba 100644
--- a/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx
+++ b/packages/sirius-web/frontend/sirius-web-application/src/extension/DefaultExtensionRegistry.tsx
@@ -51,6 +51,7 @@ import LinkIcon from '@mui/icons-material/Link';
import MenuIcon from '@mui/icons-material/Menu';
import WarningIcon from '@mui/icons-material/Warning';
import { DiagramFilter } from '../diagrams/DiagramFilter';
+import { OperationCountLink } from '../graphql/ApolloLinkMutationsStack';
import { ApolloClientOptionsConfigurer } from '../graphql/useCreateApolloClient.types';
import { apolloClientOptionsConfigurersExtensionPoint } from '../graphql/useCreateApolloClientExtensionPoints';
import { OnboardArea } from '../onboarding/OnboardArea';
@@ -251,6 +252,7 @@ const nodesApolloClientOptionsConfigurer: ApolloClientOptionsConfigurer = (curre
return {
...currentOptions,
documentTransform: newDocumentTransform,
+ link: new OperationCountLink().concat(currentOptions.link),
};
};
diff --git a/packages/sirius-web/frontend/sirius-web-application/src/graphql/ApolloLinkMutationsStack.tsx b/packages/sirius-web/frontend/sirius-web-application/src/graphql/ApolloLinkMutationsStack.tsx
new file mode 100644
index 0000000000..e160aaee98
--- /dev/null
+++ b/packages/sirius-web/frontend/sirius-web-application/src/graphql/ApolloLinkMutationsStack.tsx
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+import { ApolloLink, Operation } from '@apollo/client';
+import { Kind, OperationTypeNode } from 'graphql/language';
+
+export class OperationCountLink extends ApolloLink {
+ constructor() {
+ super();
+ }
+ override request(operation: Operation, forward) {
+ if (
+ operation.query.definitions[0].kind === Kind.OPERATION_DEFINITION &&
+ operation.query.definitions[0].operation === OperationTypeNode.MUTATION &&
+ operation.variables.input.id &&
+ !(
+ operation.operationName === 'undo' ||
+ operation.operationName === 'redo' ||
+ operation.operationName === 'layoutDiagram'
+ )
+ ) {
+ var storedUndoStack = sessionStorage.getItem('undoStack');
+ var undoStack = JSON.parse(storedUndoStack);
+
+ sessionStorage.setItem('undoStack', JSON.stringify([operation.variables.input.id, ...undoStack]));
+ sessionStorage.setItem('redoStack', JSON.stringify([]));
+ }
+
+ return forward(operation);
+ }
+}
diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx
index c8a90a90eb..a91238db46 100644
--- a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx
+++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/EditProjectView.tsx
@@ -42,6 +42,7 @@ import {
import { ProjectContext } from './ProjectContext';
import { NewDocumentModalContribution } from './TreeToolBarContributions/NewDocumentModalContribution';
import { UploadDocumentModalContribution } from './TreeToolBarContributions/UploadDocumentModalContribution';
+import { UndoRedo } from './UndoRedo';
import { useProjectAndRepresentationMetadata } from './useProjectAndRepresentationMetadata';
const useEditProjectViewStyles = makeStyles()((_) => ({
@@ -123,12 +124,14 @@ export const EditProjectView = () => {
-
+
+
+
diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/UndoRedo.tsx b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/UndoRedo.tsx
new file mode 100644
index 0000000000..64214a41d8
--- /dev/null
+++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/UndoRedo.tsx
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+
+import { gql, useMutation } from '@apollo/client';
+import { useEffect } from 'react';
+import { useParams } from 'react-router-dom';
+import { EditProjectViewParams } from './EditProjectView.types';
+import {
+ GQLRedoData,
+ GQLSuccessPayload,
+ GQLUndoData,
+ GQLUndoRedoInput,
+ GQLUndoRedoItemPayload,
+ GQLUndoVariables,
+} from './UndoRedo.types';
+
+const undoMutation = gql`
+ mutation undo($input: UndoRedoInput!) {
+ undo(input: $input) {
+ __typename
+ ... on ErrorPayload {
+ message
+ }
+ }
+ }
+`;
+
+const redoMutation = gql`
+ mutation redo($input: UndoRedoInput!) {
+ redo(input: $input) {
+ __typename
+ ... on ErrorPayload {
+ message
+ }
+ }
+ }
+`;
+
+const isSuccessPayload = (payload: GQLUndoRedoItemPayload): payload is GQLSuccessPayload =>
+ payload.__typename === 'SuccessPayload';
+
+export const UndoRedo = ({ children }: { children: React.ReactNode }) => {
+ const [undo, { data: undoData }] = useMutation(undoMutation);
+ const [redo, { data: redoData }] = useMutation(redoMutation);
+ const { projectId } = useParams();
+
+ useEffect(() => {
+ sessionStorage.setItem('undoStack', JSON.stringify([]));
+ sessionStorage.setItem('redoStack', JSON.stringify([]));
+ }, []);
+
+ const undoLastAction = () => {
+ var storedArray = sessionStorage.getItem('undoStack');
+ if (storedArray) {
+ var arr = JSON.parse(storedArray);
+ if (arr[0]) {
+ const input: GQLUndoRedoInput = {
+ id: crypto.randomUUID(),
+ editingContextId: projectId,
+ mutationId: arr[0],
+ };
+ undo({ variables: { input } });
+ }
+ }
+ };
+
+ const redoLastAction = () => {
+ var storedArray = sessionStorage.getItem('redoStack');
+ if (storedArray) {
+ var arr = JSON.parse(storedArray);
+ if (arr[0]) {
+ const input: GQLUndoRedoInput = {
+ id: crypto.randomUUID(),
+ editingContextId: projectId,
+ mutationId: arr[0],
+ };
+ redo({ variables: { input } });
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (undoData) {
+ const { undo } = undoData;
+ if (isSuccessPayload(undo)) {
+ var storedUndoStack = sessionStorage.getItem('undoStack');
+ var storedRedoStack = sessionStorage.getItem('redoStack');
+
+ //Remove first element of undo stack
+ var undoStack = JSON.parse(storedUndoStack);
+ var lastElement = undoStack.shift();
+ sessionStorage.setItem('undoStack', JSON.stringify(undoStack));
+
+ //Put the element in the 1st position of the redo stack
+ var redoStack = JSON.parse(storedRedoStack);
+ sessionStorage.setItem('redoStack', JSON.stringify([lastElement, ...redoStack]));
+ }
+ }
+ }, [undoData]);
+
+ useEffect(() => {
+ if (redoData) {
+ const { redo } = redoData;
+ if (isSuccessPayload(redo)) {
+ var storedUndoStack = sessionStorage.getItem('undoStack');
+ var storedRedoStack = sessionStorage.getItem('redoStack');
+
+ //Remove first element of redo stack
+ var redoStack = JSON.parse(storedRedoStack);
+ var lastElement = redoStack.shift();
+ sessionStorage.setItem('redoStack', JSON.stringify(redoStack));
+
+ //Put the element in the 1st position of the undo stack
+ var undoStack = JSON.parse(storedUndoStack);
+ sessionStorage.setItem('undoStack', JSON.stringify([lastElement, ...undoStack]));
+ }
+ }
+ }, [redoData]);
+
+ const undoKeyPressHandler = (e) => {
+ if (e.ctrlKey && e.key === 'z') {
+ undoLastAction();
+ }
+ };
+
+ const redoKeyPressHandler = (e) => {
+ if (e.ctrlKey && e.key === 'y') {
+ redoLastAction();
+ }
+ };
+
+ useEffect(() => {
+ window.addEventListener('keydown', undoKeyPressHandler);
+ return () => window.removeEventListener('keydown', undoKeyPressHandler);
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener('keydown', redoKeyPressHandler);
+ return () => window.removeEventListener('keydown', redoKeyPressHandler);
+ }, []);
+
+ return children;
+};
diff --git a/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/UndoRedo.types.ts b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/UndoRedo.types.ts
new file mode 100644
index 0000000000..6cf5bc7d30
--- /dev/null
+++ b/packages/sirius-web/frontend/sirius-web-application/src/views/edit-project/UndoRedo.types.ts
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * Copyright (c) 2024 Obeo.
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Obeo - initial API and implementation
+ *******************************************************************************/
+export interface GQLUndoVariables {
+ input: GQLUndoRedoInput;
+}
+
+export interface GQLUndoRedoInput {
+ id: string;
+ editingContextId: string;
+ mutationId: string;
+}
+export interface GQLUndoData {
+ undo: GQLUndoRedoItemPayload;
+}
+
+export interface GQLRedoData {
+ redo: GQLUndoRedoItemPayload;
+}
+
+export interface GQLUndoRedoItemPayload {
+ __typename: string;
+}
+
+export interface GQLSuccessPayload {
+ __typename: 'SuccessPayload';
+}