Move keyboardmapping store to stores.ts, simplified some things, updated settings.tsx to set the keyboard layout properly.

This commit is contained in:
William Johnstone 2025-02-01 18:38:40 +00:00
parent 0e855adc35
commit 7c40e2e011
No known key found for this signature in database
GPG Key ID: 89703D0D4B3BB0FE
11 changed files with 90 additions and 96 deletions

View File

@ -5,18 +5,18 @@ import {
useRTCStore, useRTCStore,
useSettingsStore, useSettingsStore,
useVideoStore, useVideoStore,
useKeyboardMappingsStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
export default function InfoBar() { export default function InfoBar() {
const [keys, setKeys] = useState(keyboardMappingsStore.keys); const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
useEffect(() => { useEffect(() => {
const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
setKeys(keyboardMappingsStore.keys); setKeys(useKeyboardMappingsStore.keys);
setModifiers(keyboardMappingsStore.modifiers); setModifiers(useKeyboardMappingsStore.modifiers);
}); });
return unsubscribeKeyboardStore; // Cleanup on unmount return unsubscribeKeyboardStore; // Cleanup on unmount
}, []); }, []);

View File

@ -4,11 +4,9 @@ import { Button } from "@components/Button";
import Card from "@components/Card"; import Card from "@components/Card";
import { ChevronDownIcon } from "@heroicons/react/16/solid"; import { ChevronDownIcon } from "@heroicons/react/16/solid";
import "react-simple-keyboard/build/css/index.css"; import "react-simple-keyboard/build/css/index.css";
import { useHidStore, useUiStore } from "@/hooks/stores"; import { useHidStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores";
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
//import { keys, modifiers } from "@/keyboardMappings/KeyboardMappingStore";
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
import useKeyboard from "@/hooks/useKeyboard"; import useKeyboard from "@/hooks/useKeyboard";
import DetachIconRaw from "@/assets/detach-icon.svg"; import DetachIconRaw from "@/assets/detach-icon.svg";
import AttachIconRaw from "@/assets/attach-icon.svg"; import AttachIconRaw from "@/assets/attach-icon.svg";
@ -22,13 +20,13 @@ const AttachIcon = ({ className }: { className?: string }) => {
}; };
function KeyboardWrapper() { function KeyboardWrapper() {
const [keys, setKeys] = useState(keyboardMappingsStore.keys); const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
useEffect(() => { useEffect(() => {
const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
setKeys(keyboardMappingsStore.keys); setKeys(useKeyboardMappingsStore.keys);
setModifiers(keyboardMappingsStore.modifiers); setModifiers(useKeyboardMappingsStore.modifiers);
}); });
return unsubscribeKeyboardStore; // Cleanup on unmount return unsubscribeKeyboardStore; // Cleanup on unmount
}, []); }, []);

View File

