diff --git a/demo/client.ts b/demo/client.ts index 8db3ec43..5d2f34f3 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -220,7 +220,7 @@ if (document.location.pathname === '/test') { // TODO: Most of below should be encapsulated within windows paddingElement = styleWindow.paddingElement; - + controlBar.setTabVisible('gpu', true); gpuWindow.setTextureAtlas(addons.webgl.instance.textureAtlas); addons.webgl.instance.onChangeTextureAtlas(e => gpuWindow.setTextureAtlas(e)); @@ -253,12 +253,6 @@ if (document.location.pathname === '/test') { addDomListener(actionElements.findPrevious, 'blur', (e) => { addons.search.instance.clearActiveDecoration(); }); - - document.getElementById('serialize').addEventListener('click', serializeButtonHandler); - document.getElementById('htmlserialize').addEventListener('click', htmlSerializeButtonHandler); - initImageAddonExposed(); - testEvents(); - progressButtons(); } function createTerminal(): Terminal { @@ -574,32 +568,6 @@ function updateTerminalSize(): void { addons.fit.instance.fit(); } -function serializeButtonHandler(): 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(): 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'; -} - (console as any).image = (source: ImageData | HTMLCanvasElement, scale: number = 1) => { function getBox(width: number, height: number): any { return { @@ -630,123 +598,3 @@ function htmlSerializeButtonHandler(): void { ); console.groupEnd(); }; - -function initImageAddonExposed(): void { - const DEFAULT_OPTIONS: IImageAddonOptions = (addons.image.instance as any)._defaultOpts; - const limitStorageElement = document.querySelector('#image-storagelimit'); - limitStorageElement.valueAsNumber = addons.image.instance.storageLimit; - addDomListener(limitStorageElement, 'change', () => { - try { - addons.image.instance.storageLimit = limitStorageElement.valueAsNumber; - limitStorageElement.valueAsNumber = addons.image.instance.storageLimit; - console.log('changed storageLimit to', addons.image.instance.storageLimit); - } catch (e) { - limitStorageElement.valueAsNumber = addons.image.instance.storageLimit; - console.log('storageLimit at', addons.image.instance.storageLimit); - throw e; - } - }); - const showPlaceholderElement = document.querySelector('#image-showplaceholder'); - showPlaceholderElement.checked = addons.image.instance.showPlaceholder; - addDomListener(showPlaceholderElement, 'change', () => { - addons.image.instance.showPlaceholder = showPlaceholderElement.checked; - }); - const ctorOptionsElement = document.querySelector('#image-options'); - ctorOptionsElement.value = JSON.stringify(DEFAULT_OPTIONS, null, 2); - - const sixelDemo = (url: string) => () => fetch(url) - .then(resp => resp.arrayBuffer()) - .then(buffer => { - term.write('\r\n'); - term.write(new Uint8Array(buffer)); - }); - - const iipDemo = (url: string) => () => fetch(url) - .then(resp => resp.arrayBuffer()) - .then(buffer => { - const data = new Uint8Array(buffer); - let sdata = ''; - for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); - term.write('\r\n'); - term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); - }); - - document.getElementById('image-demo1').addEventListener('click', - sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); - document.getElementById('image-demo2').addEventListener('click', - sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); - document.getElementById('image-demo3').addEventListener('click', - iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); - - // demo for image retrieval API - term.element.addEventListener('click', (ev: MouseEvent) => { - if (!ev.ctrlKey || !addons.image.instance) return; - - // TODO... - // if (ev.altKey) { - // const sel = term.getSelectionPosition(); - // if (sel) { - // addons.image.instance - // .extractCanvasAtBufferRange(term.getSelectionPosition()) - // ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank')); - // return; - // } - // } - - const pos = term._core._mouseService!.getCoords(ev, term._core.screenElement!, term.cols, term.rows); - const x = pos[0] - 1; - const y = pos[1] - 1; - const canvas = ev.shiftKey - // ctrl+shift+click: get single tile - ? addons.image.instance.extractTileAtBufferCell(x, term.buffer.active.viewportY + y) - // ctrl+click: get original image - : addons.image.instance.getImageAtBufferCell(x, term.buffer.active.viewportY + y); - canvas?.toBlob(data => window.open(URL.createObjectURL(data), '_blank')); - }); -} - -function testEvents(): void { - document.getElementById('event-focus').addEventListener('click', ()=> term.focus()); - document.getElementById('event-blur').addEventListener('click', ()=> term.blur()); -} - - -function progressButtons(): void { - const STATES = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' }; - const COLORS = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' }; - - function progressHandler({ state, value }: IProgressState): void { - // Simulate windows taskbar hack by windows terminal: - // Since the taskbar has no means to indicate error/pause state other than by coloring - // the current progress, we move 0 to 10% and distribute higher values in the remaining 90 % - // NOTE: This is most likely not what you want to do for other progress indicators, - // that have a proper visual state for error/paused. - value = Math.min(10 + value * 0.9, 100); - document.getElementById('progress-percent').style.width = `${value}%`; - document.getElementById('progress-percent').style.backgroundColor = COLORS[state]; - document.getElementById('progress-state').innerText = `State: ${STATES[state]}`; - - document.getElementById('progress-percent').style.display = state === 3 ? 'none' : 'block'; - document.getElementById('progress-indeterminate').style.display = state === 3 ? 'block' : 'none'; - } - - const progressAddon = addons.progress.instance; - progressAddon.onChange(progressHandler); - - // apply initial state once to make it visible on page load - const initialProgress = progressAddon.progress; - progressHandler(initialProgress); - - document.getElementById('progress-run').addEventListener('click', async () => { - term.write('\x1b]9;4;0\x1b\\'); - for (let i = 0; i <= 100; i += 5) { - term.write(`\x1b]9;4;1;${i}\x1b\\`); - await new Promise(res => setTimeout(res, 200)); - } - }); - document.getElementById('progress-0').addEventListener('click', () => term.write('\x1b]9;4;0\x1b\\')); - document.getElementById('progress-1').addEventListener('click', () => term.write('\x1b]9;4;1;20\x1b\\')); - document.getElementById('progress-2').addEventListener('click', () => term.write('\x1b]9;4;2\x1b\\')); - document.getElementById('progress-3').addEventListener('click', () => term.write('\x1b]9;4;3\x1b\\')); - document.getElementById('progress-4').addEventListener('click', () => term.write('\x1b]9;4;4\x1b\\')); -} diff --git a/demo/components/window/addonsWindow.ts b/demo/components/window/addonsWindow.ts index 415dd5a4..842597ae 100644 --- a/demo/components/window/addonsWindow.ts +++ b/demo/components/window/addonsWindow.ts @@ -3,8 +3,13 @@ * @license MIT */ +/// + +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'; @@ -55,6 +60,7 @@ export class AddonsWindow extends BaseWindow implements IControlWindow { // ImageAddon section this._buildImageSection(container); + initImageAddonExposed(this._terminal, this._addons); } private _buildSearchSection(container: HTMLElement): void { @@ -141,6 +147,7 @@ export class AddonsWindow extends BaseWindow 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 +170,7 @@ export class AddonsWindow extends BaseWindow 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 +252,109 @@ export class AddonsWindow extends BaseWindow 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'; +} + +function initImageAddonExposed(term: Terminal, addons: AddonCollection): void { + const DEFAULT_OPTIONS: IImageAddonOptions = (addons.image.instance as any)._defaultOpts; + const limitStorageElement = document.querySelector('#image-storagelimit'); + limitStorageElement.valueAsNumber = addons.image.instance.storageLimit; + addDomListener(term, limitStorageElement, 'change', () => { + try { + addons.image.instance.storageLimit = limitStorageElement.valueAsNumber; + limitStorageElement.valueAsNumber = addons.image.instance.storageLimit; + console.log('changed storageLimit to', addons.image.instance.storageLimit); + } catch (e) { + limitStorageElement.valueAsNumber = addons.image.instance.storageLimit; + console.log('storageLimit at', addons.image.instance.storageLimit); + throw e; + } + }); + const showPlaceholderElement = document.querySelector('#image-showplaceholder'); + showPlaceholderElement.checked = addons.image.instance.showPlaceholder; + addDomListener(term, showPlaceholderElement, 'change', () => { + addons.image.instance.showPlaceholder = showPlaceholderElement.checked; + }); + const ctorOptionsElement = document.querySelector('#image-options'); + ctorOptionsElement.value = JSON.stringify(DEFAULT_OPTIONS, null, 2); + + const sixelDemo = (url: string) => () => fetch(url) + .then(resp => resp.arrayBuffer()) + .then(buffer => { + term.write('\r\n'); + term.write(new Uint8Array(buffer)); + }); + + const iipDemo = (url: string) => () => fetch(url) + .then(resp => resp.arrayBuffer()) + .then(buffer => { + const data = new Uint8Array(buffer); + let sdata = ''; + for (let i = 0; i < data.length; ++i) sdata += String.fromCharCode(data[i]); + term.write('\r\n'); + term.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`); + }); + + document.getElementById('image-demo1').addEventListener('click', + sixelDemo('https://raw.githubusercontent.com/saitoha/libsixel/master/images/snake.six')); + document.getElementById('image-demo2').addEventListener('click', + sixelDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/testfiles/test2.sixel')); + document.getElementById('image-demo3').addEventListener('click', + iipDemo('https://raw.githubusercontent.com/jerch/node-sixel/master/palette.png')); + + // demo for image retrieval API + term.element.addEventListener('click', (ev: MouseEvent) => { + if (!ev.ctrlKey || !addons.image.instance) return; + + // TODO... + // if (ev.altKey) { + // const sel = term.getSelectionPosition(); + // if (sel) { + // addons.image.instance + // .extractCanvasAtBufferRange(term.getSelectionPosition()) + // ?.toBlob(data => window.open(URL.createObjectURL(data), '_blank')); + // return; + // } + // } + + const pos = (term as any)._core._mouseService!.getCoords(ev, (term as any)._core.screenElement!, term.cols, term.rows); + const x = pos[0] - 1; + const y = pos[1] - 1; + const canvas = ev.shiftKey + // ctrl+shift+click: get single tile + ? addons.image.instance.extractTileAtBufferCell(x, term.buffer.active.viewportY + y) + // ctrl+click: get original image + : addons.image.instance.getImageAtBufferCell(x, term.buffer.active.viewportY + y); + canvas?.toBlob(data => window.open(URL.createObjectURL(data), '_blank')); + }); +} + +function addDomListener(term: Terminal, element: HTMLElement, type: string, handler: (...args: any[]) => any): void { + element.addEventListener(type, handler); + (term as any)._core._register({ dispose: () => element.removeEventListener(type, handler) }); +} \ No newline at end of file diff --git a/demo/components/window/testWindow.ts b/demo/components/window/testWindow.ts index bbd1c3d0..b3d9c60d 100644 --- a/demo/components/window/testWindow.ts +++ b/demo/components/window/testWindow.ts @@ -10,6 +10,7 @@ import type { IControlWindow } from '../controlBar'; import { BaseWindow } from './baseWindow'; import type { IDisposable, Terminal } from '@xterm/xterm'; import type { AddonCollection } from 'types'; +import type { IProgressState } from '@xterm/addon-progress'; export class TestWindow extends BaseWindow implements IControlWindow { public readonly id = 'test'; @@ -84,8 +85,8 @@ export class TestWindow extends BaseWindow implements IControlWindow { // Events Test section this._addDt(dl, 'Events Test'); - this._addDdWithButton(dl, 'event-focus', 'focus', ''); - this._addDdWithButton(dl, 'event-blur', 'blur', ''); + this._addDdWithButton(dl, 'event-focus', 'focus', '', () => this._terminal.focus()); + this._addDdWithButton(dl, 'event-blur', 'blur', '', () => this._terminal.blur()); // Progress Addon section this._addDt(dl, 'Progress Addon'); @@ -116,6 +117,8 @@ export class TestWindow extends BaseWindow implements IControlWindow { stateDiv.textContent = 'State:'; stateDd.appendChild(stateDiv); dl.appendChild(stateDd); + + initProgress(this._terminal, this._addons); wrapper.appendChild(dl); container.appendChild(wrapper); @@ -842,4 +845,44 @@ function decorationStressTest(term: Terminal): void { } } } +} + +function initProgress(term: Terminal, addons: AddonCollection): void { + const STATES = { 0: 'remove', 1: 'set', 2: 'error', 3: 'indeterminate', 4: 'pause' }; + const COLORS = { 0: '', 1: 'green', 2: 'red', 3: '', 4: 'yellow' }; + + function progressHandler({ state, value }: IProgressState): void { + // Simulate windows taskbar hack by windows terminal: + // Since the taskbar has no means to indicate error/pause state other than by coloring + // the current progress, we move 0 to 10% and distribute higher values in the remaining 90 % + // NOTE: This is most likely not what you want to do for other progress indicators, + // that have a proper visual state for error/paused. + value = Math.min(10 + value * 0.9, 100); + document.getElementById('progress-percent').style.width = `${value}%`; + document.getElementById('progress-percent').style.backgroundColor = COLORS[state]; + document.getElementById('progress-state').innerText = `State: ${STATES[state]}`; + + document.getElementById('progress-percent').style.display = state === 3 ? 'none' : 'block'; + document.getElementById('progress-indeterminate').style.display = state === 3 ? 'block' : 'none'; + } + + const progressAddon = addons.progress.instance; + progressAddon.onChange(progressHandler); + + // apply initial state once to make it visible on page load + const initialProgress = progressAddon.progress; + progressHandler(initialProgress); + + document.getElementById('progress-run').addEventListener('click', async () => { + term.write('\x1b]9;4;0\x1b\\'); + for (let i = 0; i <= 100; i += 5) { + term.write(`\x1b]9;4;1;${i}\x1b\\`); + await new Promise(res => setTimeout(res, 200)); + } + }); + document.getElementById('progress-0').addEventListener('click', () => term.write('\x1b]9;4;0\x1b\\')); + document.getElementById('progress-1').addEventListener('click', () => term.write('\x1b]9;4;1;20\x1b\\')); + document.getElementById('progress-2').addEventListener('click', () => term.write('\x1b]9;4;2\x1b\\')); + document.getElementById('progress-3').addEventListener('click', () => term.write('\x1b]9;4;3\x1b\\')); + document.getElementById('progress-4').addEventListener('click', () => term.write('\x1b]9;4;4\x1b\\')); } \ No newline at end of file