99 lines
3.0 KiB
TypeScript
99 lines
3.0 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { Terminal } from 'xterm';
|
|
import * as fit from 'xterm/lib/addons/fit/fit';
|
|
import * as terminado from 'xterm/lib/addons/terminado/terminado';
|
|
|
|
@Injectable()
|
|
export class TerminadoService {
|
|
xterm: Terminal;
|
|
ws: WebSocket;
|
|
attached = false;
|
|
private dataListener: any;
|
|
private resizeListener: any;
|
|
|
|
constructor() {
|
|
Terminal.applyAddon(fit);
|
|
this.xterm = this.createTerminal();
|
|
}
|
|
|
|
createTerminal() {
|
|
return new Terminal({
|
|
theme: {
|
|
foreground: '#ffffff',
|
|
background: '#0C0C0C', // $tao-gray-800
|
|
cursor: '#ffffff',
|
|
selection: 'rgba(255, 255, 255, 0.3)',
|
|
black: '#000000',
|
|
red: '#FF5252', // $tao-red-500
|
|
brightRed: '#FF7171', // $tao-red-400
|
|
green: '#2fd19f', // $tao-bright-green-500
|
|
brightGreen: '#2fd19f', // $tao-bright-green-500
|
|
brightYellow: '#FFD283', // $tao-warm-yellow-300
|
|
yellow: '#FFB83B', // $tao-warm-yellow-500
|
|
magenta: '#FF8FC6', // $tao-pink-200
|
|
brightMagenta: '#FF8FC6', // $tao-pink-200
|
|
cyan: '#277EEC', // $tao-blue-500
|
|
blue: '#277EEC', // $tao-blue-500
|
|
brightCyan: '#42B7DF', // $tao-sky-400
|
|
brightBlue: '#19A7D8', // $tao-sky-500
|
|
white: '#FAFAFA', // $tao-gray-50
|
|
brightBlack: '#808080',
|
|
brightWhite: '#ffffff'
|
|
},
|
|
fontSize: 14
|
|
});
|
|
}
|
|
|
|
attach(element: HTMLElement) {
|
|
if (this.attached) {
|
|
return;
|
|
}
|
|
|
|
const wsproto = (location.protocol === 'https:') ? 'wss://' : 'ws://';
|
|
this.ws = new WebSocket(wsproto + window.location.host + '/terminal');
|
|
|
|
this.ws.onopen = () => {
|
|
this.attached = true;
|
|
this.xterm = this.createTerminal();
|
|
this.xterm.open(element);
|
|
this.fit();
|
|
// In order to reset the terminal state after a broken socket, we need to register the listeners manually.
|
|
(<any>this.xterm)._core.register(this.dataListener = this.xterm.onData(data => {
|
|
this.ws.send(JSON.stringify(['stdin', data]));
|
|
}));
|
|
(<any>this.xterm)._core.register(this.resizeListener = this.xterm.onResize((size: { rows: number, cols: number }) => {
|
|
this.ws.send(JSON.stringify(['set_size', size.rows, size.cols]));
|
|
}));
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
this.detach();
|
|
this.xterm.destroy();
|
|
this.attach(element);
|
|
};
|
|
|
|
this.ws.onmessage = msg => {
|
|
const data = JSON.parse(msg.data);
|
|
if (data[0] === 'stdout') {
|
|
this.xterm.write(data[1]);
|
|
}
|
|
};
|
|
}
|
|
|
|
detach() {
|
|
if (!this.attached) {
|
|
return;
|
|
}
|
|
|
|
this.attached = false;
|
|
this.dataListener.dispose();
|
|
this.resizeListener.dispose();
|
|
}
|
|
|
|
fit() {
|
|
if (this.attached) {
|
|
(<any>this.xterm).fit();
|
|
}
|
|
}
|
|
}
|