- M3: settings.ts (⚙ panel: dark/light/solarized theme + font size A-/A+), persisted to localStorage; terminal-session.applyTheme; TabApp.applySettings applies to all + new tabs - M7: dashboard.ts (▦ overlay listing every tab with connection dot + folder + Claude status; click a row to focus; live re-render while open); TabApp.snapshot()/focusTab() - Browser-verified: light theme renders + persists; dashboard lists tabs and focuses on click. 208 tests green, typechecks + build ok.
95 lines
2.6 KiB
TypeScript
95 lines
2.6 KiB
TypeScript
/**
|
|
* public/dashboard.ts — all-sessions overview (M7).
|
|
*
|
|
* A ▦ toolbar button opens an overlay listing every tab with its connection
|
|
* dot, folder name, and Claude status; click a row to focus that tab. While
|
|
* open it re-renders every second so statuses stay live.
|
|
*/
|
|
|
|
export interface TabInfo {
|
|
idx: number
|
|
title: string
|
|
conn: string // SessionStatus
|
|
claude: string // ClaudeStatus
|
|
active: boolean
|
|
pending: boolean
|
|
}
|
|
|
|
export interface DashboardHooks {
|
|
snapshot: () => TabInfo[]
|
|
focus: (idx: number) => void
|
|
}
|
|
|
|
function claudeText(claude: string): string {
|
|
if (claude === 'working') return '⚙ working'
|
|
if (claude === 'waiting') return '⏳ needs approval'
|
|
if (claude === 'idle') return '✓ idle'
|
|
return ''
|
|
}
|
|
|
|
export function mountDashboard(toolbar: HTMLElement, hooks: DashboardHooks): void {
|
|
const overlay = document.createElement('div')
|
|
overlay.id = 'dashboard'
|
|
overlay.style.display = 'none'
|
|
|
|
const card = document.createElement('div')
|
|
card.className = 'dash-card'
|
|
overlay.appendChild(card)
|
|
document.body.appendChild(overlay)
|
|
|
|
let timer: ReturnType<typeof setInterval> | null = null
|
|
|
|
const render = (): void => {
|
|
card.replaceChildren()
|
|
const title = document.createElement('div')
|
|
title.className = 'dash-title'
|
|
title.textContent = 'Sessions'
|
|
card.appendChild(title)
|
|
|
|
for (const t of hooks.snapshot()) {
|
|
const row = document.createElement('div')
|
|
row.className = t.active ? 'dash-row active' : 'dash-row'
|
|
|
|
const dot = document.createElement('span')
|
|
dot.className = `tab-dot dot-${t.conn}`
|
|
const name = document.createElement('span')
|
|
name.className = 'dash-name'
|
|
name.textContent = t.title
|
|
const claude = document.createElement('span')
|
|
claude.className = t.pending ? 'dash-claude pending' : 'dash-claude'
|
|
claude.textContent = claudeText(t.claude)
|
|
|
|
row.append(dot, name, claude)
|
|
row.addEventListener('click', () => {
|
|
hooks.focus(t.idx)
|
|
hide()
|
|
})
|
|
card.appendChild(row)
|
|
}
|
|
}
|
|
|
|
const open = (): void => {
|
|
render()
|
|
overlay.style.display = 'flex'
|
|
timer = setInterval(render, 1000)
|
|
}
|
|
const hide = (): void => {
|
|
overlay.style.display = 'none'
|
|
if (timer !== null) {
|
|
clearInterval(timer)
|
|
timer = null
|
|
}
|
|
}
|
|
overlay.addEventListener('click', (e) => {
|
|
if (e.target === overlay) hide()
|
|
})
|
|
|
|
const btn = document.createElement('button')
|
|
btn.className = 'toolbtn'
|
|
btn.textContent = '▦'
|
|
btn.title = 'All sessions'
|
|
btn.setAttribute('aria-label', 'All sessions')
|
|
btn.addEventListener('click', () => (overlay.style.display === 'none' ? open() : hide()))
|
|
toolbar.appendChild(btn)
|
|
}
|