mirror of
https://github.com/yudai/gotty.git
synced 2025-12-24 11:20:13 +01:00
xtermjs: fix inital load term size
This commit is contained in:
parent
53e31a964e
commit
71b9db14e2
6 changed files with 179 additions and 110 deletions
2
js/dist/gotty-bundle.js
vendored
2
js/dist/gotty-bundle.js
vendored
File diff suppressed because one or more lines are too long
2
js/dist/gotty-bundle.js.map
vendored
2
js/dist/gotty-bundle.js.map
vendored
File diff suppressed because one or more lines are too long
9
js/dist/waitFor.d.ts
vendored
Normal file
9
js/dist/waitFor.d.ts
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Waits for a DOM element matching the selector to exist in the document.
|
||||
* Resolves immediately if it already exists.
|
||||
*
|
||||
* @param selector CSS selector for the element to wait for
|
||||
* @param timeout Optional timeout in milliseconds (default: no timeout)
|
||||
* @returns Promise that resolves with the found element
|
||||
*/
|
||||
export declare function waitForElement<T extends Element>(selector: string, timeout?: number): Promise<T>;
|
||||
38
js/src/waitFor.ts
Normal file
38
js/src/waitFor.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Waits for a DOM element matching the selector to exist in the document.
|
||||
* Resolves immediately if it already exists.
|
||||
*
|
||||
* @param selector CSS selector for the element to wait for
|
||||
* @param timeout Optional timeout in milliseconds (default: no timeout)
|
||||
* @returns Promise that resolves with the found element
|
||||
*/
|
||||
export function waitForElement<T extends Element>(
|
||||
selector: string,
|
||||
timeout?: number,
|
||||
): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// If it already exists, resolve immediately
|
||||
const existing = document.querySelector<T>(selector);
|
||||
if (existing) {
|
||||
resolve(existing);
|
||||
return;
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const el = document.querySelector<T>(selector);
|
||||
if (el) {
|
||||
observer.disconnect();
|
||||
resolve(el);
|
||||
}
|
||||
});
|
||||
|
||||
observer.observe(document.body, { childList: true, subtree: true });
|
||||
|
||||
if (timeout) {
|
||||
setTimeout(() => {
|
||||
observer.disconnect();
|
||||
reject(new Error(`Timeout waiting for element: ${selector}`));
|
||||
}, timeout);
|
||||
}
|
||||
});
|
||||
}
|
||||
218
js/src/xterm.ts
218
js/src/xterm.ts
|
|
@ -4,118 +4,140 @@ import { IDisposable, Terminal } from "xterm";
|
|||
import { FitAddon } from "xterm-addon-fit";
|
||||
import { WebglAddon } from "xterm-addon-webgl";
|
||||
|
||||
import { waitForElement } from "./waitFor";
|
||||
|
||||
export class Xterm {
|
||||
elem: HTMLElement;
|
||||
term: Terminal;
|
||||
resizeListener: () => void;
|
||||
decoder: lib.UTF8Decoder;
|
||||
elem: HTMLElement;
|
||||
term: Terminal;
|
||||
resizeListener: () => void;
|
||||
decoder: lib.UTF8Decoder;
|
||||
|
||||
message: HTMLElement;
|
||||
messageTimeout: number;
|
||||
messageTimer: NodeJS.Timer;
|
||||
message: HTMLElement;
|
||||
messageTimeout: number;
|
||||
messageTimer: NodeJS.Timer;
|
||||
|
||||
fitAddon: FitAddon;
|
||||
disposables: IDisposable[] = [];
|
||||
fitAddon: FitAddon;
|
||||
disposables: IDisposable[] = [];
|
||||
|
||||
constructor(elem: HTMLElement) {
|
||||
this.elem = elem;
|
||||
const isWindows =
|
||||
["Windows", "Win16", "Win32", "WinCE"].indexOf(navigator.platform) >= 0;
|
||||
this.term = new Terminal({
|
||||
cursorStyle: "block",
|
||||
cursorBlink: true,
|
||||
windowsMode: isWindows,
|
||||
fontFamily:
|
||||
"DejaVu Sans Mono, Everson Mono, FreeMono, Menlo, Terminal, monospace, Apple Symbols",
|
||||
fontSize: 12,
|
||||
});
|
||||
|
||||
constructor(elem: HTMLElement) {
|
||||
this.elem = elem;
|
||||
const isWindows = ['Windows', 'Win16', 'Win32', 'WinCE'].indexOf(navigator.platform) >= 0;
|
||||
this.term = new Terminal({
|
||||
cursorStyle: "block",
|
||||
cursorBlink: true,
|
||||
windowsMode: isWindows,
|
||||
fontFamily: "DejaVu Sans Mono, Everson Mono, FreeMono, Menlo, Terminal, monospace, Apple Symbols",
|
||||
fontSize: 12,
|
||||
this.fitAddon = new FitAddon();
|
||||
this.term.loadAddon(this.fitAddon);
|
||||
|
||||
this.message = elem.ownerDocument.createElement("div");
|
||||
this.message.className = "xterm-overlay";
|
||||
this.messageTimeout = 2000;
|
||||
|
||||
this.resizeListener = () => {
|
||||
this.fitAddon.fit();
|
||||
this.term.scrollToBottom();
|
||||
this.showMessage(
|
||||
String(this.term.cols) + "x" + String(this.term.rows),
|
||||
this.messageTimeout,
|
||||
);
|
||||
};
|
||||
|
||||
this.term.open(elem);
|
||||
|
||||
this.term.focus();
|
||||
window.addEventListener("resize", () => {
|
||||
this.resizeListener();
|
||||
});
|
||||
|
||||
waitForElement<HTMLElement>(".xterm-screen > canvas", 5000)
|
||||
.then(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.resizeListener();
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
this.fitAddon = new FitAddon();
|
||||
this.term.loadAddon(this.fitAddon);
|
||||
this.decoder = new lib.UTF8Decoder();
|
||||
}
|
||||
|
||||
this.message = elem.ownerDocument.createElement("div");
|
||||
this.message.className = "xterm-overlay";
|
||||
this.messageTimeout = 2000;
|
||||
info(): { columns: number; rows: number } {
|
||||
return { columns: this.term.cols, rows: this.term.rows };
|
||||
}
|
||||
|
||||
this.resizeListener = () => {
|
||||
this.fitAddon.fit();
|
||||
this.term.scrollToBottom();
|
||||
this.showMessage(String(this.term.cols) + "x" + String(this.term.rows), this.messageTimeout);
|
||||
};
|
||||
output(data: string) {
|
||||
this.term.write(this.decoder.decode(data));
|
||||
}
|
||||
|
||||
this.term.open(elem);
|
||||
showMessage(message: string, timeout: number) {
|
||||
this.message.textContent = message;
|
||||
this.elem.appendChild(this.message);
|
||||
|
||||
this.term.focus()
|
||||
this.resizeListener();
|
||||
window.addEventListener("resize", () => { this.resizeListener(); });
|
||||
|
||||
this.decoder = new lib.UTF8Decoder()
|
||||
};
|
||||
|
||||
info(): { columns: number, rows: number } {
|
||||
return { columns: this.term.cols, rows: this.term.rows };
|
||||
};
|
||||
|
||||
output(data: string) {
|
||||
this.term.write(this.decoder.decode(data));
|
||||
};
|
||||
|
||||
showMessage(message: string, timeout: number) {
|
||||
this.message.textContent = message;
|
||||
this.elem.appendChild(this.message);
|
||||
|
||||
if (this.messageTimer) {
|
||||
clearTimeout(this.messageTimer);
|
||||
}
|
||||
if (timeout > 0) {
|
||||
this.messageTimer = setTimeout(() => {
|
||||
this.elem.removeChild(this.message);
|
||||
}, timeout);
|
||||
}
|
||||
};
|
||||
|
||||
removeMessage(): void {
|
||||
if (this.message.parentNode == this.elem) {
|
||||
this.elem.removeChild(this.message);
|
||||
}
|
||||
if (this.messageTimer) {
|
||||
clearTimeout(this.messageTimer);
|
||||
}
|
||||
|
||||
setWindowTitle(title: string) {
|
||||
document.title = title;
|
||||
};
|
||||
|
||||
setPreferences(value: object) {
|
||||
Object.keys(value).forEach((key) => {
|
||||
if (key && key == "enable-webgl") {
|
||||
this.term.loadAddon(new WebglAddon());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onInput(callback: (input: string) => void) {
|
||||
this.disposables.push(this.term.onData((data) => {
|
||||
callback(data);
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
onResize(callback: (colmuns: number, rows: number) => void) {
|
||||
this.disposables.push(this.term.onResize((data) => {
|
||||
callback(data.cols, data.rows);
|
||||
}));
|
||||
};
|
||||
|
||||
deactivate(): void {
|
||||
this.disposables.forEach(d => d.dispose())
|
||||
this.term.blur();
|
||||
if (timeout > 0) {
|
||||
this.messageTimer = setTimeout(() => {
|
||||
this.elem.removeChild(this.message);
|
||||
}, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.removeMessage();
|
||||
this.term.clear();
|
||||
removeMessage(): void {
|
||||
if (this.message.parentNode == this.elem) {
|
||||
this.elem.removeChild(this.message);
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
window.removeEventListener("resize", this.resizeListener);
|
||||
this.term.dispose();
|
||||
}
|
||||
setWindowTitle(title: string) {
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
setPreferences(value: object) {
|
||||
Object.keys(value).forEach((key) => {
|
||||
if (key && key == "enable-webgl") {
|
||||
this.term.loadAddon(new WebglAddon());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onInput(callback: (input: string) => void) {
|
||||
this.disposables.push(
|
||||
this.term.onData((data) => {
|
||||
callback(data);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
onResize(callback: (colmuns: number, rows: number) => void) {
|
||||
this.disposables.push(
|
||||
this.term.onResize((data) => {
|
||||
callback(data.cols, data.rows);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
deactivate(): void {
|
||||
this.disposables.forEach((d) => d.dispose());
|
||||
this.term.blur();
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.removeMessage();
|
||||
this.term.clear();
|
||||
}
|
||||
|
||||
close(): void {
|
||||
window.removeEventListener("resize", this.resizeListener);
|
||||
this.term.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue