Fixed multi-line command issues with switching between split and not split.

This commit is contained in:
Karmaa
2025-03-09 14:06:07 -05:00
parent 0a81bea075
commit 5ce3a80c5d
5 changed files with 80 additions and 49 deletions

View File

@@ -1,6 +1,6 @@
import PropTypes from 'prop-types';
import { CssVarsProvider } from '@mui/joy/styles';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, Select, Option, FormHelperText } from '@mui/joy';
import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, Select, Option } from '@mui/joy';
import theme from './theme';
const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => {
@@ -31,15 +31,22 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
<CssVarsProvider theme={theme}>
<Modal open={!isHidden} onClose={() => setIsAddHostHidden(true)}>
<ModalDialog
layout="center"
sx={{
backgroundColor: theme.palette.general.tertiary,
borderColor: theme.palette.general.secondary,
color: theme.palette.text.primary,
padding: 3,
borderRadius: 10,
overflowX: 'hidden',
overflowY: 'auto',
}}>
width: "auto",
maxWidth: "90vw",
minWidth: "fit-content",
overflow: "hidden",
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<DialogTitle>Add Host</DialogTitle>
<DialogContent>
<form
@@ -48,7 +55,7 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd
if (isFormValid()) handleAddHost();
}}
>
<Stack spacing={2}>
<Stack spacing={2} sx={{ width: "100%", maxWidth: "100%", overflow: "hidden" }}>
<FormControl>
<FormLabel>Host Name</FormLabel>
<Input

View File

