import { h, Component } from 'preact';
import { ITerminalOptions, ITheme } from 'xterm';
-import { Xterm } from './terminal';
+import { ClientOptions, Xterm } from './terminal';
if ((module as any).hot) {
// tslint:disable-next-line:no-var-requires
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 = {
+ rendererType: 'webgl',
+ disableLeaveAlert: false,
+ disableResizeOverlay: false,
+ titleFixed: null,
+} as ClientOptions;
const termOptions = {
fontSize: 13,
fontFamily: 'Menlo For Powerline,Consolas,Liberation Mono,Menlo,Courier,monospace',
export class App extends Component {
render() {
- return <Xterm id="terminal-container" wsUrl={wsUrl} tokenUrl={tokenUrl} options={termOptions} />;
+ return (
+ <Xterm
+ id="terminal-container"
+ wsUrl={wsUrl}
+ tokenUrl={tokenUrl}
+ clientOptions={clientOptions}
+ termOptions={termOptions}
+ />
+ );
}
}
RESIZE_TERMINAL = '1',
}
+export interface ClientOptions {
+ rendererType: 'dom' | 'canvas' | 'webgl';
+ disableLeaveAlert: boolean;
+ disableResizeOverlay: boolean;
+ titleFixed: string;
+}
+
interface Props {
id: string;
wsUrl: string;
tokenUrl: string;
- options: ITerminalOptions;
+ clientOptions: ClientOptions;
+ termOptions: ITerminalOptions;
}
export class Xterm extends Component<Props> {
private textDecoder: TextDecoder;
private container: HTMLElement;
private terminal: Terminal;
+
private fitAddon: FitAddon;
private overlayAddon: OverlayAddon;
private zmodemAddon: ZmodemAddon;
+
private socket: WebSocket;
private token: string;
private title: string;
@bind
private openTerminal() {
- this.terminal = new Terminal(this.props.options);
+ this.terminal = new Terminal(this.props.termOptions);
const { terminal, container, fitAddon, overlayAddon } = this;
window.term = terminal as TtydTerminal;
window.term.fit = () => {
socket.onerror = this.onSocketError;
}
+ @bind
+ private applyOptions(options: any) {
+ const { terminal, fitAddon } = this;
+ const isWebGL2Available = () => {
+ try {
+ const canvas = document.createElement('canvas');
+ return !!(window.WebGL2RenderingContext && canvas.getContext('webgl2'));
+ } catch (e) {
+ return false;
+ }
+ };
+
+ Object.keys(options).forEach(key => {
+ const value = options[key];
+ switch (key) {
+ case 'rendererType':
+ if (value === 'webgl' && isWebGL2Available()) {
+ terminal.loadAddon(new WebglAddon());
+ console.log(`[ttyd] WebGL renderer enabled`);
+ }
+ break;
+ case 'disableLeaveAlert':
+ if (value) {
+ window.removeEventListener('beforeunload', this.onWindowUnload);
+ console.log('[ttyd] Leave site alert disabled');
+ }
+ break;
+ case 'disableResizeOverlay':
+ if (value) {
+ console.log(`[ttyd] Resize overlay disabled`);
+ this.resizeOverlay = false;
+ }
+ break;
+ case 'titleFixed':
+ if (!value || value === '') return;
+ console.log(`[ttyd] setting fixed title: ${value}`);
+ this.titleFixed = value;
+ document.title = value;
+ break;
+ default:
+ console.log(`[ttyd] option: ${key}=${value}`);
+ terminal.setOption(key, value);
+ if (key.indexOf('font') === 0) fitAddon.fit();
+ break;
+ }
+ });
+ }
+
@bind
private onSocketOpen() {
console.log('[ttyd] websocket connection opened');
fitAddon.fit();
}
+ this.applyOptions(this.props.clientOptions);
+
terminal.focus();
}
@bind
private onSocketData(event: MessageEvent) {
- const { terminal, textDecoder, zmodemAddon, fitAddon } = this;
+ const { textDecoder, zmodemAddon } = this;
const rawData = event.data as ArrayBuffer;
const cmd = String.fromCharCode(new Uint8Array(rawData)[0]);
const data = rawData.slice(1);
document.title = this.title;
break;
case Command.SET_PREFERENCES:
- const preferences = JSON.parse(textDecoder.decode(data));
- Object.keys(preferences).forEach(key => {
- const value = preferences[key];
- switch (key) {
- case 'rendererType':
- if (preferences[key] === 'webgl') {
- terminal.loadAddon(new WebglAddon());
- console.log(`[ttyd] WebGL renderer enabled`);
- }
- break;
- case 'disableLeaveAlert':
- if (preferences[key]) {
- window.removeEventListener('beforeunload', this.onWindowUnload);
- console.log('[ttyd] Leave site alert disabled');
- }
- break;
- case 'disableResizeOverlay':
- if (preferences[key]) {
- console.log(`[ttyd] disabled resize overlay`);
- this.resizeOverlay = false;
- }
- break;
- case 'fontSize':
- console.log(`[ttyd] setting font size to ${value}`);
- terminal.setOption(key, value);
- fitAddon.fit();
- break;
- case 'titleFixed':
- console.log(`[ttyd] setting fixed title: ${value}`);
- this.titleFixed = value;
- document.title = value;
- break;
- default:
- console.log(`[ttyd] option: ${key}=${value}`);
- terminal.setOption(key, value);
- break;
- }
- });
+ this.applyOptions(JSON.parse(textDecoder.decode(data)));
break;
default:
console.warn(`[ttyd] unknown command: ${cmd}`);