Improve mobile support with half-baked custom keyboard
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -108,6 +108,17 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(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<any, SSHTerminalProps>(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<any, SSHTerminalProps>(function SSHTerminal(
|
||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||
hardRefresh();
|
||||
setVisible(true);
|
||||
terminal.focus();
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
@@ -229,7 +239,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(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<any, SSHTerminalProps>(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<any, SSHTerminalProps>(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<any, SSHTerminalProps>(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<any, SSHTerminalProps>(function SSHTerminal(
|
||||
ref={xtermRef}
|
||||
className="h-full w-full m-1"
|
||||
style={{opacity: visible && isVisible ? 1 : 0, overflow: 'hidden'}}
|
||||
onClick={() => {
|
||||
terminal.focus();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
107
src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx
Normal file
107
src/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx
Normal file
@@ -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 (
|
||||
<div className="">
|
||||
<Keyboard
|
||||
layout={{
|
||||
default: [
|
||||
"q w e r t y u i o p",
|
||||
"a s d f g h j k l",
|
||||
"{shift} z x c v b n m {backspace}",
|
||||
"{hide} {more} {space} {enter}",
|
||||
],
|
||||
shift: [
|
||||
"Q W E R T Y U I O P",
|
||||
"A S D F G H J K L",
|
||||
"{unshift} Z X C V B N M {backspace}",
|
||||
"{hide} {more} {space} {enter}",
|
||||
],
|
||||
more: [
|
||||
"{arrowLeft} {arrowRight} {arrowUp} {arrowDown} {backspace}",
|
||||
"{hide} {less} {space} {enter}",
|
||||
],
|
||||
hide: [
|
||||
"{unhide}"
|
||||
]
|
||||
}}
|
||||
layoutName={layoutName}
|
||||
onKeyPress={onKeyPress}
|
||||
display={{
|
||||
"{shift}": "up",
|
||||
"{unshift}": "dn",
|
||||
"{backspace}": "del",
|
||||
"{more}": "more",
|
||||
"{less}": "less",
|
||||
"{space}": "space",
|
||||
"{enter}": "enter",
|
||||
"{arrowLeft}": "←",
|
||||
"{arrowRight}": "→",
|
||||
"{arrowUp}": "↑",
|
||||
"{arrowDown}": "↓",
|
||||
"{hide}": "hide",
|
||||
"{unhide}": "unhide",
|
||||
}}
|
||||
theme={"hg-theme-default dark-theme"}
|
||||
useTouchEvents={true}
|
||||
buttonTheme={[
|
||||
{
|
||||
class: "hg-space-big",
|
||||
buttons: "{space}",
|
||||
},
|
||||
{
|
||||
class: "hg-space-medium",
|
||||
buttons: "{enter} {backspace}",
|
||||
},
|
||||
{
|
||||
class: "hg-space-small",
|
||||
buttons: "{hide} {less} {more}",
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
35
src/ui/Mobile/Apps/Terminal/kb-dark-theme.css
Normal file
35
src/ui/Mobile/Apps/Terminal/kb-dark-theme.css
Normal file
@@ -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;
|
||||
}
|
||||
@@ -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<any>(null);
|
||||
|
||||
function handleKeyboardInput(input: string) {
|
||||
if (!terminalRef.current?.sendInput) return;
|
||||
|
||||
const keyMap: Record<string, string> = {
|
||||
"{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 (
|
||||
<div className="h-screen w-screen bg-[#18181b]">
|
||||
<Terminal hostConfig={{
|
||||
ip: "n/a",
|
||||
port: 22,
|
||||
username: "n/a",
|
||||
password: "n/a"
|
||||
}} isVisible={true}/>
|
||||
<div className="h-screen w-screen flex flex-col bg-[#09090b] overflow-y-hidden overflow-x-hidden">
|
||||
<Terminal
|
||||
ref={terminalRef}
|
||||
hostConfig={{
|
||||
ip: "192.210.197.55",
|
||||
port: 22,
|
||||
username: "bugattiguy527",
|
||||
password: "bugatti$123"
|
||||
}}
|
||||
isVisible={true}
|
||||
/>
|
||||
<TerminalKeyboard
|
||||
onSendInput={handleKeyboardInput}
|
||||
/>
|
||||
<div className="w-full h-[80px] bg-[#18181BFF]">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user