@@ -5,12 +5,11 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp
const isSplitScreenActive = splitTabIds.length > 0;
return (
<div className="inline-flex items-center h-full px-[0.5rem]">
<div className="tablist inline-flex items-center h-full px-[0.5rem] overflow-x-auto">
{terminals.map((terminal, index) => {
const isActive = terminal.id === activeTab;
const isSplit = splitTabIds.includes(terminal.id);
const isSplitButtonDisabled = isActive && !isSplitScreenActive || splitTabIds.length >= 3 && !isSplit;
const isSplitButtonDisabled = (isActive && !isSplitScreenActive) || (splitTabIds.length >= 3 && !isSplit);
return (
<div key={terminal.id} className={index < terminals.length - 1 ? "mr-[0.5rem]" : ""}>
@@ -20,8 +19,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp
onClick={() => setActiveTab(terminal.id)}
disabled={isSplit}
sx={{
backgroundColor:
isActive ? theme.palette.general.primary : theme.palette.general.disabled,
backgroundColor: isActive ? theme.palette.general.primary : theme.palette.general.disabled,
color: theme.palette.text.primary,
"&:hover": { backgroundColor: theme.palette.general.secondary },
":disabled": { backgroundColor: theme.palette.general.disabled },
@@ -40,9 +38,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp
onClick={() => toggleSplit(terminal.id)}
disabled={isSplitButtonDisabled || isActive}
sx={{
backgroundColor: isSplit
? theme.palette.general.primary
: theme.palette.general.tertiary,
backgroundColor: isSplit ? theme.palette.general.primary : theme.palette.general.tertiary,
color: theme.palette.text.primary,
":disabled": { backgroundColor: theme.palette.general.disabled },
"&:hover": { backgroundColor: theme.palette.general.secondary },
@@ -58,7 +54,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp
{/* Close Tab Button */}
<Button
onClick={() => closeTab(terminal.id)}
disabled={isSplitScreenActive && isActive || isSplit}
disabled={(isSplitScreenActive && isActive) || isSplit}
sx={{
backgroundColor: theme.palette.general.tertiary,
color: theme.palette.text.primary,
@@ -90,4 +86,4 @@ TabList.propTypes = {
theme: PropTypes.object.isRequired,
};
export default TabList;
export default TabList;

View File

@@ -16,22 +16,21 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
const terminalContainer = terminalRef.current;
const parentContainer = terminalContainer?.parentElement;
if (!parentContainer || !isVisible) return;
if (!parentContainer || parentContainer.clientWidth === 0) return;
void parentContainer.offsetHeight;
const parentWidth = parentContainer.clientWidth;
const parentHeight = parentContainer.clientHeight;
const parentWidth = parentContainer.clientWidth - 10;
const parentHeight = parentContainer.clientHeight - 10;
terminalContainer.style.width = `${parentWidth}px`;
terminalContainer.style.height = `${parentHeight}px`;
fitAddon.current.fit();
if (socketRef.current && terminalInstance.current) {
const { cols, rows } = terminalInstance.current;
socketRef.current.emit("resize", { cols, rows });
}
requestAnimationFrame(() => {
fitAddon.current.fit();
if (socketRef.current && terminalInstance.current) {
const { cols, rows } = terminalInstance.current;
socketRef.current.emit("resize", { cols, rows });
}
});
};
useImperativeHandle(ref, () => ({
@@ -50,7 +49,6 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
},
fontSize: 14,
scrollback: 1000,
fontFamily: 'monospace',
ignoreBracketedPasteMode: true,
});
@@ -86,24 +84,29 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
terminalInstance.current.write(decoder.decode(new Uint8Array(data)));
});
let isPasting = false;
terminalInstance.current.onData((data) => {
socketRef.current.emit("data", data);
});
terminalInstance.current.attachCustomKeyEventHandler((event) => {
if (
(event.ctrlKey && event.key === "v") ||
(event.metaKey && event.key === "v") ||
(event.shiftKey && event.key === "Insert")
) {
navigator.clipboard
.readText()
.then((text) => {
socketRef.current.emit("data", text);
})
.catch((err) => {
console.error("Failed to read clipboard contents:", err);
});
console.log("Event caled");
if (isPasting) return;
isPasting = true;
setTimeout(() => {
isPasting = false;
}, 200);
if ((event.ctrlKey || event.metaKey) && event.key === "v") {
event.preventDefault();
navigator.clipboard.readText().then((text) => {
socketRef.current.emit("data", text);
}).catch((err) => {
console.error("Failed to read clipboard contents:", err);
});
return false;
}
return true;
@@ -129,20 +132,21 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
}, [hostConfig]);
useEffect(() => {
if (isVisible) {
resizeTerminal();
}
resizeTerminal();
}, [isVisible]);
useEffect(() => {
const terminalContainer = terminalRef.current;
if (!terminalContainer) return;
const parentContainer = terminalContainer.parentElement;
if (!parentContainer) return;
const observer = new ResizeObserver(() => {
resizeTerminal();
});
observer.observe(terminalContainer);
observer.observe(parentContainer);
return () => {
observer.disconnect();
@@ -153,7 +157,13 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
<div
ref={terminalRef}
className="w-full h-full overflow-hidden text-left"
style={{ display: isVisible ? "block" : "none" }}
style={{
visibility: isVisible ? 'visible' : 'hidden',
position: 'absolute',
width: '100%',
height: '100%',
transform: 'translateY(5px) translateX(5px)',
}}
/>
);
});
@@ -168,4 +178,4 @@ NewTerminal.propTypes = {
port: PropTypes.string.isRequired,
}).isRequired,
isVisible: PropTypes.bool.isRequired,
};
};

View File

@@ -46,11 +46,10 @@ io.on("connection", (socket) => {
socket.emit("error", err.message);
return;
}
stream = newStream;
// Set initial terminal size
stream.setWindow(rows, cols, rows * 100, cols * 100);
console.log(`Initial terminal size: cols=${cols}, rows=${rows}`);
// Pipe SSH output to client
stream.on("data", function (data) {
@@ -71,7 +70,6 @@ io.on("connection", (socket) => {
socket.on("resize", ({ cols, rows }) => {
if (stream && stream.setWindow) {
stream.setWindow(rows, cols, rows * 100, cols * 100);
console.log(`Terminal resized: cols=${cols}, rows=${rows}`);
}
});

View File

@@ -15,4 +15,24 @@
.terminal-container > div {
min-height: 0;
}
.tablist::-webkit-scrollbar {
width: 1px !important;
height: 1px !important;
background: transparent !important;
}
.tablist::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2) !important;
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
}