mirror of
https://github.com/OpenCut-app/OpenCut.git
synced 2026-07-13 21:52:53 +02:00
feat: define keybinding types and refactor keybinding configuration
This commit is contained in:
@@ -4,12 +4,7 @@ import { create } from "zustand";
|
|||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
import { ActionWithOptionalArgs } from "@/constants/actions";
|
import { ActionWithOptionalArgs } from "@/constants/actions";
|
||||||
import { isAppleDevice, isDOMElement, isTypableElement } from "@/lib/utils";
|
import { isAppleDevice, isDOMElement, isTypableElement } from "@/lib/utils";
|
||||||
|
import { KeybindingConfig, ShortcutKey } from "@/types/keybinding";
|
||||||
export type ShortcutKey = string;
|
|
||||||
|
|
||||||
export interface KeybindingConfig {
|
|
||||||
[key: ShortcutKey]: ActionWithOptionalArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default keybindings configuration
|
// Default keybindings configuration
|
||||||
export const defaultKeybindings: KeybindingConfig = {
|
export const defaultKeybindings: KeybindingConfig = {
|
||||||
@@ -19,23 +14,23 @@ export const defaultKeybindings: KeybindingConfig = {
|
|||||||
l: "seek-forward",
|
l: "seek-forward",
|
||||||
left: "frame-step-backward",
|
left: "frame-step-backward",
|
||||||
right: "frame-step-forward",
|
right: "frame-step-forward",
|
||||||
"shift-left": "jump-backward",
|
"shift+left": "jump-backward",
|
||||||
"shift-right": "jump-forward",
|
"shift+right": "jump-forward",
|
||||||
home: "goto-start",
|
home: "goto-start",
|
||||||
end: "goto-end",
|
end: "goto-end",
|
||||||
s: "split-element",
|
s: "split-element",
|
||||||
n: "toggle-snapping",
|
n: "toggle-snapping",
|
||||||
"ctrl-a": "select-all",
|
"ctrl+a": "select-all",
|
||||||
"ctrl-d": "duplicate-selected",
|
"ctrl+d": "duplicate-selected",
|
||||||
"ctrl-z": "undo",
|
"ctrl+z": "undo",
|
||||||
"ctrl-shift-z": "redo",
|
"ctrl+shift+z": "redo",
|
||||||
"ctrl-y": "redo",
|
"ctrl+y": "redo",
|
||||||
delete: "delete-selected",
|
delete: "delete-selected",
|
||||||
backspace: "delete-selected",
|
backspace: "delete-selected",
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface KeybindingConflict {
|
export interface KeybindingConflict {
|
||||||
key: string;
|
key: ShortcutKey;
|
||||||
existingAction: ActionWithOptionalArgs;
|
existingAction: ActionWithOptionalArgs;
|
||||||
newAction: ActionWithOptionalArgs;
|
newAction: ActionWithOptionalArgs;
|
||||||
}
|
}
|
||||||
@@ -46,8 +41,8 @@ interface KeybindingsState {
|
|||||||
keybindingsEnabled: boolean;
|
keybindingsEnabled: boolean;
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
updateKeybinding: (key: string, action: ActionWithOptionalArgs) => void;
|
updateKeybinding: (key: ShortcutKey, action: ActionWithOptionalArgs) => void;
|
||||||
removeKeybinding: (key: string) => void;
|
removeKeybinding: (key: ShortcutKey) => void;
|
||||||
resetToDefaults: () => void;
|
resetToDefaults: () => void;
|
||||||
importKeybindings: (config: KeybindingConfig) => void;
|
importKeybindings: (config: KeybindingConfig) => void;
|
||||||
exportKeybindings: () => KeybindingConfig;
|
exportKeybindings: () => KeybindingConfig;
|
||||||
@@ -56,13 +51,13 @@ interface KeybindingsState {
|
|||||||
|
|
||||||
// Validation
|
// Validation
|
||||||
validateKeybinding: (
|
validateKeybinding: (
|
||||||
key: string,
|
key: ShortcutKey,
|
||||||
action: ActionWithOptionalArgs
|
action: ActionWithOptionalArgs
|
||||||
) => KeybindingConflict | null;
|
) => KeybindingConflict | null;
|
||||||
getKeybindingsForAction: (action: ActionWithOptionalArgs) => string[];
|
getKeybindingsForAction: (action: ActionWithOptionalArgs) => ShortcutKey[];
|
||||||
|
|
||||||
// Utility
|
// Utility
|
||||||
getKeybindingString: (ev: KeyboardEvent) => string | null;
|
getKeybindingString: (ev: KeyboardEvent) => ShortcutKey | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useKeybindingsStore = create<KeybindingsState>()(
|
export const useKeybindingsStore = create<KeybindingsState>()(
|
||||||
@@ -72,7 +67,7 @@ export const useKeybindingsStore = create<KeybindingsState>()(
|
|||||||
isCustomized: false,
|
isCustomized: false,
|
||||||
keybindingsEnabled: true,
|
keybindingsEnabled: true,
|
||||||
|
|
||||||
updateKeybinding: (key: string, action: ActionWithOptionalArgs) => {
|
updateKeybinding: (key: ShortcutKey, action: ActionWithOptionalArgs) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newKeybindings = { ...state.keybindings };
|
const newKeybindings = { ...state.keybindings };
|
||||||
newKeybindings[key] = action;
|
newKeybindings[key] = action;
|
||||||
@@ -84,7 +79,7 @@ export const useKeybindingsStore = create<KeybindingsState>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
removeKeybinding: (key: string) => {
|
removeKeybinding: (key: ShortcutKey) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const newKeybindings = { ...state.keybindings };
|
const newKeybindings = { ...state.keybindings };
|
||||||
delete newKeybindings[key];
|
delete newKeybindings[key];
|
||||||
@@ -129,7 +124,10 @@ export const useKeybindingsStore = create<KeybindingsState>()(
|
|||||||
return get().keybindings;
|
return get().keybindings;
|
||||||
},
|
},
|
||||||
|
|
||||||
validateKeybinding: (key: string, action: ActionWithOptionalArgs) => {
|
validateKeybinding: (
|
||||||
|
key: ShortcutKey,
|
||||||
|
action: ActionWithOptionalArgs
|
||||||
|
) => {
|
||||||
const { keybindings } = get();
|
const { keybindings } = get();
|
||||||
const existingAction = keybindings[key];
|
const existingAction = keybindings[key];
|
||||||
|
|
||||||
@@ -147,12 +145,12 @@ export const useKeybindingsStore = create<KeybindingsState>()(
|
|||||||
getKeybindingsForAction: (action: ActionWithOptionalArgs) => {
|
getKeybindingsForAction: (action: ActionWithOptionalArgs) => {
|
||||||
const { keybindings } = get();
|
const { keybindings } = get();
|
||||||
return Object.keys(keybindings).filter(
|
return Object.keys(keybindings).filter(
|
||||||
(key) => keybindings[key] === action
|
(key) => keybindings[key as ShortcutKey] === action
|
||||||
);
|
) as ShortcutKey[];
|
||||||
},
|
},
|
||||||
|
|
||||||
getKeybindingString: (ev: KeyboardEvent) => {
|
getKeybindingString: (ev: KeyboardEvent) => {
|
||||||
return generateKeybindingString(ev);
|
return generateKeybindingString(ev) as ShortcutKey | null;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@@ -163,7 +161,7 @@ export const useKeybindingsStore = create<KeybindingsState>()(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Utility functions
|
// Utility functions
|
||||||
function generateKeybindingString(ev: KeyboardEvent): string | null {
|
function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
||||||
const target = ev.target;
|
const target = ev.target;
|
||||||
|
|
||||||
// We may or may not have a modifier key
|
// We may or may not have a modifier key
|
||||||
@@ -184,14 +182,14 @@ function generateKeybindingString(ev: KeyboardEvent): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${modifierKey}-${key}`;
|
return `${modifierKey}+${key}` as ShortcutKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
// no modifier key here then we do not do anything while on input
|
// no modifier key here then we do not do anything while on input
|
||||||
if (isDOMElement(target) && isTypableElement(target)) return null;
|
if (isDOMElement(target) && isTypableElement(target)) return null;
|
||||||
|
|
||||||
// single key while not input
|
// single key while not input
|
||||||
return `${key}`;
|
return `${key}` as ShortcutKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPressedKey(ev: KeyboardEvent): string | null {
|
function getPressedKey(ev: KeyboardEvent): string | null {
|
||||||
@@ -233,11 +231,11 @@ function getActiveModifier(ev: KeyboardEvent): string | null {
|
|||||||
shift: ev.shiftKey,
|
shift: ev.shiftKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
// active modifier: ctrl | alt | ctrl-alt | ctrl-shift | ctrl-alt-shift | alt-shift
|
// active modifier: ctrl | alt | ctrl+alt | ctrl+shift | ctrl+alt+shift | alt+shift
|
||||||
// modiferKeys object's keys are sorted to match the above order
|
// modiferKeys object's keys are sorted to match the above order
|
||||||
const activeModifier = Object.keys(modifierKeys)
|
const activeModifier = Object.keys(modifierKeys)
|
||||||
.filter((key) => modifierKeys[key as keyof typeof modifierKeys])
|
.filter((key) => modifierKeys[key as keyof typeof modifierKeys])
|
||||||
.join("-");
|
.join("+");
|
||||||
|
|
||||||
return activeModifier === "" ? null : activeModifier;
|
return activeModifier === "" ? null : activeModifier;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { ActionWithOptionalArgs } from "@/constants/actions";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alt is also regarded as macOS OPTION (⌥) key
|
||||||
|
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
||||||
|
*/
|
||||||
|
export type ModifierKeys =
|
||||||
|
| "ctrl"
|
||||||
|
| "alt"
|
||||||
|
| "shift"
|
||||||
|
| "ctrl+shift"
|
||||||
|
| "alt+shift"
|
||||||
|
| "ctrl+alt"
|
||||||
|
| "ctrl+alt+shift";
|
||||||
|
|
||||||
|
/* eslint-disable prettier/prettier */
|
||||||
|
// prettier-ignore
|
||||||
|
export type Key =
|
||||||
|
| "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j"
|
||||||
|
| "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t"
|
||||||
|
| "u" | "v" | "w" | "x" | "y" | "z" | "0" | "1" | "2" | "3"
|
||||||
|
| "4" | "5" | "6" | "7" | "8" | "9" | "up" | "down" | "left"
|
||||||
|
| "right" | "/" | "?" | "." | "enter" | "tab" | "space"
|
||||||
|
| "escape" | "esc" | "backspace" | "delete" | "home" | "end"
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
|
export type ModifierBasedShortcutKey = `${ModifierKeys}+${Key}`;
|
||||||
|
// Singular keybindings (these will be disabled when an input-ish area has been focused)
|
||||||
|
export type SingleCharacterShortcutKey = `${Key}`;
|
||||||
|
|
||||||
|
export type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey;
|
||||||
|
|
||||||
|
export type KeybindingConfig = {
|
||||||
|
[key in ShortcutKey]?: ActionWithOptionalArgs;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user