import { useCallback, useEffect, useRef, useState } from "react"; import { LuCornerDownLeft } from "react-icons/lu"; import { ExclamationCircleIcon } from "@heroicons/react/16/solid"; import { useClose } from "@headlessui/react"; import { Button } from "@components/Button"; import { GridCard } from "@components/Card"; import { TextAreaWithLabel } from "@components/TextArea"; import { SettingsPageHeader } from "@components/SettingsPageheader"; import { useJsonRpc } from "@/hooks/useJsonRpc"; import { useHidStore, useRTCStore, useUiStore } from "@/hooks/stores"; import { chars, keys, modifiers } from "@/keyboardMappings"; import notifications from "@/notifications"; const hidKeyboardPayload = (keys: number[], modifier: number) => { return { keys, modifier }; }; export default function PasteModal() { const TextAreaRef = useRef(null); const setPasteMode = useHidStore(state => state.setPasteModeEnabled); const setDisableVideoFocusTrap = useUiStore(state => state.setDisableVideoFocusTrap); const [send] = useJsonRpc(); const rpcDataChannel = useRTCStore(state => state.rpcDataChannel); const [invalidChars, setInvalidChars] = useState([]); const close = useClose(); const onCancelPasteMode = useCallback(() => { setPasteMode(false); setDisableVideoFocusTrap(false); setInvalidChars([]); }, [setDisableVideoFocusTrap, setPasteMode]); const onConfirmPaste = useCallback(async () => { setPasteMode(false); setDisableVideoFocusTrap(false); if (rpcDataChannel?.readyState !== "open" || !TextAreaRef.current) return; const text = TextAreaRef.current.value; try { for (const char of text) { const { key, shift } = chars[char] ?? {}; if (!key) continue; await new Promise((resolve, reject) => { send( "keyboardReport", hidKeyboardPayload([keys[key]], shift ? modifiers["ShiftLeft"] : 0), params => { if ("error" in params) return reject(params.error); send("keyboardReport", hidKeyboardPayload([], 0), params => { if ("error" in params) return reject(params.error); resolve(); }); }, ); }); } } catch (error) { console.error(error); notifications.error("Failed to paste text"); } }, [rpcDataChannel?.readyState, send, setDisableVideoFocusTrap, setPasteMode]); useEffect(() => { if (TextAreaRef.current) { TextAreaRef.current.focus(); } }, []); return (
e.stopPropagation()}> e.stopPropagation()} onKeyDown={e => { e.stopPropagation(); if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); onConfirmPaste(); } else if (e.key === "Escape") { e.preventDefault(); onCancelPasteMode(); } }} onChange={e => { const value = e.target.value; const invalidChars = [ ...new Set( // @ts-expect-error TS doesn't recognize Intl.Segmenter in some environments [...new Intl.Segmenter().segment(value)] .map(x => x.segment) .filter(char => !chars[char]), ), ]; setInvalidChars(invalidChars); }} /> {invalidChars.length > 0 && (
The following characters won't be pasted:{" "} {invalidChars.join(", ")}
)}
); }