mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-17 17:04:16 +02:00
add physical key mapping for useHotkeys to support alt keyboard languages (#2051)
This commit is contained in:
@@ -18,6 +18,10 @@ import { Checkbox } from '/@/shared/components/checkbox/checkbox';
|
|||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
import { Table } from '/@/shared/components/table/table';
|
import { Table } from '/@/shared/components/table/table';
|
||||||
import { TextInput } from '/@/shared/components/text-input/text-input';
|
import { TextInput } from '/@/shared/components/text-input/text-input';
|
||||||
|
import {
|
||||||
|
keyboardCodeToHotkeyKey,
|
||||||
|
MODIFIER_KEY_CODES,
|
||||||
|
} from '/@/shared/utils/keyboard-code-to-hotkey';
|
||||||
|
|
||||||
const ipc = isElectron() ? window.api.ipc : null;
|
const ipc = isElectron() ? window.api.ipc : null;
|
||||||
|
|
||||||
@@ -112,25 +116,16 @@ export const HotkeyManagerSettings = memo(() => {
|
|||||||
const debouncedSetHotkey = debounce(
|
const debouncedSetHotkey = debounce(
|
||||||
(binding: BindingActions, e: KeyboardEvent<HTMLInputElement>) => {
|
(binding: BindingActions, e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const IGNORED_KEYS = ['Control', 'Alt', 'Shift', 'Meta', ' ', 'Escape'];
|
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
if (e.ctrlKey) keys.push('mod');
|
if (e.ctrlKey) keys.push('mod');
|
||||||
if (e.altKey) keys.push('alt');
|
if (e.altKey) keys.push('alt');
|
||||||
if (e.shiftKey) keys.push('shift');
|
if (e.shiftKey) keys.push('shift');
|
||||||
if (e.metaKey) keys.push('meta');
|
if (e.metaKey) keys.push('meta');
|
||||||
if (e.key === ' ') keys.push('space');
|
|
||||||
if (!IGNORED_KEYS.includes(e.key)) {
|
if (!MODIFIER_KEY_CODES.has(e.code) && e.code !== 'Escape') {
|
||||||
if (e.code.includes('Numpad')) {
|
const hotkeyKey = keyboardCodeToHotkeyKey(e.code);
|
||||||
if (e.key === '+') keys.push('numpadadd');
|
if (hotkeyKey) {
|
||||||
else if (e.key === '-') keys.push('numpadsubtract');
|
keys.push(hotkeyKey);
|
||||||
else if (e.key === '*') keys.push('numpadmultiply');
|
|
||||||
else if (e.key === '/') keys.push('numpaddivide');
|
|
||||||
else if (e.key === '.') keys.push('numpaddecimal');
|
|
||||||
else keys.push(`numpad${e.key}`.toLowerCase());
|
|
||||||
} else if (e.key === '+') {
|
|
||||||
keys.push('equal');
|
|
||||||
} else {
|
|
||||||
keys.push(e.key?.toLowerCase());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ import {
|
|||||||
type HotkeyItem as MantineHotkeyItem,
|
type HotkeyItem as MantineHotkeyItem,
|
||||||
useHotkeys as useMantineHotkeys,
|
useHotkeys as useMantineHotkeys,
|
||||||
} from '@mantine/hooks';
|
} from '@mantine/hooks';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
import { useAppStore } from '/@/renderer/store';
|
import { useAppStore } from '/@/renderer/store';
|
||||||
|
import { withPhysicalKeys } from '/@/shared/utils/hotkeys';
|
||||||
|
|
||||||
const EMPTY_HOTKEYS: MantineHotkeyItem[] = [];
|
const EMPTY_HOTKEYS: MantineHotkeyItem[] = [];
|
||||||
|
|
||||||
@@ -13,8 +15,10 @@ export const useHotkeys = (
|
|||||||
triggerOnContentEditable?: boolean,
|
triggerOnContentEditable?: boolean,
|
||||||
) => {
|
) => {
|
||||||
const commandPaletteOpened = useAppStore((state) => state.commandPalette.opened);
|
const commandPaletteOpened = useAppStore((state) => state.commandPalette.opened);
|
||||||
|
const physicalHotkeys = useMemo(() => withPhysicalKeys(hotkeys), [hotkeys]);
|
||||||
|
|
||||||
useMantineHotkeys(
|
useMantineHotkeys(
|
||||||
commandPaletteOpened ? EMPTY_HOTKEYS : hotkeys,
|
commandPaletteOpened ? EMPTY_HOTKEYS : physicalHotkeys,
|
||||||
tagsToIgnore,
|
tagsToIgnore,
|
||||||
triggerOnContentEditable,
|
triggerOnContentEditable,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,7 +2,17 @@ import {
|
|||||||
type HotkeyItem as MantineHotkeyItem,
|
type HotkeyItem as MantineHotkeyItem,
|
||||||
useHotkeys as useMantineHotkeys,
|
useHotkeys as useMantineHotkeys,
|
||||||
} from '@mantine/hooks';
|
} from '@mantine/hooks';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
export const useHotkeys = useMantineHotkeys;
|
import { withPhysicalKeys } from '/@/shared/utils/hotkeys';
|
||||||
|
|
||||||
|
export const useHotkeys = (
|
||||||
|
hotkeys: MantineHotkeyItem[],
|
||||||
|
tagsToIgnore?: string[],
|
||||||
|
triggerOnContentEditable?: boolean,
|
||||||
|
) => {
|
||||||
|
const physicalHotkeys = useMemo(() => withPhysicalKeys(hotkeys), [hotkeys]);
|
||||||
|
useMantineHotkeys(physicalHotkeys, tagsToIgnore, triggerOnContentEditable);
|
||||||
|
};
|
||||||
|
|
||||||
export type HotkeyItem = MantineHotkeyItem;
|
export type HotkeyItem = MantineHotkeyItem;
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import type { HotkeyItem } from '@mantine/hooks';
|
||||||
|
|
||||||
|
const RESERVED_KEYS = new Set(['alt', 'ctrl', 'meta', 'mod', 'shift']);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts stored hotkey strings to Mantine's physical-key format.
|
||||||
|
* Mantine matches KeyboardEvent.code via normalizeKey, which turns Digit1 into
|
||||||
|
* "digit1" but leaves "1" as "1" — so mod+1 must become mod+Digit1.
|
||||||
|
*/
|
||||||
|
export const toPhysicalHotkey = (hotkey: string): string =>
|
||||||
|
hotkey
|
||||||
|
.split('+')
|
||||||
|
.map((part) => part.trim())
|
||||||
|
.map((part) => {
|
||||||
|
if (part === '[plus]') {
|
||||||
|
return part;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = part.toLowerCase();
|
||||||
|
if (RESERVED_KEYS.has(lower)) {
|
||||||
|
return lower;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d$/.test(part)) {
|
||||||
|
return `Digit${part}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return part;
|
||||||
|
})
|
||||||
|
.join('+');
|
||||||
|
|
||||||
|
export const withPhysicalKeys = (hotkeys: HotkeyItem[]): HotkeyItem[] =>
|
||||||
|
hotkeys.map(([hotkey, handler, options]) => [
|
||||||
|
toPhysicalHotkey(hotkey),
|
||||||
|
handler,
|
||||||
|
{ ...options, usePhysicalKeys: true },
|
||||||
|
]);
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
const CODE_TO_HOTKEY_KEY: Record<string, string> = {
|
||||||
|
ArrowDown: 'arrowdown',
|
||||||
|
ArrowLeft: 'arrowleft',
|
||||||
|
ArrowRight: 'arrowright',
|
||||||
|
ArrowUp: 'arrowup',
|
||||||
|
Backspace: 'backspace',
|
||||||
|
Delete: 'delete',
|
||||||
|
End: 'end',
|
||||||
|
Enter: 'enter',
|
||||||
|
Equal: 'equal',
|
||||||
|
Escape: 'escape',
|
||||||
|
Home: 'home',
|
||||||
|
Insert: 'insert',
|
||||||
|
Minus: 'minus',
|
||||||
|
PageDown: 'pagedown',
|
||||||
|
PageUp: 'pageup',
|
||||||
|
Space: 'space',
|
||||||
|
Tab: 'tab',
|
||||||
|
};
|
||||||
|
|
||||||
|
const NUMPAD_CODE_TO_HOTKEY_KEY: Record<string, string> = {
|
||||||
|
Add: 'numpadadd',
|
||||||
|
Decimal: 'numpaddecimal',
|
||||||
|
Divide: 'numpaddivide',
|
||||||
|
Enter: 'numpadenter',
|
||||||
|
Multiply: 'numpadmultiply',
|
||||||
|
Subtract: 'numpadsubtract',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MODIFIER_KEY_CODES = new Set([
|
||||||
|
'AltLeft',
|
||||||
|
'AltRight',
|
||||||
|
'ControlLeft',
|
||||||
|
'ControlRight',
|
||||||
|
'MetaLeft',
|
||||||
|
'MetaRight',
|
||||||
|
'ShiftLeft',
|
||||||
|
'ShiftRight',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const keyboardCodeToHotkeyKey = (code: string): null | string => {
|
||||||
|
const mapped = CODE_TO_HOTKEY_KEY[code];
|
||||||
|
if (mapped) {
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.startsWith('Key')) {
|
||||||
|
return code.slice(3).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.startsWith('Digit')) {
|
||||||
|
return code.slice(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code.startsWith('Numpad')) {
|
||||||
|
const suffix = code.slice(6);
|
||||||
|
const numpadMapped = NUMPAD_CODE_TO_HOTKEY_KEY[suffix];
|
||||||
|
if (numpadMapped) {
|
||||||
|
return numpadMapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^\d$/.test(suffix)) {
|
||||||
|
return `numpad${suffix}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user