mirror of
https://github.com/tsl0922/ttyd.git
synced 2026-01-03 01:14:24 +01:00
xterm.js 3.14.2
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Component, h } from 'preact';
|
||||
|
||||
import { ITerminalOptions, ITheme } from 'xterm';
|
||||
import Terminal from './terminal';
|
||||
import Xterm from './terminal';
|
||||
|
||||
if ((module as any).hot) {
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
@@ -40,7 +40,7 @@ const termOptions = {
|
||||
export default class App extends Component {
|
||||
public render() {
|
||||
return (
|
||||
<Terminal id='terminal-container' url={url} options={termOptions} />
|
||||
<Xterm id='terminal-container' url={url} options={termOptions} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { bind } from 'decko';
|
||||
import { Component, h } from 'preact';
|
||||
import { ITerminalOptions, Terminal as Xterm } from 'xterm';
|
||||
import { ITerminalOptions, Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { OverlayAddon } from './overlay';
|
||||
|
||||
import 'xterm/dist/xterm.css';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
import * as overlay from './overlay';
|
||||
|
||||
const enum Command {
|
||||
// server side
|
||||
@@ -17,22 +18,19 @@ const enum Command {
|
||||
RESIZE_TERMINAL = '1'
|
||||
}
|
||||
|
||||
interface ITerminal extends Xterm {
|
||||
fit(): void;
|
||||
showOverlay(msg: string, timeout?: number): void;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
url: string;
|
||||
options: ITerminalOptions;
|
||||
}
|
||||
|
||||
export default class Terminal extends Component<Props> {
|
||||
export default class Xterm extends Component<Props> {
|
||||
private textEncoder: TextEncoder;
|
||||
private textDecoder: TextDecoder;
|
||||
private container: HTMLElement;
|
||||
private terminal: ITerminal;
|
||||
private terminal: Terminal;
|
||||
private fitAddon: FitAddon;
|
||||
private overlayAddon: OverlayAddon;
|
||||
private socket: WebSocket;
|
||||
private title: string;
|
||||
private autoReconnect: number;
|
||||
@@ -41,11 +39,10 @@ export default class Terminal extends Component<Props> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
Xterm.applyAddon(fit);
|
||||
Xterm.applyAddon(overlay);
|
||||
|
||||
this.textEncoder = new TextEncoder();
|
||||
this.textDecoder = new TextDecoder();
|
||||
this.fitAddon = new FitAddon();
|
||||
this.overlayAddon = new OverlayAddon();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
@@ -68,9 +65,9 @@ export default class Terminal extends Component<Props> {
|
||||
|
||||
@bind
|
||||
private onWindowResize() {
|
||||
const { terminal } = this;
|
||||
const { fitAddon } = this;
|
||||
clearTimeout(this.resizeTimeout);
|
||||
this.resizeTimeout = setTimeout(() => terminal.fit(), 250) as any;
|
||||
this.resizeTimeout = setTimeout(() => fitAddon.fit(), 250) as any;
|
||||
}
|
||||
|
||||
private onWindowUnload(event: BeforeUnloadEvent): string {
|
||||
@@ -86,7 +83,7 @@ export default class Terminal extends Component<Props> {
|
||||
}
|
||||
|
||||
this.socket = new WebSocket(this.props.url, ['tty']);
|
||||
this.terminal = new Xterm(this.props.options) as ITerminal;
|
||||
this.terminal = new Terminal(this.props.options);
|
||||
const { socket, terminal, container } = this;
|
||||
|
||||
socket.binaryType = 'arraybuffer';
|
||||
@@ -94,6 +91,9 @@ export default class Terminal extends Component<Props> {
|
||||
socket.onmessage = this.onSocketData;
|
||||
socket.onclose = this.onSocketClose;
|
||||
|
||||
terminal.loadAddon(this.fitAddon);
|
||||
terminal.loadAddon(this.overlayAddon);
|
||||
|
||||
terminal.onTitleChange((data) => {
|
||||
if (data && data !== '') {
|
||||
document.title = (data + ' | ' + this.title);
|
||||
@@ -111,18 +111,18 @@ export default class Terminal extends Component<Props> {
|
||||
@bind
|
||||
private onSocketOpen() {
|
||||
console.log('Websocket connection opened');
|
||||
const { socket, textEncoder, terminal } = this;
|
||||
const { socket, textEncoder, fitAddon } = this;
|
||||
|
||||
socket.send(textEncoder.encode(JSON.stringify({AuthToken: ''})));
|
||||
terminal.fit();
|
||||
fitAddon.fit();
|
||||
}
|
||||
|
||||
@bind
|
||||
private onSocketClose(event: CloseEvent) {
|
||||
console.log('Websocket connection closed with code: ' + event.code);
|
||||
|
||||
const { terminal, openTerminal, autoReconnect } = this;
|
||||
terminal.showOverlay('Connection Closed', null);
|
||||
const { overlayAddon, openTerminal, autoReconnect } = this;
|
||||
overlayAddon.showOverlay('Connection Closed', null);
|
||||
window.removeEventListener('beforeunload', this.onWindowUnload);
|
||||
|
||||
// 1008: POLICY_VIOLATION - Auth failure
|
||||
@@ -141,25 +141,25 @@ export default class Terminal extends Component<Props> {
|
||||
|
||||
const rawData = new Uint8Array(event.data);
|
||||
const cmd = String.fromCharCode(rawData[0]);
|
||||
const data = rawData.slice(1).buffer;
|
||||
const data = rawData.slice(1);
|
||||
|
||||
switch(cmd) {
|
||||
case Command.OUTPUT:
|
||||
terminal.write(textDecoder.decode(data));
|
||||
terminal.writeUtf8(data);
|
||||
break;
|
||||
case Command.SET_WINDOW_TITLE:
|
||||
this.title = textDecoder.decode(data);
|
||||
this.title = textDecoder.decode(data.buffer);
|
||||
document.title = this.title;
|
||||
break;
|
||||
case Command.SET_PREFERENCES:
|
||||
const preferences = JSON.parse(textDecoder.decode(data));
|
||||
const preferences = JSON.parse(textDecoder.decode(data.buffer));
|
||||
Object.keys(preferences).forEach((key) => {
|
||||
console.log('Setting ' + key + ': ' + preferences[key]);
|
||||
terminal.setOption(key, preferences[key]);
|
||||
});
|
||||
break;
|
||||
case Command.SET_RECONNECT:
|
||||
this.autoReconnect = JSON.parse(textDecoder.decode(data));
|
||||
this.autoReconnect = parseInt(textDecoder.decode(data.buffer));
|
||||
console.log('Enabling reconnect: ' + this.autoReconnect + ' seconds');
|
||||
break;
|
||||
default:
|
||||
@@ -170,12 +170,12 @@ export default class Terminal extends Component<Props> {
|
||||
|
||||
@bind
|
||||
private onTerminalResize(size: {cols: number, rows: number}) {
|
||||
const { terminal, socket, textEncoder } = this;
|
||||
const { overlayAddon, socket, textEncoder } = this;
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
const msg = JSON.stringify({columns: size.cols, rows: size.rows});
|
||||
socket.send(textEncoder.encode(Command.RESIZE_TERMINAL + msg));
|
||||
}
|
||||
setTimeout(() => {terminal.showOverlay(size.cols + 'x' + size.rows)}, 500);
|
||||
setTimeout(() => {overlayAddon.showOverlay(size.cols + 'x' + size.rows)}, 500);
|
||||
}
|
||||
|
||||
@bind
|
||||
|
||||
@@ -1,20 +1,65 @@
|
||||
// ported from hterm.Terminal.prototype.showOverlay
|
||||
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
|
||||
import { Terminal } from 'xterm';
|
||||
import { ITerminalAddon, Terminal } from 'xterm';
|
||||
|
||||
interface IOverlayAddonTerminal extends Terminal {
|
||||
__overlayNode: HTMLElement | null;
|
||||
__overlayTimeout: number | null;
|
||||
}
|
||||
export class OverlayAddon implements ITerminalAddon {
|
||||
private terminal: Terminal | undefined;
|
||||
private overlayNode: HTMLElement | null;
|
||||
private overlayTimeout: number | null;
|
||||
|
||||
export function showOverlay(term: Terminal, msg: string, timeout?: number): void {
|
||||
const addonTerminal = <IOverlayAddonTerminal> term;
|
||||
if (!addonTerminal.__overlayNode) {
|
||||
if (!term.element) {
|
||||
constructor() {}
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this.terminal = terminal;
|
||||
this.overlayNode = this.createOverlayNode();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
document.removeChild(this.overlayNode);
|
||||
}
|
||||
|
||||
public showOverlay(msg: string, timeout?: number): void {
|
||||
const {terminal, overlayNode } = this;
|
||||
|
||||
overlayNode.style.color = '#101010';
|
||||
overlayNode.style.backgroundColor = '#f0f0f0';
|
||||
overlayNode.textContent = msg;
|
||||
overlayNode.style.opacity = '0.75';
|
||||
|
||||
if (!overlayNode.parentNode) {
|
||||
terminal.element.appendChild(overlayNode);
|
||||
}
|
||||
|
||||
const divSize = terminal.element.getBoundingClientRect();
|
||||
const overlaySize = overlayNode.getBoundingClientRect();
|
||||
|
||||
overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + 'px';
|
||||
overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
|
||||
|
||||
if (this.overlayTimeout) {
|
||||
clearTimeout(this.overlayTimeout);
|
||||
}
|
||||
if (timeout === null) {
|
||||
return;
|
||||
}
|
||||
addonTerminal.__overlayNode = document.createElement('div');
|
||||
addonTerminal.__overlayNode.style.cssText = (
|
||||
|
||||
const self = this;
|
||||
self.overlayTimeout = <number><any>setTimeout(() => {
|
||||
overlayNode.style.opacity = '0';
|
||||
self.overlayTimeout = <number><any>setTimeout(() => {
|
||||
if (overlayNode.parentNode) {
|
||||
overlayNode.parentNode.removeChild(overlayNode);
|
||||
}
|
||||
self.overlayTimeout = null;
|
||||
overlayNode.style.opacity = '0.75';
|
||||
}, 200);
|
||||
}, timeout || 1500);
|
||||
}
|
||||
|
||||
private createOverlayNode(): HTMLElement {
|
||||
const overlayNode = document.createElement('div');
|
||||
|
||||
overlayNode.style.cssText = (
|
||||
'border-radius: 15px;' +
|
||||
'font-size: xx-large;' +
|
||||
'opacity: 0.75;' +
|
||||
@@ -25,50 +70,11 @@ export function showOverlay(term: Terminal, msg: string, timeout?: number): void
|
||||
'-moz-user-select: none;' +
|
||||
'-moz-transition: opacity 180ms ease-in;');
|
||||
|
||||
addonTerminal.__overlayNode.addEventListener('mousedown', (e) => {
|
||||
overlayNode.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, true);
|
||||
|
||||
return overlayNode;
|
||||
}
|
||||
|
||||
addonTerminal.__overlayNode.style.color = '#101010';
|
||||
addonTerminal.__overlayNode.style.backgroundColor = '#f0f0f0';
|
||||
|
||||
addonTerminal.__overlayNode.textContent = msg;
|
||||
addonTerminal.__overlayNode.style.opacity = '0.75';
|
||||
|
||||
if (!addonTerminal.__overlayNode.parentNode) {
|
||||
term.element.appendChild(addonTerminal.__overlayNode);
|
||||
}
|
||||
|
||||
const divSize = term.element.getBoundingClientRect();
|
||||
const overlaySize = addonTerminal.__overlayNode.getBoundingClientRect();
|
||||
|
||||
addonTerminal.__overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + 'px';
|
||||
addonTerminal.__overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
|
||||
|
||||
if (addonTerminal.__overlayTimeout) {
|
||||
clearTimeout(addonTerminal.__overlayTimeout);
|
||||
}
|
||||
|
||||
if (timeout === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addonTerminal.__overlayTimeout = <number><any>setTimeout(() => {
|
||||
addonTerminal.__overlayNode.style.opacity = '0';
|
||||
addonTerminal.__overlayTimeout = <number><any>setTimeout(() => {
|
||||
if (addonTerminal.__overlayNode.parentNode) {
|
||||
addonTerminal.__overlayNode.parentNode.removeChild(addonTerminal.__overlayNode);
|
||||
}
|
||||
addonTerminal.__overlayTimeout = null;
|
||||
addonTerminal.__overlayNode.style.opacity = '0.75';
|
||||
}, 200);
|
||||
}, timeout || 1500);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).showOverlay = function (msg: string, timeout?: number): void {
|
||||
return showOverlay(this, msg, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user