import { Component, ElementRef, ViewChild, AfterViewInit, AfterViewChecked, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'app-terminal', standalone: true, imports: [CommonModule], templateUrl: './terminal.component.html', styleUrl: './terminal.component.scss', }) export class TerminalComponent implements AfterViewInit, AfterViewChecked { @ViewChild('terminalBody') terminalBody!: ElementRef; @ViewChild('termInput') termInput!: ElementRef; /** Command history for up/down arrow navigation. */ private history: string[] = []; private historyIndex = -1; /** Lines of terminal output. */ readonly lines = signal([ 'Butterfly Terminal v0.1', 'Type "help" for available commands.', '', ]); ngAfterViewInit(): void { this.termInput?.nativeElement.focus(); } onInput(event: Event): void { // Just keep focus. } onKeyDown(event: KeyboardEvent): void { if (event.key === 'Enter') { const input = this.termInput.nativeElement; const cmd = input.value.trim(); input.value = ''; if (cmd) { this.history.push(cmd); this.historyIndex = this.history.length; this.executeCommand(cmd); } this.lines.update((l) => [...l, `> ${cmd}`]); } else if (event.key === 'ArrowUp') { event.preventDefault(); if (this.historyIndex > 0) { this.historyIndex--; this.termInput.nativeElement.value = this.history[this.historyIndex]; } } else if (event.key === 'ArrowDown') { event.preventDefault(); if (this.historyIndex < this.history.length - 1) { this.historyIndex++; this.termInput.nativeElement.value = this.history[this.historyIndex]; } else { this.historyIndex = this.history.length; this.termInput.nativeElement.value = ''; } } } private executeCommand(cmd: string): void { const parts = cmd.split(/\s+/); const command = parts[0].toLowerCase(); switch (command) { case 'help': this.addOutput('Available commands: help, clear, echo, date, uptime, whoami, about'); break; case 'clear': this.lines.set([]); return; case 'echo': this.addOutput(parts.slice(1).join(' ')); break; case 'date': this.addOutput(new Date().toString()); break; case 'uptime': this.addOutput(`System uptime: ${Math.floor(performance.now() / 1000)}s`); break; case 'whoami': this.addOutput('butterfly-user'); break; case 'about': this.addOutput('Butterfly Desktop Environment v0.1'); this.addOutput('A browser-based remote desktop environment.'); break; default: this.addOutput(`Unknown command: ${command}. Type "help" for available commands.`); } } private addOutput(text: string): void { this.lines.update((l) => [...l, text]); } /** Auto-scroll to bottom whenever the view updates. */ ngAfterViewChecked(): void { this.scrollToBottom(); } /** Focus the input when the terminal is clicked. */ onClick(): void { this.termInput?.nativeElement.focus(); } /** Called after view updates to scroll to bottom. */ scrollToBottom(): void { if (this.terminalBody) { this.terminalBody.nativeElement.scrollTop = this.terminalBody.nativeElement.scrollHeight; } } }