The glossy emoji (🔍⚙▦🕘⌨🔗📱) clashed with the Amber dark theme. Swap them for clean lucide line icons (MIT, stroke=currentColor) so they take the theme colour — muted by default, Amber on hover — and add a styled CSS hover tooltip (data-tip, right-anchored so it never clips the edge) showing what each does. - public/icons.ts: 7 line icons (search/settings/dashboard/history/keyboard/ share/device), script-generated from lucide - 7 toolbar modules: innerHTML = ICON_* + data-tip + aria-label - style.css .toolbtn: 18px svg, --text-dim → --accent on hover, [data-tip] tooltip Frontend-only. web typecheck + build clean; bundle has 0 toolbar emoji; verified in-browser: line icons in theme colour, amber + tooltip on hover.
98 lines
2.8 KiB
TypeScript
98 lines
2.8 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.
|
|
*/
|
|
|
|
import { ICON_DASHBOARD } from './icons.js'
|
|
|
|
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.innerHTML = ICON_DASHBOARD
|
|
btn.dataset.tip = 'All sessions'
|
|
btn.setAttribute('aria-label', 'All sessions')
|
|
btn.setAttribute('aria-label', 'All sessions')
|
|
btn.addEventListener('click', () => (overlay.style.display === 'none' ? open() : hide()))
|
|
toolbar.appendChild(btn)
|
|
}
|