116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
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<HTMLDivElement>;
|
|
@ViewChild('termInput') termInput!: ElementRef<HTMLInputElement>;
|
|
|
|
/** Command history for up/down arrow navigation. */
|
|
private history: string[] = [];
|
|
private historyIndex = -1;
|
|
|
|
/** Lines of terminal output. */
|
|
readonly lines = signal<string[]>([
|
|
'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;
|
|
}
|
|
}
|
|
}
|