Added initial split screen mode and overall better UI sizing for terminals. (very buggy)

This commit is contained in:
Karmaa
2025-02-26 00:58:22 -06:00
parent cf5f0c41eb
commit cf8fff572a
3 changed files with 95 additions and 54 deletions

View File

@@ -23,7 +23,7 @@ function App() {
}); });
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false); const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
// Toggle Launchpad when "L" key is pressed // Handle keypress for opening launchpad
useEffect(() => { useEffect(() => {
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
if (e.ctrlKey && e.key === "l") { if (e.ctrlKey && e.key === "l") {
@@ -38,6 +38,7 @@ function App() {
}; };
}, []); }, []);
// Handle adding a host
const handleAddHost = () => { const handleAddHost = () => {
if (form.ip && form.user && form.password && form.port) { if (form.ip && form.user && form.password && form.port) {
const newTerminal = { const newTerminal = {
@@ -49,6 +50,8 @@ function App() {
password: form.password, password: form.password,
port: Number(form.port), port: Number(form.port),
}, },
isSplit: false,
terminalRef: null, // Reference to the terminal instance
}; };
setTerminals([...terminals, newTerminal]); setTerminals([...terminals, newTerminal]);
setActiveTab(nextId); setActiveTab(nextId);
@@ -60,6 +63,7 @@ function App() {
} }
}; };
// Close a terminal tab
const closeTab = (id) => { const closeTab = (id) => {
const newTerminals = terminals.filter((t) => t.id !== id); const newTerminals = terminals.filter((t) => t.id !== id);
setTerminals(newTerminals); setTerminals(newTerminals);
@@ -68,39 +72,42 @@ function App() {
} }
}; };
// Toggle split for a specific tab
const toggleSplit = (id) => {
setTerminals(terminals.map(t =>
t.id === id ? { ...t, isSplit: !t.isSplit } : t
));
};
// Get the split terminals
const splitTerminals = terminals.filter(t => t.isSplit);
const mainTerminal = terminals.find(t => t.id === activeTab);
return ( return (
<CssVarsProvider theme={theme}> <CssVarsProvider theme={theme}>
<div className="flex h-screen bg-neutral-900 overflow-hidden"> <div className="flex h-screen bg-neutral-900 overflow-hidden">
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
{/* Topbar */} {/* Topbar */}
<div className="bg-neutral-800 text-white p-4 flex items-center justify-between gap-4 min-h-[65px]"> <div className="bg-neutral-800 text-white p-4 flex items-center justify-between gap-4 min-h-[75px] max-h-[75px]">
{/* Left: Title */}
<div className="bg-neutral-700 flex justify-center items-center gap-2 p-3 rounded-lg h-[52px]"> <div className="bg-neutral-700 flex justify-center items-center gap-2 p-3 rounded-lg h-[52px]">
<img <img src={TermixIcon} alt="Termix Icon" className="w-[30px] h-[30px]" />
src={TermixIcon}
alt="Termix Icon"
className="w-[30px] h-[30px]"
/>
<h2 className="text-lg font-bold ml-[-2px]">Termix</h2> <h2 className="text-lg font-bold ml-[-2px]">Termix</h2>
</div> </div>
{/* Middle: Tabs with scroll */}
<div className="flex-1 bg-neutral-700 rounded-lg overflow-hidden h-[52px] flex items-center"> <div className="flex-1 bg-neutral-700 rounded-lg overflow-hidden h-[52px] flex items-center">
<div <div className="flex-1 overflow-x-auto overflow-y-hidden scrollbar-thin scrollbar-thumb-neutral-500 scrollbar-track-neutral-700 h-[52px] scrollbar-thumb-rounded-full scrollbar-track-rounded-full scrollbar-h-1">
className="flex-1 overflow-x-auto overflow-y-hidden scrollbar-thin scrollbar-thumb-neutral-500 scrollbar-track-neutral-700 h-[52px] scrollbar-thumb-rounded-full scrollbar-track-rounded-full scrollbar-h-1"
style={{ whiteSpace: "nowrap" }}
>
<TabList <TabList
terminals={terminals} terminals={terminals}
activeTab={activeTab} activeTab={activeTab}
setActiveTab={setActiveTab} setActiveTab={setActiveTab}
closeTab={closeTab} closeTab={closeTab}
toggleSplit={toggleSplit}
theme={theme} theme={theme}
/> />
</div> </div>
</div> </div>
{/* Open Launchpad Button */} {/* Launchpad Button */}
<Button <Button
onClick={() => setIsLaunchpadOpen(true)} onClick={() => setIsLaunchpadOpen(true)}
sx={{ sx={{
@@ -109,21 +116,13 @@ function App() {
flexShrink: 0, flexShrink: 0,
height: "52px", height: "52px",
width: "52px", width: "52px",
padding: 0, // To ensure no padding around the image padding: 0,
}} }}
> >
<img <img src={RocketIcon} alt="Launchpad" style={{ width: "70%", height: "70", objectFit: "contain" }} />
src={RocketIcon}
alt="L"
style={{
width: "70%",
height: "70",
objectFit: "contain",
}}
/>
</Button> </Button>
{/* Right: Create Host Button */} {/* Add Host Button */}
<Button <Button
onClick={() => setIsAddHostHidden(false)} onClick={() => setIsAddHostHidden(false)}
sx={{ sx={{
@@ -136,9 +135,7 @@ function App() {
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
lineHeight: "normal",
paddingTop: "2px", paddingTop: "2px",
verticalAlign: "middle",
}} }}
> >
+ +
@@ -146,20 +143,48 @@ function App() {
</div> </div>
{/* Terminal Views */} {/* Terminal Views */}
<div className="flex-1 relative pt-12 overflow-hidden"> <div className="flex-1 relative p-4">
{terminals.map((terminal) => ( {splitTerminals.length > 0 ? (
<div <div className={`grid ${splitTerminals.length === 1 ? 'grid-cols-1' : 'grid-cols-2'} gap-4 h-full`}>
key={terminal.id} {splitTerminals.map((terminal) => (
className={`absolute top-0 left-0 right-0 bottom-0 ${ <div key={terminal.id} className="bg-neutral-800 rounded-lg overflow-hidden shadow-xl border-5 border-neutral-700 h-full">
terminal.id === activeTab ? "block" : "hidden" <NewTerminal
}`} hostConfig={terminal.hostConfig}
> ref={(ref) => {
<NewTerminal hostConfig={terminal.hostConfig} /> if (ref && !terminal.terminalRef) {
// Store the terminal instance reference
setTerminals(prev => prev.map(t =>
t.id === terminal.id ? { ...t, terminalRef: ref } : t
));
}
}}
/>
</div>
))}
</div> </div>
))} ) : (
<div className="absolute top-4 left-4 right-4 bottom-4">
{mainTerminal && (
<div className="bg-neutral-800 rounded-lg overflow-hidden shadow-xl border-5 border-neutral-700 h-full">
<NewTerminal
hostConfig={mainTerminal.hostConfig}
ref={(ref) => {
if (ref && !mainTerminal.terminalRef) {
// Store the terminal instance reference
setTerminals(prev => prev.map(t =>
t.id === mainTerminal.id ? { ...t, terminalRef: ref } : t
));
}
}}
/>
</div>
)}
</div>
)}
</div> </div>
</div> </div>
{/* Modal for adding a host */}
<AddHostModal <AddHostModal
isHidden={isAddHostHidden} isHidden={isAddHostHidden}
form={form} form={form}
@@ -169,9 +194,7 @@ function App() {
/> />
{/* Launchpad Component */} {/* Launchpad Component */}
{isLaunchpadOpen && ( {isLaunchpadOpen && <Launchpad onClose={() => setIsLaunchpadOpen(false)} />}
<Launchpad onClose={() => setIsLaunchpadOpen(false)} />
)}
</div> </div>
</CssVarsProvider> </CssVarsProvider>
); );

View File

@@ -1,14 +1,11 @@
import {Button, ButtonGroup} from "@mui/joy"; import { Button, ButtonGroup } from "@mui/joy";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
function TabList({ terminals, activeTab, setActiveTab, closeTab, theme }) { function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, theme }) {
return ( return (
<div className="inline-flex items-center h-full px-[0.5rem]"> <div className="inline-flex items-center h-full px-[0.5rem]">
{terminals.map((terminal, index) => ( {terminals.map((terminal, index) => (
<div <div key={terminal.id} className={index < terminals.length - 1 ? "mr-[0.5rem]" : ""}>
key={terminal.id}
className={index < terminals.length - 1 ? "mr-[0.5rem]" : ""}
>
<ButtonGroup> <ButtonGroup>
<Button <Button
onClick={() => setActiveTab(terminal.id)} onClick={() => setActiveTab(terminal.id)}
@@ -29,6 +26,23 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, theme }) {
> >
{terminal.title} {terminal.title}
</Button> </Button>
<Button
onClick={() => toggleSplit(terminal.id)}
disabled={terminal.id === activeTab}
sx={{
backgroundColor: theme.palette.neutral[700],
color: theme.palette.text.primary,
"&:hover": { backgroundColor: theme.palette.neutral[300] },
borderTopRightRadius: "4px",
borderBottomRightRadius: "4px",
height: "40px",
fontSize: "1rem",
opacity: terminal.id === activeTab ? 0.5 : 1,
cursor: terminal.id === activeTab ? "not-allowed" : "pointer",
}}
>
/
</Button>
<Button <Button
onClick={() => closeTab(terminal.id)} onClick={() => closeTab(terminal.id)}
sx={{ sx={{
@@ -38,6 +52,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, theme }) {
borderTopRightRadius: "4px", borderTopRightRadius: "4px",
borderBottomRightRadius: "4px", borderBottomRightRadius: "4px",
height: "40px", height: "40px",
fontSize: "1rem",
}} }}
> >
× ×
@@ -54,6 +69,7 @@ TabList.propTypes = {
activeTab: PropTypes.any, activeTab: PropTypes.any,
setActiveTab: PropTypes.func.isRequired, setActiveTab: PropTypes.func.isRequired,
closeTab: PropTypes.func.isRequired, closeTab: PropTypes.func.isRequired,
toggleSplit: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired, theme: PropTypes.object.isRequired,
}; };

View File

@@ -16,7 +16,7 @@ export function NewTerminal({ hostConfig }) {
const terminal = new Terminal({ const terminal = new Terminal({
cursorBlink: true, cursorBlink: true,
theme: { theme: {
background: "#0f0f0f", background: "#242424",
foreground: "#ffffff", foreground: "#ffffff",
cursor: "#ffffff", cursor: "#ffffff",
}, },
@@ -36,19 +36,21 @@ export function NewTerminal({ hostConfig }) {
// Resize function // Resize function
const resizeTerminal = () => { const resizeTerminal = () => {
const terminalContainer = terminalRef.current; const terminalContainer = terminalRef.current;
const topbarHeight = 65; const parentContainer = terminalContainer?.parentElement;
const availableWidth = window.innerWidth;
const availableHeight = window.innerHeight - topbarHeight;
terminalContainer.style.width = `${availableWidth}px`; if (!parentContainer) return;
terminalContainer.style.height = `${availableHeight}px`;
const parentWidth = parentContainer.clientWidth;
const parentHeight = parentContainer.clientHeight;
terminalContainer.style.width = `${parentWidth}px`;
terminalContainer.style.height = `${parentHeight}px`;
fitAddon.fit(); fitAddon.fit();
const { cols, rows } = terminal; const { cols, rows } = terminal;
if (socketRef.current) { if (socketRef.current) {
socketRef.current.emit("resize", { cols, rows }); socketRef.current.emit("resize", { cols, rows });
console.log(`Terminal resized: cols=${cols}, rows=${rows}`);
} }
}; };
@@ -118,7 +120,7 @@ export function NewTerminal({ hostConfig }) {
return ( return (
<div <div
ref={terminalRef} ref={terminalRef}
className="w-full h-full min-h-[400px] overflow-hidden text-left" className="w-full h-full overflow-hidden text-left"
/> />
); );
} }