Skip to content

Commit 948afc9

Browse files
authored
Merge pull request #56 from DataFlowAnalysis/fix/delete-not-working
Fix delete key not working
2 parents 5156ac1 + 7850c4f commit 948afc9

File tree

5 files changed

+114
-0
lines changed

5 files changed

+114
-0
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {
2+
BringToFrontCommand,
3+
CenterCommand,
4+
CommandStack,
5+
FitToScreenCommand,
6+
HiddenCommand,
7+
ICommand,
8+
SelectCommand,
9+
SetViewportCommand,
10+
} from "sprotty";
11+
12+
/**
13+
* Custom command stack implementations that only pushes
14+
* commands that modify the diagram to the undo stack.
15+
* Commands like selections, viewport moves etc. are filtered out
16+
* and not pushed to the undo stack. Because of this they will not
17+
* be undone when the user presses Ctrl+Z.
18+
*
19+
* This is done because the commands like selections clutter up
20+
* the stack and the user has to undo many commands without
21+
* really knowing what they are undoing when the selections/viewport moves
22+
* are small.
23+
*/
24+
export class DiagramModificationCommandStack extends CommandStack {
25+
protected override isPushToUndoStack(command: ICommand): boolean {
26+
return !(
27+
command instanceof HiddenCommand ||
28+
command instanceof SelectCommand ||
29+
command instanceof SetViewportCommand ||
30+
command instanceof BringToFrontCommand ||
31+
command instanceof FitToScreenCommand ||
32+
command instanceof CenterCommand
33+
);
34+
}
35+
}

frontend/webEditor/src/commandPalette/di.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ import { ContainerModule } from "inversify";
22
import { CommandPalette, TYPES } from "sprotty";
33
import { WebEditorCommandPaletteActionProvider } from "./commandPaletteProvider";
44
import { WebEditorCommandPalette } from "./commandPalette";
5+
import { DiagramModificationCommandStack } from "./commandStack";
56

67
export const commandPaletteModule = new ContainerModule((bind, _, __, rebind) => {
78
rebind(CommandPalette).to(WebEditorCommandPalette).inSingletonScope();
89

910
bind(WebEditorCommandPaletteActionProvider).toSelf().inSingletonScope();
1011
bind(TYPES.ICommandPaletteActionProvider).toService(WebEditorCommandPaletteActionProvider);
12+
rebind(TYPES.ICommandStack).to(DiagramModificationCommandStack).inSingletonScope();
1113
});
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {
2+
CommitModelAction,
3+
KeyListener,
4+
SModelElementImpl,
5+
isDeletable,
6+
isSelectable,
7+
SConnectableElementImpl,
8+
SChildElementImpl,
9+
} from "sprotty";
10+
import { Action, DeleteElementAction } from "sprotty-protocol";
11+
import { matchesKeystroke } from "sprotty/lib/utils/keyboard";
12+
13+
/**
14+
* Custom sprotty key listener that deletes all selected elements when the user presses the delete key.
15+
*/
16+
export class DeleteKeyListener extends KeyListener {
17+
override keyDown(element: SModelElementImpl, event: KeyboardEvent): Action[] {
18+
if (matchesKeystroke(event, "Delete")) {
19+
return this.deleteSelectedElements(element);
20+
}
21+
return [];
22+
}
23+
24+
private deleteSelectedElements(element: SModelElementImpl): Action[] {
25+
const index = element.root.index;
26+
const selectedElements = Array.from(
27+
index
28+
.all()
29+
.filter((e) => isDeletable(e) && isSelectable(e) && e.selected)
30+
.filter((e) => e.id !== e.root.id), // Deleting the model root would be a bad idea
31+
);
32+
33+
const deleteElementIds = selectedElements.flatMap((e) => {
34+
const ids = [e.id];
35+
36+
if (e instanceof SConnectableElementImpl) {
37+
// This element can be connected to other elements, so we need to delete all edges connected to it as well.
38+
// Otherwise the edges would be left dangling in the graph.
39+
ids.push(...this.getEdgeIdsOfElement(e));
40+
}
41+
if (e instanceof SChildElementImpl) {
42+
// Add all children and their edges to the list of elements to delete
43+
// This is needed when the edges are not connected to the element itself but to a port of the element.
44+
e.children.forEach((child) => {
45+
ids.push(child.id);
46+
if (child instanceof SConnectableElementImpl) {
47+
ids.push(...this.getEdgeIdsOfElement(child));
48+
}
49+
});
50+
}
51+
52+
return ids;
53+
});
54+
55+
if (deleteElementIds.length > 0) {
56+
const uniqueIds = [...new Set(deleteElementIds)];
57+
58+
return [DeleteElementAction.create(uniqueIds), CommitModelAction.create()];
59+
} else {
60+
return [];
61+
}
62+
}
63+
64+
private getEdgeIdsOfElement(element: SConnectableElementImpl): string[] {
65+
return [...element.incomingEdges.map((e) => e.id), ...element.outgoingEdges.map((e) => e.id)];
66+
}
67+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { ContainerModule } from "inversify";
2+
import { DeleteKeyListener } from "./deleteKeyListener";
3+
import { TYPES } from "sprotty";
4+
5+
export const deleteKeyModule = new ContainerModule((bind) => {
6+
bind(DeleteKeyListener).toSelf().inSingletonScope();
7+
bind(TYPES.KeyListener).toService(DeleteKeyListener);
8+
});

frontend/webEditor/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { constraintModule } from "./constraint/di.config";
2525
import { assignmentModule } from "./assignment/di.config";
2626
import { editorModeOverwritesModule } from "./editModeOverwrites/di.config";
2727
import { loadingIndicatorModule } from "./loadingIndicator/di.config";
28+
import { deleteKeyModule } from "./deleteKey/di.config";
2829

2930
const container = new Container();
3031

@@ -53,6 +54,7 @@ container.load(
5354
assignmentModule,
5455
editorModeOverwritesModule,
5556
loadingIndicatorModule,
57+
deleteKeyModule,
5658
);
5759

5860
const startUpAgents = container.getAll<IStartUpAgent>(StartUpAgent);

0 commit comments

Comments
 (0)