mirror of https://github.com/jetkvm/kvm.git
refactor(ui): Improve header styling and detach bug
- Remove unused AttachIcon and related SVG asset. - Replace icon usage with a styled LinkButton to improve consistency. - Simplify and reformat VirtualKeyboard component for better readability.
This commit is contained in:
parent
771b1387fe
commit
a97c0c2465
|
@ -1,8 +0,0 @@
|
|||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 6C2 4.89543 2.89543 4 4 4H20C21.1046 4 22 4.89543 22 6V18C22 19.1046 21.1046 20 20 20H4C2.89543 20 2 19.1046 2 18V6Z"
|
||||
fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M20 6H4V18H20V6ZM4 4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H20C21.1046 20 22 19.1046 22 18V6C22 4.89543 21.1046 4 20 4H4Z"
|
||||
fill="black"/>
|
||||
<path d="M4 13H20V18H4V13Z" fill="black"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 511 B |
|
@ -2,33 +2,31 @@ import { ChevronDownIcon } from "@heroicons/react/16/solid";
|
|||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import Keyboard from "react-simple-keyboard";
|
||||
import { LuKeyboard } from "react-icons/lu";
|
||||
|
||||
import Card from "@components/Card";
|
||||
// eslint-disable-next-line import/order
|
||||
import { Button } from "@components/Button";
|
||||
import { Button, LinkButton } from "@components/Button";
|
||||
|
||||
import "react-simple-keyboard/build/css/index.css";
|
||||
|
||||
import AttachIconRaw from "@/assets/attach-icon.svg";
|
||||
import DetachIconRaw from "@/assets/detach-icon.svg";
|
||||
import { cx } from "@/cva.config";
|
||||
import { useHidStore, useUiStore } from "@/hooks/stores";
|
||||
import useKeyboard from "@/hooks/useKeyboard";
|
||||
import useKeyboardLayout from "@/hooks/useKeyboardLayout";
|
||||
import { keys, modifiers, latchingKeys, decodeModifiers } from "@/keyboardMappings";
|
||||
import { decodeModifiers, keys, latchingKeys, modifiers } from "@/keyboardMappings";
|
||||
|
||||
export const DetachIcon = ({ className }: { className?: string }) => {
|
||||
return <img src={DetachIconRaw} alt="Detach Icon" className={className} />;
|
||||
};
|
||||
|
||||
const AttachIcon = ({ className }: { className?: string }) => {
|
||||
return <img src={AttachIconRaw} alt="Attach Icon" className={className} />;
|
||||
};
|
||||
|
||||
function KeyboardWrapper() {
|
||||
const keyboardRef = useRef<HTMLDivElement>(null);
|
||||
const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } = useUiStore();
|
||||
const { keysDownState, /* keyboardLedState,*/ isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } = useHidStore();
|
||||
const { isAttachedVirtualKeyboardVisible, setAttachedVirtualKeyboardVisibility } =
|
||||
useUiStore();
|
||||
const { keysDownState, isVirtualKeyboardEnabled, setVirtualKeyboardEnabled } =
|
||||
useHidStore();
|
||||
const { handleKeyPress, executeMacro } = useKeyboard();
|
||||
const { selectedKeyboard } = useKeyboardLayout();
|
||||
|
||||
|
@ -44,29 +42,28 @@ function KeyboardWrapper() {
|
|||
return selectedKeyboard.virtualKeyboard;
|
||||
}, [selectedKeyboard]);
|
||||
|
||||
//const isCapsLockActive = useMemo(() => {
|
||||
// return (keyboardLedState.caps_lock);
|
||||
//}, [keyboardLedState]);
|
||||
|
||||
const { isShiftActive, /*isControlActive, isAltActive, isMetaActive, isAltGrActive*/ } = useMemo(() => {
|
||||
const { isShiftActive } = useMemo(() => {
|
||||
return decodeModifiers(keysDownState.modifier);
|
||||
}, [keysDownState]);
|
||||
|
||||
const mainLayoutName = useMemo(() => {
|
||||
const layoutName = isShiftActive ? "shift": "default";
|
||||
return layoutName;
|
||||
return isShiftActive ? "shift" : "default";
|
||||
}, [isShiftActive]);
|
||||
|
||||
const keyNamesForDownKeys = useMemo(() => {
|
||||
const activeModifierMask = keysDownState.modifier || 0;
|
||||
const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name);
|
||||
const modifierNames = Object.entries(modifiers)
|
||||
.filter(([_, mask]) => (activeModifierMask & mask) !== 0)
|
||||
.map(([name, _]) => name);
|
||||
|
||||
const keysDown = keysDownState.keys || [];
|
||||
const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name);
|
||||
const keyNames = Object.entries(keys)
|
||||
.filter(([_, value]) => keysDown.includes(value))
|
||||
.map(([name, _]) => name);
|
||||
|
||||
return [...modifierNames,...keyNames, ' ']; // we have to have at least one space to avoid keyboard whining
|
||||
return [...modifierNames, ...keyNames, " "]; // we have to have at least one space to avoid keyboard whining
|
||||
}, [keysDownState]);
|
||||
|
||||
|
||||
const startDrag = useCallback((e: MouseEvent | TouchEvent) => {
|
||||
if (!keyboardRef.current) return;
|
||||
if (e instanceof TouchEvent && e.touches.length > 1) return;
|
||||
|
@ -110,6 +107,9 @@ function KeyboardWrapper() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// Is the keyboard detached or attached?
|
||||
if (isAttachedVirtualKeyboardVisible) return;
|
||||
|
||||
const handle = keyboardRef.current;
|
||||
if (handle) {
|
||||
handle.addEventListener("touchstart", startDrag);
|
||||
|
@ -134,15 +134,12 @@ function KeyboardWrapper() {
|
|||
document.removeEventListener("mousemove", onDrag);
|
||||
document.removeEventListener("touchmove", onDrag);
|
||||
};
|
||||
}, [endDrag, onDrag, startDrag]);
|
||||
}, [isAttachedVirtualKeyboardVisible, endDrag, onDrag, startDrag]);
|
||||
|
||||
const onKeyUp = useCallback(
|
||||
async (_: string, e: MouseEvent | undefined) => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
},
|
||||
[]
|
||||
);
|
||||
const onKeyUp = useCallback(async (_: string, e: MouseEvent | undefined) => {
|
||||
e?.preventDefault();
|
||||
e?.stopPropagation();
|
||||
}, []);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
async (key: string, e: MouseEvent | undefined) => {
|
||||
|
@ -151,24 +148,30 @@ function KeyboardWrapper() {
|
|||
|
||||
// handle the fake key-macros we have defined for common combinations
|
||||
if (key === "CtrlAltDelete") {
|
||||
await executeMacro([ { keys: ["Delete"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]);
|
||||
await executeMacro([
|
||||
{ keys: ["Delete"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 },
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "AltMetaEscape") {
|
||||
await executeMacro([ { keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 } ]);
|
||||
await executeMacro([
|
||||
{ keys: ["Escape"], modifiers: ["AltLeft", "MetaLeft"], delay: 100 },
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "CtrlAltBackspace") {
|
||||
await executeMacro([ { keys: ["Backspace"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 } ]);
|
||||
await executeMacro([
|
||||
{ keys: ["Backspace"], modifiers: ["ControlLeft", "AltLeft"], delay: 100 },
|
||||
]);
|
||||
return;
|
||||
}
|
||||
|
||||
// if they press any of the latching keys, we send a keypress down event and the release it automatically (on timer)
|
||||
if (latchingKeys.includes(key)) {
|
||||
console.debug(`Latching key pressed: ${key} sending down and delayed up pair`);
|
||||
handleKeyPress(keys[key], true)
|
||||
handleKeyPress(keys[key], true);
|
||||
setTimeout(() => handleKeyPress(keys[key], false), 100);
|
||||
return;
|
||||
}
|
||||
|
@ -176,8 +179,10 @@ function KeyboardWrapper() {
|
|||
// if they press any of the dynamic keys, we send a keypress down event but we don't release it until they click it again
|
||||
if (Object.keys(modifiers).includes(key)) {
|
||||
const currentlyDown = keyNamesForDownKeys.includes(key);
|
||||
console.debug(`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`);
|
||||
handleKeyPress(keys[key], !currentlyDown)
|
||||
console.debug(
|
||||
`Dynamic key pressed: ${key} was currently down: ${currentlyDown}, toggling state`,
|
||||
);
|
||||
handleKeyPress(keys[key], !currentlyDown);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -211,7 +216,7 @@ function KeyboardWrapper() {
|
|||
<div
|
||||
className={cx(
|
||||
!isAttachedVirtualKeyboardVisible
|
||||
? "fixed left-0 top-0 z-50 select-none"
|
||||
? "fixed top-0 left-0 z-10 select-none"
|
||||
: "relative",
|
||||
)}
|
||||
ref={keyboardRef}
|
||||
|
@ -224,7 +229,7 @@ function KeyboardWrapper() {
|
|||
<Card
|
||||
className={cx("overflow-hidden", {
|
||||
"rounded-none": isAttachedVirtualKeyboardVisible,
|
||||
"keyboard-detached": !isAttachedVirtualKeyboardVisible
|
||||
"keyboard-detached": !isAttachedVirtualKeyboardVisible,
|
||||
})}
|
||||
>
|
||||
<div className="flex items-center justify-center border-b border-b-slate-800/30 bg-white px-2 py-4 dark:border-b-slate-300/20 dark:bg-slate-800">
|
||||
|
@ -241,15 +246,22 @@ function KeyboardWrapper() {
|
|||
size="XS"
|
||||
theme="light"
|
||||
text="Attach"
|
||||
LeadingIcon={AttachIcon}
|
||||
onClick={() => setAttachedVirtualKeyboardVisibility(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<h2 className="select-none self-center font-sans text-[12px] text-slate-700 dark:text-slate-300">
|
||||
Virtual Keyboard<span className="text-[10px]"> - {selectedKeyboard.name}</span>
|
||||
<h2 className="self-center font-sans text-sm leading-none font-medium text-slate-700 select-none dark:text-slate-300">
|
||||
Virtual Keyboard
|
||||
</h2>
|
||||
<div className="absolute right-2">
|
||||
<div className="absolute right-2 flex items-center gap-x-2">
|
||||
<LinkButton
|
||||
size="XS"
|
||||
to="settings/keyboard"
|
||||
theme="light"
|
||||
text={selectedKeyboard.name}
|
||||
TrailingIcon={LuKeyboard}
|
||||
/>
|
||||
<div className="h-[16px] w-px bg-slate-800/20 dark:bg-slate-200/20" />
|
||||
<Button
|
||||
size="XS"
|
||||
theme="light"
|
||||
|
@ -318,7 +330,7 @@ function KeyboardWrapper() {
|
|||
stopMouseUpPropagation={true}
|
||||
/>
|
||||
</div>
|
||||
{ /* TODO add optional number pad */ }
|
||||
{/* TODO add optional number pad */}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
|
Loading…
Reference in New Issue