Terminal sessions move from the rail into a tab strip on the terminal page
The rail is now a flat list of pages; Terminal is one of them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
7eb00d089f
commit
91b7f4f009
+16
-49
@@ -1,58 +1,25 @@
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { Term } from './api'
|
||||
import { Spinner } from './ui'
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
// A group is either a page (clicking the head opens it — Dashboard) or a folder of
|
||||
// leaves (Terminal). `soon` groups are inert placeholders for the roadmap.
|
||||
export interface Group { id: string; label: string; icon: ReactNode; soon?: boolean; page?: boolean }
|
||||
// Every group is a page: clicking it opens it. `soon` groups are inert placeholders
|
||||
// for the roadmap.
|
||||
export interface Group { id: string; label: string; icon: ReactNode; soon?: boolean }
|
||||
|
||||
interface Props {
|
||||
groups: Group[]; terms: Term[]; active: string | null; view: string; creating?: boolean; header?: ReactNode
|
||||
onSelect: (id: string) => void; onView: (id: string) => void; onNew: () => void
|
||||
onRename: (t: Term) => void; onClose: (t: Term) => void
|
||||
}
|
||||
interface Props { groups: Group[]; view: string; header?: ReactNode; onView: (id: string) => void }
|
||||
|
||||
export default function Rail({ groups, terms, active, view, creating, header, onSelect, onView, onNew, onRename, onClose }: Props) {
|
||||
const [open, setOpen] = useState<string>('terminal')
|
||||
export default function Rail({ groups, view, header, onView }: Props) {
|
||||
return (
|
||||
<nav className="rail">
|
||||
{header}
|
||||
{groups.map((g) => {
|
||||
const expanded = !g.page && open === g.id
|
||||
const isPage = !!g.page && view === g.id
|
||||
return (
|
||||
<div key={g.id} className={'group' + (expanded ? ' open' : '') + (g.soon ? ' soon' : '')}>
|
||||
<button className={'group-head' + (isPage ? ' active' : '')} disabled={g.soon}
|
||||
onClick={() => { if (g.soon) return; if (g.page) onView(g.id); else setOpen(expanded ? '' : g.id) }}>
|
||||
<span className="chev">{g.page ? '' : expanded ? '▾' : '▸'}</span>
|
||||
<span className="gicon">{g.icon}</span>
|
||||
<span>{g.label}</span>
|
||||
{g.soon && <span className="badge">soon</span>}
|
||||
</button>
|
||||
{expanded && g.id === 'terminal' && (
|
||||
<div className="leaves">
|
||||
{terms.map((t) => {
|
||||
const sel = view === 'terminal' && t.id === active
|
||||
return (
|
||||
<div key={t.id} className={'leaf' + (sel ? ' active' : '') + (t.running ? '' : ' exited')}
|
||||
onClick={() => onSelect(t.id)}>
|
||||
<span className={'sdot' + (t.running ? ' run' : '')} />
|
||||
<span className="lname" title={t.name}>{t.name}</span>
|
||||
{sel && (
|
||||
<span className="leaf-actions">
|
||||
<button className="icon-btn" title="rename" onClick={(e) => { e.stopPropagation(); onRename(t) }}>✎</button>
|
||||
<button className="icon-btn" title="close" onClick={(e) => { e.stopPropagation(); onClose(t) }}>✕</button>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
<button className="leaf new" disabled={creating} onClick={onNew}>{creating ? <><Spinner /> creating…</> : '+ New Terminal'}</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{groups.map((g) => (
|
||||
<div key={g.id} className={'group' + (g.soon ? ' soon' : '')}>
|
||||
<button className={'group-head' + (view === g.id ? ' active' : '')} disabled={g.soon}
|
||||
onClick={() => { if (!g.soon) onView(g.id) }}>
|
||||
<span className="gicon">{g.icon}</span>
|
||||
<span>{g.label}</span>
|
||||
{g.soon && <span className="badge">soon</span>}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user