import { useCallback, useEffect, useRef, useState } from "react"; import Keyboard from "react-simple-keyboard"; import { ChevronDownIcon } from "@heroicons/react/16/solid"; import { motion, AnimatePresence } from "framer-motion"; import Card from "@components/Card"; // eslint-disable-next-line import/order import { Button } from "@components/Button"; import "react-simple-keyboard/build/css/index.css"; import { useHidStore, useUiStore } from "@/hooks/stores"; import { cx } from "@/cva.config"; import { keys, modifiers, keyDisplayMap } from "@/keyboardMappings"; import useKeyboard from "@/hooks/useKeyboard"; import DetachIconRaw from "@/assets/detach-icon.svg"; import AttachIconRaw from "@/assets/attach-icon.svg"; export const DetachIcon = ({ className }: { className?: string }) => { return ; }; const AttachIcon = ({ className }: { className?: string }) => { return ; }; function KeyboardWrapper() { const [layoutName, setLayoutName] = useState("default"); const keyboardRef = useRef(null); const showAttachedVirtualKeyboard = useUiStore( state => state.isAttachedVirtualKeyboardVisible, ); const setShowAttachedVirtualKeyboard = useUiStore( state => state.setAttachedVirtualKeyboardVisibility, ); const { sendKeyboardEvent, resetKeyboardState } = useKeyboard(); const [isDragging, setIsDragging] = useState(false); const [position, setPosition] = useState({ x: 0, y: 0 }); const [newPosition, setNewPosition] = useState({ x: 0, y: 0 }); const isCapsLockActive = useHidStore(state => state.isCapsLockActive); const setIsCapsLockActive = useHidStore(state => state.setIsCapsLockActive); const startDrag = useCallback((e: MouseEvent | TouchEvent) => { if (!keyboardRef.current) return; if (e instanceof TouchEvent && e.touches.length > 1) return; setIsDragging(true); const clientX = e instanceof TouchEvent ? e.touches[0].clientX : e.clientX; const clientY = e instanceof TouchEvent ? e.touches[0].clientY : e.clientY; const rect = keyboardRef.current.getBoundingClientRect(); setPosition({ x: clientX - rect.left, y: clientY - rect.top, }); }, []); const onDrag = useCallback( (e: MouseEvent | TouchEvent) => { if (!keyboardRef.current) return; if (isDragging) { const clientX = e instanceof TouchEvent ? e.touches[0].clientX : e.clientX; const clientY = e instanceof TouchEvent ? e.touches[0].clientY : e.clientY; const newX = clientX - position.x; const newY = clientY - position.y; const rect = keyboardRef.current.getBoundingClientRect(); const maxX = window.innerWidth - rect.width; const maxY = window.innerHeight - rect.height; setNewPosition({ x: Math.min(maxX, Math.max(0, newX)), y: Math.min(maxY, Math.max(0, newY)), }); } }, [isDragging, position.x, position.y], ); const endDrag = useCallback(() => { setIsDragging(false); }, []); useEffect(() => { const handle = keyboardRef.current; if (handle) { handle.addEventListener("touchstart", startDrag); handle.addEventListener("mousedown", startDrag); } document.addEventListener("mouseup", endDrag); document.addEventListener("touchend", endDrag); document.addEventListener("mousemove", onDrag); document.addEventListener("touchmove", onDrag); return () => { if (handle) { handle.removeEventListener("touchstart", startDrag); handle.removeEventListener("mousedown", startDrag); } document.removeEventListener("mouseup", endDrag); document.removeEventListener("touchend", endDrag); document.removeEventListener("mousemove", onDrag); document.removeEventListener("touchmove", onDrag); }; }, [endDrag, onDrag, startDrag]); const onKeyDown = useCallback( (key: string) => { const isKeyShift = key === "{shift}" || key === "ShiftLeft" || key === "ShiftRight"; const isKeyCaps = key === "CapsLock"; const cleanKey = key.replace(/[()]/g, ""); const keyHasShiftModifier = key.includes("("); // Handle toggle of layout for shift or caps lock const toggleLayout = () => { setLayoutName(prevLayout => (prevLayout === "default" ? "shift" : "default")); }; if (key === "CtrlAltDelete") { sendKeyboardEvent( [keys["Delete"]], [modifiers["ControlLeft"], modifiers["AltLeft"]], ); setTimeout(resetKeyboardState, 100); return; } if (key === "AltMetaEscape") { sendKeyboardEvent( [keys["Escape"]], [modifiers["MetaLeft"], modifiers["AltLeft"]], ); setTimeout(resetKeyboardState, 100); return; } if (isKeyShift || isKeyCaps) { toggleLayout(); if (isCapsLockActive) { setIsCapsLockActive(false); sendKeyboardEvent([keys["CapsLock"]], []); return; } } // Handle caps lock state change if (isKeyCaps) { setIsCapsLockActive(!isCapsLockActive); } // Collect new active keys and modifiers const newKeys = keys[cleanKey] ? [keys[cleanKey]] : []; const newModifiers = keyHasShiftModifier && !isCapsLockActive ? [modifiers["ShiftLeft"]] : []; // Update current keys and modifiers sendKeyboardEvent(newKeys, newModifiers); // If shift was used as a modifier and caps lock is not active, revert to default layout if (keyHasShiftModifier && !isCapsLockActive) { setLayoutName("default"); } setTimeout(resetKeyboardState, 100); }, [isCapsLockActive, sendKeyboardEvent, resetKeyboardState, setIsCapsLockActive], ); const virtualKeyboard = useHidStore(state => state.isVirtualKeyboardEnabled); const setVirtualKeyboard = useHidStore(state => state.setVirtualKeyboardEnabled); return ( {virtualKeyboard && ( {showAttachedVirtualKeyboard ? ( setShowAttachedVirtualKeyboard(false)} /> ) : ( setShowAttachedVirtualKeyboard(true)} /> )} Virtual Keyboard setVirtualKeyboard(false)} /> )} ); } export default KeyboardWrapper;