217 lines
9.5 KiB
TypeScript
217 lines
9.5 KiB
TypeScript
/**
|
|
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { BaseWindow } from './baseWindow';
|
|
import type { IControlWindow } from '../controlBar';
|
|
import type { IImageAddonOptions } from '@xterm/addon-image';
|
|
|
|
export class AddonImageWindow extends BaseWindow implements IControlWindow {
|
|
public readonly id = 'addon-image';
|
|
public readonly label = 'image';
|
|
|
|
private _imageStorageLimitInput!: HTMLInputElement;
|
|
private _imageShowPlaceholderCheckbox!: HTMLInputElement;
|
|
private _imageOptionsTextarea!: HTMLTextAreaElement;
|
|
|
|
public build(container: HTMLElement): void {
|
|
// 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);
|
|
container.appendChild(storageLimitLabel);
|
|
container.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);
|
|
container.appendChild(placeholderLabel);
|
|
container.appendChild(document.createElement('br'));
|
|
container.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);
|
|
container.appendChild(optionsLabel);
|
|
|
|
container.appendChild(document.createElement('br'));
|
|
container.appendChild(document.createElement('br'));
|
|
|
|
// Sixel demos
|
|
const dlSixel = document.createElement('dl');
|
|
const dtSixel = document.createElement('dt');
|
|
dtSixel.textContent = 'Sixel';
|
|
dlSixel.appendChild(dtSixel);
|
|
this._addDdWithButton(dlSixel, 'image-demo1', 'snake');
|
|
this._addDdWithButton(dlSixel, 'image-demo2', 'oranges');
|
|
container.appendChild(dlSixel);
|
|
|
|
// IIP demos
|
|
const dlIip = document.createElement('dl');
|
|
const dtIip = document.createElement('dt');
|
|
dtIip.textContent = 'IIP (iTerm)';
|
|
dlIip.appendChild(dtIip);
|
|
this._addDdWithButton(dlIip, 'image-demo3', 'palette (png File)');
|
|
this._addDdWithButton(dlIip, 'image-demo4', 'dice (qoi MultipartFile)');
|
|
this._addDdWithButton(dlIip, 'image-demo5', 'rose (webp File)');
|
|
this._addDdWithButton(dlIip, 'image-demo6', 'kimono (avif MultipartFile)');
|
|
container.appendChild(dlIip);
|
|
|
|
// Kitty demos
|
|
const dlKitty = document.createElement('dl');
|
|
const dtKitty = document.createElement('dt');
|
|
dtKitty.textContent = 'Kitty';
|
|
dlKitty.appendChild(dtKitty);
|
|
this._addDdWithButton(dlKitty, 'image-demo-kitty1', 'palette');
|
|
container.appendChild(dlKitty);
|
|
|
|
this._initImageAddonExposed();
|
|
}
|
|
|
|
public get imageStorageLimitInput(): HTMLInputElement {
|
|
return this._imageStorageLimitInput;
|
|
}
|
|
|
|
public get imageShowPlaceholderCheckbox(): HTMLInputElement {
|
|
return this._imageShowPlaceholderCheckbox;
|
|
}
|
|
|
|
public get imageOptionsTextarea(): HTMLTextAreaElement {
|
|
return this._imageOptionsTextarea;
|
|
}
|
|
|
|
private _addDdWithButton(dl: HTMLElement, id: string, label: string): void {
|
|
const dd = document.createElement('dd');
|
|
const button = document.createElement('button');
|
|
button.id = id;
|
|
button.textContent = label;
|
|
dd.appendChild(button);
|
|
dl.appendChild(dd);
|
|
}
|
|
|
|
private _initImageAddonExposed(): void {
|
|
const imageAddon = this._addons.image.instance!;
|
|
const defaultOptions: IImageAddonOptions = (imageAddon as any)._defaultOpts;
|
|
const limitStorageElement = document.querySelector<HTMLInputElement>('#image-storagelimit')!;
|
|
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
|
this._addDomListener(limitStorageElement, 'change', () => {
|
|
try {
|
|
imageAddon.storageLimit = limitStorageElement.valueAsNumber;
|
|
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
|
console.log('changed storageLimit to', imageAddon.storageLimit);
|
|
} catch (e) {
|
|
limitStorageElement.valueAsNumber = imageAddon.storageLimit;
|
|
console.log('storageLimit at', imageAddon.storageLimit);
|
|
throw e;
|
|
}
|
|
});
|
|
const showPlaceholderElement = document.querySelector<HTMLInputElement>('#image-showplaceholder')!;
|
|
showPlaceholderElement.checked = imageAddon.showPlaceholder;
|
|
this._addDomListener(showPlaceholderElement, 'change', () => {
|
|
imageAddon.showPlaceholder = showPlaceholderElement.checked;
|
|
});
|
|
const ctorOptionsElement = document.querySelector<HTMLTextAreaElement>('#image-options')!;
|
|
ctorOptionsElement.value = JSON.stringify(defaultOptions, null, 2);
|
|
|
|
const sixelDemo = (url: string) => () => fetch(url)
|
|
.then(resp => resp.arrayBuffer())
|
|
.then(buffer => {
|
|
this._terminal.write('\r\n');
|
|
this._terminal.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]);
|
|
this._terminal.write('\r\n');
|
|
this._terminal.write(`\x1b]1337;File=inline=1;size=${data.length}:${btoa(sdata)}\x1b\\`);
|
|
});
|
|
|
|
const iipDemoMulti = (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]);
|
|
const encoded = btoa(sdata);
|
|
this._terminal.write('\r\n');
|
|
this._terminal.write(`\x1b]1337;MultipartFile=inline=1\x1b\\`);
|
|
for (let i = 0; i < encoded.length; i += 100) {
|
|
this._terminal.write(`\x1b]1337;FilePart=${encoded.slice(i, i + 100)}\x1b\\`);
|
|
}
|
|
this._terminal.write(`\x1b]1337;FileEnd\x1b\\`);
|
|
});
|
|
|
|
const kittyDemo = (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]);
|
|
const payload = btoa(sdata);
|
|
this._terminal.write('\r\n');
|
|
this._terminal.write(`\x1b_Ga=T,f=100;${payload}\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/xtermjs/xterm.js/master/addons/addon-image/fixture/palette.png'));
|
|
document.getElementById('image-demo4')!.addEventListener('click',
|
|
iipDemoMulti('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/testimages/dice.qoi'));
|
|
document.getElementById('image-demo5')!.addEventListener('click',
|
|
iipDemo('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/testimages/1_webp_a.webp'));
|
|
document.getElementById('image-demo6')!.addEventListener('click',
|
|
iipDemoMulti('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/testimages/kimono.crop.avif'));
|
|
document.getElementById('image-demo-kitty1')!.addEventListener('click',
|
|
kittyDemo('https://raw.githubusercontent.com/xtermjs/xterm.js/master/addons/addon-image/fixture/palette.png'));
|
|
|
|
// demo for image retrieval API
|
|
this._terminal.element!.addEventListener('click', (ev: MouseEvent) => {
|
|
if (!ev.ctrlKey || !imageAddon) 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 = (this._terminal as any)._core._mouseCoordsService!.getCoords(ev, (this._terminal as any)._core.screenElement!, this._terminal.cols, this._terminal.rows);
|
|
const x = pos[0] - 1;
|
|
const y = pos[1] - 1;
|
|
// ctrl+click: get original image
|
|
const canvas = imageAddon.getImageAtBufferCell(x, this._terminal.buffer.active.viewportY + y);
|
|
canvas?.toBlob(data => data && window.open(URL.createObjectURL(data), '_blank'));
|
|
});
|
|
}
|
|
|
|
private _addDomListener(element: HTMLElement, type: string, handler: (...args: any[]) => any): void {
|
|
element.addEventListener(type, handler);
|
|
(this._terminal as any)._core._register({ dispose: () => element.removeEventListener(type, handler) });
|
|
}
|
|
}
|