Skip to content

Commit 866a326

Browse files
committed
[5651] Add support for undo redo on label appearance and label layout data
Bug: #5651 Signed-off-by: Michaël Charfadi <michael.charfadi@obeosoft.com>
1 parent 866621d commit 866a326

File tree

18 files changed

+1001
-24
lines changed

18 files changed

+1001
-24
lines changed

CHANGELOG.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
=== New Features
3434

3535
- https://github.com/eclipse-sirius/sirius-web/issues/5300[#5300] [diagram] Add undo redo for node layout
36-
36+
- https://github.com/eclipse-sirius/sirius-web/issues/5651[#5651] [diagram] Add undo redo for label layout and appearance
3737

3838
=== Improvements
3939

packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/DiagramCreationService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.eclipse.sirius.components.diagrams.components.DiagramComponentProps.Builder;
3636
import org.eclipse.sirius.components.diagrams.description.DiagramDescription;
3737
import org.eclipse.sirius.components.diagrams.events.IDiagramEvent;
38+
import org.eclipse.sirius.components.diagrams.events.undoredo.DiagramLabelLayoutEvent;
3839
import org.eclipse.sirius.components.diagrams.events.undoredo.DiagramNodeLayoutEvent;
3940
import org.eclipse.sirius.components.diagrams.layoutdata.DiagramLayoutData;
4041
import org.eclipse.sirius.components.diagrams.renderer.DiagramRenderer;
@@ -154,9 +155,14 @@ private Diagram doRender(Object targetObject, IEditingContext editingContext, Di
154155
.filter(DiagramNodeLayoutEvent.class::isInstance)
155156
.map(DiagramNodeLayoutEvent.class::cast).toList();
156157

158+
List<DiagramLabelLayoutEvent> diagramLabelLayoutEvents = diagramEvents.stream()
159+
.filter(DiagramLabelLayoutEvent.class::isInstance)
160+
.map(DiagramLabelLayoutEvent.class::cast).toList();
161+
157162
var newLayoutData = optionalPreviousDiagram.map(Diagram::getLayoutData).orElse(new DiagramLayoutData(Map.of(), Map.of(), Map.of()));
158163

159164
diagramNodeLayoutEvents.forEach(nodeLayoutDataEvent -> newLayoutData.nodeLayoutData().put(nodeLayoutDataEvent.nodeId(), nodeLayoutDataEvent.nodeLayoutData()));
165+
diagramLabelLayoutEvents.forEach(nodeLabelDataEvent -> newLayoutData.labelLayoutData().put(nodeLabelDataEvent.nodeId(), nodeLabelDataEvent.labelLayoutData()));
160166

161167
newDiagram = Diagram.newDiagram(newDiagram)
162168
.layoutData(newLayoutData)

packages/diagrams/backend/sirius-components-collaborative-diagrams/src/main/java/org/eclipse/sirius/components/collaborative/diagrams/DiagramQueryService.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,9 @@ public Optional<Node> findNodeById(Diagram diagram, String nodeId) {
4040
@Override
4141
public Optional<Node> findNodeByLabelId(Diagram diagram, String labelId) {
4242
return this.findNode(node -> {
43-
if (node.getInsideLabel() != null) {
44-
return Objects.equals(node.getInsideLabel().getId(), labelId);
45-
}
46-
return node.getOutsideLabels().stream().anyMatch(label -> Objects.equals(label.id(), labelId));
43+
boolean isCandidateNode = node.getInsideLabel() != null && Objects.equals(node.getInsideLabel().getId(), labelId);
44+
isCandidateNode = isCandidateNode || node.getOutsideLabels().stream().anyMatch(label -> Objects.equals(label.id(), labelId));
45+
return isCandidateNode;
4746
}, diagram.getNodes());
4847
}
4948

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.sirius.components.diagrams.events.undoredo;
14+
15+
import org.eclipse.sirius.components.diagrams.events.IDiagramEvent;
16+
import org.eclipse.sirius.components.diagrams.layoutdata.LabelLayoutData;
17+
18+
/**
19+
* Diagram label layout position change.
20+
*
21+
* @author mcharfadi
22+
*/
23+
public record DiagramLabelLayoutEvent(String nodeId, LabelLayoutData labelLayoutData) implements IDiagramEvent {
24+
}

packages/diagrams/frontend/sirius-components-diagrams/src/renderer/move/useLabelMove.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,23 @@
1313
import { Edge, Node, useReactFlow } from '@xyflow/react';
1414
import { useCallback } from 'react';
1515
import { DraggableData } from 'react-draggable';
16-
import { UseLabelMoveValue } from './useLabelMove.types';
17-
import { OutsideLabel, NodeData, EdgeData } from '../DiagramRenderer.types';
18-
import { useSynchronizeLayoutData } from '../layout/useSynchronizeLayoutData';
19-
import { RawDiagram } from '../layout/layout.types';
16+
import { EdgeData, NodeData, OutsideLabel } from '../DiagramRenderer.types';
2017
import { MultiLabelEdgeData } from '../edge/MultiLabelEdge.types';
18+
import { RawDiagram } from '../layout/layout.types';
19+
import { useSynchronizeLayoutData } from '../layout/useSynchronizeLayoutData';
20+
import { UseLabelMoveValue } from './useLabelMove.types';
21+
22+
const addUndoForLayout = (mutationId: string) => {
23+
var storedUndoStack = sessionStorage.getItem('undoStack');
24+
var storedRedoStack = sessionStorage.getItem('redoStack');
25+
26+
if (storedUndoStack && storedRedoStack) {
27+
var undoStack: String[] = JSON.parse(storedUndoStack);
28+
if (!undoStack.find((id) => id === mutationId)) {
29+
sessionStorage.setItem('undoStack', JSON.stringify([mutationId, ...undoStack]));
30+
}
31+
}
32+
};
2133

2234
export const useLabelMove = (): UseLabelMoveValue => {
2335
const { getNodes, getEdges } = useReactFlow<Node<NodeData>, Edge<EdgeData>>();
@@ -37,7 +49,10 @@ export const useLabelMove = (): UseLabelMoveValue => {
3749
nodes: nodes,
3850
edges: getEdges(),
3951
};
40-
synchronizeLayoutData(crypto.randomUUID(), 'layout', finalDiagram);
52+
53+
var id = crypto.randomUUID();
54+
synchronizeLayoutData(id, 'layout', finalDiagram);
55+
addUndoForLayout(id);
4156
},
4257
[getNodes, synchronizeLayoutData]
4358
);
@@ -67,7 +82,10 @@ export const useLabelMove = (): UseLabelMoveValue => {
6782
nodes: [...getNodes()],
6883
edges: edges,
6984
};
70-
synchronizeLayoutData(crypto.randomUUID(), 'layout', finalDiagram);
85+
86+
var id = crypto.randomUUID();
87+
synchronizeLayoutData(id, 'layout', finalDiagram);
88+
addUndoForLayout(id);
7189
},
7290
[getEdges, synchronizeLayoutData]
7391
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.sirius.web.application.undo.services.api;
14+
15+
import org.eclipse.sirius.components.diagrams.Edge;
16+
import org.eclipse.sirius.components.diagrams.Node;
17+
import org.eclipse.sirius.components.diagrams.events.appearance.IAppearanceChange;
18+
import org.eclipse.sirius.components.diagrams.events.appearance.label.ILabelAppearanceChange;
19+
20+
import java.util.List;
21+
import java.util.Optional;
22+
23+
/**
24+
* Use to compute undo label appearance change.
25+
*
26+
* @author mcharfadi
27+
*/
28+
public interface ILabelAppearanceChangeUndoRecorder {
29+
30+
List<IAppearanceChange> computeUndoNodeLabelAppearanceChanges(Node previousNode, String labelId, Optional<ILabelAppearanceChange> change);
31+
32+
List<IAppearanceChange> computeUndoEdgeLabelAppearanceChanges(Edge previousEdge, String labelId, Optional<ILabelAppearanceChange> change);
33+
}

packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/services/api/INodeAppearanceChangeUndoRecorder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import java.util.Optional;
2121

2222
/**
23-
* Use to compute undo appearance change.
23+
* Use to compute undo node appearance change.
2424
*
2525
* @author mcharfadi
2626
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.sirius.web.application.undo.services.changes;
14+
15+
import org.eclipse.sirius.components.diagrams.events.appearance.IAppearanceChange;
16+
17+
import java.util.List;
18+
import java.util.UUID;
19+
20+
/**
21+
* Used to record changes made to the appearance of a diagram label.
22+
*
23+
* @author mcharfadi
24+
*/
25+
public record DiagramLabelAppearanceChange(UUID inputId, String representationId, List<IAppearanceChange> undoChanges, List<IAppearanceChange> redoChanges) implements IDiagramChange {
26+
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.sirius.web.application.undo.services.changes;
14+
15+
import org.eclipse.sirius.components.diagrams.events.undoredo.DiagramLabelLayoutEvent;
16+
17+
import java.util.List;
18+
import java.util.UUID;
19+
20+
/**
21+
* Used to record changes for node layout.
22+
*
23+
* @author mcharfadi
24+
*/
25+
public record DiagramLabelLayoutChange(UUID inputId, String representationId, List<DiagramLabelLayoutEvent> undoChanges, List<DiagramLabelLayoutEvent> redoChanges) implements IDiagramChange {
26+
}

packages/sirius-web/backend/sirius-web-application/src/main/java/org/eclipse/sirius/web/application/undo/services/handler/DiagramNodeLayoutChangeHandler.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
import org.eclipse.sirius.components.collaborative.diagrams.DiagramEventProcessor;
1616
import org.eclipse.sirius.components.collaborative.representations.api.IRepresentationEventProcessorRegistry;
1717
import org.eclipse.sirius.components.core.api.IEditingContext;
18+
import org.eclipse.sirius.components.diagrams.events.undoredo.DiagramLabelLayoutEvent;
1819
import org.eclipse.sirius.components.diagrams.events.undoredo.DiagramNodeLayoutEvent;
1920
import org.eclipse.sirius.web.application.editingcontext.EditingContext;
2021
import org.eclipse.sirius.web.application.undo.services.api.IRepresentationChangeHandler;
22+
import org.eclipse.sirius.web.application.undo.services.changes.DiagramLabelLayoutChange;
2123
import org.eclipse.sirius.web.application.undo.services.changes.DiagramNodeLayoutChange;
2224
import org.springframework.stereotype.Service;
2325

@@ -42,7 +44,7 @@ public DiagramNodeLayoutChangeHandler(IRepresentationEventProcessorRegistry repr
4244
public boolean canHandle(UUID inputId, IEditingContext editingContext) {
4345
return editingContext instanceof EditingContext siriusEditingContext && siriusEditingContext.getInputId2RepresentationChanges().get(inputId) != null &&
4446
siriusEditingContext.getInputId2RepresentationChanges().get(inputId).stream()
45-
.anyMatch(DiagramNodeLayoutChange.class::isInstance);
47+
.anyMatch(change -> change instanceof DiagramNodeLayoutChange || change instanceof DiagramLabelLayoutChange);
4648
}
4749

4850
@Override
@@ -59,6 +61,18 @@ public void redo(UUID inputId, IEditingContext editingContext) {
5961
});
6062
}
6163
});
64+
65+
siriusEditingContext.getInputId2RepresentationChanges().get(inputId).stream().filter(DiagramLabelLayoutChange.class::isInstance)
66+
.map(DiagramLabelLayoutChange.class::cast)
67+
.forEach(change -> {
68+
var representationEventProcessorEntry = this.representationEventProcessorRegistry.get(editingContext.getId(), change.representationId());
69+
if (representationEventProcessorEntry != null && representationEventProcessorEntry.getRepresentationEventProcessor() instanceof DiagramEventProcessor eventProcessor) {
70+
var diagramContext = eventProcessor.getDiagramContext();
71+
change.redoChanges().forEach(labelLayoutChange -> {
72+
diagramContext.diagramEvents().add(new DiagramLabelLayoutEvent(labelLayoutChange.nodeId(), labelLayoutChange.labelLayoutData()));
73+
});
74+
}
75+
});
6276
}
6377
}
6478

@@ -76,6 +90,19 @@ public void undo(UUID inputId, IEditingContext editingContext) {
7690
});
7791
}
7892
});
93+
94+
siriusEditingContext.getInputId2RepresentationChanges().get(inputId).stream().filter(DiagramLabelLayoutChange.class::isInstance)
95+
.map(DiagramLabelLayoutChange.class::cast)
96+
.forEach(change -> {
97+
var representationEventProcessorEntry = this.representationEventProcessorRegistry.get(editingContext.getId(), change.representationId());
98+
if (representationEventProcessorEntry != null && representationEventProcessorEntry.getRepresentationEventProcessor() instanceof DiagramEventProcessor eventProcessor) {
99+
var diagramContext = eventProcessor.getDiagramContext();
100+
change.undoChanges().forEach(labelLayoutChange -> {
101+
diagramContext.diagramEvents().add(new DiagramLabelLayoutEvent(labelLayoutChange.nodeId(), labelLayoutChange.labelLayoutData()));
102+
});
103+
}
104+
});
79105
}
106+
80107
}
81108
}

0 commit comments

Comments
 (0)