Prep for release, added ssh sidebar/topbar shrinking.
This commit is contained in:
@@ -22,6 +22,11 @@ export function SSH({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
const [allSplitScreenTab, setAllSplitScreenTab] = useState<number[]>([]);
|
||||
const nextTabId = useRef(1);
|
||||
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(true);
|
||||
const [isTopbarOpen, setIsTopbarOpen] = useState<boolean>(true);
|
||||
const SIDEBAR_WIDTH = 256;
|
||||
const HANDLE_THICKNESS = 6;
|
||||
|
||||
const [panelRects, setPanelRects] = useState<Record<string, DOMRect | null>>({});
|
||||
const panelRefs = useRef<Record<string, HTMLDivElement | null>>({});
|
||||
const panelGroupRefs = useRef<{ [key: string]: any }>({});
|
||||
@@ -587,20 +592,25 @@ export function SSH({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{display: 'flex', width: '100vw', height: '100vh', overflow: 'hidden'}}>
|
||||
<div style={{
|
||||
width: 256,
|
||||
flexShrink: 0,
|
||||
height: '100vh',
|
||||
position: 'relative',
|
||||
zIndex: 2,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
border: 'none'
|
||||
}}>
|
||||
<div style={{display: 'flex', width: '100vw', height: '100vh', overflow: 'hidden', position: 'relative'}}>
|
||||
{/* Sidebar (collapsible) */}
|
||||
<div
|
||||
style={{
|
||||
width: isSidebarOpen ? SIDEBAR_WIDTH : 0,
|
||||
flexShrink: 0,
|
||||
height: '100vh',
|
||||
position: 'relative',
|
||||
zIndex: 2,
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
border: 'none',
|
||||
overflow: 'hidden',
|
||||
transition: 'width 240ms ease-in-out',
|
||||
willChange: 'width',
|
||||
}}
|
||||
>
|
||||
<SSHSidebar
|
||||
onSelectView={onSelectView}
|
||||
onAddHostSubmit={onAddHostSubmit}
|
||||
onHostConnect={onHostConnect}
|
||||
allTabs={allTabs}
|
||||
runCommandOnTabs={(tabIds: number[], command: string) => {
|
||||
@@ -610,8 +620,12 @@ export function SSH({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
}
|
||||
});
|
||||
}}
|
||||
onCloseSidebar={() => setIsSidebarOpen(false)}
|
||||
open={isSidebarOpen}
|
||||
onOpenChange={setIsSidebarOpen}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="terminal-container"
|
||||
style={{
|
||||
@@ -621,10 +635,26 @@ export function SSH({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
overflow: 'hidden',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
paddingLeft: isSidebarOpen ? 0 : HANDLE_THICKNESS,
|
||||
paddingTop: isTopbarOpen ? 0 : HANDLE_THICKNESS,
|
||||
border: 'none',
|
||||
transition: 'padding-left 240ms ease-in-out, padding-top 240ms ease-in-out',
|
||||
willChange: 'padding',
|
||||
}}
|
||||
>
|
||||
<div style={{position: 'absolute', top: 0, left: 0, width: '100%', zIndex: 10}}>
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: isTopbarOpen ? 46 : 0,
|
||||
overflow: 'hidden',
|
||||
zIndex: 10,
|
||||
transition: 'height 240ms ease-in-out',
|
||||
willChange: 'height',
|
||||
}}
|
||||
>
|
||||
<SSHTopbar
|
||||
allTabs={allTabs}
|
||||
currentTab={currentTab ?? -1}
|
||||
@@ -632,9 +662,35 @@ export function SSH({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
allSplitScreenTab={allSplitScreenTab}
|
||||
setSplitScreenTab={setSplitScreenTab}
|
||||
setCloseTab={setCloseTab}
|
||||
onHideTopbar={() => setIsTopbarOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{height: 'calc(100% - 46px)', marginTop: 46, position: 'relative'}}>
|
||||
{!isTopbarOpen && (
|
||||
<div
|
||||
onClick={() => setIsTopbarOpen(true)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: HANDLE_THICKNESS,
|
||||
background: '#222224',
|
||||
cursor: 'pointer',
|
||||
zIndex: 12,
|
||||
}}
|
||||
title="Show top bar"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Main terminal area (height adapts to topbar) */}
|
||||
<div
|
||||
style={{
|
||||
height: isTopbarOpen ? 'calc(100% - 46px)' : '100%',
|
||||
marginTop: isTopbarOpen ? 46 : 0,
|
||||
position: 'relative',
|
||||
transition: 'margin-top 240ms ease-in-out, height 240ms ease-in-out',
|
||||
}}
|
||||
>
|
||||
{allTabs.length === 0 && (
|
||||
<div style={{
|
||||
position: 'absolute',
|
||||
@@ -699,6 +755,24 @@ export function SSH({onSelectView}: ConfigEditorProps): React.ReactElement {
|
||||
{renderSplitOverlays()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sidebar reopen handle */}
|
||||
{!isSidebarOpen && (
|
||||
<div
|
||||
onClick={() => setIsSidebarOpen(true)}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: HANDLE_THICKNESS,
|
||||
height: '100%',
|
||||
background: '#222224',
|
||||
cursor: 'pointer',
|
||||
zIndex: 20,
|
||||
}}
|
||||
title="Show sidebar"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import React, {useState} from 'react';
|
||||
|
||||
import {
|
||||
CornerDownLeft,
|
||||
Hammer, Pin
|
||||
Hammer, Pin, Menu
|
||||
} from "lucide-react"
|
||||
|
||||
import {
|
||||
@@ -63,14 +63,26 @@ interface SSHHost {
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface SidebarProps {
|
||||
export interface SidebarProps {
|
||||
onSelectView: (view: string) => void;
|
||||
onHostConnect: (hostConfig: any) => void;
|
||||
allTabs: { id: number; title: string; terminalRef: React.RefObject<any> }[];
|
||||
runCommandOnTabs: (tabIds: number[], command: string) => void;
|
||||
onCloseSidebar?: () => void;
|
||||
onAddHostSubmit?: (data: any) => void;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function SSHSidebar({onSelectView, onHostConnect, allTabs, runCommandOnTabs}: SidebarProps): React.ReactElement {
|
||||
export function SSHSidebar({
|
||||
onSelectView,
|
||||
onHostConnect,
|
||||
allTabs,
|
||||
runCommandOnTabs,
|
||||
onCloseSidebar,
|
||||
open,
|
||||
onOpenChange
|
||||
}: SidebarProps): React.ReactElement {
|
||||
const [hosts, setHosts] = useState<SSHHost[]>([]);
|
||||
const [hostsLoading, setHostsLoading] = useState(false);
|
||||
const [hostsError, setHostsError] = useState<string | null>(null);
|
||||
@@ -199,12 +211,32 @@ export function SSHSidebar({onSelectView, onHostConnect, allTabs, runCommandOnTa
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<SidebarProvider open={open} onOpenChange={onOpenChange}>
|
||||
<Sidebar className="h-full flex flex-col overflow-hidden">
|
||||
<SidebarContent className="flex flex-col flex-grow h-full overflow-hidden">
|
||||
<SidebarGroup className="flex flex-col flex-grow h-full overflow-hidden">
|
||||
<SidebarGroupLabel className="text-lg font-bold text-white flex items-center gap-2">
|
||||
Termix / Terminal
|
||||
<SidebarGroupLabel
|
||||
className="text-lg font-bold text-white flex items-center justify-between gap-2 w-full">
|
||||
<span>Termix / Terminal</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onCloseSidebar?.()}
|
||||
title="Hide sidebar"
|
||||
style={{
|
||||
height: 28,
|
||||
width: 28,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'hsl(240 5% 9%)',
|
||||
color: 'hsl(240 5% 64.9%)',
|
||||
border: '1px solid hsl(240 3.7% 15.9%)',
|
||||
borderRadius: 6,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
>
|
||||
<Menu className="h-4 w-4"/>
|
||||
</button>
|
||||
</SidebarGroupLabel>
|
||||
<Separator className="p-0.25 mt-1 mb-1"/>
|
||||
<SidebarGroupContent className="flex flex-col flex-grow h-full overflow-hidden">
|
||||
|
||||
@@ -173,7 +173,7 @@ export const SSHTerminal = forwardRef<any, SSHTerminalProps>(function SSHTermina
|
||||
fitAddon.fit();
|
||||
setVisible(true);
|
||||
|
||||
const cols = terminal.cols + 1;
|
||||
const cols = terminal.cols;
|
||||
const rows = terminal.rows;
|
||||
const wsUrl = window.location.hostname === 'localhost'
|
||||
? 'ws://localhost:8082'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {SSHTabList} from "@/apps/SSH/Terminal/SSHTabList.tsx";
|
||||
import React from "react";
|
||||
import {ChevronUp} from "lucide-react";
|
||||
|
||||
interface TerminalTab {
|
||||
id: number;
|
||||
@@ -13,6 +14,7 @@ interface SSHTopbarProps {
|
||||
allSplitScreenTab: number[];
|
||||
setSplitScreenTab: (tab: number) => void;
|
||||
setCloseTab: (tab: number) => void;
|
||||
onHideTopbar?: () => void;
|
||||
}
|
||||
|
||||
export function SSHTopbar({
|
||||
@@ -21,7 +23,8 @@ export function SSHTopbar({
|
||||
setActiveTab,
|
||||
allSplitScreenTab,
|
||||
setSplitScreenTab,
|
||||
setCloseTab
|
||||
setCloseTab,
|
||||
onHideTopbar
|
||||
}: SSHTopbarProps): React.ReactElement {
|
||||
return (
|
||||
<div className="flex h-11.5 z-100" style={{
|
||||
@@ -30,15 +33,41 @@ export function SSHTopbar({
|
||||
width: '100%',
|
||||
backgroundColor: '#18181b',
|
||||
borderBottom: '1px solid #222224',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<SSHTabList
|
||||
allTabs={allTabs}
|
||||
currentTab={currentTab}
|
||||
setActiveTab={setActiveTab}
|
||||
allSplitScreenTab={allSplitScreenTab}
|
||||
setSplitScreenTab={setSplitScreenTab}
|
||||
setCloseTab={setCloseTab}
|
||||
/>
|
||||
<div style={{flex: 1, minWidth: 0, height: '100%', overflowX: 'auto'}}>
|
||||
<div style={{minWidth: 'max-content', height: '100%', paddingLeft: 8}}>
|
||||
<SSHTabList
|
||||
allTabs={allTabs}
|
||||
currentTab={currentTab}
|
||||
setActiveTab={setActiveTab}
|
||||
allSplitScreenTab={allSplitScreenTab}
|
||||
setSplitScreenTab={setSplitScreenTab}
|
||||
setCloseTab={setCloseTab}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{flex: '0 0 auto', paddingRight: 8, paddingLeft: 16}}>
|
||||
<button
|
||||
onClick={() => onHideTopbar?.()}
|
||||
style={{
|
||||
height: 28,
|
||||
width: 28,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'hsl(240 5% 9%)',
|
||||
color: 'hsl(240 5% 64.9%)',
|
||||
border: '1px solid hsl(240 3.7% 15.9%)',
|
||||
borderRadius: 6,
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
title="Hide top bar"
|
||||
>
|
||||
<ChevronUp size={16} strokeWidth={2}/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user