mirror of
https://github.com/sipeed/NanoKVM-USB.git
synced 2026-09-11 05:59:57 -05:00
feat: add custom shortcut
This commit is contained in:
@@ -87,7 +87,7 @@ export const Keyboard = () => {
|
||||
|
||||
// send keyboard data
|
||||
async function sendKeyData(event: KeyboardEvent) {
|
||||
const modifiers = getModifiers(event);
|
||||
const modifiers = Modifiers.getModifiers(event, pressedModifiersRef.current);
|
||||
const keys = [
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -98,32 +98,5 @@ export const Keyboard = () => {
|
||||
await device.sendKeyboardData(modifiers, keys);
|
||||
}
|
||||
|
||||
function getModifiers(event: KeyboardEvent) {
|
||||
const modifiers = new Modifiers();
|
||||
|
||||
if (event.ctrlKey) {
|
||||
modifiers.leftCtrl = pressedModifiersRef.current.has('ControlLeft');
|
||||
modifiers.rightCtrl = pressedModifiersRef.current.has('ControlRight');
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
modifiers.leftShift = pressedModifiersRef.current.has('ShiftLeft');
|
||||
modifiers.rightShift = pressedModifiersRef.current.has('ShiftRight');
|
||||
}
|
||||
if (event.altKey) {
|
||||
modifiers.leftAlt = pressedModifiersRef.current.has('AltLeft');
|
||||
modifiers.rightAlt = pressedModifiersRef.current.has('AltRight');
|
||||
}
|
||||
if (event.metaKey) {
|
||||
modifiers.leftWindows = pressedModifiersRef.current.has('MetaLeft');
|
||||
modifiers.rightWindows = pressedModifiersRef.current.has('MetaRight');
|
||||
}
|
||||
if (event.getModifierState('AltGraph')) {
|
||||
modifiers.leftCtrl = true;
|
||||
modifiers.rightAlt = true;
|
||||
}
|
||||
|
||||
return modifiers;
|
||||
}
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
159
browser/src/components/menu/keyboard/shortcut-custom.tsx
Normal file
159
browser/src/components/menu/keyboard/shortcut-custom.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Input, InputRef, Modal, Space } from 'antd';
|
||||
import { useSetAtom } from 'jotai';
|
||||
import { KeyboardIcon, SquarePenIcon } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { isKeyboardEnableAtom } from '@/jotai/keyboard.ts';
|
||||
import { modifierKeys, Modifiers, ShortcutProps } from '@/libs/device/keyboard.ts';
|
||||
|
||||
interface KeyboardShortcutCustomProps {
|
||||
addShortcut: (shortcut: ShortcutProps) => void;
|
||||
}
|
||||
|
||||
export const KeyboardShortcutCustom = ({ addShortcut }: KeyboardShortcutCustomProps) => {
|
||||
const { t } = useTranslation();
|
||||
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const [isCapturingShortcut, setIsCapturingShortcut] = useState<boolean>(false);
|
||||
const [shortcut, setShortcut] = useState<ShortcutProps | null>(null);
|
||||
const [shortcutLabel, setShortcutLabel] = useState<string>('');
|
||||
const [shortcutFixedLabel, setShortcutFixedLabel] = useState<string>('');
|
||||
const pressedModifiersRef = useRef<Set<string>>(new Set());
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
|
||||
function getPressedShortcutString(event: KeyboardEvent) {
|
||||
let pressedKey: string = '';
|
||||
pressedKey += event.ctrlKey ? 'Ctrl + ' : '';
|
||||
pressedKey += event.shiftKey ? 'Shift + ' : '';
|
||||
pressedKey += event.altKey ? 'Alt + ' : '';
|
||||
pressedKey += event.metaKey ? 'Meta + ' : '';
|
||||
if (!modifierKeys.has(event.key)) {
|
||||
if (event.code.startsWith('Key') || event.code.startsWith('Digit')) {
|
||||
pressedKey += event.code.slice(-1);
|
||||
} else {
|
||||
pressedKey += event.key;
|
||||
}
|
||||
}
|
||||
return pressedKey;
|
||||
}
|
||||
|
||||
function handleModelDone() {
|
||||
if (shortcut == null) {
|
||||
cleanShortcut();
|
||||
return;
|
||||
}
|
||||
addShortcut({
|
||||
modifiers: shortcut.modifiers,
|
||||
label: shortcutLabel == '' ? shortcutFixedLabel : shortcutLabel,
|
||||
keyCode: shortcut.keyCode
|
||||
});
|
||||
cleanShortcut();
|
||||
}
|
||||
|
||||
function cleanShortcut() {
|
||||
setIsCapturingShortcut(false);
|
||||
setIsModalOpen(false);
|
||||
setShortcut(null);
|
||||
setShortcutLabel('');
|
||||
setShortcutFixedLabel('');
|
||||
}
|
||||
|
||||
async function handleKeyDown(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const label = getPressedShortcutString(event);
|
||||
setShortcutFixedLabel(label);
|
||||
|
||||
if (modifierKeys.has(event.key)) {
|
||||
if (event.type == 'keydown') {
|
||||
pressedModifiersRef.current.add(event.code);
|
||||
} else {
|
||||
pressedModifiersRef.current.delete(event.code);
|
||||
}
|
||||
} else {
|
||||
setIsCapturingShortcut(false);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyDown);
|
||||
const modifiers = Modifiers.getModifiers(event, pressedModifiersRef.current);
|
||||
const ret: ShortcutProps = {
|
||||
modifiers: modifiers,
|
||||
label: label,
|
||||
keyCode: event.code
|
||||
};
|
||||
setShortcut(ret);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
const element = document.documentElement;
|
||||
element.requestFullscreen().then();
|
||||
// @ts-expect-error - https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/lock
|
||||
navigator.keyboard?.lock();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsKeyboardEnable(!isModalOpen);
|
||||
}, [isModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current && !isCapturingShortcut) {
|
||||
inputRef.current.blur();
|
||||
}
|
||||
}, [isCapturingShortcut]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span>{t('keyboard.shortcut.custom')}</span>
|
||||
</div>
|
||||
<Modal
|
||||
width={400}
|
||||
title={t('keyboard.shortcut.custom')}
|
||||
open={isModalOpen}
|
||||
onOk={handleModelDone}
|
||||
onCancel={cleanShortcut}
|
||||
okText={t('keyboard.shortcut.save')}
|
||||
cancelText={t('keyboard.shortcut.cancel')}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<div className="flex">
|
||||
{t('keyboard.shortcut.captureTips')}
|
||||
<a onClick={handleFullscreen}>{t('keyboard.shortcut.enterFullScreen')}</a>
|
||||
</div>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
placeholder={t('keyboard.shortcut.capture')}
|
||||
value={shortcutFixedLabel}
|
||||
prefix={<KeyboardIcon size={16} />}
|
||||
onFocus={() => {
|
||||
setIsCapturingShortcut(true);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyDown);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsCapturingShortcut(false);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyDown);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
placeholder={shortcutFixedLabel || t('keyboard.shortcut.label')}
|
||||
value={shortcutLabel}
|
||||
prefix={<SquarePenIcon size={16} />}
|
||||
onChange={(e) => setShortcutLabel(e.target.value)}
|
||||
/>
|
||||
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,17 +1,12 @@
|
||||
import { useState } from 'react';
|
||||
import { SendHorizonal } from 'lucide-react';
|
||||
|
||||
import { useState, PropsWithChildren } from 'react';
|
||||
import { device } from '@/libs/device';
|
||||
import { Modifiers } from '@/libs/device/keyboard.ts';
|
||||
import { KeyboardCodes } from '@/libs/keyboard';
|
||||
import { ShortcutProps } from '@/libs/device/keyboard.ts'
|
||||
|
||||
interface ShortcutProps {
|
||||
label: string;
|
||||
modifiers?: Partial<Modifiers>;
|
||||
keyCode: string;
|
||||
}
|
||||
type ShortcutPropsWithChildren = ShortcutProps & PropsWithChildren
|
||||
|
||||
export const Shortcut = ({ label, modifiers = {}, keyCode }: ShortcutProps) => {
|
||||
export const Shortcut = ({ label, modifiers = {}, keyCode , children}: ShortcutPropsWithChildren) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
async function handleClick(): Promise<void> {
|
||||
@@ -40,8 +35,8 @@ export const Shortcut = ({ label, modifiers = {}, keyCode }: ShortcutProps) => {
|
||||
className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<SendHorizonal size={18} />
|
||||
<span>{label}</span>
|
||||
<span className="w-full">{label}</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,42 +1,92 @@
|
||||
import { useState } from 'react';
|
||||
import { SendHorizonal } from 'lucide-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Popover } from 'antd';
|
||||
import { SendHorizonal, Trash } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Popover } from 'antd';
|
||||
|
||||
import { ShortcutProps } from '@/libs/device/keyboard.ts';
|
||||
import { Shortcut } from './shortcut.tsx';
|
||||
import { KeyboardShortcutCustom } from './shortcut-custom.tsx';
|
||||
|
||||
export const KeyboardShortcutsMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [storedShortcuts, setStoredShortcuts] = useState<ShortcutProps[]>([]);
|
||||
const predefinedShortcuts : ShortcutProps[] = [
|
||||
{
|
||||
label: t('keyboard.shortcut.ctrlAltDel'),
|
||||
modifiers: { leftCtrl: true, leftAlt: true },
|
||||
keyCode: 'Delete',
|
||||
},
|
||||
{
|
||||
label: t('keyboard.shortcut.ctrlD'),
|
||||
modifiers: { leftCtrl: true },
|
||||
keyCode: 'KeyD',
|
||||
},
|
||||
{
|
||||
label: t('keyboard.shortcut.winTab'),
|
||||
modifiers: { leftWindows: true },
|
||||
keyCode: 'Tab',
|
||||
},
|
||||
]
|
||||
|
||||
const addShortcut = (shortcut: ShortcutProps) => {
|
||||
if (shortcut == null) return;
|
||||
|
||||
let shortcuts = localStorage.getItem("shortcuts");
|
||||
if (shortcuts === null) {
|
||||
shortcuts = "[]";
|
||||
}
|
||||
|
||||
const shortcutsObject: ShortcutProps[] = JSON.parse(shortcuts);
|
||||
shortcutsObject.push(shortcut);
|
||||
localStorage.setItem("shortcuts", JSON.stringify(shortcutsObject));
|
||||
setStoredShortcuts(shortcutsObject);
|
||||
}
|
||||
|
||||
const removeShortcut = (indexToRemove: number) => {
|
||||
const newShortcuts = storedShortcuts.filter(
|
||||
(_, index) => index !== indexToRemove
|
||||
);
|
||||
localStorage.setItem("shortcuts", JSON.stringify(newShortcuts));
|
||||
setStoredShortcuts(newShortcuts);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const shortcuts = localStorage.getItem("shortcuts");
|
||||
if (shortcuts === null) {
|
||||
predefinedShortcuts.forEach(shortcut => {
|
||||
addShortcut(shortcut);
|
||||
})
|
||||
} else {
|
||||
const shortcutsObject: ShortcutProps[] = JSON.parse(shortcuts);
|
||||
setStoredShortcuts(shortcutsObject);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<div className="flex flex-col gap-1">
|
||||
{[
|
||||
{
|
||||
label: t('keyboard.ctrlAltDel'),
|
||||
modifiers: { leftCtrl: true, leftAlt: true },
|
||||
keyCode: 'Delete',
|
||||
},
|
||||
{
|
||||
label: t('keyboard.ctrlD'),
|
||||
modifiers: { leftCtrl: true },
|
||||
keyCode: 'KeyD',
|
||||
},
|
||||
{
|
||||
label: t('keyboard.winTab'),
|
||||
modifiers: { leftWindows: true },
|
||||
keyCode: 'Tab',
|
||||
},
|
||||
].map((shortcut) => (
|
||||
<Shortcut
|
||||
key={shortcut.keyCode}
|
||||
label={shortcut.label}
|
||||
modifiers={shortcut.modifiers}
|
||||
keyCode={shortcut.keyCode}
|
||||
/>
|
||||
{storedShortcuts.map((shortcut, index) => (
|
||||
<>
|
||||
<Shortcut
|
||||
key={index}
|
||||
label={shortcut.label}
|
||||
modifiers={shortcut.modifiers}
|
||||
keyCode={shortcut.keyCode}
|
||||
>
|
||||
<Button
|
||||
className="ml-auto"
|
||||
type="text"
|
||||
danger
|
||||
icon={<Trash size={16} />}
|
||||
onClick={() => {removeShortcut(index)}}
|
||||
/>
|
||||
</Shortcut>
|
||||
</>
|
||||
))}
|
||||
<Divider style={{ margin: '5px 0 5px 0' }} />
|
||||
<KeyboardShortcutCustom
|
||||
addShortcut={addShortcut}/>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
@@ -48,7 +98,7 @@ export const KeyboardShortcutsMenu = () => {
|
||||
>
|
||||
<div className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60">
|
||||
<SendHorizonal size={18} />
|
||||
<span>{t('keyboard.shortcuts')}</span>
|
||||
<span>{t('keyboard.shortcut.title')}</span>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
@@ -38,10 +38,12 @@ const be = {
|
||||
keyboard: {
|
||||
paste: 'Plakken',
|
||||
virtualKeyboard: 'Virtueel klavier',
|
||||
shortcuts: 'Sneltoetsen',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
shortcut: {
|
||||
title: 'Sneltoetsen',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -38,10 +38,12 @@ const de = {
|
||||
keyboard: {
|
||||
paste: 'Einfügen',
|
||||
virtualKeyboard: 'Virtuelle Tastatur',
|
||||
shortcuts: 'Tastenkürzel',
|
||||
ctrlAltDel: 'Strg + Alt + Entfernen',
|
||||
ctrlD: 'Strg + D',
|
||||
winTab: 'Win + Tab',
|
||||
shortcut: {
|
||||
title: 'Tastenkürzel',
|
||||
ctrlAltDel: 'Strg + Alt + Entfernen',
|
||||
ctrlD: 'Strg + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -38,10 +38,19 @@ const en = {
|
||||
keyboard: {
|
||||
paste: 'Paste',
|
||||
virtualKeyboard: 'Keyboard',
|
||||
shortcuts: 'Shortcuts',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
shortcut: {
|
||||
title: 'Shortcut',
|
||||
custom: 'Custom Shortcut',
|
||||
capture: 'Click here to capture shortcut',
|
||||
label: 'Label',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
enterFullScreen: 'Enter Full Screen',
|
||||
captureTips: 'Capturing the Meta key separately requires operation in full-screen mode.'
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -38,7 +38,11 @@ const ko = {
|
||||
keyboard: {
|
||||
paste: '붙여넣기',
|
||||
virtualKeyboard: '가상 키보드',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -38,10 +38,12 @@ const nl = {
|
||||
keyboard: {
|
||||
paste: 'Plakken',
|
||||
virtualKeyboard: 'Virtueel toetsenbord',
|
||||
shortcuts: 'Sneltoetsen',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
shortcut: {
|
||||
title: 'Sneltoetsen',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -37,7 +37,11 @@ const pl = {
|
||||
keyboard: {
|
||||
paste: 'Wklej',
|
||||
virtualKeyboard: 'Klawiatura',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -37,7 +37,11 @@ const pt_br = {
|
||||
keyboard: {
|
||||
paste: 'Colar',
|
||||
virtualKeyboard: 'Teclado Virtual',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -38,10 +38,12 @@ const ru = {
|
||||
keyboard: {
|
||||
paste: 'Вставить текст',
|
||||
virtualKeyboard: 'Виртуальная клавиатура',
|
||||
shortcuts: 'Сочетания клавиш',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
shortcut: {
|
||||
title: 'Сочетания клавиш',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -36,10 +36,19 @@ const zh = {
|
||||
keyboard: {
|
||||
paste: '粘贴',
|
||||
virtualKeyboard: '虚拟键盘',
|
||||
shortcuts: '快捷键',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
shortcut: {
|
||||
title: '快捷键',
|
||||
custom: '自定义快捷键',
|
||||
capture: '点击此处捕获快捷键',
|
||||
label: '备注',
|
||||
cancel: '取消',
|
||||
save: '保存',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
enterFullScreen: '进入全屏',
|
||||
captureTips: '单独捕获 Meta 键需要在全屏下操作,'
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -35,7 +35,11 @@ const zh_tw = {
|
||||
keyboard: {
|
||||
paste: '貼上',
|
||||
virtualKeyboard: '虛擬鍵盤',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { setBit } from './utils.ts';
|
||||
|
||||
export class Modifiers {
|
||||
interface ShortcutProps {
|
||||
label: string;
|
||||
modifiers: Partial<Modifiers>;
|
||||
keyCode: string;
|
||||
}
|
||||
|
||||
const modifierKeys = new Set(['Control', 'Shift', 'Alt', 'Meta']);
|
||||
|
||||
class Modifiers {
|
||||
public rightWindows: boolean = false;
|
||||
public rightAlt: boolean = false;
|
||||
public rightShift: boolean = false;
|
||||
@@ -22,4 +30,32 @@ export class Modifiers {
|
||||
b = setBit(b, 7, this.rightWindows);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static getModifiers(event: KeyboardEvent, pressedModifiers: Set<string>) {
|
||||
const modifiers = new Modifiers();
|
||||
|
||||
if (event.ctrlKey) {
|
||||
modifiers.leftCtrl = pressedModifiers.has('ControlLeft');
|
||||
modifiers.rightCtrl = pressedModifiers.has('ControlRight');
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
modifiers.leftShift = pressedModifiers.has('ShiftLeft');
|
||||
modifiers.rightShift = pressedModifiers.has('ShiftRight');
|
||||
}
|
||||
if (event.altKey) {
|
||||
modifiers.leftAlt = pressedModifiers.has('AltLeft');
|
||||
modifiers.rightAlt = pressedModifiers.has('AltRight');
|
||||
}
|
||||
if (event.metaKey) {
|
||||
modifiers.leftWindows = pressedModifiers.has('MetaLeft');
|
||||
modifiers.rightWindows = pressedModifiers.has('MetaRight');
|
||||
}
|
||||
if (event.getModifierState('AltGraph')) {
|
||||
modifiers.leftCtrl = true;
|
||||
modifiers.rightAlt = true;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
export { type ShortcutProps, Modifiers, modifierKeys };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ReactElement, useEffect, useRef } from 'react'
|
||||
import { Modifiers } from '@renderer/libs/device/keyboard';
|
||||
import { Modifiers } from '@renderer/libs/device/keyboard'
|
||||
import { KeyboardCodes } from '@renderer/libs/keyboard'
|
||||
import { IpcEvents } from '@common/ipc-events'
|
||||
|
||||
@@ -59,7 +59,7 @@ export const Keyboard = (): ReactElement => {
|
||||
}
|
||||
|
||||
async function sendKeyData(event: KeyboardEvent) {
|
||||
const modifiers = getModifiers(event)
|
||||
const modifiers = Modifiers.getModifiers(event, pressedModifiersRef.current)
|
||||
const keys = [
|
||||
0x00,
|
||||
0x00,
|
||||
@@ -70,33 +70,6 @@ export const Keyboard = (): ReactElement => {
|
||||
await window.electron.ipcRenderer.invoke(IpcEvents.SEND_KEYBOARD, modifiers.encode(), keys)
|
||||
}
|
||||
|
||||
function getModifiers(event: KeyboardEvent) {
|
||||
const modifiers = new Modifiers()
|
||||
|
||||
if (event.ctrlKey) {
|
||||
modifiers.leftCtrl = pressedModifiersRef.current.has('ControlLeft')
|
||||
modifiers.rightCtrl = pressedModifiersRef.current.has('ControlRight')
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
modifiers.leftShift = pressedModifiersRef.current.has('ShiftLeft')
|
||||
modifiers.rightShift = pressedModifiersRef.current.has('ShiftRight')
|
||||
}
|
||||
if (event.altKey) {
|
||||
modifiers.leftAlt = pressedModifiersRef.current.has('AltLeft')
|
||||
modifiers.rightAlt = pressedModifiersRef.current.has('AltRight')
|
||||
}
|
||||
if (event.metaKey) {
|
||||
modifiers.leftWindows = pressedModifiersRef.current.has('MetaLeft')
|
||||
modifiers.rightWindows = pressedModifiersRef.current.has('MetaRight')
|
||||
}
|
||||
if (event.getModifierState('AltGraph')) {
|
||||
modifiers.leftCtrl = true
|
||||
modifiers.rightAlt = true
|
||||
}
|
||||
|
||||
return modifiers
|
||||
}
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { ReactElement, useState } from 'react'
|
||||
import { SendHorizonal } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import { IpcEvents } from '@common/ipc-events'
|
||||
import { KeyboardCodes } from '@renderer/libs/keyboard'
|
||||
|
||||
export const CtrlAltDel = (): ReactElement => {
|
||||
const { t } = useTranslation()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
async function ctrlAltDel(): Promise<void> {
|
||||
if (isLoading) return
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
await send(5, KeyboardCodes.get('Delete')!) // modifier 5 is ctrlLeft + altLeft
|
||||
|
||||
await send(0, 0)
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function send(modifier: number, key: number): Promise<void> {
|
||||
await window.electron.ipcRenderer.invoke(IpcEvents.SEND_KEYBOARD, modifier, key)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60"
|
||||
onClick={ctrlAltDel}
|
||||
>
|
||||
<SendHorizonal size={18} />
|
||||
<span>{t('keyboard.ctrlAltDel')}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
import { ReactElement, useState } from 'react'
|
||||
import { Popover } from 'antd'
|
||||
import { KeyboardIcon } from 'lucide-react'
|
||||
import { useState, ReactElement } from 'react';
|
||||
import { Popover } from 'antd';
|
||||
import { KeyboardIcon } from 'lucide-react';
|
||||
|
||||
import { CtrlAltDel } from './ctrl-alt-del'
|
||||
import { Paste } from './paste'
|
||||
import { VirtualKeyboard } from './virtual-keyboard'
|
||||
import { Paste } from './paste';
|
||||
import { VirtualKeyboard } from './virtual-keyboard';
|
||||
import { KeyboardShortcutsMenu } from './shortcuts-menu';
|
||||
|
||||
export const Keyboard = (): ReactElement => {
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false)
|
||||
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
|
||||
|
||||
const content = (
|
||||
<>
|
||||
<Paste />
|
||||
<CtrlAltDel />
|
||||
<VirtualKeyboard />
|
||||
<KeyboardShortcutsMenu />
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ export const Keyboard = (): ReactElement => {
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={setIsPopoverOpen}
|
||||
>
|
||||
<div className="flex h-[28px] cursor-pointer items-center justify-center rounded px-2 text-white hover:bg-neutral-700/70">
|
||||
<div className="flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded text-white hover:bg-neutral-700/70">
|
||||
<KeyboardIcon size={18} />
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { Input, InputRef, Modal, Space } from 'antd';
|
||||
import { KeyboardIcon, SquarePenIcon } from 'lucide-react';
|
||||
import { isKeyboardEnableAtom } from '@renderer/jotai/keyboard';
|
||||
import { modifierKeys, Modifiers, ShortcutProps } from '@renderer/libs/device/keyboard';
|
||||
import { useSetAtom } from 'jotai'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
interface KeyboardShortcutCustomProps {
|
||||
addShortcut: (shortcut: ShortcutProps) => void;
|
||||
}
|
||||
|
||||
export const KeyboardShortcutCustom = ({ addShortcut }: KeyboardShortcutCustomProps) => {
|
||||
const { t } = useTranslation();
|
||||
const setIsKeyboardEnable = useSetAtom(isKeyboardEnableAtom);
|
||||
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
|
||||
const [isCapturingShortcut, setIsCapturingShortcut] = useState<boolean>(false);
|
||||
const [shortcut, setShortcut] = useState<ShortcutProps | null>(null);
|
||||
const [shortcutLabel, setShortcutLabel] = useState<string>('');
|
||||
const [shortcutFixedLabel, setShortcutFixedLabel] = useState<string>('');
|
||||
const pressedModifiersRef = useRef<Set<string>>(new Set());
|
||||
const inputRef = useRef<InputRef>(null);
|
||||
|
||||
function getPressedShortcutString(event: KeyboardEvent) {
|
||||
let pressedKey: string = '';
|
||||
pressedKey += event.ctrlKey ? 'Ctrl + ' : '';
|
||||
pressedKey += event.shiftKey ? 'Shift + ' : '';
|
||||
pressedKey += event.altKey ? 'Alt + ' : '';
|
||||
pressedKey += event.metaKey ? 'Meta + ' : '';
|
||||
if (!modifierKeys.has(event.key)) {
|
||||
if (event.code.startsWith('Key') || event.code.startsWith('Digit')) {
|
||||
pressedKey += event.code.slice(-1);
|
||||
} else {
|
||||
pressedKey += event.key;
|
||||
}
|
||||
}
|
||||
return pressedKey;
|
||||
}
|
||||
|
||||
function handleModelDone() {
|
||||
if (shortcut == null) {
|
||||
cleanShortcut();
|
||||
return;
|
||||
}
|
||||
addShortcut({
|
||||
modifiers: shortcut.modifiers,
|
||||
label: shortcutLabel == '' ? shortcutFixedLabel : shortcutLabel,
|
||||
keyCode: shortcut.keyCode
|
||||
});
|
||||
cleanShortcut();
|
||||
}
|
||||
|
||||
function cleanShortcut() {
|
||||
setIsCapturingShortcut(false);
|
||||
setIsModalOpen(false);
|
||||
setShortcut(null);
|
||||
setShortcutLabel('');
|
||||
setShortcutFixedLabel('');
|
||||
}
|
||||
|
||||
async function handleKeyDown(event: KeyboardEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
const label = getPressedShortcutString(event);
|
||||
setShortcutFixedLabel(label);
|
||||
|
||||
if (modifierKeys.has(event.key)) {
|
||||
if (event.type == 'keydown') {
|
||||
pressedModifiersRef.current.add(event.code);
|
||||
} else {
|
||||
pressedModifiersRef.current.delete(event.code);
|
||||
}
|
||||
} else {
|
||||
setIsCapturingShortcut(false);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyDown);
|
||||
const modifiers = Modifiers.getModifiers(event, pressedModifiersRef.current);
|
||||
const ret: ShortcutProps = {
|
||||
modifiers: modifiers,
|
||||
label: label,
|
||||
keyCode: event.code
|
||||
};
|
||||
setShortcut(ret);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
const element = document.documentElement;
|
||||
element.requestFullscreen().then();
|
||||
// @ts-expect-error - https://developer.mozilla.org/en-US/docs/Web/API/Keyboard/lock
|
||||
navigator.keyboard?.lock();
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsKeyboardEnable(!isModalOpen);
|
||||
}, [isModalOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current && !isCapturingShortcut) {
|
||||
inputRef.current.blur();
|
||||
}
|
||||
}, [isCapturingShortcut]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60"
|
||||
onClick={() => {
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span>{t('keyboard.shortcut.custom')}</span>
|
||||
</div>
|
||||
<Modal
|
||||
width={400}
|
||||
title={t('keyboard.shortcut.custom')}
|
||||
open={isModalOpen}
|
||||
onOk={handleModelDone}
|
||||
onCancel={cleanShortcut}
|
||||
okText={t('keyboard.shortcut.save')}
|
||||
cancelText={t('keyboard.shortcut.cancel')}>
|
||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||||
<div className="flex">
|
||||
{t('keyboard.shortcut.captureTips')}
|
||||
<a onClick={handleFullscreen}>{t('keyboard.shortcut.enterFullScreen')}</a>
|
||||
</div>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
placeholder={t('keyboard.shortcut.capture')}
|
||||
value={shortcutFixedLabel}
|
||||
prefix={<KeyboardIcon size={16} />}
|
||||
onFocus={() => {
|
||||
setIsCapturingShortcut(true);
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
window.addEventListener('keyup', handleKeyDown);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsCapturingShortcut(false);
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
window.removeEventListener('keyup', handleKeyDown);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
placeholder={shortcutFixedLabel || t('keyboard.shortcut.label')}
|
||||
value={shortcutLabel}
|
||||
prefix={<SquarePenIcon size={16} />}
|
||||
onChange={(e) => setShortcutLabel(e.target.value)}
|
||||
/>
|
||||
|
||||
</Space>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useState, PropsWithChildren } from 'react';
|
||||
import { SendHorizonal } from 'lucide-react';
|
||||
import { Modifiers, ShortcutProps } from '@renderer/libs/device/keyboard';
|
||||
import { KeyboardCodes } from '@renderer/libs/keyboard'
|
||||
import { IpcEvents } from '@common/ipc-events'
|
||||
|
||||
type ShortcutPropsWithChildren = ShortcutProps & PropsWithChildren
|
||||
|
||||
export const Shortcut = (
|
||||
{ label, modifiers = {}, keyCode , children}: ShortcutPropsWithChildren
|
||||
) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
async function handleClick(): Promise<void> {
|
||||
if (isLoading) return;
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
const mods = new Modifiers();
|
||||
Object.assign(mods, modifiers);
|
||||
await send(mods, KeyboardCodes.get(keyCode)!);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function send(mods: Modifiers, code: number) {
|
||||
const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00];
|
||||
await window.electron.ipcRenderer.invoke(IpcEvents.SEND_KEYBOARD, mods.encode(), keys)
|
||||
await window.electron.ipcRenderer.invoke(
|
||||
IpcEvents.SEND_KEYBOARD,
|
||||
new Modifiers().encode(),
|
||||
[0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<SendHorizonal size={18} />
|
||||
<span className="w-full">{label}</span>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Divider, Popover } from 'antd';
|
||||
import { SendHorizonal, Trash } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ShortcutProps } from '@renderer/libs/device/keyboard';
|
||||
import { Shortcut } from './shortcut';
|
||||
import { KeyboardShortcutCustom } from './shortcut-custom';
|
||||
|
||||
export const KeyboardShortcutsMenu = () => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [storedShortcuts, setStoredShortcuts] = useState<ShortcutProps[]>([]);
|
||||
const predefinedShortcuts : ShortcutProps[] = [
|
||||
{
|
||||
label: t('keyboard.shortcut.ctrlAltDel'),
|
||||
modifiers: { leftCtrl: true, leftAlt: true },
|
||||
keyCode: 'Delete',
|
||||
},
|
||||
{
|
||||
label: t('keyboard.shortcut.ctrlD'),
|
||||
modifiers: { leftCtrl: true },
|
||||
keyCode: 'KeyD',
|
||||
},
|
||||
{
|
||||
label: t('keyboard.shortcut.winTab'),
|
||||
modifiers: { leftWindows: true },
|
||||
keyCode: 'Tab',
|
||||
},
|
||||
]
|
||||
|
||||
const addShortcut = (shortcut: ShortcutProps) => {
|
||||
if (shortcut == null) return;
|
||||
|
||||
let shortcuts = localStorage.getItem("shortcuts");
|
||||
if (shortcuts === null) {
|
||||
shortcuts = "[]";
|
||||
}
|
||||
|
||||
const shortcutsObject: ShortcutProps[] = JSON.parse(shortcuts);
|
||||
shortcutsObject.push(shortcut);
|
||||
localStorage.setItem("shortcuts", JSON.stringify(shortcutsObject));
|
||||
setStoredShortcuts(shortcutsObject);
|
||||
}
|
||||
|
||||
const removeShortcut = (indexToRemove: number) => {
|
||||
const newShortcuts = storedShortcuts.filter(
|
||||
(_, index) => index !== indexToRemove
|
||||
);
|
||||
localStorage.setItem("shortcuts", JSON.stringify(newShortcuts));
|
||||
setStoredShortcuts(newShortcuts);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const shortcuts = localStorage.getItem("shortcuts");
|
||||
if (shortcuts === null) {
|
||||
predefinedShortcuts.forEach(shortcut => {
|
||||
addShortcut(shortcut);
|
||||
})
|
||||
} else {
|
||||
const shortcutsObject: ShortcutProps[] = JSON.parse(shortcuts);
|
||||
setStoredShortcuts(shortcutsObject);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
content={
|
||||
<div className="flex flex-col gap-1">
|
||||
{storedShortcuts.map((shortcut, index) => (
|
||||
<>
|
||||
<Shortcut
|
||||
key={index}
|
||||
label={shortcut.label}
|
||||
modifiers={shortcut.modifiers}
|
||||
keyCode={shortcut.keyCode}
|
||||
>
|
||||
<Button
|
||||
className="ml-auto"
|
||||
type="text"
|
||||
danger
|
||||
icon={<Trash size={16} />}
|
||||
onClick={() => {removeShortcut(index)}}
|
||||
/>
|
||||
</Shortcut>
|
||||
</>
|
||||
))}
|
||||
<Divider style={{ margin: '5px 0 5px 0' }} />
|
||||
<KeyboardShortcutCustom
|
||||
addShortcut={addShortcut}/>
|
||||
</div>
|
||||
}
|
||||
trigger="click"
|
||||
placement="rightTop"
|
||||
align={{ offset: [14, 0] }}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
arrow={false}
|
||||
>
|
||||
<div className="flex h-[30px] cursor-pointer items-center space-x-1 rounded px-3 text-neutral-300 hover:bg-neutral-700/60">
|
||||
<SendHorizonal size={18} />
|
||||
<span>{t('keyboard.shortcut.title')}</span>
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
@@ -40,7 +40,12 @@ const be = {
|
||||
keyboard: {
|
||||
paste: 'Plakken',
|
||||
virtualKeyboard: 'Virtueel klavier',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
title: 'Sneltoetsen',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -40,7 +40,12 @@ const de = {
|
||||
keyboard: {
|
||||
paste: 'Einfügen',
|
||||
virtualKeyboard: 'Virtuelle Tastatur',
|
||||
ctrlAltDel: 'Strg + Alt + Entfernen'
|
||||
shortcut: {
|
||||
title: 'Tastenkürzel',
|
||||
ctrlAltDel: 'Strg + Alt + Entfernen',
|
||||
ctrlD: 'Strg + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -40,7 +40,19 @@ const en = {
|
||||
keyboard: {
|
||||
paste: 'Paste',
|
||||
virtualKeyboard: 'Keyboard',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
title: 'Shortcut',
|
||||
custom: 'Custom Shortcut',
|
||||
capture: 'Click here to capture shortcut',
|
||||
label: 'Label',
|
||||
cancel: 'Cancel',
|
||||
save: 'Save',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
enterFullScreen: 'Enter Full Screen',
|
||||
captureTips: 'Capturing the Meta key separately requires operation in full-screen mode.'
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -30,10 +30,14 @@ const ko = {
|
||||
cancel: '취소'
|
||||
}
|
||||
},
|
||||
keyboard: {
|
||||
kekeyboard: {
|
||||
paste: '붙여넣기',
|
||||
virtualKeyboard: '가상 키보드',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -40,7 +40,12 @@ const nl = {
|
||||
keyboard: {
|
||||
paste: 'Plakken',
|
||||
virtualKeyboard: 'Virtueel toetsenbord',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
title: 'Sneltoetsen',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -32,7 +32,11 @@ const pl = {
|
||||
keyboard: {
|
||||
paste: 'Wklej',
|
||||
virtualKeyboard: 'Klawiatura',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -32,7 +32,11 @@ const pt_br = {
|
||||
keyboard: {
|
||||
paste: 'Colar',
|
||||
virtualKeyboard: 'Teclado Virtual',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -40,7 +40,12 @@ const ru = {
|
||||
keyboard: {
|
||||
paste: 'Вставить текст',
|
||||
virtualKeyboard: 'Виртуальная клавиатура',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
title: 'Сочетания клавиш',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -36,10 +36,22 @@ const zh = {
|
||||
cancel: '取消'
|
||||
}
|
||||
},
|
||||
keyboard: {
|
||||
kkeyboard: {
|
||||
paste: '粘贴',
|
||||
virtualKeyboard: '虚拟键盘',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
title: '快捷键',
|
||||
custom: '自定义快捷键',
|
||||
capture: '点击此处捕获快捷键',
|
||||
label: '备注',
|
||||
cancel: '取消',
|
||||
save: '保存',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
enterFullScreen: '进入全屏',
|
||||
captureTips: '单独捕获 Meta 键需要在全屏下操作,'
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -35,7 +35,11 @@ const zh_tw = {
|
||||
keyboard: {
|
||||
paste: '貼上',
|
||||
virtualKeyboard: '虛擬鍵盤',
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete'
|
||||
shortcut: {
|
||||
ctrlAltDel: 'Ctrl + Alt + Delete',
|
||||
ctrlD: 'Ctrl + D',
|
||||
winTab: 'Win + Tab',
|
||||
},
|
||||
},
|
||||
mouse: {
|
||||
cursor: {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { setBit } from './utils';
|
||||
|
||||
export class Modifiers {
|
||||
interface ShortcutProps {
|
||||
label: string;
|
||||
modifiers: Partial<Modifiers>;
|
||||
keyCode: string;
|
||||
}
|
||||
|
||||
const modifierKeys = new Set(['Control', 'Shift', 'Alt', 'Meta']);
|
||||
|
||||
class Modifiers {
|
||||
public rightWindows: boolean = false;
|
||||
public rightAlt: boolean = false;
|
||||
public rightShift: boolean = false;
|
||||
@@ -22,4 +30,32 @@ export class Modifiers {
|
||||
b = setBit(b, 7, this.rightWindows);
|
||||
return b;
|
||||
}
|
||||
|
||||
public static getModifiers(event: KeyboardEvent, pressedModifiers: Set<string>) {
|
||||
const modifiers = new Modifiers();
|
||||
|
||||
if (event.ctrlKey) {
|
||||
modifiers.leftCtrl = pressedModifiers.has('ControlLeft');
|
||||
modifiers.rightCtrl = pressedModifiers.has('ControlRight');
|
||||
}
|
||||
if (event.shiftKey) {
|
||||
modifiers.leftShift = pressedModifiers.has('ShiftLeft');
|
||||
modifiers.rightShift = pressedModifiers.has('ShiftRight');
|
||||
}
|
||||
if (event.altKey) {
|
||||
modifiers.leftAlt = pressedModifiers.has('AltLeft');
|
||||
modifiers.rightAlt = pressedModifiers.has('AltRight');
|
||||
}
|
||||
if (event.metaKey) {
|
||||
modifiers.leftWindows = pressedModifiers.has('MetaLeft');
|
||||
modifiers.rightWindows = pressedModifiers.has('MetaRight');
|
||||
}
|
||||
if (event.getModifierState('AltGraph')) {
|
||||
modifiers.leftCtrl = true;
|
||||
modifiers.rightAlt = true;
|
||||
}
|
||||
return modifiers;
|
||||
}
|
||||
}
|
||||
|
||||
export { type ShortcutProps, Modifiers, modifierKeys };
|
||||
|
||||
Reference in New Issue
Block a user