From 089182b8c5456c38ecd426acfb2abe25e56f49cb Mon Sep 17 00:00:00 2001 From: Shuanglei Tao Date: Sat, 15 Jun 2019 09:15:47 +0800 Subject: [PATCH] xterm.js 3.14.2 --- html/package.json | 3 +- html/src/components/app.tsx | 4 +- html/src/components/terminal/index.tsx | 54 ++++++------ html/src/components/terminal/overlay.ts | 110 +++++++++++++----------- html/yarn.lock | 13 ++- src/index.html | 2 +- 6 files changed, 99 insertions(+), 87 deletions(-) diff --git a/html/package.json b/html/package.json index e5767ab..55ec364 100644 --- a/html/package.json +++ b/html/package.json @@ -50,6 +50,7 @@ "dependencies": { "decko": "^1.2.0", "preact": "^8.4.2", - "xterm": "^3.13.2" + "xterm": "^3.14.2", + "xterm-addon-fit": "^0.1.0-beta1" } } diff --git a/html/src/components/app.tsx b/html/src/components/app.tsx index a5d04cc..52c2bb9 100644 --- a/html/src/components/app.tsx +++ b/html/src/components/app.tsx @@ -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 ( - + ); } } diff --git a/html/src/components/terminal/index.tsx b/html/src/components/terminal/index.tsx index 36a0e01..0c606e6 100644 --- a/html/src/components/terminal/index.tsx +++ b/html/src/components/terminal/index.tsx @@ -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 { +export default class Xterm extends Component { 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 { 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 { @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 { } 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 { 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 { @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 { 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 { @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 diff --git a/html/src/components/terminal/overlay.ts b/html/src/components/terminal/overlay.ts index 2d35752..cb41dae 100644 --- a/html/src/components/terminal/overlay.ts +++ b/html/src/components/terminal/overlay.ts @@ -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; + + 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'; -export function showOverlay(term: Terminal, msg: string, timeout?: number): void { - const addonTerminal = term; - if (!addonTerminal.__overlayNode) { - if (!term.element) { + 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 = setTimeout(() => { + overlayNode.style.opacity = '0'; + self.overlayTimeout = 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); - } - - 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; + return overlayNode; } - - addonTerminal.__overlayTimeout = setTimeout(() => { - addonTerminal.__overlayNode.style.opacity = '0'; - addonTerminal.__overlayTimeout = 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 { - (terminalConstructor.prototype).showOverlay = function (msg: string, timeout?: number): void { - return showOverlay(this, msg, timeout); - }; } diff --git a/html/yarn.lock b/html/yarn.lock index 809f82d..fe147ed 100644 --- a/html/yarn.lock +++ b/html/yarn.lock @@ -8358,10 +8358,15 @@ xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= -xterm@^3.13.2: - version "3.13.2" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.13.2.tgz#987c3a7fe3d23d6c03ce0eaf06c0554c38935570" - integrity sha512-4utKoF16/pzH6+EkFaaay+qPCozf5RP2P0JuH6rvIGHY0CRwgU2LwbQ/24DY+TvaZ5m+kwvIUvPqIBoMZYfgOg== +xterm-addon-fit@^0.1.0-beta1: + version "0.1.0-beta1" + resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.1.0-beta1.tgz#ec483fe4cad466de290be7e5539c8beed757b851" + integrity sha512-4HVm1RVqvBiIxBUxjjN63KIumI/mZYJ5lXkoR/onO//mh2Z2e257DmX1UqgCd2gi99EmZTt5CtEUinUANAkbvg== + +xterm@^3.14.2: + version "3.14.2" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-3.14.2.tgz#f1285288bdc7df7056ef4bde64311900748c3e5d" + integrity sha512-L50XMhfAC953/3EzmL+lq8jyD6CQ3SZ83UDrxalpKzE3d7Wo5JqehSd4yOrHYZYrijOT3pX6kBsZEI9uuZ1lmQ== y18n@^3.2.1: version "3.2.1" diff --git a/src/index.html b/src/index.html index 72a9e97..d15274c 100644 --- a/src/index.html +++ b/src/index.html @@ -1 +1 @@ -ttyd - Terminal \ No newline at end of file +ttyd - Terminal \ No newline at end of file -- 2.43.4