Improve keyboard view and fix various issues with it
This commit is contained in:
@@ -123,6 +123,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
@apply border-border outline-ring/50;
|
@apply border-border outline-ring/50;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,49 +98,6 @@ 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";
|
|
||||||
|
|
||||||
const preventKeyboard = () => {
|
|
||||||
textarea.blur();
|
|
||||||
};
|
|
||||||
|
|
||||||
textarea.addEventListener('focus', preventKeyboard);
|
|
||||||
textarea.blur();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
textarea.removeEventListener('focus', preventKeyboard);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, [terminal]);
|
|
||||||
|
|
||||||
function syncOverlay() {
|
|
||||||
if (!terminal || !overlayTextareaRef.current) return;
|
|
||||||
const buffer = terminal.buffer.active;
|
|
||||||
let text = "";
|
|
||||||
for (let i = 0; i < buffer.length; i++) {
|
|
||||||
text += buffer.getLine(i)?.translateToString() + "\n";
|
|
||||||
}
|
|
||||||
overlayTextareaRef.current.value = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!terminal) return;
|
|
||||||
syncOverlay();
|
|
||||||
|
|
||||||
const disposeRender = terminal.onRender(() => syncOverlay());
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
disposeRender.dispose();
|
|
||||||
};
|
|
||||||
}, [terminal]);
|
|
||||||
|
|
||||||
function handleWindowResize() {
|
function handleWindowResize() {
|
||||||
if (!isVisibleRef.current) return;
|
if (!isVisibleRef.current) return;
|
||||||
fitAddonRef.current?.fit();
|
fitAddonRef.current?.fit();
|
||||||
@@ -300,26 +257,10 @@ export const Terminal = forwardRef<any, SSHTerminalProps>(function SSHTerminal(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="h-full w-full m-1 relative"
|
ref={xtermRef}
|
||||||
style={{ opacity: visible && isVisible ? 1 : 0 }}
|
className="h-full w-full m-1"
|
||||||
>
|
style={{opacity: visible && isVisible ? 1 : 0, overflow: 'hidden'}}
|
||||||
<div ref={xtermRef} className="h-full w-full" />
|
|
||||||
|
|
||||||
<textarea
|
|
||||||
ref={overlayTextareaRef}
|
|
||||||
readOnly
|
|
||||||
className="absolute top-0 left-0 w-full h-full"
|
|
||||||
style={{
|
|
||||||
opacity: 0.01,
|
|
||||||
cursor: "text",
|
|
||||||
background: "none",
|
|
||||||
border: "none",
|
|
||||||
resize: "none",
|
|
||||||
userSelect: "text",
|
|
||||||
WebkitUserSelect: "text",
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,6 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) {
|
|||||||
const [isCtrl, setIsCtrl] = useState(false);
|
const [isCtrl, setIsCtrl] = useState(false);
|
||||||
const [isAlt, setIsAlt] = useState(false);
|
const [isAlt, setIsAlt] = useState(false);
|
||||||
|
|
||||||
const handlePaste = useCallback(async () => {
|
|
||||||
if (navigator.clipboard?.readText) {
|
|
||||||
try {
|
|
||||||
const text = await navigator.clipboard.readText();
|
|
||||||
if (text) {
|
|
||||||
onSendInput(text);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Paste failed:", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [onSendInput]);
|
|
||||||
|
|
||||||
const onKeyPress = useCallback((button: string) => {
|
const onKeyPress = useCallback((button: string) => {
|
||||||
if (button === "{shift}") {
|
if (button === "{shift}") {
|
||||||
setLayoutName("shift");
|
setLayoutName("shift");
|
||||||
@@ -60,11 +47,6 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button === "{paste}") {
|
|
||||||
handlePaste();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let input = button;
|
let input = button;
|
||||||
|
|
||||||
const specialKeyMap: { [key: string]: string } = {
|
const specialKeyMap: { [key: string]: string } = {
|
||||||
@@ -103,7 +85,7 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSendInput(input);
|
onSendInput(input);
|
||||||
}, [onSendInput, handlePaste, isCtrl, isAlt]);
|
}, [onSendInput, isCtrl, isAlt]);
|
||||||
|
|
||||||
const buttonTheme = [
|
const buttonTheme = [
|
||||||
{
|
{
|
||||||
@@ -151,7 +133,7 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) {
|
|||||||
"! @ # $ % ^ & * ( ) _ +",
|
"! @ # $ % ^ & * ( ) _ +",
|
||||||
"[ ] { } | \\ ; : ' \" , . / < >",
|
"[ ] { } | \\ ; : ' \" , . / < >",
|
||||||
"F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12",
|
"F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12",
|
||||||
"{arrowLeft} {arrowRight} {arrowUp} {arrowDown} {paste} {backspace}",
|
"{arrowLeft} {arrowRight} {arrowUp} {arrowDown} {backspace}",
|
||||||
"{hide} {less} {space} {enter}",
|
"{hide} {less} {space} {enter}",
|
||||||
],
|
],
|
||||||
hide: [
|
hide: [
|
||||||
@@ -178,7 +160,6 @@ export function TerminalKeyboard({onSendInput}: TerminalKeyboardProps) {
|
|||||||
"{tab}": "tab",
|
"{tab}": "tab",
|
||||||
"{ctrl}": "ctrl",
|
"{ctrl}": "ctrl",
|
||||||
"{alt}": "alt",
|
"{alt}": "alt",
|
||||||
"{paste}": "paste",
|
|
||||||
"{end}": "end",
|
"{end}": "end",
|
||||||
"{home}": "home",
|
"{home}": "home",
|
||||||
"{pgUp}": "pgUp",
|
"{pgUp}": "pgUp",
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ const AppContent: FC = () => {
|
|||||||
{tabs.map(tab => (
|
{tabs.map(tab => (
|
||||||
<div
|
<div
|
||||||
key={tab.id}
|
key={tab.id}
|
||||||
className="absolute inset-0"
|
className="absolute inset-0 mb-2"
|
||||||
style={{
|
style={{
|
||||||
visibility: tab.id === currentTab ? 'visible' : 'hidden',
|
visibility: tab.id === currentTab ? 'visible' : 'hidden',
|
||||||
opacity: ready ? 1 : 0,
|
opacity: ready ? 1 : 0,
|
||||||
@@ -148,13 +148,18 @@ const AppContent: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{tabs.length === 0 && (
|
{tabs.length === 0 && (
|
||||||
<div className="flex items-center justify-center h-full text-white">
|
<div className="flex flex-col items-center justify-center h-full text-white gap-3 px-4 text-center">
|
||||||
Select a host to start a terminal session.
|
<h1 className="text-lg font-semibold">
|
||||||
|
Select a host to start your terminal session
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-gray-300 max-w-xs">
|
||||||
|
Mobile support is currently limited. A dedicated mobile app is coming soon to enhance your experience.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{currentTab &&
|
{currentTab &&
|
||||||
<div className="mb-1">
|
<div className="mb-1 z-10">
|
||||||
<TerminalKeyboard onSendInput={handleKeyboardInput}/>
|
<TerminalKeyboard onSendInput={handleKeyboardInput}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export function LeftSidebar({isSidebarOpen, setIsSidebarOpen, onHostConnect, dis
|
|||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<Separator/>
|
<Separator/>
|
||||||
<SidebarContent className="px-2 py-2">
|
<SidebarContent className="px-2 py-2">
|
||||||
<div className="!bg-[#222225] rounded-lg mb-2">
|
<div className="!bg-[#222225] rounded-lg">
|
||||||
<Input
|
<Input
|
||||||
value={search}
|
value={search}
|
||||||
onChange={e => setSearch(e.target.value)}
|
onChange={e => setSearch(e.target.value)}
|
||||||
|
|||||||
Reference in New Issue
Block a user