mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
More refactors
This commit is contained in:
+22
-11
@@ -19,6 +19,7 @@ if ('WebAssembly' in window) {
|
||||
|
||||
import { Terminal, ITerminalOptions, type IDisposable, type ITheme } from '@xterm/xterm';
|
||||
import { AttachAddon } from '@xterm/addon-attach';
|
||||
import { AddonsWindow } from './components/window/addonsWindow';
|
||||
import { ControlBar } from './components/controlBar';
|
||||
import { GpuWindow } from './components/window/gpuWindow';
|
||||
import { OptionsWindow } from './components/window/optionsWindow';
|
||||
@@ -61,6 +62,8 @@ let protocol;
|
||||
let socketURL;
|
||||
let socket;
|
||||
let pid;
|
||||
let controlBar: ControlBar;
|
||||
let addonsWindow: AddonsWindow;
|
||||
let gpuWindow: GpuWindow;
|
||||
let optionsWindow: OptionsWindow;
|
||||
let styleWindow: StyleWindow;
|
||||
@@ -120,11 +123,10 @@ const addons: { [T in AddonType]: IDemoAddon<T> } = {
|
||||
};
|
||||
|
||||
let terminalContainer = document.getElementById('terminal-container');
|
||||
const actionElements = {
|
||||
find: document.querySelector('#find') as HTMLInputElement,
|
||||
findNext: document.querySelector('#find-next') as HTMLInputElement,
|
||||
findPrevious: document.querySelector('#find-previous') as HTMLInputElement,
|
||||
findResults: document.querySelector('#find-results')
|
||||
let actionElements: {
|
||||
findNext: HTMLInputElement;
|
||||
findPrevious: HTMLInputElement;
|
||||
findResults: HTMLElement;
|
||||
};
|
||||
let paddingElement: HTMLInputElement;
|
||||
|
||||
@@ -240,13 +242,18 @@ if (document.location.pathname === '/test') {
|
||||
window.WebLinksAddon = WebLinksAddon;
|
||||
window.WebglAddon = WebglAddon;
|
||||
} else {
|
||||
const controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), [
|
||||
{ id: 'addons', label: 'Addons' }
|
||||
]);
|
||||
controlBar = new ControlBar(document.getElementById('sidebar'), document.querySelector('.banner-tabs'), []);
|
||||
addonsWindow = new AddonsWindow();
|
||||
controlBar.registerWindow(addonsWindow);
|
||||
actionElements = {
|
||||
findNext: addonsWindow.findNextInput,
|
||||
findPrevious: addonsWindow.findPreviousInput,
|
||||
findResults: addonsWindow.findResultsSpan
|
||||
};
|
||||
gpuWindow = new GpuWindow();
|
||||
controlBar.registerWindow(gpuWindow, { afterId: 'addons', hidden: true, smallTab: true });
|
||||
optionsWindow = new OptionsWindow(updateTerminalSize, updateTerminalContainerBackground);
|
||||
controlBar.registerWindow(optionsWindow);
|
||||
gpuWindow = new GpuWindow();
|
||||
controlBar.registerWindow(gpuWindow);
|
||||
styleWindow = new StyleWindow();
|
||||
controlBar.registerWindow(styleWindow);
|
||||
paddingElement = styleWindow.paddingElement;
|
||||
@@ -254,6 +261,7 @@ if (document.location.pathname === '/test') {
|
||||
controlBar.registerWindow(testWindow);
|
||||
vtWindow = new VtWindow();
|
||||
controlBar.registerWindow(vtWindow);
|
||||
controlBar.activateDefaultTab();
|
||||
createTerminal();
|
||||
document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler);
|
||||
document.getElementById('create-new-window').addEventListener('click', createNewWindowButtonHandler);
|
||||
@@ -344,6 +352,7 @@ function createTerminal(): void {
|
||||
try {
|
||||
typedTerm.loadAddon(addons.webgl.instance);
|
||||
term.open(terminalContainer);
|
||||
controlBar.setTabVisible('gpu', true);
|
||||
gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
|
||||
addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e));
|
||||
addons.webgl.instance.onAddTextureAtlasCanvas(e => gpuWindow.appendTextureAtlas(e));
|
||||
@@ -472,6 +481,7 @@ function initAddons(term: Terminal): void {
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
function postInitWebgl(): void {
|
||||
controlBar.setTabVisible('gpu', true);
|
||||
setTimeout(() => {
|
||||
gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas);
|
||||
addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e));
|
||||
@@ -479,6 +489,7 @@ function initAddons(term: Terminal): void {
|
||||
}, 500);
|
||||
}
|
||||
function preDisposeWebgl(): void {
|
||||
controlBar.setTabVisible('gpu', false);
|
||||
if (addons.webgl.instance.textureAtlas) {
|
||||
addons.webgl.instance.textureAtlas.remove();
|
||||
}
|
||||
@@ -594,7 +605,7 @@ function initAddons(term: Terminal): void {
|
||||
|
||||
fragment.appendChild(wrapper);
|
||||
});
|
||||
const container = document.getElementById('addons-container');
|
||||
const container = addonsWindow.addonsContainer;
|
||||
container.innerHTML = '';
|
||||
container.appendChild(fragment);
|
||||
}
|
||||
|
||||
@@ -146,14 +146,35 @@ export class ControlBar {
|
||||
}
|
||||
}
|
||||
|
||||
public registerWindow(window: IControlWindow): void {
|
||||
public registerWindow(window: IControlWindow, options?: { afterId?: string; hidden?: boolean; smallTab?: boolean }): void {
|
||||
// Create button
|
||||
const button = document.createElement('button');
|
||||
button.id = `${window.id}button`;
|
||||
button.className = 'tabLinks';
|
||||
button.textContent = window.label;
|
||||
button.addEventListener('click', (e) => this._openSection(e, window.id));
|
||||
this._tabContainer.appendChild(button);
|
||||
|
||||
// Apply small tab styling
|
||||
if (options?.smallTab) {
|
||||
button.style.fontSize = '0.85em';
|
||||
}
|
||||
|
||||
// Insert after specified tab or append at end
|
||||
if (options?.afterId) {
|
||||
const afterTab = this._tabs.get(options.afterId);
|
||||
if (afterTab?.button.nextSibling) {
|
||||
this._tabContainer.insertBefore(button, afterTab.button.nextSibling);
|
||||
} else {
|
||||
this._tabContainer.appendChild(button);
|
||||
}
|
||||
} else {
|
||||
this._tabContainer.appendChild(button);
|
||||
}
|
||||
|
||||
// Hide if specified
|
||||
if (options?.hidden) {
|
||||
button.style.display = 'none';
|
||||
}
|
||||
|
||||
// Create content container
|
||||
const content = document.createElement('div');
|
||||
@@ -168,7 +189,42 @@ export class ControlBar {
|
||||
this._tabs.set(window.id, { button, content });
|
||||
}
|
||||
|
||||
public setTabVisible(tabId: string, visible: boolean): void {
|
||||
const tab = this._tabs.get(tabId);
|
||||
if (tab) {
|
||||
tab.button.style.display = visible ? '' : 'none';
|
||||
// If hiding the active tab, switch to first visible tab
|
||||
if (!visible && this._activeTabId === tabId) {
|
||||
for (const [id, t] of this._tabs) {
|
||||
if (t.button.style.display !== 'none') {
|
||||
this._activateTab(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get activeTabId(): string | null {
|
||||
return this._activeTabId;
|
||||
}
|
||||
|
||||
public activateDefaultTab(): void {
|
||||
// Restore saved tab or default to first visible tab
|
||||
const savedTab = localStorage.getItem('tab');
|
||||
if (savedTab && this._tabs.has(savedTab)) {
|
||||
const tab = this._tabs.get(savedTab);
|
||||
if (tab && tab.button.style.display !== 'none') {
|
||||
this._activateTab(savedTab);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Fall back to first visible tab
|
||||
for (const [id, tab] of this._tabs) {
|
||||
if (tab.button.style.display !== 'none') {
|
||||
this._activateTab(id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import type { Terminal } from '@xterm/xterm';
|
||||
import type { IControlWindow } from '../controlBar';
|
||||
|
||||
export class AddonsWindow implements IControlWindow {
|
||||
public readonly id = 'addons';
|
||||
public readonly label = 'Addons';
|
||||
|
||||
private _addonsContainer: HTMLElement;
|
||||
private _findNextInput: HTMLInputElement;
|
||||
private _findPreviousInput: HTMLInputElement;
|
||||
private _findResultsSpan: HTMLElement;
|
||||
private _regexCheckbox: HTMLInputElement;
|
||||
private _caseSensitiveCheckbox: HTMLInputElement;
|
||||
private _wholeWordCheckbox: HTMLInputElement;
|
||||
private _highlightAllMatchesCheckbox: HTMLInputElement;
|
||||
private _serializeOutputPre: HTMLPreElement;
|
||||
private _htmlSerializeOutputPre: HTMLPreElement;
|
||||
private _htmlSerializeOutputResult: HTMLElement;
|
||||
private _writeToTerminalCheckbox: HTMLInputElement;
|
||||
private _imageStorageLimitInput: HTMLInputElement;
|
||||
private _imageShowPlaceholderCheckbox: HTMLInputElement;
|
||||
private _imageOptionsTextarea: HTMLTextAreaElement;
|
||||
|
||||
public build(container: HTMLElement): void {
|
||||
// Heading
|
||||
const heading = document.createElement('h3');
|
||||
heading.textContent = 'Addons';
|
||||
container.appendChild(heading);
|
||||
|
||||
// Description
|
||||
const description = document.createElement('p');
|
||||
description.textContent = 'Addons can be loaded and unloaded on a particular terminal to extend its functionality.';
|
||||
container.appendChild(description);
|
||||
|
||||
// Addons container (checkboxes go here)
|
||||
this._addonsContainer = document.createElement('div');
|
||||
this._addonsContainer.id = 'addons-container';
|
||||
container.appendChild(this._addonsContainer);
|
||||
|
||||
// Addons Control section
|
||||
const controlHeading = document.createElement('h3');
|
||||
controlHeading.textContent = 'Addons Control';
|
||||
container.appendChild(controlHeading);
|
||||
|
||||
// SearchAddon section
|
||||
this._buildSearchSection(container);
|
||||
|
||||
// SerializeAddon section
|
||||
this._buildSerializeSection(container);
|
||||
|
||||
// ImageAddon section
|
||||
this._buildImageSection(container);
|
||||
}
|
||||
|
||||
private _buildSearchSection(container: HTMLElement): void {
|
||||
const h4 = document.createElement('h4');
|
||||
h4.textContent = 'SearchAddon';
|
||||
container.appendChild(h4);
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
wrapper.style.display = 'flex';
|
||||
wrapper.style.flexDirection = 'column';
|
||||
|
||||
// Find next
|
||||
const findNextLabel = document.createElement('label');
|
||||
findNextLabel.textContent = 'Find next ';
|
||||
this._findNextInput = document.createElement('input');
|
||||
this._findNextInput.id = 'find-next';
|
||||
findNextLabel.appendChild(this._findNextInput);
|
||||
wrapper.appendChild(findNextLabel);
|
||||
|
||||
// Find previous
|
||||
const findPrevLabel = document.createElement('label');
|
||||
findPrevLabel.textContent = 'Find previous ';
|
||||
this._findPreviousInput = document.createElement('input');
|
||||
this._findPreviousInput.id = 'find-previous';
|
||||
findPrevLabel.appendChild(this._findPreviousInput);
|
||||
wrapper.appendChild(findPrevLabel);
|
||||
|
||||
// Results
|
||||
const resultsDiv = document.createElement('div');
|
||||
resultsDiv.textContent = 'Results: ';
|
||||
this._findResultsSpan = document.createElement('span');
|
||||
this._findResultsSpan.id = 'find-results';
|
||||
resultsDiv.appendChild(this._findResultsSpan);
|
||||
wrapper.appendChild(resultsDiv);
|
||||
|
||||
// Regex checkbox
|
||||
const regexLabel = document.createElement('label');
|
||||
this._regexCheckbox = document.createElement('input');
|
||||
this._regexCheckbox.type = 'checkbox';
|
||||
this._regexCheckbox.id = 'regex';
|
||||
regexLabel.appendChild(this._regexCheckbox);
|
||||
regexLabel.appendChild(document.createTextNode('Use regex'));
|
||||
wrapper.appendChild(regexLabel);
|
||||
|
||||
// Case sensitive checkbox
|
||||
const caseLabel = document.createElement('label');
|
||||
this._caseSensitiveCheckbox = document.createElement('input');
|
||||
this._caseSensitiveCheckbox.type = 'checkbox';
|
||||
this._caseSensitiveCheckbox.id = 'case-sensitive';
|
||||
caseLabel.appendChild(this._caseSensitiveCheckbox);
|
||||
caseLabel.appendChild(document.createTextNode('Case sensitive'));
|
||||
wrapper.appendChild(caseLabel);
|
||||
|
||||
// Whole word checkbox
|
||||
const wholeWordLabel = document.createElement('label');
|
||||
this._wholeWordCheckbox = document.createElement('input');
|
||||
this._wholeWordCheckbox.type = 'checkbox';
|
||||
this._wholeWordCheckbox.id = 'whole-word';
|
||||
wholeWordLabel.appendChild(this._wholeWordCheckbox);
|
||||
wholeWordLabel.appendChild(document.createTextNode('Whole word'));
|
||||
wrapper.appendChild(wholeWordLabel);
|
||||
|
||||
// Highlight all matches checkbox
|
||||
const highlightLabel = document.createElement('label');
|
||||
this._highlightAllMatchesCheckbox = document.createElement('input');
|
||||
this._highlightAllMatchesCheckbox.type = 'checkbox';
|
||||
this._highlightAllMatchesCheckbox.id = 'highlight-all-matches';
|
||||
this._highlightAllMatchesCheckbox.checked = true;
|
||||
highlightLabel.appendChild(this._highlightAllMatchesCheckbox);
|
||||
highlightLabel.appendChild(document.createTextNode('Highlight All Matches'));
|
||||
wrapper.appendChild(highlightLabel);
|
||||
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
|
||||
private _buildSerializeSection(container: HTMLElement): void {
|
||||
const h4 = document.createElement('h4');
|
||||
h4.textContent = 'SerializeAddon';
|
||||
container.appendChild(h4);
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
// Serialize button
|
||||
const serializeBtn = document.createElement('button');
|
||||
serializeBtn.id = 'serialize';
|
||||
serializeBtn.textContent = 'Serialize the content of terminal';
|
||||
wrapper.appendChild(serializeBtn);
|
||||
|
||||
// Write to terminal checkbox
|
||||
const writeLabel = document.createElement('label');
|
||||
this._writeToTerminalCheckbox = document.createElement('input');
|
||||
this._writeToTerminalCheckbox.type = 'checkbox';
|
||||
this._writeToTerminalCheckbox.id = 'write-to-terminal';
|
||||
writeLabel.appendChild(this._writeToTerminalCheckbox);
|
||||
writeLabel.appendChild(document.createTextNode('Write back to terminal'));
|
||||
wrapper.appendChild(writeLabel);
|
||||
|
||||
// Serialize output
|
||||
const outputDiv = document.createElement('div');
|
||||
this._serializeOutputPre = document.createElement('pre');
|
||||
this._serializeOutputPre.id = 'serialize-output';
|
||||
outputDiv.appendChild(this._serializeOutputPre);
|
||||
wrapper.appendChild(outputDiv);
|
||||
|
||||
// HTML serialize button
|
||||
const htmlSerializeBtn = document.createElement('button');
|
||||
htmlSerializeBtn.id = 'htmlserialize';
|
||||
htmlSerializeBtn.textContent = 'Serialize the content of terminal in HTML';
|
||||
wrapper.appendChild(htmlSerializeBtn);
|
||||
|
||||
// HTML serialize result
|
||||
this._htmlSerializeOutputResult = document.createElement('span');
|
||||
this._htmlSerializeOutputResult.id = 'htmlserialize-output-result';
|
||||
wrapper.appendChild(this._htmlSerializeOutputResult);
|
||||
|
||||
// HTML serialize output
|
||||
const htmlOutputDiv = document.createElement('div');
|
||||
this._htmlSerializeOutputPre = document.createElement('pre');
|
||||
this._htmlSerializeOutputPre.id = 'htmlserialize-output';
|
||||
htmlOutputDiv.appendChild(this._htmlSerializeOutputPre);
|
||||
wrapper.appendChild(htmlOutputDiv);
|
||||
|
||||
container.appendChild(wrapper);
|
||||
}
|
||||
|
||||
private _buildImageSection(container: HTMLElement): void {
|
||||
const h4 = document.createElement('h4');
|
||||
h4.textContent = 'Image Addon';
|
||||
container.appendChild(h4);
|
||||
|
||||
const details = document.createElement('details');
|
||||
const summary = document.createElement('summary');
|
||||
summary.textContent = 'image addon settings';
|
||||
details.appendChild(summary);
|
||||
|
||||
const wrapper = document.createElement('div');
|
||||
|
||||
// Storage limit
|
||||
const storageLimitLabel = document.createElement('label');
|
||||
storageLimitLabel.textContent = 'Storage Limit (in MB) ';
|
||||
this._imageStorageLimitInput = document.createElement('input');
|
||||
this._imageStorageLimitInput.type = 'number';
|
||||
this._imageStorageLimitInput.id = 'image-storagelimit';
|
||||
storageLimitLabel.appendChild(this._imageStorageLimitInput);
|
||||
wrapper.appendChild(storageLimitLabel);
|
||||
wrapper.appendChild(document.createElement('br'));
|
||||
|
||||
// Show placeholder
|
||||
const placeholderLabel = document.createElement('label');
|
||||
placeholderLabel.textContent = 'Show Placeholder ';
|
||||
this._imageShowPlaceholderCheckbox = document.createElement('input');
|
||||
this._imageShowPlaceholderCheckbox.type = 'checkbox';
|
||||
this._imageShowPlaceholderCheckbox.id = 'image-showplaceholder';
|
||||
placeholderLabel.appendChild(this._imageShowPlaceholderCheckbox);
|
||||
wrapper.appendChild(placeholderLabel);
|
||||
wrapper.appendChild(document.createElement('br'));
|
||||
wrapper.appendChild(document.createElement('br'));
|
||||
|
||||
// Ctor options
|
||||
const optionsLabel = document.createElement('label');
|
||||
optionsLabel.appendChild(document.createTextNode('Ctor options (applied on addon relaunch)'));
|
||||
optionsLabel.appendChild(document.createElement('br'));
|
||||
this._imageOptionsTextarea = document.createElement('textarea');
|
||||
this._imageOptionsTextarea.id = 'image-options';
|
||||
this._imageOptionsTextarea.cols = 40;
|
||||
this._imageOptionsTextarea.rows = 12;
|
||||
optionsLabel.appendChild(this._imageOptionsTextarea);
|
||||
wrapper.appendChild(optionsLabel);
|
||||
|
||||
details.appendChild(wrapper);
|
||||
container.appendChild(details);
|
||||
}
|
||||
|
||||
public get addonsContainer(): HTMLElement {
|
||||
return this._addonsContainer;
|
||||
}
|
||||
|
||||
public get findNextInput(): HTMLInputElement {
|
||||
return this._findNextInput;
|
||||
}
|
||||
|
||||
public get findPreviousInput(): HTMLInputElement {
|
||||
return this._findPreviousInput;
|
||||
}
|
||||
|
||||
public get findResultsSpan(): HTMLElement {
|
||||
return this._findResultsSpan;
|
||||
}
|
||||
}
|
||||
@@ -21,45 +21,6 @@
|
||||
<div id="terminal-container"></div>
|
||||
</div>
|
||||
<div id="sidebar" class="grid">
|
||||
<div id="addons" class="tabContent">
|
||||
<h3>Addons</h3>
|
||||
<p>Addons can be loaded and unloaded on a particular terminal to extend its functionality.</p>
|
||||
<div id="addons-container"></div>
|
||||
<h3>Addons Control</h3>
|
||||
<h4>SearchAddon</h4>
|
||||
<div style= "display:flex; flex-direction:column;">
|
||||
<label>Find next <input id="find-next"/></label>
|
||||
<label>Find previous <input id="find-previous"/></label>
|
||||
<div>Results: <span id="find-results"></span></div>
|
||||
<label><input type="checkbox" id="regex"/>Use regex</label>
|
||||
<label><input type="checkbox" id="case-sensitive"/>Case sensitive</label>
|
||||
<label><input type="checkbox" id="whole-word"/>Whole word</label>
|
||||
<label><input type="checkbox" id="highlight-all-matches" checked/>Highlight All Matches</label>
|
||||
</div>
|
||||
<h4>SerializeAddon</h4>
|
||||
<div>
|
||||
<button id="serialize">Serialize the content of terminal</button>
|
||||
<label><input type="checkbox" id="write-to-terminal">Write back to terminal</label>
|
||||
<div><pre id="serialize-output"></pre></div>
|
||||
|
||||
<button id="htmlserialize">Serialize the content of terminal in HTML</button>
|
||||
<span id="htmlserialize-output-result"></span>
|
||||
<div><pre id="htmlserialize-output"></pre></div>
|
||||
</div>
|
||||
<h4>Image Addon</h4>
|
||||
<details>
|
||||
<summary>image addon settings</summary>
|
||||
<div>
|
||||
<label>Storage Limit (in MB) <input type="number" id="image-storagelimit"/></label><br/>
|
||||
<label>Show Placeholder <input type="checkbox" id="image-showplaceholder"/></label>
|
||||
<br/><br/>
|
||||
<label>
|
||||
Ctor options (applied on addon relaunch)<br/>
|
||||
<textarea id="image-options" cols="40" rows="12"></textarea>
|
||||
</label>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="dist/client-bundle.js" defer ></script>
|
||||
|
||||
Reference in New Issue
Block a user