/** * @license * Copyright (C) 2018 "IoT.bzh" * Author Sebastien Douheret * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Component, ElementRef, ViewChild, Input, Output, HostListener, EventEmitter, AfterViewInit } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Terminal } from 'xterm'; import * as fit from 'xterm/lib/addons/fit/fit'; export interface ITerminalFont { fontFamily: string; fontSize: string; lineHeight: number; charWidth: number; charHeight: number; } @Component({ selector: 'xds-terminal', styles: [], template: `
`, }) export class TerminalComponent implements AfterViewInit { private _xterm: Terminal; private _initDone: boolean; @ViewChild('terminalContainer') termContainer: ElementRef; @Output() stdin = new EventEmitter(); @Output() resize = new EventEmitter<{ cols: number, rows: number }>(); constructor() { this._initDone = false; Terminal.applyAddon(fit); this._xterm = new Terminal({ cursorBlink: true, // useStyle: true, scrollback: 1000, rows: 24, cols: 80, }); } // getting the nativeElement only possible after view init ngAfterViewInit() { // this now finds the #terminal element this._xterm.open(this.termContainer.nativeElement); // the number of rows will determine the size of the terminal screen (this._xterm).fit(); // Bind input key this._xterm.on('data', (data) => { // console.log(data.charCodeAt(0)); this.stdin.emit(this._sanitizeInput(data)); return false; }); this._initDone = true; } @Input('stdout') set writeData(data) { if (this._initDone && data !== undefined) { this._xterm.write(data); } } @Input('disable') set disable(value: boolean) { if (!this._initDone) { return; } this._xterm.setOption('disableStdin', value); if (value) { this._xterm.blur(); } else { this._xterm.focus(); } this._resize(); } @HostListener('window:resize', ['$event']) onWindowResize(event) { this._resize(); } /*** Private functions ***/ private _sanitizeInput(d) { // TODO sanitize ? return d; } private _resize() { const geom = fit.proposeGeometry(this._xterm); // console.log('DEBUG cols ' + String(geom.cols) + ' rows ' + String(geom.rows)); if (geom.cols < 0 || geom.cols > 2000 || geom.rows < 0 || geom.rows > 2000) { return; } // Update xterm size this._xterm.resize(geom.cols, geom.rows); // Send resize event to update remote terminal this.resize.emit({ cols: geom.cols, rows: geom.rows }); } }