mirror of
https://github.com/luckfox-eng29/kvm.git
synced 2026-01-18 03:28:19 +01:00
feat(ui): enable multiple keyboard layouts for "paste text" to remote host (#405)
* Enable multiple keyboard layouts for paste text from host * Trema is the more robust method for capital umlauts * Improve error handling and pre-loading * Improve accent handling * Remove obscure Alt-Gr keys, unsure if they are supported everywhere * Add Swiss French * Change line ordering * Fix whitespace * Add French (France) * Add English (UK) * Add Swedish * Add Spanish * Fix fr_FR special characters * Add more keys to Spanish * Remove default value shift: false * Add Norwegian * Operator precedence 🤦 * Add Italian * Add Czech * Move guard statements outside of loop * Move language name definitions into the keyboard layout files * Change the locale names to their native language German->Deutsch et. al. * Move hold key handling into Go backend analogous to https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt * Remove trailing whitespace * Fix * Add Belgisch Nederlands * Add JSONRPC handling * Use useSettingsStore * Revert "Move hold key handling into Go backend analogous to https://www.kernel.org/doc/Documentation/usb/gadget_hid.txt" This reverts commit 146cee9309ca7a8b7ab103e955f3fcc38a4bc692. * Move FeatureFlag to navigation * Fix: flip Y/Z * Add useEffect dependencies * Embolden language * Add to useCallback dependencies --------- Co-authored-by: Marc Brooks <IDisposable@gmail.com>
This commit is contained in:
74
ui/src/routes/devices.$id.settings.keyboard.tsx
Normal file
74
ui/src/routes/devices.$id.settings.keyboard.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import { useCallback, useEffect } from "react";
|
||||
|
||||
import { useSettingsStore } from "@/hooks/stores";
|
||||
import { useJsonRpc } from "@/hooks/useJsonRpc";
|
||||
import notifications from "@/notifications";
|
||||
import { SettingsPageHeader } from "@components/SettingsPageheader";
|
||||
import { layouts } from "@/keyboardLayouts";
|
||||
|
||||
import { SelectMenuBasic } from "../components/SelectMenuBasic";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsKeyboardRoute() {
|
||||
const keyboardLayout = useSettingsStore(state => state.keyboardLayout);
|
||||
const setKeyboardLayout = useSettingsStore(
|
||||
state => state.setKeyboardLayout,
|
||||
);
|
||||
|
||||
const layoutOptions = Object.entries(layouts).map(([code, language]) => { return { value: code, label: language } })
|
||||
|
||||
const [send] = useJsonRpc();
|
||||
|
||||
useEffect(() => {
|
||||
send("getKeyboardLayout", {}, resp => {
|
||||
if ("error" in resp) return;
|
||||
setKeyboardLayout(resp.result as string);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onKeyboardLayoutChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const layout = e.target.value;
|
||||
send("setKeyboardLayout", { layout }, resp => {
|
||||
if ("error" in resp) {
|
||||
notifications.error(
|
||||
`Failed to set keyboard layout: ${resp.error.data || "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
notifications.success("Keyboard layout set successfully");
|
||||
setKeyboardLayout(layout);
|
||||
});
|
||||
},
|
||||
[send, setKeyboardLayout],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<SettingsPageHeader
|
||||
title="Keyboard"
|
||||
description="Configure keyboard layout settings for your device"
|
||||
/>
|
||||
|
||||
<div className="space-y-4">
|
||||
{ /* this menu item could be renamed to plain "Keyboard layout" in the future, when also the virtual keyboard layout mappings are being implemented */ }
|
||||
<SettingsItem
|
||||
title="Paste text"
|
||||
description="Keyboard layout of target operating system"
|
||||
>
|
||||
<SelectMenuBasic
|
||||
size="SM"
|
||||
label=""
|
||||
fullWidth
|
||||
value={keyboardLayout}
|
||||
onChange={onKeyboardLayoutChange}
|
||||
options={layoutOptions}
|
||||
/>
|
||||
</SettingsItem>
|
||||
<p className="text-xs text-slate-600 dark:text-slate-400">
|
||||
Pasting text sends individual key strokes to the target device. The keyboard layout determines which key codes are being sent. Ensure that the keyboard layout in JetKVM matches the settings in the operating system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { cx } from "../cva.config";
|
||||
|
||||
import { SettingsItem } from "./devices.$id.settings";
|
||||
|
||||
export default function SettingsKeyboardMouseRoute() {
|
||||
export default function SettingsMouseRoute() {
|
||||
const hideCursor = useSettingsStore(state => state.isCursorHidden);
|
||||
const setHideCursor = useSettingsStore(state => state.setCursorVisibility);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NavLink, Outlet, useLocation } from "react-router-dom";
|
||||
import {
|
||||
LuSettings,
|
||||
LuMouse,
|
||||
LuKeyboard,
|
||||
LuVideo,
|
||||
LuCpu,
|
||||
@@ -19,6 +20,7 @@ import { LinkButton } from "@/components/Button";
|
||||
import LoadingSpinner from "@/components/LoadingSpinner";
|
||||
import { useUiStore } from "@/hooks/stores";
|
||||
import useKeyboard from "@/hooks/useKeyboard";
|
||||
import { FeatureFlag } from "../components/FeatureFlag";
|
||||
|
||||
import { cx } from "../cva.config";
|
||||
|
||||
@@ -149,11 +151,25 @@ export default function SettingsRoute() {
|
||||
className={({ isActive }) => (isActive ? "active" : "")}
|
||||
>
|
||||
<div className="flex items-center gap-x-2 rounded-md px-2.5 py-2.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700 in-[.active]:bg-blue-50 in-[.active]:text-blue-700! md:in-[.active]:bg-transparent dark:in-[.active]:bg-blue-900 dark:in-[.active]:text-blue-200! dark:md:in-[.active]:bg-transparent">
|
||||
<LuKeyboard className="h-4 w-4 shrink-0" />
|
||||
|
||||
<LuMouse className="h-4 w-4 shrink-0" />
|
||||
<h1>Mouse</h1>
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
<FeatureFlag minAppVersion="0.4.0" name="Paste text">
|
||||
<div className="shrink-0">
|
||||
<NavLink
|
||||
to="keyboard"
|
||||
className={({ isActive }) => (isActive ? "active" : "")}
|
||||
>
|
||||
<div className="flex items-center gap-x-2 rounded-md px-2.5 py-2.5 text-sm transition-colors hover:bg-slate-100 dark:hover:bg-slate-700 [.active_&]:bg-blue-50 [.active_&]:!text-blue-700 md:[.active_&]:bg-transparent dark:[.active_&]:bg-blue-900 dark:[.active_&]:!text-blue-200 dark:md:[.active_&]:bg-transparent">
|
||||
<LuKeyboard className="h-4 w-4 shrink-0" />
|
||||
<h1>Keyboard</h1>
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
</FeatureFlag>
|
||||
<div className="shrink-0">
|
||||
<NavLink
|
||||
to="video"
|
||||
|
||||
Reference in New Issue
Block a user