mirror of
https://github.com/tsl0922/ttyd.git
synced 2026-01-02 00:44:23 +01:00
html: upgrade to gts 4.0.0
This commit is contained in:
@@ -4,12 +4,11 @@ import { ITerminalOptions, ITheme } from 'xterm';
|
||||
import { ClientOptions, FlowControl, Xterm } from './terminal';
|
||||
|
||||
if ((module as any).hot) {
|
||||
// tslint:disable-next-line:no-var-requires
|
||||
require('preact/debug');
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const path = window.location.pathname.replace(/[\/]+$/, '');
|
||||
const path = window.location.pathname.replace(/[/]+$/, '');
|
||||
const wsUrl = [protocol, '//', window.location.host, path, '/ws', window.location.search].join('');
|
||||
const tokenUrl = [window.location.protocol, '//', window.location.host, path, '/token'].join('');
|
||||
const clientOptions = {
|
||||
@@ -17,6 +16,7 @@ const clientOptions = {
|
||||
disableLeaveAlert: false,
|
||||
disableResizeOverlay: false,
|
||||
enableZmodem: false,
|
||||
enableTrzsz: false,
|
||||
enableSixel: false,
|
||||
titleFixed: null,
|
||||
} as ClientOptions;
|
||||
|
||||
@@ -44,7 +44,7 @@ export interface ClientOptions {
|
||||
enableZmodem: boolean;
|
||||
enableTrzsz: boolean;
|
||||
enableSixel: boolean;
|
||||
titleFixed: string;
|
||||
titleFixed: string | null;
|
||||
}
|
||||
|
||||
type Options = ITerminalOptions & ClientOptions;
|
||||
@@ -70,21 +70,21 @@ interface State {
|
||||
}
|
||||
|
||||
export class Xterm extends Component<Props, State> {
|
||||
private textEncoder: TextEncoder;
|
||||
private textDecoder: TextDecoder;
|
||||
private textEncoder = new TextEncoder();
|
||||
private textDecoder = new TextDecoder();
|
||||
private container: HTMLElement;
|
||||
private terminal: Terminal;
|
||||
|
||||
private written = 0;
|
||||
private pending = 0;
|
||||
|
||||
private fitAddon: FitAddon;
|
||||
private overlayAddon: OverlayAddon;
|
||||
private webglAddon: WebglAddon;
|
||||
private canvasAddon: CanvasAddon;
|
||||
private fitAddon = new FitAddon();
|
||||
private overlayAddon = new OverlayAddon();
|
||||
private webglAddon?: WebglAddon;
|
||||
private canvasAddon?: CanvasAddon;
|
||||
|
||||
private socket: WebSocket;
|
||||
private writeFunc: (data: ArrayBuffer) => void;
|
||||
private writeFunc = (data: ArrayBuffer) => this.writeData(new Uint8Array(data));
|
||||
private token: string;
|
||||
private opened = false;
|
||||
private title: string;
|
||||
@@ -96,12 +96,6 @@ export class Xterm extends Component<Props, State> {
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.writeFunc = data => this.writeData(new Uint8Array(data));
|
||||
this.textEncoder = new TextEncoder();
|
||||
this.textDecoder = new TextDecoder();
|
||||
this.fitAddon = new FitAddon();
|
||||
this.overlayAddon = new OverlayAddon();
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@@ -123,7 +117,7 @@ export class Xterm extends Component<Props, State> {
|
||||
|
||||
render({ id }: Props, { zmodem, trzsz }: State) {
|
||||
return (
|
||||
<div id={id} ref={c => (this.container = c)}>
|
||||
<div id={id} ref={c => (this.container = c as HTMLElement)}>
|
||||
{(zmodem || trzsz) && (
|
||||
<ZmodemAddon
|
||||
zmodem={zmodem}
|
||||
@@ -293,9 +287,9 @@ export class Xterm extends Component<Props, State> {
|
||||
disposeWebglRenderer();
|
||||
try {
|
||||
this.terminal.loadAddon(this.canvasAddon);
|
||||
console.log(`[ttyd] canvas renderer loaded`);
|
||||
console.log('[ttyd] canvas renderer loaded');
|
||||
} catch (e) {
|
||||
console.log(`[ttyd] canvas renderer could not be loaded, falling back to dom renderer`, e);
|
||||
console.log('[ttyd] canvas renderer could not be loaded, falling back to dom renderer', e);
|
||||
disposeCanvasRenderer();
|
||||
}
|
||||
};
|
||||
@@ -308,9 +302,9 @@ export class Xterm extends Component<Props, State> {
|
||||
this.webglAddon?.dispose();
|
||||
});
|
||||
terminal.loadAddon(this.webglAddon);
|
||||
console.log(`[ttyd] WebGL renderer loaded`);
|
||||
console.log('[ttyd] WebGL renderer loaded');
|
||||
} catch (e) {
|
||||
console.log(`[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer`, e);
|
||||
console.log('[ttyd] WebGL renderer could not be loaded, falling back to canvas renderer', e);
|
||||
disposeWebglRenderer();
|
||||
enableCanvasRenderer();
|
||||
}
|
||||
@@ -346,13 +340,13 @@ export class Xterm extends Component<Props, State> {
|
||||
break;
|
||||
case 'disableResizeOverlay':
|
||||
if (value) {
|
||||
console.log(`[ttyd] Resize overlay disabled`);
|
||||
console.log('[ttyd] Resize overlay disabled');
|
||||
this.resizeOverlay = false;
|
||||
}
|
||||
break;
|
||||
case 'disableReconnect':
|
||||
if (value) {
|
||||
console.log(`[ttyd] Reconnect disabled`);
|
||||
console.log('[ttyd] Reconnect disabled');
|
||||
this.reconnect = false;
|
||||
this.doReconnect = false;
|
||||
}
|
||||
@@ -360,13 +354,13 @@ export class Xterm extends Component<Props, State> {
|
||||
case 'enableZmodem':
|
||||
if (value) {
|
||||
this.setState({ zmodem: true });
|
||||
console.log(`[ttyd] Zmodem enabled`);
|
||||
console.log('[ttyd] Zmodem enabled');
|
||||
}
|
||||
break;
|
||||
case 'enableTrzsz':
|
||||
if (value) {
|
||||
this.setState({ trzsz: true });
|
||||
console.log(`[ttyd] trzsz enabled`);
|
||||
console.log('[ttyd] trzsz enabled');
|
||||
}
|
||||
break;
|
||||
case 'enableSixel':
|
||||
@@ -375,7 +369,7 @@ export class Xterm extends Component<Props, State> {
|
||||
new Blob([worker], { type: 'text/javascript' })
|
||||
);
|
||||
terminal.loadAddon(new ImageAddon(imageWorkerUrl));
|
||||
console.log(`[ttyd] Sixel enabled`);
|
||||
console.log('[ttyd] Sixel enabled');
|
||||
}
|
||||
break;
|
||||
case 'titleFixed':
|
||||
@@ -401,21 +395,19 @@ export class Xterm extends Component<Props, State> {
|
||||
private onSocketOpen() {
|
||||
console.log('[ttyd] websocket connection opened');
|
||||
|
||||
const { socket, textEncoder, terminal, fitAddon, overlayAddon } = this;
|
||||
const dims = fitAddon.proposeDimensions();
|
||||
const { socket, textEncoder, terminal, overlayAddon } = this;
|
||||
socket.send(
|
||||
textEncoder.encode(
|
||||
JSON.stringify({
|
||||
AuthToken: this.token,
|
||||
columns: dims.cols,
|
||||
rows: dims.rows,
|
||||
columns: terminal.cols,
|
||||
rows: terminal.rows,
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
if (this.opened) {
|
||||
terminal.reset();
|
||||
terminal.resize(dims.cols, dims.rows);
|
||||
terminal.options.disableStdin = false;
|
||||
overlayAddon.showOverlay('Reconnected', 300);
|
||||
} else {
|
||||
@@ -432,12 +424,12 @@ export class Xterm extends Component<Props, State> {
|
||||
console.log(`[ttyd] websocket connection closed with code: ${event.code}`);
|
||||
|
||||
const { refreshToken, connect, doReconnect, overlayAddon } = this;
|
||||
overlayAddon.showOverlay('Connection Closed', null);
|
||||
overlayAddon.showOverlay('Connection Closed');
|
||||
this.setState({ zmodem: false, trzsz: false });
|
||||
|
||||
// 1000: CLOSE_NORMAL
|
||||
if (event.code !== 1000 && doReconnect) {
|
||||
overlayAddon.showOverlay('Reconnecting...', null);
|
||||
overlayAddon.showOverlay('Reconnecting...');
|
||||
refreshToken().then(connect);
|
||||
} else {
|
||||
const { terminal } = this;
|
||||
@@ -445,11 +437,11 @@ export class Xterm extends Component<Props, State> {
|
||||
const event = e.domEvent;
|
||||
if (event.key === 'Enter') {
|
||||
keyDispose.dispose();
|
||||
overlayAddon.showOverlay('Reconnecting...', null);
|
||||
overlayAddon.showOverlay('Reconnecting...');
|
||||
refreshToken().then(connect);
|
||||
}
|
||||
});
|
||||
overlayAddon.showOverlay('Press ⏎ to Reconnect', null);
|
||||
overlayAddon.showOverlay('Press ⏎ to Reconnect');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,8 +467,10 @@ export class Xterm extends Component<Props, State> {
|
||||
document.title = this.title;
|
||||
break;
|
||||
case Command.SET_PREFERENCES:
|
||||
const prefs = JSON.parse(textDecoder.decode(data));
|
||||
this.applyOptions({ ...this.props.clientOptions, ...prefs } as Options);
|
||||
this.applyOptions({
|
||||
...this.props.clientOptions,
|
||||
...JSON.parse(textDecoder.decode(data)),
|
||||
} as Options);
|
||||
break;
|
||||
default:
|
||||
console.warn(`[ttyd] unknown command: ${cmd}`);
|
||||
@@ -494,7 +488,7 @@ export class Xterm extends Component<Props, State> {
|
||||
|
||||
if (resizeOverlay) {
|
||||
setTimeout(() => {
|
||||
overlayAddon.showOverlay(`${size.cols}x${size.rows}`);
|
||||
overlayAddon.showOverlay(`${size.cols}x${size.rows}`, 300);
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// ported from hterm.Terminal.prototype.showOverlay
|
||||
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
|
||||
import { bind } from 'decko';
|
||||
import { ITerminalAddon, Terminal } from 'xterm';
|
||||
|
||||
export class OverlayAddon implements ITerminalAddon {
|
||||
private terminal: Terminal | undefined;
|
||||
private overlayNode: HTMLElement | null;
|
||||
private overlayTimeout: number | null;
|
||||
private terminal: Terminal;
|
||||
private overlayNode: HTMLElement;
|
||||
private overlayTimeout?: number;
|
||||
|
||||
constructor() {
|
||||
this.overlayNode = document.createElement('div');
|
||||
@@ -35,8 +36,10 @@ position: absolute;
|
||||
|
||||
dispose(): void {}
|
||||
|
||||
@bind
|
||||
showOverlay(msg: string, timeout?: number): void {
|
||||
const { terminal, overlayNode } = this;
|
||||
if (!terminal.element) return;
|
||||
|
||||
overlayNode.style.color = '#101010';
|
||||
overlayNode.style.backgroundColor = '#f0f0f0';
|
||||
@@ -53,21 +56,16 @@ position: absolute;
|
||||
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;
|
||||
}
|
||||
if (this.overlayTimeout) clearTimeout(this.overlayTimeout);
|
||||
if (!timeout) return;
|
||||
|
||||
const self = this;
|
||||
self.overlayTimeout = setTimeout(() => {
|
||||
this.overlayTimeout = setTimeout(() => {
|
||||
overlayNode.style.opacity = '0';
|
||||
self.overlayTimeout = setTimeout(() => {
|
||||
this.overlayTimeout = setTimeout(() => {
|
||||
if (overlayNode.parentNode) {
|
||||
overlayNode.parentNode.removeChild(overlayNode);
|
||||
}
|
||||
self.overlayTimeout = null;
|
||||
this.overlayTimeout = undefined;
|
||||
overlayNode.style.opacity = '0.75';
|
||||
}, 200) as any;
|
||||
}, timeout || 1500) as any;
|
||||
|
||||
@@ -20,7 +20,7 @@ interface State {
|
||||
}
|
||||
|
||||
export class ZmodemAddon extends Component<Props, State> implements ITerminalAddon {
|
||||
private terminal: Terminal | undefined;
|
||||
private terminal: Terminal;
|
||||
private disposables: IDisposable[] = [];
|
||||
private sentry: Zmodem.Sentry;
|
||||
private session: Zmodem.Session;
|
||||
@@ -81,7 +81,7 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
|
||||
}
|
||||
|
||||
@bind
|
||||
private handleError(e: Error, reason: string) {
|
||||
private handleError(e: any, reason: string) {
|
||||
console.error(`[ttyd] zmodem ${reason}: `, e);
|
||||
this.reset();
|
||||
}
|
||||
@@ -115,12 +115,14 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
|
||||
on_retract: () => reset(),
|
||||
on_detect: detection => zmodemDetect(detection),
|
||||
});
|
||||
this.disposables.push(terminal.onKey(e => {
|
||||
const event = e.domEvent;
|
||||
if (event.ctrlKey && event.key === 'c') {
|
||||
if (this.denier) this.denier();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(
|
||||
terminal.onKey(e => {
|
||||
const event = e.domEvent;
|
||||
if (event.ctrlKey && event.key === 'c') {
|
||||
if (this.denier) this.denier();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@bind
|
||||
@@ -144,7 +146,7 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
|
||||
this.setState({ modal: false });
|
||||
|
||||
const { session, writeProgress, handleError } = this;
|
||||
const files: FileList = (event.target as HTMLInputElement).files;
|
||||
const files = (event.target as HTMLInputElement).files;
|
||||
|
||||
Zmodem.Browser.send_files(session, files, {
|
||||
on_progress: (_, offer) => writeProgress(offer),
|
||||
@@ -158,15 +160,11 @@ export class ZmodemAddon extends Component<Props, State> implements ITerminalAdd
|
||||
const { session, writeProgress, handleError } = this;
|
||||
|
||||
session.on('offer', offer => {
|
||||
const fileBuffer = [];
|
||||
offer.on('input', payload => {
|
||||
writeProgress(offer);
|
||||
fileBuffer.push(new Uint8Array(payload));
|
||||
});
|
||||
offer.on('input', () => writeProgress(offer));
|
||||
offer
|
||||
.accept()
|
||||
.then(() => {
|
||||
const blob = new Blob(fileBuffer, { type: 'application/octet-stream' });
|
||||
.then(payloads => {
|
||||
const blob = new Blob(payloads, { type: 'application/octet-stream' });
|
||||
saveAs(blob, offer.get_details().name);
|
||||
})
|
||||
.catch(e => handleError(e, 'receive'));
|
||||
|
||||
Reference in New Issue
Block a user