diff --git a/web/src/client/app-new-entry.ts b/web/src/client/app-new-entry.ts new file mode 100644 index 00000000..8d59a506 --- /dev/null +++ b/web/src/client/app-new-entry.ts @@ -0,0 +1,2 @@ +// Entry point for the new app +import './app-new.js'; \ No newline at end of file diff --git a/web/src/client/app-new.ts b/web/src/client/app-new.ts new file mode 100644 index 00000000..2ec857b0 --- /dev/null +++ b/web/src/client/app-new.ts @@ -0,0 +1,144 @@ +import { LitElement, html } from 'lit'; +import { customElement, state } from 'lit/decorators.js'; + +// Import components +import './components/app-header.js'; +import './components/session-create-form.js'; +import './components/session-list.js'; + +import type { Session } from './components/session-list.js'; + +@customElement('vibetunnel-app-new') +export class VibeTunnelAppNew extends LitElement { + // Disable shadow DOM to use Tailwind + createRenderRoot() { + return this; + } + + @state() private errorMessage = ''; + @state() private sessions: Session[] = []; + @state() private loading = false; + + private hotReloadWs: WebSocket | null = null; + + connectedCallback() { + super.connectedCallback(); + this.setupHotReload(); + this.loadSessions(); + this.startAutoRefresh(); + } + + disconnectedCallback() { + super.disconnectedCallback(); + if (this.hotReloadWs) { + this.hotReloadWs.close(); + } + } + + private showError(message: string) { + this.errorMessage = message; + // Clear error after 5 seconds + setTimeout(() => { + this.errorMessage = ''; + }, 5000); + } + + private clearError() { + this.errorMessage = ''; + } + + private async loadSessions() { + this.loading = true; + try { + const response = await fetch('/api/sessions'); + if (response.ok) { + const sessionsData = await response.json(); + this.sessions = sessionsData.map((session: any) => ({ + id: session.id, + command: session.command, + workingDir: session.workingDir, + status: session.status, + exitCode: session.exitCode, + startedAt: session.startedAt, + lastModified: session.lastModified, + pid: session.pid + })); + this.clearError(); + } else { + this.showError('Failed to load sessions'); + } + } catch (error) { + console.error('Error loading sessions:', error); + this.showError('Failed to load sessions'); + } finally { + this.loading = false; + } + } + + private startAutoRefresh() { + // Refresh sessions every 3 seconds + setInterval(() => { + this.loadSessions(); + }, 3000); + } + + private handleSessionCreated(e: CustomEvent) { + console.log('Session created:', e.detail); + this.showError('Session created successfully!'); + this.loadSessions(); // Refresh the list + } + + private handleSessionSelect(e: CustomEvent) { + const session = e.detail as Session; + console.log('Session selected:', session); + this.showError(`Terminal view not implemented yet for session: ${session.id}`); + } + + private handleSessionKilled(e: CustomEvent) { + console.log('Session killed:', e.detail); + this.loadSessions(); // Refresh the list + } + + private handleRefresh() { + this.loadSessions(); + } + + private handleError(e: CustomEvent) { + this.showError(e.detail); + } + + private setupHotReload(): void { + if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + const wsUrl = `${protocol}//${window.location.host}?hotReload=true`; + + this.hotReloadWs = new WebSocket(wsUrl); + this.hotReloadWs.onmessage = (event) => { + const message = JSON.parse(event.data); + if (message.type === 'reload') { + window.location.reload(); + } + }; + } + } + + render() { + return html` +