Another UI overall. Shift to launchpad, removed sidebar, turned everything into components.

This commit is contained in:
Karmaa
2025-02-25 00:08:38 -06:00
parent b43ca54fa0
commit cf5f0c41eb
6 changed files with 197 additions and 66 deletions

View File

@@ -1,9 +1,13 @@
import { useState } from "react";
import { useState, useEffect } from "react";
import { NewTerminal } from "./Terminal.jsx";
import AddHostModal from './AddHostModal.jsx';
import {Button, ButtonGroup} from '@mui/joy';
import { CssVarsProvider } from '@mui/joy';
import theme from './theme';
import AddHostModal from "./AddHostModal.jsx";
import { Button } from "@mui/joy";
import { CssVarsProvider } from "@mui/joy";
import theme from "./theme";
import TabList from "./TabList.jsx";
import Launchpad from "./Launchpad.jsx";
import TermixIcon from "./images/termix_icon.png";
import RocketIcon from './images/launchpad_rocket.png';
function App() {
const [isAddHostHidden, setIsAddHostHidden] = useState(true);
@@ -15,8 +19,24 @@ function App() {
ip: "",
user: "",
password: "",
port: 22
port: 22,
});
const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false);
// Toggle Launchpad when "L" key is pressed
useEffect(() => {
const handleKeyDown = (e) => {
if (e.ctrlKey && e.key === "l") {
e.preventDefault();
setIsLaunchpadOpen((prev) => !prev);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => {
window.removeEventListener("keydown", handleKeyDown);
};
}, []);
const handleAddHost = () => {
if (form.ip && form.user && form.password && form.port) {
@@ -41,7 +61,7 @@ function App() {
};
const closeTab = (id) => {
const newTerminals = terminals.filter(t => t.id !== id);
const newTerminals = terminals.filter((t) => t.id !== id);
setTerminals(newTerminals);
if (activeTab === id) {
setActiveTab(newTerminals[0]?.id || null);
@@ -51,74 +71,88 @@ function App() {
return (
<CssVarsProvider theme={theme}>
<div className="flex h-screen bg-neutral-900 overflow-hidden">
{/* Sidebar */}
<div className="w-64 bg-neutral-800 text-white p-6 flex flex-col justify-between fixed left-0 top-0 bottom-0">
<div className="flex flex-col items-center">
<h2 className="text-2xl font-bold mb-8">Termix</h2>
<div className="flex-1 flex flex-col overflow-hidden">
{/* Topbar */}
<div className="bg-neutral-800 text-white p-4 flex items-center justify-between gap-4 min-h-[65px]">
{/* Left: Title */}
<div className="bg-neutral-700 flex justify-center items-center gap-2 p-3 rounded-lg h-[52px]">
<img
src={TermixIcon}
alt="Termix Icon"
className="w-[30px] h-[30px]"
/>
<h2 className="text-lg font-bold ml-[-2px]">Termix</h2>
</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 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
terminals={terminals}
activeTab={activeTab}
setActiveTab={setActiveTab}
closeTab={closeTab}
theme={theme}
/>
</div>
</div>
{/* Open Launchpad Button */}
<Button
onClick={() => setIsLaunchpadOpen(true)}
sx={{
backgroundColor: theme.palette.neutral[700],
"&:hover": { backgroundColor: theme.palette.neutral[300] },
flexShrink: 0,
height: "52px",
width: "52px",
padding: 0, // To ensure no padding around the image
}}
>
<img
src={RocketIcon}
alt="L"
style={{
width: "70%",
height: "70",
objectFit: "contain",
}}
/>
</Button>
{/* Right: Create Host Button */}
<Button
onClick={() => setIsAddHostHidden(false)}
sx={{
backgroundColor: theme.palette.neutral[500],
'&:hover': {
backgroundColor: theme.palette.neutral[900],
},
backgroundColor: theme.palette.neutral[700],
"&:hover": { backgroundColor: theme.palette.neutral[300] },
flexShrink: 0,
height: "52px",
width: "52px",
fontSize: "3.5rem",
display: "flex",
justifyContent: "center",
alignItems: "center",
lineHeight: "normal",
paddingTop: "2px",
verticalAlign: "middle",
}}
>
Create Host
+
</Button>
</div>
</div>
{/* Main Content Area */}
<div className="flex-1 flex flex-col ml-64 overflow-hidden">
{/* Topbar */}
<div className="bg-neutral-800 text-white p-4 flex justify-between items-center space-x-2 overflow-x-auto whitespace-nowrap min-h-[64px]">
<div className="flex items-center gap-2">
{terminals.map((terminal, index) => (
<div key={terminal.id} className="flex items-center gap-2">
{/* Tab Button Group */}
<ButtonGroup>
<Button
onClick={() => setActiveTab(terminal.id)}
sx={{
backgroundColor: terminal.id === activeTab ? theme.palette.neutral[500] : theme.palette.neutral[900],
color: theme.palette.text.primary,
'&:hover': {
backgroundColor: theme.palette.neutral[300],
},
}}
>
{terminal.title}
</Button>
<Button
onClick={() => closeTab(terminal.id)}
sx={{
backgroundColor: theme.palette.neutral[700],
color: theme.palette.text.primary,
'&:hover': {
backgroundColor: theme.palette.neutral[300],
},
}}
>
×
</Button>
</ButtonGroup>
{/* Separator (except after the last tab) */}
{index !== terminals.length - 1 && (
<div className="w-px h-6 bg-gray-600"></div>
)}
</div>
))}
</div>
</div>
{/* Terminal Views */}
<div className="flex-1 relative pt-12 overflow-hidden">
{terminals.map((terminal) => (
<div
key={terminal.id}
className={`absolute top-0 left-0 right-0 bottom-0 ${terminal.id === activeTab ? "block" : "hidden"}`}
className={`absolute top-0 left-0 right-0 bottom-0 ${
terminal.id === activeTab ? "block" : "hidden"
}`}
>
<NewTerminal hostConfig={terminal.hostConfig} />
</div>
@@ -126,7 +160,6 @@ function App() {
</div>
</div>
{/* Add Host Modal */}
<AddHostModal
isHidden={isAddHostHidden}
form={form}
@@ -134,6 +167,11 @@ function App() {
handleAddHost={handleAddHost}
setIsAddHostHidden={setIsAddHostHidden}
/>
{/* Launchpad Component */}
{isLaunchpadOpen && (
<Launchpad onClose={() => setIsLaunchpadOpen(false)} />
)}
</div>
</CssVarsProvider>
);

34
src/Launchpad.jsx Normal file
View File

@@ -0,0 +1,34 @@
import {Button} from "@mui/joy";
import PropTypes from 'prop-types';
function Launchpad({ onClose }) {
return (
<div
style={{
position: "fixed",
top: "20%",
left: "20%",
width: "60%",
height: "60%",
backgroundColor: "gray",
zIndex: 1000,
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "8px",
}}
>
<div className="text-center">
<h2 className="text-2xl font-bold mb-4">Launchpad</h2>
<p className="mb-4">This is your all-in-one launchpad panel.</p>
<Button onClick={onClose}>Close</Button>
</div>
</div>
);
}
Launchpad.propTypes = {
onClose: PropTypes.func.isRequired,
};
export default Launchpad;

60
src/TabList.jsx Normal file
View File

@@ -0,0 +1,60 @@
import {Button, ButtonGroup} from "@mui/joy";
import PropTypes from "prop-types";
function TabList({ terminals, activeTab, setActiveTab, closeTab, theme }) {
return (
<div className="inline-flex items-center h-full px-[0.5rem]">
{terminals.map((terminal, index) => (
<div
key={terminal.id}
className={index < terminals.length - 1 ? "mr-[0.5rem]" : ""}
>
<ButtonGroup>
<Button
onClick={() => setActiveTab(terminal.id)}
sx={{
backgroundColor:
terminal.id === activeTab
? theme.palette.neutral[500]
: theme.palette.neutral[800],
color: theme.palette.text.primary,
"&:hover": { backgroundColor: theme.palette.neutral[300] },
borderTopLeftRadius: "4px",
borderBottomLeftRadius: "4px",
height: "40px",
whiteSpace: "nowrap",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
{terminal.title}
</Button>
<Button
onClick={() => closeTab(terminal.id)}
sx={{
backgroundColor: theme.palette.neutral[700],
color: theme.palette.text.primary,
"&:hover": { backgroundColor: theme.palette.neutral[300] },
borderTopRightRadius: "4px",
borderBottomRightRadius: "4px",
height: "40px",
}}
>
×
</Button>
</ButtonGroup>
</div>
))}
</div>
);
}
TabList.propTypes = {
terminals: PropTypes.array.isRequired,
activeTab: PropTypes.any,
setActiveTab: PropTypes.func.isRequired,
closeTab: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
};
export default TabList;

View File

@@ -36,9 +36,8 @@ export function NewTerminal({ hostConfig }) {
// Resize function
const resizeTerminal = () => {
const terminalContainer = terminalRef.current;
const sidebarWidth = 14 * 16; // Sidebar width in pixels
const topbarHeight = 64; // Topbar height in pixels
const availableWidth = window.innerWidth - sidebarWidth;
const topbarHeight = 65;
const availableWidth = window.innerWidth;
const availableHeight = window.innerHeight - topbarHeight;
terminalContainer.style.width = `${availableWidth}px`;

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
src/images/termix_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB