From 5026248b81167425278ff43ddf81c2aed2ccf854 Mon Sep 17 00:00:00 2001 From: Daniel Huber <30466471+daniel0611@users.noreply.github.com> Date: Sat, 21 Oct 2023 21:06:07 +0200 Subject: [PATCH] Refactor copy and paste into own feature --- src/common/di.config.ts | 10 +-- src/features/copyPaste/di.config.ts | 16 ++++ src/features/copyPaste/keyListener.ts | 53 ++++++++++++ .../copyPaste/pasteCommand.ts} | 81 +++---------------- src/index.ts | 2 + 5 files changed, 88 insertions(+), 74 deletions(-) create mode 100644 src/features/copyPaste/di.config.ts create mode 100644 src/features/copyPaste/keyListener.ts rename src/{common/copyPaste.ts => features/copyPaste/pasteCommand.ts} (69%) diff --git a/src/common/di.config.ts b/src/common/di.config.ts index 69476f0..6c5a463 100644 --- a/src/common/di.config.ts +++ b/src/common/di.config.ts @@ -16,13 +16,10 @@ import { DeleteKeyListener } from "./deleteKeyListener"; import { EDITOR_TYPES } from "../utils"; import { DynamicChildrenProcessor } from "../features/dfdElements/dynamicChildren"; import { FitToScreenKeyListener as CenterDiagramKeyListener } from "./fitToScreenKeyListener"; -import { CopyPasteFeature, PasteClipboardCommand } from "./copyPaste"; import "./commonStyling.css"; export const dfdCommonModule = new ContainerModule((bind, unbind, isBound, rebind) => { - const context = { bind, unbind, isBound, rebind }; - bind(ServerCommandPaletteActionProvider).toSelf().inSingletonScope(); bind(TYPES.ICommandPaletteActionProvider).toService(ServerCommandPaletteActionProvider); @@ -31,9 +28,6 @@ export const dfdCommonModule = new ContainerModule((bind, unbind, isBound, rebin bind(CenterDiagramKeyListener).toSelf().inSingletonScope(); rebind(CenterKeyboardListener).toService(CenterDiagramKeyListener); - bind(TYPES.KeyListener).to(CopyPasteFeature).inSingletonScope(); - configureCommand(context, PasteClipboardCommand); - bind(HelpUI).toSelf().inSingletonScope(); bind(TYPES.IUIExtension).toService(HelpUI); bind(EDITOR_TYPES.DefaultUIElement).toService(HelpUI); @@ -45,8 +39,12 @@ export const dfdCommonModule = new ContainerModule((bind, unbind, isBound, rebin bind(DynamicChildrenProcessor).toSelf().inSingletonScope(); // For some reason the CreateElementAction and Command exist but in no sprotty module is the command registered, so we need to do this here. + const context = { bind, unbind, isBound, rebind }; configureCommand(context, CreateElementCommand); + // Configure zoom limits + // Without these you could zoom in/out to infinity by accident resulting in your diagram being "gone". + // You can still get back to the diagram using the fit to screen action but these zoom limits prevents this from happening in the most cases. configureViewerOptions(context, { zoomLimits: { min: 0.05, max: 20 }, }); diff --git a/src/features/copyPaste/di.config.ts b/src/features/copyPaste/di.config.ts new file mode 100644 index 0000000..21fca86 --- /dev/null +++ b/src/features/copyPaste/di.config.ts @@ -0,0 +1,16 @@ +import { ContainerModule } from "inversify"; +import { TYPES, configureCommand } from "sprotty"; +import { CopyPasteKeyListener } from "./keyListener"; +import { PasteElementsCommand } from "./pasteCommand"; + +/** + * This feature allows the user to copy and paste elements. + * When ctrl+c is pressed, all selected elements are copied into an internal array. + * When ctrl+v is pressed, all elements in the internal array are pasted with an fixed offset. + * Nodes are copied with their ports and edges are copied if source and target were copied as well. + */ +export const copyPasteModule = new ContainerModule((bind, unbind, isBound, rebind) => { + const context = { bind, unbind, isBound, rebind }; + bind(TYPES.KeyListener).to(CopyPasteKeyListener).inSingletonScope(); + configureCommand(context, PasteElementsCommand); +}); diff --git a/src/features/copyPaste/keyListener.ts b/src/features/copyPaste/keyListener.ts new file mode 100644 index 0000000..0f1f1d1 --- /dev/null +++ b/src/features/copyPaste/keyListener.ts @@ -0,0 +1,53 @@ +import { injectable } from "inversify"; +import { PasteElementsAction } from "./pasteCommand"; +import { CommitModelAction, KeyListener, SModelElementImpl, SModelRootImpl, isSelected } from "sprotty"; +import { Action } from "sprotty-protocol"; +import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; + +/** + * This class is responsible for listening to ctrl+c and ctrl+v events. + * On copy the selected elements are copied into an internal array. + * On paste the {@link PasteElementsAction} is executed to paste the elements. + * This is done inside a command, so that it can be undone/redone. + */ +@injectable() +export class CopyPasteKeyListener implements KeyListener { + private copyElements: SModelElementImpl[] = []; + + keyUp(_element: SModelElementImpl, _event: KeyboardEvent): Action[] { + return []; + } + + keyDown(element: SModelElementImpl, event: KeyboardEvent): Action[] { + if (matchesKeystroke(event, "KeyC", "ctrl")) { + return this.copy(element.root); + } else if (matchesKeystroke(event, "KeyV", "ctrl")) { + return this.paste(); + } + + return []; + } + + /** + * Copy all selected elements into the "clipboard" (the internal element array) + */ + private copy(root: SModelRootImpl): Action[] { + this.copyElements = []; // Clear the clipboard + + // Find selected elements + root.index + .all() + .filter((element) => isSelected(element)) + .forEach((e) => this.copyElements.push(e)); + + return []; + } + + /** + * Pastes elements by creating new elements and copying the properties of the copied elements. + * This is done inside a command, so that it can be undone/redone. + */ + private paste(): Action[] { + return [PasteElementsAction.create(this.copyElements), CommitModelAction.create()]; + } +} diff --git a/src/common/copyPaste.ts b/src/features/copyPaste/pasteCommand.ts similarity index 69% rename from src/common/copyPaste.ts rename to src/features/copyPaste/pasteCommand.ts index 36f7d37..f66adb4 100644 --- a/src/common/copyPaste.ts +++ b/src/features/copyPaste/pasteCommand.ts @@ -1,81 +1,28 @@ +import { inject, injectable } from "inversify"; import { Command, CommandExecutionContext, CommandReturn, - CommitModelAction, - KeyListener, SChildElementImpl, SEdgeImpl, SModelElementImpl, - SModelRootImpl, SNodeImpl, TYPES, isSelectable, - isSelected, } from "sprotty"; +import { DynamicChildrenProcessor } from "../dfdElements/dynamicChildren"; +import { generateRandomSprottyId } from "../../utils"; +import { DfdNode, DfdNodeImpl } from "../dfdElements/nodes"; import { Action, SPort } from "sprotty-protocol"; -import { matchesKeystroke } from "sprotty/lib/utils/keyboard"; -import { generateRandomSprottyId } from "../utils"; -import { DynamicChildrenProcessor } from "../features/dfdElements/dynamicChildren"; -import { injectable, inject } from "inversify"; -import { DfdNode, DfdNodeImpl } from "../features/dfdElements/nodes"; -import { ArrowEdge, ArrowEdgeImpl } from "../features/dfdElements/edges"; +import { ArrowEdge, ArrowEdgeImpl } from "../dfdElements/edges"; -/** - * This feature allows the user to copy and paste elements. - * When ctrl+c is pressed, all selected elements are copied into an internal array. - * When ctrl+v is pressed, all elements in the internal array are pasted with an fixed offset. - * Nodes are copied with their ports and edges are copied if source and target were copied as well. - */ -@injectable() -export class CopyPasteFeature implements KeyListener { - private copyElements: SModelElementImpl[] = []; - - keyUp(_element: SModelElementImpl, _event: KeyboardEvent): Action[] { - return []; - } - - keyDown(element: SModelElementImpl, event: KeyboardEvent): Action[] { - if (matchesKeystroke(event, "KeyC", "ctrl")) { - return this.copy(element.root); - } else if (matchesKeystroke(event, "KeyV", "ctrl")) { - return this.paste(); - } - - return []; - } - - /** - * Copy all selected elements into the "clipboard" (the internal element array) - */ - private copy(root: SModelRootImpl): Action[] { - this.copyElements = []; // Clear the clipboard - - // Find selected elements - root.index - .all() - .filter((element) => isSelected(element)) - .forEach((e) => this.copyElements.push(e)); - - return []; - } - - /** - * Pastes elements by creating new elements and copying the properties of the copied elements. - * This is done inside a command, so that it can be undone/redone. - */ - private paste(): Action[] { - return [PasteClipboardAction.create(this.copyElements), CommitModelAction.create()]; - } -} - -interface PasteClipboardAction extends Action { - kind: typeof PasteClipboardAction.KIND; +export interface PasteElementsAction extends Action { + kind: typeof PasteElementsAction.KIND; copyElements: SModelElementImpl[]; } -export namespace PasteClipboardAction { - export const KIND = "paste-clipboard"; - export function create(copyElements: SModelElementImpl[]): PasteClipboardAction { +export namespace PasteElementsAction { + export const KIND = "paste-clipboard-elements"; + export function create(copyElements: SModelElementImpl[]): PasteElementsAction { return { kind: KIND, copyElements, @@ -89,8 +36,8 @@ export namespace PasteClipboardAction { * This is done inside a command, so that it can be undone/redone. */ @injectable() -export class PasteClipboardCommand extends Command { - public static readonly KIND = PasteClipboardAction.KIND; +export class PasteElementsCommand extends Command { + public static readonly KIND = PasteElementsAction.KIND; @inject(DynamicChildrenProcessor) private dynamicChildrenProcessor: DynamicChildrenProcessor = new DynamicChildrenProcessor(); @@ -99,7 +46,7 @@ export class PasteClipboardCommand extends Command { // id that the newly created copy target element has. private copyElementIdMapping: Record = {}; - constructor(@inject(TYPES.Action) private readonly action: PasteClipboardAction) { + constructor(@inject(TYPES.Action) private readonly action: PasteElementsAction) { super(); } @@ -176,8 +123,6 @@ export class PasteClipboardCommand extends Command { const newSourceId = this.copyElementIdMapping[element.sourceId]; const newTargetId = this.copyElementIdMapping[element.targetId]; - console.log("edge", newSourceId, newTargetId, element); - if (!newSourceId || !newTargetId) { // Not both source and target are copied, ignore this edge return; diff --git a/src/index.ts b/src/index.ts index 46ee40a..096e821 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ import { toolPaletteModule } from "./features/toolPalette/di.config"; import { serializeModule } from "./features/serialize/di.config"; import { LoadDefaultDiagramAction } from "./features/serialize/loadDefaultDiagram"; import { dfdElementsModule } from "./features/dfdElements/di.config"; +import { copyPasteModule } from "./features/copyPaste/di.config"; import { EDITOR_TYPES } from "./utils"; import "sprotty/css/sprotty.css"; @@ -82,6 +83,7 @@ container.load( serializeModule, dfdLabelModule, toolPaletteModule, + copyPasteModule, ); const dispatcher = container.get(TYPES.IActionDispatcher);