Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions frontend/webEditor/src/commandPalette/commandStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {
BringToFrontCommand,
CenterCommand,
CommandStack,
FitToScreenCommand,
HiddenCommand,
ICommand,
SelectCommand,
SetViewportCommand,
} from "sprotty";

/**
* Custom command stack implementations that only pushes
* commands that modify the diagram to the undo stack.
* Commands like selections, viewport moves etc. are filtered out
* and not pushed to the undo stack. Because of this they will not
* be undone when the user presses Ctrl+Z.
*
* This is done because the commands like selections clutter up
* the stack and the user has to undo many commands without
* really knowing what they are undoing when the selections/viewport moves
* are small.
*/
export class DiagramModificationCommandStack extends CommandStack {
protected override isPushToUndoStack(command: ICommand): boolean {
return !(
command instanceof HiddenCommand ||
command instanceof SelectCommand ||
command instanceof SetViewportCommand ||
command instanceof BringToFrontCommand ||
command instanceof FitToScreenCommand ||
command instanceof CenterCommand
);
}
}
2 changes: 2 additions & 0 deletions frontend/webEditor/src/commandPalette/di.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import { ContainerModule } from "inversify";
import { CommandPalette, TYPES } from "sprotty";
import { WebEditorCommandPaletteActionProvider } from "./commandPaletteProvider";
import { WebEditorCommandPalette } from "./commandPalette";
import { DiagramModificationCommandStack } from "./commandStack";

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

bind(WebEditorCommandPaletteActionProvider).toSelf().inSingletonScope();
bind(TYPES.ICommandPaletteActionProvider).toService(WebEditorCommandPaletteActionProvider);
rebind(TYPES.ICommandStack).to(DiagramModificationCommandStack).inSingletonScope();
});
67 changes: 67 additions & 0 deletions frontend/webEditor/src/deleteKey/deleteKeyListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
CommitModelAction,
KeyListener,
SModelElementImpl,
isDeletable,
isSelectable,
SConnectableElementImpl,
SChildElementImpl,
} from "sprotty";
import { Action, DeleteElementAction } from "sprotty-protocol";
import { matchesKeystroke } from "sprotty/lib/utils/keyboard";

/**
* Custom sprotty key listener that deletes all selected elements when the user presses the delete key.
*/
export class DeleteKeyListener extends KeyListener {
override keyDown(element: SModelElementImpl, event: KeyboardEvent): Action[] {
if (matchesKeystroke(event, "Delete")) {
return this.deleteSelectedElements(element);
}
return [];
}

private deleteSelectedElements(element: SModelElementImpl): Action[] {
const index = element.root.index;
const selectedElements = Array.from(
index
.all()
.filter((e) => isDeletable(e) && isSelectable(e) && e.selected)
.filter((e) => e.id !== e.root.id), // Deleting the model root would be a bad idea
);

const deleteElementIds = selectedElements.flatMap((e) => {
const ids = [e.id];

if (e instanceof SConnectableElementImpl) {
// This element can be connected to other elements, so we need to delete all edges connected to it as well.
// Otherwise the edges would be left dangling in the graph.
ids.push(...this.getEdgeIdsOfElement(e));
}
if (e instanceof SChildElementImpl) {
// Add all children and their edges to the list of elements to delete
// This is needed when the edges are not connected to the element itself but to a port of the element.
e.children.forEach((child) => {
ids.push(child.id);
if (child instanceof SConnectableElementImpl) {
ids.push(...this.getEdgeIdsOfElement(child));
}
});
}

return ids;
});

if (deleteElementIds.length > 0) {
const uniqueIds = [...new Set(deleteElementIds)];

return [DeleteElementAction.create(uniqueIds), CommitModelAction.create()];
} else {
return [];
}
}

private getEdgeIdsOfElement(element: SConnectableElementImpl): string[] {
return [...element.incomingEdges.map((e) => e.id), ...element.outgoingEdges.map((e) => e.id)];
}
}
8 changes: 8 additions & 0 deletions frontend/webEditor/src/deleteKey/di.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ContainerModule } from "inversify";
import { DeleteKeyListener } from "./deleteKeyListener";
import { TYPES } from "sprotty";

export const deleteKeyModule = new ContainerModule((bind) => {
bind(DeleteKeyListener).toSelf().inSingletonScope();
bind(TYPES.KeyListener).toService(DeleteKeyListener);
});
2 changes: 2 additions & 0 deletions frontend/webEditor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { constraintModule } from "./constraint/di.config";
import { assignmentModule } from "./assignment/di.config";
import { editorModeOverwritesModule } from "./editModeOverwrites/di.config";
import { loadingIndicatorModule } from "./loadingIndicator/di.config";
import { deleteKeyModule } from "./deleteKey/di.config";

const container = new Container();

Expand Down Expand Up @@ -53,6 +54,7 @@ container.load(
assignmentModule,
editorModeOverwritesModule,
loadingIndicatorModule,
deleteKeyModule,
);

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