kvm/ui/src/components/InfoBar.tsx

181 lines
6.4 KiB
TypeScript

import { useEffect, useMemo } from "react";
import { cx } from "@/cva.config";
import {
HidState,
MouseState,
RTCState,
SettingsState,
useHidStore,
useMouseStore,
useRTCStore,
useSettingsStore,
useVideoStore,
VideoState,
} from "@/hooks/stores";
import { keys, modifiers } from "@/keyboardMappings";
export default function InfoBar() {
const keysDownState = useHidStore((state: HidState) => state.keysDownState);
const mouseX = useMouseStore((state: MouseState) => state.mouseX);
const mouseY = useMouseStore((state: MouseState) => state.mouseY);
const mouseMove = useMouseStore((state: MouseState) => state.mouseMove);
const videoClientSize = useVideoStore(
(state: VideoState) => `${Math.round(state.clientWidth)}x${Math.round(state.clientHeight)}`,
);
const videoSize = useVideoStore(
(state: VideoState) => `${Math.round(state.width)}x${Math.round(state.height)}`,
);
const rpcDataChannel = useRTCStore((state: RTCState) => state.rpcDataChannel);
const settings = useSettingsStore();
const showPressedKeys = useSettingsStore((state: SettingsState) => state.showPressedKeys);
useEffect(() => {
if (!rpcDataChannel) return;
rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
rpcDataChannel.onerror = (e: Event) =>
console.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
}, [rpcDataChannel]);
const keyboardLedState = useHidStore((state: HidState) => state.keyboardLedState);
const isTurnServerInUse = useRTCStore((state: RTCState) => state.isTurnServerInUse);
const usbState = useHidStore((state: HidState) => state.usbState);
const hdmiState = useVideoStore((state: VideoState) => state.hdmiState);
const displayKeys = useMemo(() => {
if (!showPressedKeys)
return "";
const activeModifierMask = keysDownState.modifier || 0;
const keysDown = keysDownState.keys || [];
const modifierNames = Object.entries(modifiers).filter(([_, mask]) => (activeModifierMask & mask) !== 0).map(([name, _]) => name);
const keyNames = Object.entries(keys).filter(([_, value]) => keysDown.includes(value)).map(([name, _]) => name);
return [...modifierNames,...keyNames].join(", ");
}, [keysDownState, showPressedKeys]);
return (
<div className="bg-white border-t border-t-slate-800/30 text-slate-800 dark:border-t-slate-300/20 dark:bg-slate-900 dark:text-slate-300">
<div className="flex flex-wrap items-stretch justify-between gap-1">
<div className="flex items-center">
<div className="flex flex-wrap items-center pl-2 gap-x-4">
{settings.debugMode ? (
<div className="flex">
<span className="text-xs font-semibold">Resolution:</span>{" "}
<span className="text-xs">{videoSize}</span>
</div>
) : null}
{settings.debugMode ? (
<div className="flex">
<span className="text-xs font-semibold">Video Size: </span>
<span className="text-xs">{videoClientSize}</span>
</div>
) : null}
{(settings.debugMode && settings.mouseMode == "absolute") ? (
<div className="flex w-[118px] items-center gap-x-1">
<span className="text-xs font-semibold">Pointer:</span>
<span className="text-xs">
{mouseX},{mouseY}
</span>
</div>
) : null}
{(settings.debugMode && settings.mouseMode == "relative") ? (
<div className="flex w-[118px] items-center gap-x-1">
<span className="text-xs font-semibold">Last Move:</span>
<span className="text-xs">
{mouseMove ?
`${mouseMove.x},${mouseMove.y} ${mouseMove.buttons ? `(${mouseMove.buttons})` : ""}` :
"N/A"}
</span>
</div>
) : null}
{settings.debugMode && (
<div className="flex w-[156px] items-center gap-x-1">
<span className="text-xs font-semibold">USB State:</span>
<span className="text-xs">{usbState}</span>
</div>
)}
{settings.debugMode && (
<div className="flex w-[156px] items-center gap-x-1">
<span className="text-xs font-semibold">HDMI State:</span>
<span className="text-xs">{hdmiState}</span>
</div>
)}
{showPressedKeys && (
<div className="flex items-center gap-x-1">
<span className="text-xs font-semibold">Keys:</span>
<h2 className="text-xs">
{displayKeys}
</h2>
</div>
)}
</div>
</div>
<div className="flex items-center divide-x first:divide-l divide-slate-800/20 dark:divide-slate-300/20">
{isTurnServerInUse && (
<div className="shrink-0 p-1 px-1.5 text-xs text-black dark:text-white">
Relayed by Cloudflare
</div>
)}
<div
className={cx(
"shrink-0 p-1 px-1.5 text-xs",
keyboardLedState.caps_lock
? "text-black dark:text-white"
: "text-slate-800/20 dark:text-slate-300/20",
)}
>
Caps Lock
</div>
<div
className={cx(
"shrink-0 p-1 px-1.5 text-xs",
keyboardLedState.num_lock
? "text-black dark:text-white"
: "text-slate-800/20 dark:text-slate-300/20",
)}
>
Num Lock
</div>
<div
className={cx(
"shrink-0 p-1 px-1.5 text-xs",
keyboardLedState.scroll_lock
? "text-black dark:text-white"
: "text-slate-800/20 dark:text-slate-300/20",
)}
>
Scroll Lock
</div>
{keyboardLedState.compose ? (
<div className="shrink-0 p-1 px-1.5 text-xs">
Compose
</div>
) : null}
{keyboardLedState.kana ? (
<div className="shrink-0 p-1 px-1.5 text-xs">
Kana
</div>
) : null}
{keyboardLedState.shift ? (
<div className="shrink-0 p-1 px-1.5 text-xs">
Shift
</div>
) : null}
</div>
</div>
</div>
);
}