Fixed up the split screen a lot. 3+ screens at once causes inability to unsplit. Still a WIP.
This commit is contained in:
101
src/App.jsx
101
src/App.jsx
@@ -67,29 +67,35 @@ function App() {
|
|||||||
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);
|
||||||
setSplitTabIds(prev => prev.filter(tabId => tabId !== id));
|
|
||||||
if (activeTab === id) {
|
if (activeTab === id) {
|
||||||
setActiveTab(newTerminals[0]?.id || null);
|
setActiveTab(newTerminals[0]?.id || null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Toggle split for a specific tab
|
// Toggle split for a terminal tab
|
||||||
const toggleSplit = (id) => {
|
const toggleSplit = (id) => {
|
||||||
setSplitTabIds(prev => {
|
if (splitTabIds.length >= 3) return; // Prevent more than 2 tabs from splitting
|
||||||
if (prev.includes(id)) {
|
|
||||||
return prev.filter(tabId => tabId !== id);
|
setSplitTabIds((prev) =>
|
||||||
} else {
|
prev.includes(id) ? prev.filter((splitId) => splitId !== id) : [...prev, id]
|
||||||
if (prev.length >= 1) return prev; // Limit to 1 split
|
);
|
||||||
return [...prev, id, activeTab].filter((tabId, index, self) => self.indexOf(tabId) === index);
|
|
||||||
}
|
if (splitTabIds.includes(id)) {
|
||||||
});
|
setSplitTabIds((prev) => prev.filter((splitId) => splitId !== id));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get grid layout class based on split count
|
// Determine the layout based on the number of split tabs
|
||||||
const getGridLayout = (count) => {
|
const getLayoutStyle = () => {
|
||||||
if (count === 1) return 'grid-cols-1';
|
if (splitTabIds.length === 1) {
|
||||||
if (count === 2) return 'grid-cols-2';
|
// Horizontal split (2 tabs: left-right)
|
||||||
return 'grid-cols-1';
|
return "flex flex-row h-full gap-4";
|
||||||
|
} else if (splitTabIds.length > 1) {
|
||||||
|
// 2x2 Grid layout (4 tabs max), with evenly spaced rows
|
||||||
|
return "grid grid-cols-2 grid-rows-2 gap-4 h-full overflow-hidden";
|
||||||
|
}
|
||||||
|
// No split, main tab takes the entire screen
|
||||||
|
return "flex flex-col h-full";
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -153,48 +159,29 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Terminal Views */}
|
{/* Terminal Views */}
|
||||||
<div className="flex-1 relative p-4">
|
<div className={`relative p-4 ${getLayoutStyle()}`}>
|
||||||
{splitTabIds.length > 0 ? (
|
{terminals.map((terminal) => (
|
||||||
<div className={`grid gap-4 h-full ${getGridLayout(splitTabIds.length)}`}>
|
<div
|
||||||
{splitTabIds.map(id => {
|
key={terminal.id}
|
||||||
const terminal = terminals.find(t => t.id === id);
|
className={`bg-neutral-800 rounded-lg overflow-hidden shadow-xl border-5 border-neutral-700 ${splitTabIds.includes(terminal.id) || activeTab === terminal.id ? "block" : "hidden"} flex-1`}
|
||||||
return terminal ? (
|
>
|
||||||
<div key={terminal.id} className="bg-neutral-800 rounded-lg overflow-hidden shadow-xl border-5 border-neutral-700 h-full">
|
<NewTerminal
|
||||||
<NewTerminal
|
key={terminal.id}
|
||||||
key={terminal.id}
|
hostConfig={terminal.hostConfig}
|
||||||
hostConfig={terminal.hostConfig}
|
ref={(ref) => {
|
||||||
ref={(ref) => {
|
if (ref && !terminal.terminalRef) {
|
||||||
if (ref && !terminal.terminalRef) {
|
setTerminals((prev) =>
|
||||||
setTerminals(prev => prev.map(t =>
|
prev.map((t) =>
|
||||||
t.id === terminal.id ? { ...t, terminalRef: ref } : t
|
t.id === terminal.id
|
||||||
));
|
? { ...t, terminalRef: ref }
|
||||||
}
|
: t
|
||||||
}}
|
)
|
||||||
/>
|
);
|
||||||
</div>
|
}
|
||||||
) : null;
|
}}
|
||||||
})}
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
))}
|
||||||
<div className="absolute top-4 left-4 right-4 bottom-4">
|
|
||||||
{terminals.find(t => t.id === activeTab) && (
|
|
||||||
<div className="bg-neutral-800 rounded-lg overflow-hidden shadow-xl border-5 border-neutral-700 h-full">
|
|
||||||
<NewTerminal
|
|
||||||
key={activeTab}
|
|
||||||
hostConfig={terminals.find(t => t.id === activeTab).hostConfig}
|
|
||||||
ref={(ref) => {
|
|
||||||
const terminal = terminals.find(t => t.id === activeTab);
|
|
||||||
if (ref && terminal && !terminal.terminalRef) {
|
|
||||||
setTerminals(prev => prev.map(t =>
|
|
||||||
t.id === activeTab ? { ...t, terminalRef: ref } : t
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -212,4 +199,4 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
141
src/TabList.jsx
141
src/TabList.jsx
@@ -2,74 +2,81 @@ import { Button, ButtonGroup } from "@mui/joy";
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, splitTabIds, theme }) {
|
function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, splitTabIds, theme }) {
|
||||||
|
const isSplitScreenActive = splitTabIds.length > 0;
|
||||||
|
|
||||||
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 key={terminal.id} className={index < terminals.length - 1 ? "mr-[0.5rem]" : ""}>
|
const isActive = terminal.id === activeTab;
|
||||||
<ButtonGroup>
|
const isSplit = splitTabIds.includes(terminal.id);
|
||||||
<Button
|
|
||||||
onClick={() => splitTabIds.length === 0 && setActiveTab(terminal.id)}
|
// Disable split screen button for the active tab (before and after splitting)
|
||||||
disabled={splitTabIds.length > 0}
|
const isSplitButtonDisabled = isActive && !isSplitScreenActive || splitTabIds.length >= 3 && !isSplit;
|
||||||
sx={{
|
|
||||||
backgroundColor:
|
return (
|
||||||
terminal.id === activeTab
|
<div key={terminal.id} className={index < terminals.length - 1 ? "mr-[0.5rem]" : ""}>
|
||||||
? theme.palette.neutral[500]
|
<ButtonGroup>
|
||||||
: theme.palette.neutral[800],
|
{/* Set active tab button */}
|
||||||
color: theme.palette.text.primary,
|
<Button
|
||||||
"&:hover": { backgroundColor: theme.palette.neutral[300] },
|
onClick={() => setActiveTab(terminal.id)}
|
||||||
borderTopLeftRadius: "4px",
|
disabled={isSplit} // Disabled for split screen tabs
|
||||||
borderBottomLeftRadius: "4px",
|
sx={{
|
||||||
height: "40px",
|
backgroundColor:
|
||||||
whiteSpace: "nowrap",
|
isActive ? theme.palette.neutral[500] : theme.palette.neutral[800],
|
||||||
overflow: "hidden",
|
color: theme.palette.text.primary,
|
||||||
textOverflow: "ellipsis",
|
"&:hover": { backgroundColor: theme.palette.neutral[300] },
|
||||||
}}
|
":disabled": { backgroundColor: theme.palette.neutral[800] },
|
||||||
>
|
borderTopLeftRadius: "4px",
|
||||||
{terminal.title}
|
borderBottomLeftRadius: "4px",
|
||||||
</Button>
|
height: "40px",
|
||||||
<Button
|
whiteSpace: "nowrap",
|
||||||
onClick={() => toggleSplit(terminal.id)}
|
overflow: "hidden",
|
||||||
disabled={
|
textOverflow: "ellipsis",
|
||||||
(splitTabIds.length >= 1 && !splitTabIds.includes(terminal.id)) ||
|
}}
|
||||||
(splitTabIds.length === 0 && terminal.id === activeTab)
|
>
|
||||||
}
|
{terminal.title}
|
||||||
sx={{
|
</Button>
|
||||||
backgroundColor: splitTabIds.includes(terminal.id)
|
{/* Split screen button */}
|
||||||
? theme.palette.neutral[500]
|
<Button
|
||||||
: theme.palette.neutral[700],
|
onClick={() => toggleSplit(terminal.id)}
|
||||||
color: theme.palette.text.primary,
|
disabled={isSplitButtonDisabled || isActive} // Disable for the active tab (before and after split)
|
||||||
"&:hover": { backgroundColor: theme.palette.neutral[300] },
|
sx={{
|
||||||
borderTopRightRadius: "4px",
|
backgroundColor: isSplit
|
||||||
borderBottomRightRadius: "4px",
|
? theme.palette.neutral[500] // Split tabs get color 700
|
||||||
height: "40px",
|
: theme.palette.neutral[700], // Active tab has disabled color
|
||||||
fontSize: "1rem",
|
color: theme.palette.text.primary,
|
||||||
opacity: (splitTabIds.length >= 1 && !splitTabIds.includes(terminal.id)) ||
|
":disabled": { backgroundColor: theme.palette.neutral[800] },
|
||||||
(splitTabIds.length === 0 && terminal.id === activeTab) ? 0.5 : 1,
|
"&:hover": { backgroundColor: theme.palette.neutral[300] },
|
||||||
cursor: (splitTabIds.length >= 1 && !splitTabIds.includes(terminal.id)) ||
|
borderTopRightRadius: "4px",
|
||||||
(splitTabIds.length === 0 && terminal.id === activeTab)
|
borderBottomRightRadius: "4px",
|
||||||
? "not-allowed"
|
height: "40px",
|
||||||
: "pointer",
|
fontSize: "1rem",
|
||||||
}}
|
cursor: isSplitButtonDisabled ? "not-allowed" : "pointer",
|
||||||
>
|
}}
|
||||||
/
|
>
|
||||||
</Button>
|
/
|
||||||
<Button
|
</Button>
|
||||||
onClick={() => closeTab(terminal.id)}
|
{/* Close tab button */}
|
||||||
sx={{
|
<Button
|
||||||
backgroundColor: theme.palette.neutral[700],
|
onClick={() => closeTab(terminal.id)}
|
||||||
color: theme.palette.text.primary,
|
disabled={isSplitScreenActive && isActive || isSplit}
|
||||||
"&:hover": { backgroundColor: theme.palette.neutral[300] },
|
sx={{
|
||||||
borderTopRightRadius: "4px",
|
backgroundColor: theme.palette.neutral[700],
|
||||||
borderBottomRightRadius: "4px",
|
color: theme.palette.text.primary,
|
||||||
height: "40px",
|
"&:hover": { backgroundColor: theme.palette.neutral[300] },
|
||||||
fontSize: "1rem",
|
":disabled": { backgroundColor: theme.palette.neutral[800] },
|
||||||
}}
|
borderTopRightRadius: "4px",
|
||||||
>
|
borderBottomRightRadius: "4px",
|
||||||
×
|
height: "40px",
|
||||||
</Button>
|
fontSize: "1rem",
|
||||||
</ButtonGroup>
|
}}
|
||||||
</div>
|
>
|
||||||
))}
|
×
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -84,4 +91,4 @@ TabList.propTypes = {
|
|||||||
theme: PropTypes.object.isRequired,
|
theme: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TabList;
|
export default TabList;
|
||||||
|
|||||||
Reference in New Issue
Block a user