Merge pull request #44 from ShuJun-Junical/main

feat(browser): add keyboard shortcuts menu, add Ctrl+D and Win+Tab shortcuts
This commit is contained in:
wenjie
2025-10-30 10:14:34 +08:00
committed by GitHub
9 changed files with 102 additions and 27 deletions

View File

@@ -4,7 +4,7 @@ import { KeyboardIcon } from 'lucide-react';
import { Paste } from './paste.tsx';
import { VirtualKeyboard } from './virtual-keyboard.tsx';
import { CtrlAltDel } from './ctrl-alt-del';
import { KeyboardShortcutsMenu } from './shortcuts-menu.tsx';
export const Keyboard = () => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
@@ -12,8 +12,8 @@ export const Keyboard = () => {
const content = (
<>
<Paste />
<CtrlAltDel />
<VirtualKeyboard />
<KeyboardShortcutsMenu />
</>
);

View File

@@ -1,24 +1,27 @@
import { useState } from 'react';
import { SendHorizonal } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { device } from '@/libs/device';
import { Modifiers } from '@/libs/device/keyboard.ts';
import { KeyboardCodes } from '@/libs/keyboard';
export const CtrlAltDel = () => {
const { t } = useTranslation();
interface ShortcutProps {
label: string;
modifiers?: Partial<Modifiers>;
keyCode: string;
}
export const Shortcut = ({ label, modifiers = {}, keyCode }: ShortcutProps) => {
const [isLoading, setIsLoading] = useState(false);
async function ctrlAltDel(): Promise<void> {
async function handleClick(): Promise<void> {
if (isLoading) return;
setIsLoading(true);
try {
const modifiers = new Modifiers();
modifiers.leftCtrl = true;
modifiers.leftAlt = true;
await send(modifiers, KeyboardCodes.get('Delete')!);
const mods = new Modifiers();
Object.assign(mods, modifiers);
await send(mods, KeyboardCodes.get(keyCode)!);
} catch (e) {
console.log(e);
} finally {
@@ -26,20 +29,19 @@ export const CtrlAltDel = () => {
}
}
async function send(modifiers: Modifiers, code: number) {
async function send(mods: Modifiers, code: number) {
const keys = [0x00, 0x00, code, 0x00, 0x00, 0x00];
await device.sendKeyboardData(modifiers, keys);
await device.sendKeyboardData(mods, keys);
await device.sendKeyboardData(new Modifiers(), [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={ctrlAltDel}
onClick={handleClick}
>
<SendHorizonal size={18} />
<span>{t('keyboard.ctrlAltDel')}</span>
<span>{label}</span>
</div>
);
};

View File

@@ -0,0 +1,55 @@
import { useState } from 'react';
import { SendHorizonal } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { Popover } from 'antd';
import { Shortcut } from './shortcut.tsx';
export const KeyboardShortcutsMenu = () => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
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}
/>
))}
</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.shortcuts')}</span>
</div>
</Popover>
);
};

View File

@@ -37,7 +37,10 @@ const be = {
keyboard: {
paste: 'Plakken',
virtualKeyboard: 'Virtueel klavier',
ctrlAltDel: 'Ctrl + Alt + Delete'
shortcuts: 'Sneltoetsen',
ctrlAltDel: 'Ctrl + Alt + Delete',
ctrlD: 'Ctrl + D',
winTab: 'Win + Tab',
},
mouse: {
cursor: {
@@ -62,5 +65,5 @@ const be = {
}
}
};
export default be;
export default be;

View File

@@ -37,7 +37,10 @@ const de = {
keyboard: {
paste: 'Einfügen',
virtualKeyboard: 'Virtuelle Tastatur',
ctrlAltDel: 'Strg + Alt + Entfernen'
shortcuts: 'Tastenkürzel',
ctrlAltDel: 'Strg + Alt + Entfernen',
ctrlD: 'Strg + D',
winTab: 'Win + Tab',
},
mouse: {
cursor: {
@@ -62,5 +65,5 @@ const de = {
}
}
};
export default de;
export default de;

View File

@@ -37,7 +37,10 @@ const en = {
keyboard: {
paste: 'Paste',
virtualKeyboard: 'Keyboard',
ctrlAltDel: 'Ctrl + Alt + Delete'
shortcuts: 'Shortcuts',
ctrlAltDel: 'Ctrl + Alt + Delete',
ctrlD: 'Ctrl + D',
winTab: 'Win + Tab',
},
mouse: {
cursor: {

View File

@@ -37,7 +37,10 @@ const nl = {
keyboard: {
paste: 'Plakken',
virtualKeyboard: 'Virtueel toetsenbord',
ctrlAltDel: 'Ctrl + Alt + Delete'
shortcuts: 'Sneltoetsen',
ctrlAltDel: 'Ctrl + Alt + Delete',
ctrlD: 'Ctrl + D',
winTab: 'Win + Tab',
},
mouse: {
cursor: {
@@ -62,5 +65,5 @@ const nl = {
}
}
};
export default nl;
export default nl;

View File

@@ -37,7 +37,10 @@ const ru = {
keyboard: {
paste: 'Вставить текст',
virtualKeyboard: 'Виртуальная клавиатура',
ctrlAltDel: 'Ctrl + Alt + Delete'
shortcuts: 'Сочетания клавиш',
ctrlAltDel: 'Ctrl + Alt + Delete',
ctrlD: 'Ctrl + D',
winTab: 'Win + Tab',
},
mouse: {
cursor: {

View File

@@ -35,7 +35,10 @@ const zh = {
keyboard: {
paste: '粘贴',
virtualKeyboard: '虚拟键盘',
ctrlAltDel: 'Ctrl + Alt + Delete'
shortcuts: '快捷键',
ctrlAltDel: 'Ctrl + Alt + Delete',
ctrlD: 'Ctrl + D',
winTab: 'Win + Tab',
},
mouse: {
cursor: {