diff --git a/.env b/.env index 35279ff7..6ee5e06e 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ VERSION=1.5.0 -VITE_API_HOST=10.31.2.21 \ No newline at end of file +VITE_API_HOST=localhost \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a9ede780..146574d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,7 @@ "react-i18next": "^15.7.3", "react-resizable-panels": "^3.0.3", "react-responsive": "^10.0.1", + "react-simple-keyboard": "^3.8.120", "react-xtermjs": "^1.0.10", "sonner": "^2.0.7", "speakeasy": "^2.0.0", @@ -12275,6 +12276,16 @@ "react": ">=16.8.0" } }, + "node_modules/react-simple-keyboard": { + "version": "3.8.120", + "resolved": "https://registry.npmjs.org/react-simple-keyboard/-/react-simple-keyboard-3.8.120.tgz", + "integrity": "sha512-VREEGZWXUeqRKvRVg0n8hmoAqz/TSWZEs5UwbfLuan4yKvOQZUFHtS11QGnvIVYjkThh+JYslO2CHT4Lxf5d0w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/package.json b/package.json index 611c2d89..9ab75270 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-i18next": "^15.7.3", "react-resizable-panels": "^3.0.3", "react-responsive": "^10.0.1", + "react-simple-keyboard": "^3.8.120", "react-xtermjs": "^1.0.10", "sonner": "^2.0.7", "speakeasy": "^2.0.0", diff --git a/src/ui/Mobile/Apps/Terminal/Terminal.tsx b/src/ui/Mobile/Apps/Terminal/Terminal.tsx index 0717ba0a..5b3d1435 100644 --- a/src/ui/Mobile/Apps/Terminal/Terminal.tsx +++ b/src/ui/Mobile/Apps/Terminal/Terminal.tsx @@ -108,6 +108,17 @@ export const Terminal = forwardRef(function SSHTerminal( return () => window.removeEventListener('resize', handleWindowResize); }, []); + useEffect(() => { + if (!terminal) return; + + const textarea = (terminal as any)._core?._textarea as HTMLTextAreaElement | undefined; + if (textarea) { + textarea.setAttribute("readonly", "true"); + textarea.setAttribute("inputmode", "none"); + textarea.style.caretColor = "transparent"; + } + }, [terminal]); + function handleWindowResize() { if (!isVisibleRef.current) return; fitAddonRef.current?.fit(); @@ -168,7 +179,7 @@ export const Terminal = forwardRef(function SSHTerminal( scrollback: 10000, fontSize: 14, fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace', - theme: {background: '#18181b', foreground: '#f7f7f7'}, + theme: {background: '#09090b', foreground: '#f7f7f7'}, allowTransparency: true, convertEol: true, windowsMode: false, @@ -209,7 +220,6 @@ export const Terminal = forwardRef(function SSHTerminal( if (terminal) scheduleNotify(terminal.cols, terminal.rows); hardRefresh(); setVisible(true); - terminal.focus(); }, 100); return () => { @@ -229,7 +239,7 @@ export const Terminal = forwardRef(function SSHTerminal( scrollback: 10000, fontSize: 14, fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace', - theme: {background: '#18181b', foreground: '#f7f7f7'}, + theme: {background: '#09090b', foreground: '#f7f7f7'}, allowTransparency: true, convertEol: true, windowsMode: false, @@ -274,7 +284,6 @@ export const Terminal = forwardRef(function SSHTerminal( if (terminal) scheduleNotify(terminal.cols, terminal.rows); hardRefresh(); setVisible(true); - terminal.focus(); }, 0); const cols = terminal.cols; @@ -313,7 +322,6 @@ export const Terminal = forwardRef(function SSHTerminal( fitAddonRef.current?.fit(); if (terminal) scheduleNotify(terminal.cols, terminal.rows); hardRefresh(); - terminal.focus(); }, 0); } }, [isVisible, terminal]); @@ -324,9 +332,6 @@ export const Terminal = forwardRef(function SSHTerminal( fitAddonRef.current?.fit(); if (terminal) scheduleNotify(terminal.cols, terminal.rows); hardRefresh(); - if (terminal && isVisible) { - terminal.focus(); - } }, 0); }, [isVisible, terminal]); @@ -335,9 +340,6 @@ export const Terminal = forwardRef(function SSHTerminal( ref={xtermRef} className="h-full w-full m-1" style={{opacity: visible && isVisible ? 1 : 0, overflow: 'hidden'}} - onClick={() => { - terminal.focus(); - }} /> ); }); diff --git a/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx new file mode 100644 index 00000000..ca295c52 --- /dev/null +++ b/src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx @@ -0,0 +1,107 @@ +import React, {useState} from "react"; +import Keyboard from "react-simple-keyboard"; +import "react-simple-keyboard/build/css/index.css"; +import "./kb-dark-theme.css"; + +interface TerminalKeyboardProps { + onSendInput: (input: string) => void; +} + +export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) { + const [layoutName, setLayoutName] = useState("default"); + + const onKeyPress = (button: string) => { + if (button === "{shift}") { + setLayoutName("shift"); + return; + } + + if (button === "{unshift}") { + setLayoutName("default"); + return; + } + + if (button === "{more}") { + setLayoutName("more") + return; + } + + if (button === "{less}") { + setLayoutName("default"); + return; + } + + if (button === "{hide}") { + setLayoutName("hide"); + return; + } + + if (button === "{unhide}") { + setLayoutName("default"); + return; + } + + onSendInput(button); + }; + + return ( +
+ +
+ ); +} \ No newline at end of file diff --git a/src/ui/Mobile/Apps/Terminal/kb-dark-theme.css b/src/ui/Mobile/Apps/Terminal/kb-dark-theme.css new file mode 100644 index 00000000..d2f70424 --- /dev/null +++ b/src/ui/Mobile/Apps/Terminal/kb-dark-theme.css @@ -0,0 +1,35 @@ +.simple-keyboard.dark-theme { + background-color: rgb(24, 24, 27); + border-radius: 0; +} + +.simple-keyboard.dark-theme .hg-button { + height: 50px; + display: flex; + justify-content: center; + align-items: center; + background: rgba(0, 0, 0, 0.5); + color: #bfbfbf; + border-bottom-color: rgb(122, 122, 122); +} + +.simple-keyboard.dark-theme .hg-button:active { + background: rgba(83, 83, 83, 0.5); + color: #bfbfbf; +} + +#root .simple-keyboard.dark-theme + .simple-keyboard-preview { + background: rgba(83, 83, 83, 0.5); +} + +.hg-space-big { + width: 100px; +} + +.hg-space-medium { + width: 60px; +} + +.hg-space-small { + width: 1px; +} \ No newline at end of file diff --git a/src/ui/Mobile/MobileApp.tsx b/src/ui/Mobile/MobileApp.tsx index 8b04071e..73591a7a 100644 --- a/src/ui/Mobile/MobileApp.tsx +++ b/src/ui/Mobile/MobileApp.tsx @@ -1,14 +1,55 @@ +import {useRef} from "react"; import {Terminal} from "@/ui/Mobile/Apps/Terminal/Terminal.tsx"; +import {TerminalKeyboard} from "@/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx"; export function MobileApp() { + const terminalRef = useRef(null); + + function handleKeyboardInput(input: string) { + if (!terminalRef.current?.sendInput) return; + + const keyMap: Record = { + "{backspace}": "\x7f", + "{space}": " ", + "{tab}": "\t", + "{enter}": "\r", + "{escape}": "\x1b", + "{arrowUp}": "\x1b[A", + "{arrowDown}": "\x1b[B", + "{arrowRight}": "\x1b[C", + "{arrowLeft}": "\x1b[D", + "{delete}": "\x1b[3~", + "{home}": "\x1b[H", + "{end}": "\x1b[F", + "{pageUp}": "\x1b[5~", + "{pageDown}": "\x1b[6~", + }; + + if (input in keyMap) { + terminalRef.current.sendInput(keyMap[input]); + } else { + terminalRef.current.sendInput(input); + } + } + return ( -
- +
+ + +
+ +
) } \ No newline at end of file