@ -6,8 +6,8 @@ import {
useSettingsStore, useSettingsStore,
useUiStore, useUiStore,
useVideoStore, useVideoStore,
useKeyboardMappingsStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
import { useResizeObserver } from "@/hooks/useResizeObserver"; import { useResizeObserver } from "@/hooks/useResizeObserver";
import { cx } from "@/cva.config"; import { cx } from "@/cva.config";
import VirtualKeyboard from "@components/VirtualKeyboard"; import VirtualKeyboard from "@components/VirtualKeyboard";
@ -18,13 +18,13 @@ import { useJsonRpc } from "@/hooks/useJsonRpc";
import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./VideoOverlay"; import { ConnectionErrorOverlay, HDMIErrorOverlay, LoadingOverlay } from "./VideoOverlay";
export default function WebRTCVideo() { export default function WebRTCVideo() {
const [keys, setKeys] = useState(keyboardMappingsStore.keys); const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
useEffect(() => { useEffect(() => {
const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
setKeys(keyboardMappingsStore.keys); setKeys(useKeyboardMappingsStore.keys);
setModifiers(keyboardMappingsStore.modifiers); setModifiers(useKeyboardMappingsStore.modifiers);
}); });
return unsubscribeKeyboardStore; // Cleanup on unmount return unsubscribeKeyboardStore; // Cleanup on unmount
}, []); }, []);
@ -218,12 +218,15 @@ export default function WebRTCVideo() {
const prev = useHidStore.getState(); const prev = useHidStore.getState();
let code = e.code; let code = e.code;
const key = e.key; const key = e.key;
console.log(e);
console.log(key);
// if (document.activeElement?.id !== "videoFocusTrap") { // if (document.activeElement?.id !== "videoFocusTrap") {hH
// console.log("KEYUP: Not focusing on the video", document.activeElement); // console.log("KEYUP: Not focusing on the video", document.activeElement);
// return; // return;
// } // }
console.log(document.activeElement); //
// console.log(document.activeElement);
setIsNumLockActive(e.getModifierState("NumLock")); setIsNumLockActive(e.getModifierState("NumLock"));
setIsCapsLockActive(e.getModifierState("CapsLock")); setIsCapsLockActive(e.getModifierState("CapsLock"));
@ -289,6 +292,7 @@ export default function WebRTCVideo() {
prev.activeModifiers.filter(k => k !== modifiers[e.code]), prev.activeModifiers.filter(k => k !== modifiers[e.code]),
); );
console.log(e.key);
sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]); sendKeyboardEvent([...new Set(newKeys)], [...new Set(newModifiers)]);
}, },
[ [

View File

@ -3,28 +3,27 @@ import { GridCard } from "@components/Card";
import { TextAreaWithLabel } from "@components/TextArea"; import { TextAreaWithLabel } from "@components/TextArea";
import { SectionHeader } from "@components/SectionHeader"; import { SectionHeader } from "@components/SectionHeader";
import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores"; import { useHidStore, useRTCStore, useUiStore, useKeyboardMappingsStore } from "@/hooks/stores";
import notifications from "../../notifications"; import notifications from "../../notifications";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { LuCornerDownLeft } from "react-icons/lu"; import { LuCornerDownLeft } from "react-icons/lu";
import { ExclamationCircleIcon } from "@heroicons/react/16/solid"; import { ExclamationCircleIcon } from "@heroicons/react/16/solid";
import { useClose } from "@headlessui/react"; import { useClose } from "@headlessui/react";
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
const hidKeyboardPayload = (keys: number[], modifier: number) => { const hidKeyboardPayload = (keys: number[], modifier: number) => {
return { keys, modifier }; return { keys, modifier };
}; };
export default function PasteModal() { export default function PasteModal() {
const [keys, setKeys] = useState(keyboardMappingsStore.keys); const [keys, setKeys] = useState(useKeyboardMappingsStore.keys);
const [chars, setChars] = useState(keyboardMappingsStore.chars); const [chars, setChars] = useState(useKeyboardMappingsStore.chars);
const [modifiers, setModifiers] = useState(keyboardMappingsStore.modifiers); const [modifiers, setModifiers] = useState(useKeyboardMappingsStore.modifiers);
useEffect(() => { useEffect(() => {
const unsubscribeKeyboardStore = keyboardMappingsStore.subscribe(() => { const unsubscribeKeyboardStore = useKeyboardMappingsStore.subscribe(() => {
setKeys(keyboardMappingsStore.keys); setKeys(useKeyboardMappingsStore.keys);
setChars(keyboardMappingsStore.chars); setChars(useKeyboardMappingsStore.chars);
setModifiers(keyboardMappingsStore.modifiers); setModifiers(useKeyboardMappingsStore.modifiers);
}); });
return unsubscribeKeyboardStore; // Cleanup on unmount return unsubscribeKeyboardStore; // Cleanup on unmount
}, []); }, []);
@ -54,13 +53,14 @@ export default function PasteModal() {
try { try {
for (const char of text) { for (const char of text) {
const { key, shift, alt } = chars[char] ?? {}; const { key, shift, altLeft, altRight } = chars[char] ?? {};
if (!key) continue; if (!key) continue;
// Build the modifier bitmask // Build the modifier bitmask
const modifier = const modifier =
(shift ? modifiers["ShiftLeft"] : 0) | (shift ? modifiers["ShiftLeft"] : 0) |
(alt ? modifiers["AltLeft"] : 0); (altLeft ? modifiers["AltLeft"] : 0) |
(altRight ? modifiers["AltRight"] : 0); // This is important for a lot of keyboard layouts, right and left alt have different functions
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
send( send(

View File

@ -4,6 +4,7 @@ import {
useSettingsStore, useSettingsStore,
useUiStore, useUiStore,
useUpdateStore, useUpdateStore,
useKeyboardMappingsStore,
} from "@/hooks/stores"; } from "@/hooks/stores";
import { Checkbox } from "@components/Checkbox"; import { Checkbox } from "@components/Checkbox";
import { Button, LinkButton } from "@components/Button"; import { Button, LinkButton } from "@components/Button";
@ -25,8 +26,6 @@ import LocalAuthPasswordDialog from "@/components/LocalAuthPasswordDialog";
import { LocalDevice } from "@routes/devices.$id"; import { LocalDevice } from "@routes/devices.$id";
import { useRevalidator } from "react-router-dom"; import { useRevalidator } from "react-router-dom";
import { ShieldCheckIcon } from "@heroicons/react/20/solid"; import { ShieldCheckIcon } from "@heroicons/react/20/solid";
import { keyboardMappingsStore } from "@/keyboardMappings/KeyboardMappingStore";
import { KeyboardLayout } from "@/keyboardMappings/KeyboardLayouts";
export function SettingsItem({ export function SettingsItem({
title, title,
@ -157,8 +156,7 @@ export default function SettingsSidebar() {
); );
return; return;
} }
// TODO set this to update to the actual layout chosen useKeyboardMappingsStore.setLayout(keyboardLayout)
keyboardMappingsStore.setLayout(KeyboardLayout.UKApple)
setKeyboardLayout(keyboardLayout); setKeyboardLayout(keyboardLayout);
}); });
}; };
@ -294,6 +292,7 @@ export default function SettingsSidebar() {
send("getKeyboardLayout", {}, resp => { send("getKeyboardLayout", {}, resp => {
if ("error" in resp) return; if ("error" in resp) return;
setKeyboardLayout(String(resp.result)); setKeyboardLayout(String(resp.result));
useKeyboardMappingsStore.setLayout(String(resp.result))
}); });
send("getStreamQualityFactor", {}, resp => { send("getStreamQualityFactor", {}, resp => {
@ -545,7 +544,7 @@ export default function SettingsSidebar() {
size="SM" size="SM"
label="" label=""
// TODO figure out how to make this selector wider like the EDID one? // TODO figure out how to make this selector wider like the EDID one?
//fullWidth //fullWidthƒ
value={keyboardLayout} value={keyboardLayout}
options={[ options={[
{ value: "uk", label: "GB" }, { value: "uk", label: "GB" },

View File

@ -1,5 +1,6 @@
import { create } from "zustand"; import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware"; import { createJSONStorage, persist } from "zustand/middleware";
import { getKeyboardMappings } from "@/keyboardMappings/KeyboardLayouts";
// Utility function to append stats to a Map // Utility function to append stats to a Map
const appendStatToMap = <T extends { timestamp: number }>( const appendStatToMap = <T extends { timestamp: number }>(
@ -528,3 +529,39 @@ export const useLocalAuthModalStore = create<LocalAuthModalState>(set => ({
setModalView: view => set({ modalView: view }), setModalView: view => set({ modalView: view }),
setErrorMessage: message => set({ errorMessage: message }), setErrorMessage: message => set({ errorMessage: message }),
})); }));
class KeyboardMappingsStore {
private _layout: string = 'us';
private _subscribers: (() => void)[] = [];
public keys = getKeyboardMappings(this._layout).keys;
public chars = getKeyboardMappings(this._layout).chars;
public modifiers = getKeyboardMappings(this._layout).modifiers;
setLayout(newLayout: string) {
if (this._layout === newLayout) return;
this._layout = newLayout;
const updatedMappings = getKeyboardMappings(newLayout);
this.keys = updatedMappings.keys;
this.chars = updatedMappings.chars;
this.modifiers = updatedMappings.modifiers;
this._notifySubscribers();
}
getLayout() {
return this._layout;
}
subscribe(callback: () => void) {
this._subscribers.push(callback);
return () => {
this._subscribers = this._subscribers.filter(sub => sub !== callback); // Cleanup
};
}
private _notifySubscribers() {
this._subscribers.forEach(callback => callback());
}
}
export const useKeyboardMappingsStore = new KeyboardMappingsStore();

View File

@ -1,20 +1,15 @@
import {keysUKApple, charsUKApple, modifiersUKApple } from './layouts/uk_apple'; import {keysUKApple, charsUKApple, modifiersUKApple } from './layouts/uk_apple';
import {keysUS, charsUS, modifiersUS } from './layouts/us'; import {keysUS, charsUS, modifiersUS } from './layouts/us';
export enum KeyboardLayout { export function getKeyboardMappings(layout: string) {
US = "us", switch (layout) {
UKApple = "uk_apple", case "uk_apple":
} return {
keys: keysUKApple,
export function getKeyboardMappings(layout: KeyboardLayout) { chars: charsUKApple,
switch (layout) { modifiers: modifiersUKApple,
case KeyboardLayout.UKApple: };
return { case "us":
keys: keysUKApple,
chars: charsUKApple,
modifiers: modifiersUKApple,
};
case KeyboardLayout.US:
default: default:
return { return {
keys: keysUS, keys: keysUS,
@ -22,4 +17,4 @@ export function getKeyboardMappings(layout: KeyboardLayout) {
modifiers: modifiersUS, modifiers: modifiersUS,
}; };
} }
} }

View File

@ -1,39 +0,0 @@
import { getKeyboardMappings, KeyboardLayout } from "@/keyboardMappings/KeyboardLayouts";
// TODO Move this in with all the other stores?
class KeyboardMappingsStore {
private _layout: KeyboardLayout = KeyboardLayout.US;
private _subscribers: (() => void)[] = [];
public keys = getKeyboardMappings(this._layout).keys;
public chars = getKeyboardMappings(this._layout).chars;
public modifiers = getKeyboardMappings(this._layout).modifiers;
setLayout(newLayout: KeyboardLayout) {
if (this._layout === newLayout) return;
this._layout = newLayout;
const updatedMappings = getKeyboardMappings(newLayout);
this.keys = updatedMappings.keys;
this.chars = updatedMappings.chars;
this.modifiers = updatedMappings.modifiers;
this._notifySubscribers();
}
getLayout() {
return this._layout;
}
subscribe(callback: () => void) {
this._subscribers.push(callback);
return () => {
this._subscribers = this._subscribers.filter(sub => sub !== callback); // Cleanup
};
}
private _notifySubscribers() {
this._subscribers.forEach(callback => callback());
}
}
export const keyboardMappingsStore = new KeyboardMappingsStore();

View File

View File

@ -12,11 +12,11 @@ export const charsUKApple = {
"~": { key: "Backquote", shift: true }, "~": { key: "Backquote", shift: true },
"\\" : { key: "Backslash", shift: false }, "\\" : { key: "Backslash", shift: false },
"|": { key: "Backslash", shift: true }, "|": { key: "Backslash", shift: true },
"#": { key: "Digit3", shift: false, alt: true }, "#": { key: "Digit3", shift: false, altLeft: true },
"£": { key: "Digit3", shift: true }, "£": { key: "Digit3", shift: true },
"@": { key: "Digit2", shift: true }, "@": { key: "Digit2", shift: true },
"\"": { key: "Quote", shift: true }, "\"": { key: "Quote", shift: true },
} as Record<string, { key: string | number; shift: boolean; alt?: boolean; }>; } as Record<string, { key: string | number; shift: boolean; altLeft?: boolean; altRight?: boolean; }>;
// Modifiers are typically the same between UK and US layouts // Modifiers are typically the same between UK and US layouts
export const modifiersUKApple = { export const modifiersUKApple = {

View File

@ -200,8 +200,8 @@ export const charsUS = {
"\n": { key: "Enter", shift: false }, "\n": { key: "Enter", shift: false },
Enter: { key: "Enter", shift: false }, Enter: { key: "Enter", shift: false },
Tab: { key: "Tab", shift: false }, Tab: { key: "Tab", shift: false },
} as Record<string, { key: string | number; shift: boolean; alt?: boolean; }>; } as Record<string, { key: string | number; shift: boolean; altLeft?: boolean; altRight?: boolean; }>;
export const modifiersUS = { export const modifiersUS = {
ControlLeft: 0x01, ControlLeft: 0x01,
ControlRight: 0x10, ControlRight: 0x10,