Merge pull request #5500 from Tyriar/demo2

More demo refactors
This commit is contained in:
Daniel Imms
2025-12-26 10:51:49 -08:00
committed by GitHub
12 changed files with 1053 additions and 994 deletions
+50 -919
View File
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -3,6 +3,11 @@
* @license MIT
*/
/// <reference path="../../typings/xterm.d.ts"/>
import type { Terminal } from '@xterm/xterm';
import type { AddonCollection } from 'types';
export interface ITabConfig {
id: string;
label: string;
@@ -12,6 +17,7 @@ export interface IControlWindow {
readonly id: string;
readonly label: string;
build(container: HTMLElement): void;
setTerminal(terminal: Terminal): void;
}
export class ControlBar {
@@ -146,7 +152,7 @@ export class ControlBar {
}
}
public registerWindow(window: IControlWindow, options?: { afterId?: string; hidden?: boolean; smallTab?: boolean }): void {
public registerWindow<T extends IControlWindow>(window: T, options?: { afterId?: string; hidden?: boolean; smallTab?: boolean }): T {
// Create button
const button = document.createElement('button');
button.id = `${window.id}button`;
@@ -187,6 +193,8 @@ export class ControlBar {
window.build(content);
this._tabs.set(window.id, { button, content });
return window;
}
public setTabVisible(tabId: string, visible: boolean): void {
+37 -3
View File
@@ -3,10 +3,15 @@
* @license MIT
*/
import type { Terminal } from '@xterm/xterm';
import type { IControlWindow } from '../controlBar';
/// <reference path="../../../typings/xterm.d.ts"/>
export class AddonsWindow implements IControlWindow {
import type { Terminal } from '@xterm/xterm';
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar';
import type { AddonCollection } from 'types';
import type { IImageAddonOptions } from '@xterm/addon-image';
export class AddonsWindow extends BaseWindow implements IControlWindow {
public readonly id = 'addons';
public readonly label = 'Addons';
@@ -141,6 +146,7 @@ export class AddonsWindow implements IControlWindow {
const serializeBtn = document.createElement('button');
serializeBtn.id = 'serialize';
serializeBtn.textContent = 'Serialize the content of terminal';
serializeBtn.addEventListener('click', () => serializeButtonHandler(this._terminal, this._addons));
wrapper.appendChild(serializeBtn);
// Write to terminal checkbox
@@ -163,6 +169,7 @@ export class AddonsWindow implements IControlWindow {
const htmlSerializeBtn = document.createElement('button');
htmlSerializeBtn.id = 'htmlserialize';
htmlSerializeBtn.textContent = 'Serialize the content of terminal in HTML';
htmlSerializeBtn.addEventListener('click', () => htmlSerializeButtonHandler(this._terminal, this._addons));
wrapper.appendChild(htmlSerializeBtn);
// HTML serialize result
@@ -244,3 +251,30 @@ export class AddonsWindow implements IControlWindow {
return this._findResultsSpan;
}
}
function serializeButtonHandler(term: Terminal, addons: AddonCollection): void {
const output = addons.serialize.instance.serialize();
const outputString = JSON.stringify(output);
document.getElementById('serialize-output').innerText = outputString;
if ((document.getElementById('write-to-terminal') as HTMLInputElement).checked) {
term.reset();
term.write(output);
}
}
function htmlSerializeButtonHandler(term: Terminal, addons: AddonCollection): void {
const output = addons.serialize.instance.serializeAsHTML();
document.getElementById('htmlserialize-output').innerText = output;
// Deprecated, but the most supported for now.
function listener(e: any): void {
e.clipboardData.setData('text/html', output);
e.preventDefault();
}
document.addEventListener('copy', listener);
document.execCommand('copy');
document.removeEventListener('copy', listener);
document.getElementById('htmlserialize-output-result').innerText = 'Copied to clipboard';
}
+29
View File
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2025 The xterm.js authors. All rights reserved.
* @license MIT
*/
/// <reference path="../../../typings/xterm.d.ts"/>
import type { AddonCollection } from '../../types';
import type { IControlWindow } from '../controlBar';
import type { Terminal } from '@xterm/xterm';
export abstract class BaseWindow implements IControlWindow {
protected get _terminal(): Terminal { return this.__terminal; }
constructor(
private __terminal: Terminal,
protected readonly _addons: AddonCollection,
) {
}
setTerminal(terminal: Terminal): void {
this.__terminal = terminal;
}
abstract id: string;
abstract label: string;
abstract build(container: HTMLElement): void;
}
+2 -1
View File
@@ -3,9 +3,10 @@
* @license MIT
*/
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar';
export class GpuWindow implements IControlWindow {
export class GpuWindow extends BaseWindow implements IControlWindow {
public readonly id = 'gpu';
public readonly label = 'GPU';
+28 -28
View File
@@ -3,8 +3,10 @@
* @license MIT
*/
import { BaseWindow } from './baseWindow';
import type { Terminal, ITheme } from '@xterm/xterm';
import type { IControlWindow } from '../controlBar';
import type { AddonCollection } from 'types';
const xtermjsTheme: ITheme = {
foreground: '#F8F8F8',
@@ -29,23 +31,23 @@ const xtermjsTheme: ITheme = {
brightWhite: '#FFFFFF'
};
export class OptionsWindow implements IControlWindow {
export class OptionsWindow extends BaseWindow implements IControlWindow {
public readonly id = 'options';
public readonly label = 'Options';
private _container: HTMLElement;
private _optionsContainer: HTMLElement;
private _term: Terminal;
private _autoResize: boolean = true;
private _updateTerminalSize: () => void;
private _updateTerminalContainerBackground: () => void;
constructor(
updateTerminalSize: () => void,
updateTerminalContainerBackground: () => void
terminal: Terminal,
addons: AddonCollection,
private readonly _handlers: {
updateTerminalSize: () => void,
updateTerminalContainerBackground: () => void
},
) {
this._updateTerminalSize = updateTerminalSize;
this._updateTerminalContainerBackground = updateTerminalContainerBackground;
super(terminal, addons)
}
public build(container: HTMLElement): void {
@@ -64,9 +66,7 @@ export class OptionsWindow implements IControlWindow {
container.appendChild(this._optionsContainer);
}
public initOptions(term: Terminal, addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {
this._term = term;
public initOptions(addDomListener: (el: HTMLElement, type: string, handler: (...args: any[]) => any) => void): void {
const blacklistedOptions = [
'cancelEvents',
'convertEol',
@@ -91,11 +91,11 @@ export class OptionsWindow implements IControlWindow {
wordSeparator: null,
colsRows: null
};
const options = Object.getOwnPropertyNames(term.options);
const options = Object.getOwnPropertyNames(this._terminal.options);
const booleanOptions: string[] = [];
const numberOptions: string[] = [];
options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => {
switch (typeof term.options[o]) {
switch (typeof this._terminal.options[o]) {
case 'boolean':
booleanOptions.push(o);
break;
@@ -112,21 +112,21 @@ export class OptionsWindow implements IControlWindow {
let html = '';
html += '<div class="option-group">';
booleanOptions.forEach(o => {
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${term.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
html += `<div class="option"><label><input id="opt-${o}" type="checkbox" ${this._terminal.options[o] ? 'checked' : ''}/> ${o}</label></div>`;
});
html += '</div><div class="option-group">';
numberOptions.forEach(o => {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${term.options[o] ?? ''}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="number" value="${this._terminal.options[o] ?? ''}" step="${o === 'lineHeight' || o === 'scrollSensitivity' ? '0.1' : '1'}"/></label></div>`;
});
html += '</div><div class="option-group">';
Object.keys(stringOptions).forEach(o => {
if (o === 'colsRows') {
html += `<div class="option"><label>size (<var>cols</var><code>x</code><var>rows</var> or <code>auto</code>) <input id="opt-${o}" type="text" value="auto"/></label></div>`;
} else if (stringOptions[o]) {
const selectedOption = o === 'theme' ? 'xtermjs' : term.options[o];
const selectedOption = o === 'theme' ? 'xtermjs' : this._terminal.options[o];
html += `<div class="option"><label>${o} <select id="opt-${o}">${stringOptions[o]!.map(v => `<option ${v === selectedOption ? 'selected' : ''}>${v}</option>`).join('')}</select></label></div>`;
} else {
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${term.options[o]}"/></label></div>`;
html += `<div class="option"><label>${o} <input id="opt-${o}" type="text" value="${this._terminal.options[o]}"/></label></div>`;
}
});
html += '</div>';
@@ -138,7 +138,7 @@ export class OptionsWindow implements IControlWindow {
const input = document.getElementById(`opt-${o}`) as HTMLInputElement;
addDomListener(input, 'change', () => {
console.log('change', o, input.checked);
term.options[o] = input.checked;
this._terminal.options[o] = input.checked;
});
});
numberOptions.forEach(o => {
@@ -146,16 +146,16 @@ export class OptionsWindow implements IControlWindow {
addDomListener(input, 'change', () => {
console.log('change', o, input.value);
if (o === 'lineHeight') {
term.options.lineHeight = parseFloat(input.value);
this._terminal.options.lineHeight = parseFloat(input.value);
} else if (o === 'scrollSensitivity') {
term.options.scrollSensitivity = parseFloat(input.value);
this._terminal.options.scrollSensitivity = parseFloat(input.value);
} else if (o === 'scrollback') {
term.options.scrollback = parseInt(input.value);
setTimeout(() => this._updateTerminalSize(), 5);
this._terminal.options.scrollback = parseInt(input.value);
setTimeout(() => this._handlers.updateTerminalSize(), 5);
} else {
term.options[o] = parseInt(input.value);
this._terminal.options[o] = parseInt(input.value);
}
this._updateTerminalSize();
this._handlers.updateTerminalSize();
});
});
Object.keys(stringOptions).forEach(o => {
@@ -167,11 +167,11 @@ export class OptionsWindow implements IControlWindow {
const m = input.value.match(/^([0-9]+)x([0-9]+)$/);
if (m) {
this._autoResize = false;
term.resize(parseInt(m[1]), parseInt(m[2]));
this._terminal.resize(parseInt(m[1]), parseInt(m[2]));
} else {
this._autoResize = true;
input.value = 'auto';
this._updateTerminalSize();
this._handlers.updateTerminalSize();
}
} else if (o === 'theme') {
switch (input.value) {
@@ -232,9 +232,9 @@ export class OptionsWindow implements IControlWindow {
break;
}
}
term.options[o] = value;
this._terminal.options[o] = value;
if (o === 'theme') {
this._updateTerminalContainerBackground();
this._handlers.updateTerminalContainerBackground();
}
});
});
+2 -1
View File
@@ -3,9 +3,10 @@
* @license MIT
*/
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar';
export class StyleWindow implements IControlWindow {
export class StyleWindow extends BaseWindow implements IControlWindow {
public readonly id = 'style';
public readonly label = 'Style';
File diff suppressed because it is too large Load Diff
+2 -11
View File
@@ -3,10 +3,11 @@
* @license MIT
*/
import { BaseWindow } from './baseWindow';
import type { IControlWindow } from '../controlBar';
import type { Terminal } from '@xterm/xterm';
export class VtWindow implements IControlWindow {
export class VtWindow extends BaseWindow implements IControlWindow {
public readonly id = 'vt';
public readonly label = 'VT';
@@ -23,16 +24,6 @@ export class VtWindow implements IControlWindow {
const vtContainer = document.createElement('div');
vtContainer.id = 'vt-container';
container.appendChild(vtContainer);
}
public initTerminal(term: Terminal): void {
this._term = term;
this._addVtButtons();
}
private _addVtButtons(): void {
const vtContainer = this._container.querySelector('#vt-container');
if (!vtContainer) return;
const vtFragment = document.createDocumentFragment();
const buttonSpecs: { [key: string]: { label: string; description: string; paramCount?: number } } = {
+6 -2
View File
@@ -9,9 +9,13 @@ body {
grid-template-rows: 24px 1fr;
}
/* Top banner */
body:not(#test) {
height: initial;
display: initial;
}
#banner {
background: #2D2E2C;
background: #161716;
display: flex;
align-items: center;
justify-content: space-between;
+14 -2
View File
@@ -3,9 +3,21 @@
<head>
<title>xterm.js integration test fixture</title>
<link rel="stylesheet" href="/xterm.css" />
<link rel="stylesheet" href="/style.css" />
<style>
body {
font-family: helvetica, sans-serif, arial;
font-size: 1em;
color: #111;
}
#terminal-container {
height: 60%;
margin: 0 auto;
padding: 2px;
}
</style>
</head>
<body>
<body id="test">
<div id="terminal-container"></div>
<script src="/dist/client-bundle.js" defer></script>
</body>
+58
View File
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*
* This file is the entry point for browserify.
*/
import type { ImageAddon } from '@xterm/addon-image';
import type { AttachAddon } from '@xterm/addon-attach';
import type { ClipboardAddon } from '@xterm/addon-clipboard';
import type { FitAddon } from '@xterm/addon-fit';
import type { LigaturesAddon } from '@xterm/addon-ligatures';
import type { ProgressAddon } from '@xterm/addon-progress';
import type { SearchAddon } from '@xterm/addon-search';
import type { SerializeAddon } from '@xterm/addon-serialize';
import type { UnicodeGraphemesAddon } from '@xterm/addon-unicode-graphemes';
import type { Unicode11Addon } from '@xterm/addon-unicode11';
import type { WebLinksAddon } from '@xterm/addon-web-links';
import type { WebglAddon } from '@xterm/addon-webgl';
export type AddonType = 'attach' | 'clipboard' | 'fit' | 'image' | 'progress' | 'search' | 'serialize' | 'unicode11' | 'unicodeGraphemes' | 'webLinks' | 'webgl' | 'ligatures';
export interface IDemoAddon<T extends AddonType> {
name: T;
canChange: boolean;
ctor: (
T extends 'attach' ? typeof AttachAddon :
T extends 'clipboard' ? typeof ClipboardAddon :
T extends 'fit' ? typeof FitAddon :
T extends 'image' ? typeof ImageAddon :
T extends 'ligatures' ? typeof LigaturesAddon :
T extends 'progress' ? typeof ProgressAddon :
T extends 'search' ? typeof SearchAddon :
T extends 'serialize' ? typeof SerializeAddon :
T extends 'webLinks' ? typeof WebLinksAddon :
T extends 'unicode11' ? typeof Unicode11Addon :
T extends 'unicodeGraphemes' ? typeof UnicodeGraphemesAddon :
T extends 'webgl' ? typeof WebglAddon :
never
);
instance?: (
T extends 'attach' ? AttachAddon :
T extends 'clipboard' ? ClipboardAddon :
T extends 'fit' ? FitAddon :
T extends 'image' ? ImageAddon :
T extends 'ligatures' ? LigaturesAddon :
T extends 'progress' ? ProgressAddon :
T extends 'search' ? SearchAddon :
T extends 'serialize' ? SerializeAddon :
T extends 'webLinks' ? WebLinksAddon :
T extends 'unicode11' ? Unicode11Addon :
T extends 'unicodeGraphemes' ? UnicodeGraphemesAddon :
T extends 'webgl' ? WebglAddon :
never
);
}
export type AddonCollection = { [T in AddonType]: IDemoAddon<T> };