canvas-website/multmux/packages/cli/src/ui/Terminal.ts

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);
}
}