155 lines
3.7 KiB
TypeScript
155 lines
3.7 KiB
TypeScript
import blessed from 'blessed';
|
|
import { WebSocketClient } from '../connection/WebSocketClient';
|
|
|
|
export class TerminalUI {
|
|
private screen: blessed.Widgets.Screen;
|
|
private terminal: blessed.Widgets.BoxElement;
|
|
private statusBar: blessed.Widgets.BoxElement;
|
|
private buffer: string = '';
|
|
|
|
constructor(private client: WebSocketClient) {
|
|
// Create screen
|
|
this.screen = blessed.screen({
|
|
smartCSR: true,
|
|
title: 'mulTmux',
|
|
});
|
|
|
|
// Status bar
|
|
this.statusBar = blessed.box({
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: 1,
|
|
style: {
|
|
fg: 'white',
|
|
bg: 'blue',
|
|
},
|
|
content: ' mulTmux - Connecting...',
|
|
});
|
|
|
|
// Terminal output
|
|
this.terminal = blessed.box({
|
|
top: 1,
|
|
left: 0,
|
|
width: '100%',
|
|
height: '100%-1',
|
|
scrollable: true,
|
|
alwaysScroll: true,
|
|
scrollbar: {
|
|
style: {
|
|
bg: 'blue',
|
|
},
|
|
},
|
|
keys: true,
|
|
vi: true,
|
|
mouse: true,
|
|
content: '',
|
|
});
|
|
|
|
this.screen.append(this.statusBar);
|
|
this.screen.append(this.terminal);
|
|
|
|
// Focus terminal
|
|
this.terminal.focus();
|
|
|
|
// Setup event handlers
|
|
this.setupEventHandlers();
|
|
|
|
// Render
|
|
this.screen.render();
|
|
}
|
|
|
|
private setupEventHandlers(): void {
|
|
// Handle terminal output from server
|
|
this.client.on('output', (data: string) => {
|
|
this.buffer += data;
|
|
this.terminal.setContent(this.buffer);
|
|
this.terminal.setScrollPerc(100);
|
|
this.screen.render();
|
|
});
|
|
|
|
// Handle connection events
|
|
this.client.on('connected', () => {
|
|
this.updateStatus('Connected', 'green');
|
|
});
|
|
|
|
this.client.on('joined', (info: any) => {
|
|
this.updateStatus(`Session: ${info.sessionName} (${info.clientId.slice(0, 8)})`, 'green');
|
|
});
|
|
|
|
this.client.on('disconnected', () => {
|
|
this.updateStatus('Disconnected', 'red');
|
|
});
|
|
|
|
this.client.on('reconnecting', (attempt: number) => {
|
|
this.updateStatus(`Reconnecting (${attempt}/5)...`, 'yellow');
|
|
});
|
|
|
|
this.client.on('presence', (data: any) => {
|
|
if (data.action === 'join') {
|
|
this.showNotification(`User joined (${data.totalClients} online)`);
|
|
} else if (data.action === 'leave') {
|
|
this.showNotification(`User left (${data.totalClients} online)`);
|
|
}
|
|
});
|
|
|
|
// Handle keyboard input
|
|
this.screen.on('keypress', (ch: string, key: any) => {
|
|
if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {
|
|
this.close();
|
|
return;
|
|
}
|
|
|
|
// Send input to server
|
|
if (ch) {
|
|
this.client.sendInput(ch);
|
|
} else if (key.name) {
|
|
// Handle special keys
|
|
const specialKeys: { [key: string]: string } = {
|
|
enter: '\r',
|
|
backspace: '\x7f',
|
|
tab: '\t',
|
|
up: '\x1b[A',
|
|
down: '\x1b[B',
|
|
right: '\x1b[C',
|
|
left: '\x1b[D',
|
|
};
|
|
|
|
if (specialKeys[key.name]) {
|
|
this.client.sendInput(specialKeys[key.name]);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Handle resize
|
|
this.screen.on('resize', () => {
|
|
const { width, height } = this.terminal;
|
|
this.client.resize(width as number, (height as number) - 1);
|
|
});
|
|
|
|
// Quit on Ctrl-C
|
|
this.screen.key(['C-c'], () => {
|
|
this.close();
|
|
});
|
|
}
|
|
|
|
private updateStatus(text: string, color: string = 'blue'): void {
|
|
this.statusBar.style.bg = color;
|
|
this.statusBar.setContent(` mulTmux - ${text}`);
|
|
this.screen.render();
|
|
}
|
|
|
|
private showNotification(text: string): void {
|
|
// Append notification to buffer
|
|
this.buffer += `\n[mulTmux] ${text}\n`;
|
|
this.terminal.setContent(this.buffer);
|
|
this.screen.render();
|
|
}
|
|
|
|
close(): void {
|
|
this.client.disconnect();
|
|
this.screen.destroy();
|
|
process.exit(0);
|
|
}
|
|
}
|