From 926e7e2eb77518b9f54292676f1b7f97fb905daf Mon Sep 17 00:00:00 2001 From: Janne Savolainen Date: Mon, 3 Apr 2023 08:10:01 +0300 Subject: [PATCH] Introduce modifier for ctrl or command based on platform in use Signed-off-by: Janne Savolainen --- .../src/invoke-shortcut.injectable.ts | 57 ++++++--- .../src/keyboard-shortcut-injection-token.ts | 9 +- .../src/keyboard-shortcuts.test.tsx | 120 ++++++++++++++++++ .../src/platform.injectable.ts | 11 ++ 4 files changed, 179 insertions(+), 18 deletions(-) create mode 100644 packages/business-features/keyboard-shortcuts/src/platform.injectable.ts diff --git a/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts b/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts index 00c7734a4e..5b0c324203 100644 --- a/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts +++ b/packages/business-features/keyboard-shortcuts/src/invoke-shortcut.injectable.ts @@ -6,6 +6,7 @@ import { KeyboardShortcut, keyboardShortcutInjectionToken, } from "./keyboard-shortcut-injection-token"; +import platformInjectable from "./platform.injectable"; export type InvokeShortcut = (event: KeyboardEvent) => void; @@ -28,36 +29,58 @@ const toShortcutsWithMatchingScope = (shortcut: KeyboardShortcut) => { const toBindingWithDefaults = (binding: Binding) => isString(binding) - ? { code: binding, shift: false, ctrl: false, altOrOption: false, meta: false } - : { ctrl: false, shift: false, altOrOption: false, meta: false, ...binding }; + ? { + code: binding, + shift: false, + ctrl: false, + altOrOption: false, + meta: false, + ctrlOrCommand: false, + } + : { + ctrl: false, + shift: false, + altOrOption: false, + meta: false, + ctrlOrCommand: false, + ...binding, + }; -const toShortcutsWithMatchingBinding = (event: KeyboardEvent) => (shortcut: KeyboardShortcut) => { - const binding = toBindingWithDefaults(shortcut.binding); +const toShortcutsWithMatchingBinding = + (event: KeyboardEvent, platform: string) => (shortcut: KeyboardShortcut) => { + const binding = toBindingWithDefaults(shortcut.binding); - const shiftModifierMatches = binding.shift === event.shiftKey; - const ctrlModifierMatches = binding.ctrl === event.ctrlKey; - const altModifierMatches = binding.altOrOption === event.altKey; - const metaModifierMatches = binding.meta === event.metaKey; + const shiftModifierMatches = binding.shift === event.shiftKey; + const altModifierMatches = binding.altOrOption === event.altKey; - return ( - event.code === binding.code && - shiftModifierMatches && - ctrlModifierMatches && - altModifierMatches && - metaModifierMatches - ); -}; + const isMac = platform === "darwin"; + + const ctrlModifierMatches = + binding.ctrl === event.ctrlKey || (!isMac && binding.ctrlOrCommand === event.ctrlKey); + + const metaModifierMatches = + binding.meta === event.metaKey || (isMac && binding.ctrlOrCommand === event.metaKey); + + return ( + event.code === binding.code && + shiftModifierMatches && + ctrlModifierMatches && + altModifierMatches && + metaModifierMatches + ); + }; const invokeShortcutInjectable = getInjectable({ id: "invoke-shortcut", instantiate: (di): InvokeShortcut => { const getShortcuts = () => di.injectMany(keyboardShortcutInjectionToken); + const platform = di.inject(platformInjectable); return (event) => { const shortcutsToInvoke = pipeline( getShortcuts(), - filter(toShortcutsWithMatchingBinding(event)), + filter(toShortcutsWithMatchingBinding(event, platform)), filter(toShortcutsWithMatchingScope), ); diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts index 299775bbe3..637990b107 100644 --- a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-injection-token.ts @@ -2,7 +2,14 @@ import { getInjectionToken } from "@ogre-tools/injectable"; export type Binding = | string - | { code: string; shift?: boolean; ctrl?: boolean; altOrOption?: boolean; meta?: boolean }; + | { + code: string; + shift?: boolean; + ctrl?: boolean; + altOrOption?: boolean; + meta?: boolean; + ctrlOrCommand?: boolean; + }; export type KeyboardShortcut = { binding: Binding; diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx index 9706d18819..d2bbfb85a4 100644 --- a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcuts.test.tsx @@ -14,6 +14,7 @@ import { Discover, discoverFor } from "@k8slens/react-testing-library-discovery" import { startApplicationInjectionToken } from "@k8slens/application"; import { renderInjectionToken } from "@k8slens/react-application"; import { reactApplicationChildrenInjectionToken } from "@k8slens/react-application"; +import platformInjectable from "./platform.injectable"; describe("keyboard-shortcuts", () => { let di: DiContainer; @@ -203,6 +204,8 @@ describe("keyboard-shortcuts", () => { ].forEach(({ binding, keyboard, scenario, shouldCallCallback }) => { // eslint-disable-next-line jest/valid-title it(scenario, () => { + const invokeMock = jest.fn(); + const shortcutInjectable = getInjectable({ id: "shortcut", @@ -230,4 +233,121 @@ describe("keyboard-shortcuts", () => { }); }); }); + + describe("given in mac and keyboard shortcut with modifier for ctrl or command", () => { + beforeEach(async () => { + di.override(platformInjectable, () => "darwin"); + + invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding: { code: "KeyK", ctrlOrCommand: true }, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + }); + + it("when pressing the keyboard shortcut with command, calls the callback", () => { + userEvent.keyboard("{Meta>}[KeyK]"); + + expect(invokeMock).toHaveBeenCalled(); + }); + + it("when pressing the keyboard shortcut with ctrl, does not call the callback", () => { + userEvent.keyboard("{Control>}[KeyK]"); + + expect(invokeMock).not.toHaveBeenCalled(); + }); + }); + + describe("given in windows and keyboard shortcut with modifier for ctrl or command", () => { + beforeEach(async () => { + di.override(platformInjectable, () => "win32"); + + invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding: { code: "KeyK", ctrlOrCommand: true }, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + }); + + it("when pressing the keyboard shortcut with windows, does not call the callback", () => { + userEvent.keyboard("{Meta>}[KeyK]"); + + expect(invokeMock).not.toHaveBeenCalled(); + }); + + it("when pressing the keyboard shortcut with ctrl, calls the callback", () => { + userEvent.keyboard("{Control>}[KeyK]"); + + expect(invokeMock).toHaveBeenCalled(); + }); + }); + + describe("given in any other platform and keyboard shortcut with modifier for ctrl or command", () => { + beforeEach(async () => { + di.override(platformInjectable, () => "some-other-platform"); + + invokeMock = jest.fn(); + + const shortcutInjectable = getInjectable({ + id: "shortcut", + + instantiate: () => ({ + binding: { code: "KeyK", ctrlOrCommand: true }, + invoke: invokeMock, + }), + + injectionToken: keyboardShortcutInjectionToken, + }); + + runInAction(() => { + di.register(shortcutInjectable); + }); + + const startApplication = di.inject(startApplicationInjectionToken); + + await startApplication(); + }); + + it("when pressing the keyboard shortcut with meta, does not call the callback", () => { + userEvent.keyboard("{Meta>}[KeyK]"); + + expect(invokeMock).not.toHaveBeenCalled(); + }); + + it("when pressing the keyboard shortcut with ctrl, calls the callback", () => { + userEvent.keyboard("{Control>}[KeyK]"); + + expect(invokeMock).toHaveBeenCalled(); + }); + }); }); diff --git a/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts b/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts new file mode 100644 index 0000000000..407af8a43d --- /dev/null +++ b/packages/business-features/keyboard-shortcuts/src/platform.injectable.ts @@ -0,0 +1,11 @@ +import { getInjectable } from "@ogre-tools/injectable"; + +export const allPlatforms = ["win32", "darwin", "linux"] as const; + +const platformInjectable = getInjectable({ + id: "platform", + instantiate: () => process.platform as (typeof allPlatforms)[number], + causesSideEffects: true, +}); + +export default platformInjectable;