Improve mobile support with half-baked custom keyboard
This commit is contained in:
2
.env
2
.env
@@ -1,2 +1,2 @@
|
|||||||
VERSION=1.5.0
|
VERSION=1.5.0
|
||||||
VITE_API_HOST=10.31.2.21
|
VITE_API_HOST=localhost
|
||||||
11
package-lock.json
generated
11
package-lock.json
generated
@@ -66,6 +66,7 @@
|
|||||||
"react-i18next": "^15.7.3",
|
"react-i18next": "^15.7.3",
|
||||||
"react-resizable-panels": "^3.0.3",
|
"react-resizable-panels": "^3.0.3",
|
||||||
"react-responsive": "^10.0.1",
|
"react-responsive": "^10.0.1",
|
||||||
|
"react-simple-keyboard": "^3.8.120",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
@@ -12275,6 +12276,16 @@
|
|||||||
"react": ">=16.8.0"
|
"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": {
|
"node_modules/react-style-singleton": {
|
||||||
"version": "2.2.3",
|
"version": "2.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
|
"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-i18next": "^15.7.3",
|
||||||
"react-resizable-panels": "^3.0.3",
|
"react-resizable-panels": "^3.0.3",
|
||||||
"react-responsive": "^10.0.1",
|
"react-responsive": "^10.0.1",
|
||||||
|
"react-simple-keyboard": "^3.8.120",
|
||||||
"react-xtermjs": "^1.0.10",
|
"react-xtermjs": "^1.0.10",
|
||||||
"sonner": "^2.0.7",
|
"sonner": "^2.0.7",
|
||||||
"speakeasy": "^2.0.0",
|
"speakeasy": "^2.0.0",
|
||||||
|
|||||||
@@ -108,6 +108,17 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
return () => window.removeEventListener('resize', handleWindowResize);
|
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() {
|
function handleWindowResize() {
|
||||||
if (!isVisibleRef.current) return;
|
if (!isVisibleRef.current) return;
|
||||||
fitAddonRef.current?.fit();
|
fitAddonRef.current?.fit();
|
||||||
@@ -168,7 +179,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
scrollback: 10000,
|
scrollback: 10000,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace',
|
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,
|
allowTransparency: true,
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
windowsMode: false,
|
windowsMode: false,
|
||||||
@@ -209,7 +220,6 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||||
hardRefresh();
|
hardRefresh();
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
terminal.focus();
|
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -229,7 +239,7 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
scrollback: 10000,
|
scrollback: 10000,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontFamily: '"JetBrains Mono Nerd Font", "MesloLGS NF", "FiraCode Nerd Font", "Cascadia Code", "JetBrains Mono", Consolas, "Courier New", monospace',
|
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,
|
allowTransparency: true,
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
windowsMode: false,
|
windowsMode: false,
|
||||||
@@ -274,7 +284,6 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||||
hardRefresh();
|
hardRefresh();
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
terminal.focus();
|
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
const cols = terminal.cols;
|
const cols = terminal.cols;
|
||||||
@@ -313,7 +322,6 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
fitAddonRef.current?.fit();
|
fitAddonRef.current?.fit();
|
||||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||||
hardRefresh();
|
hardRefresh();
|
||||||
terminal.focus();
|
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
}, [isVisible, terminal]);
|
}, [isVisible, terminal]);
|
||||||
@@ -324,9 +332,6 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
fitAddonRef.current?.fit();
|
fitAddonRef.current?.fit();
|
||||||
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
if (terminal) scheduleNotify(terminal.cols, terminal.rows);
|
||||||
hardRefresh();
|
hardRefresh();
|
||||||
if (terminal && isVisible) {
|
|
||||||
terminal.focus();
|
|
||||||
}
|
|
||||||
}, 0);
|
}, 0);
|
||||||
}, [isVisible, terminal]);
|
}, [isVisible, terminal]);
|
||||||
|
|
||||||
@@ -335,9 +340,6 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
ref={xtermRef}
|
ref={xtermRef}
|
||||||
className="h-full w-full m-1"
|
className="h-full w-full m-1"
|
||||||
style={{opacity: visible && isVisible ? 1 : 0, overflow: 'hidden'}}
|
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 {Terminal} from "@/ui/Mobile/Apps/Terminal/Terminal.tsx";
|
||||||
|
import {TerminalKeyboard} from "@/ui/Mobile/Apps/Terminal/TerminalKeyboard.tsx";
|
||||||
|
|
||||||
export function MobileApp() {
|
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 (
|
return (
|
||||||
<div className="h-screen w-screen bg-[#18181b]">
|
<div className="h-screen w-screen flex flex-col bg-[#09090b] overflow-y-hidden overflow-x-hidden">
|
||||||
<Terminal hostConfig={{
|
<Terminal
|
||||||
ip: "n/a",
|
ref={terminalRef}
|
||||||
|
hostConfig={{
|
||||||
|
ip: "192.210.197.55",
|
||||||
port: 22,
|
port: 22,
|
||||||
username: "n/a",
|
username: "bugattiguy527",
|
||||||
password: "n/a"
|
password: "bugatti$123"
|
||||||
}} isVisible={true}/>
|
}}
|
||||||
|
isVisible={true}
|
||||||
|
/>
|
||||||
|
<TerminalKeyboard
|
||||||
|
onSendInput={handleKeyboardInput}
|
||||||
|
/>
|
||||||
|
<div className="w-full h-[80px] bg-[#18181BFF]">
|
||||||
|
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user