/** * App Header Component * * Displays the VibeTunnel logo, session statistics, and control buttons. * Provides controls for creating sessions, toggling exited sessions visibility, * killing all sessions, and cleaning up exited sessions. * * @fires create-session - When create button is clicked * @fires hide-exited-change - When hide/show exited toggle is clicked (detail: boolean) * @fires kill-all-sessions - When kill all button is clicked * @fires clean-exited-sessions - When clean exited button is clicked * @fires open-file-browser - When browse button is clicked */ import { LitElement, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import type { Session } from './session-list.js'; import './terminal-icon.js'; @customElement('app-header') export class AppHeader extends LitElement { createRenderRoot() { return this; } @property({ type: Array }) sessions: Session[] = []; @property({ type: Boolean }) hideExited = true; @state() private killingAll = false; private handleCreateSession(e: MouseEvent) { // Capture button position for view transition const button = e.currentTarget as HTMLButtonElement; const rect = button.getBoundingClientRect(); // Store position in CSS custom properties for the transition document.documentElement.style.setProperty('--vt-button-x', `${rect.left + rect.width / 2}px`); document.documentElement.style.setProperty('--vt-button-y', `${rect.top + rect.height / 2}px`); document.documentElement.style.setProperty('--vt-button-width', `${rect.width}px`); document.documentElement.style.setProperty('--vt-button-height', `${rect.height}px`); this.dispatchEvent(new CustomEvent('create-session')); } private handleKillAll() { if (this.killingAll) return; this.killingAll = true; this.requestUpdate(); this.dispatchEvent(new CustomEvent('kill-all-sessions')); // Reset the state after a delay to allow for the kill operations to complete setTimeout(() => { this.killingAll = false; this.requestUpdate(); }, 3000); // 3 seconds should be enough for most kill operations } private handleCleanExited() { this.dispatchEvent(new CustomEvent('clean-exited-sessions')); } private handleOpenFileBrowser() { this.dispatchEvent(new CustomEvent('open-file-browser')); } render() { const runningSessions = this.sessions.filter((session) => session.status === 'running'); const exitedSessions = this.sessions.filter((session) => session.status === 'exited'); // Reset killing state if no more running sessions if (this.killingAll && runningSessions.length === 0) { this.killingAll = false; } return html`
VibeTunnel

${runningSessions.length} ${runningSessions.length === 1 ? 'session' : 'sessions'} ${exitedSessions.length > 0 ? `• ${exitedSessions.length} exited` : ''}

${exitedSessions.length > 0 ? html` ` : ''} ${!this.hideExited && exitedSessions.length > 0 ? html` ` : ''} ${runningSessions.length > 0 && !this.killingAll ? html` ` : ''}
`; } }