export class TerminalConsole { constructor(t) { this.t = t; this.term = null; this.fitAddon = null; this.socket = null; this.currentPrompt = "PS>"; this.commandBuffer = ""; this.footerStateEl = null; this.connectionStateEl = null; this.resizeHandler = this.handleResize.bind(this); } mount() { const terminalRoot = document.getElementById("terminal"); this.footerStateEl = document.getElementById("footerState"); this.connectionStateEl = document.getElementById("connectionState"); if (!terminalRoot) { return; } this.term = new Terminal({ cursorBlink: true, fontFamily: 'Consolas, "IBM Plex Mono", monospace', fontSize: 15, lineHeight: 1.25, letterSpacing: 0.2, theme: { background: "#081426", foreground: "#eef5ff", cursor: "#7af1d0", cursorAccent: "#081426", selectionBackground: "rgba(122, 241, 208, 0.18)", black: "#081426", red: "#ff8d8d", green: "#7cf6d1", yellow: "#ffd479", blue: "#8dbaff", magenta: "#d6a4ff", cyan: "#73d5ff", white: "#eef5ff", brightBlack: "#5d718f", brightRed: "#ffb0b0", brightGreen: "#a2ffe3", brightYellow: "#ffe4a6", brightBlue: "#b3d1ff", brightMagenta: "#e6c1ff", brightCyan: "#9be6ff", brightWhite: "#ffffff", }, }); this.fitAddon = new FitAddon.FitAddon(); this.term.loadAddon(this.fitAddon); this.term.open(terminalRoot); this.fitAddon.fit(); this.bindTerminalInput(); this.setConnectionState("socket.connecting", "status-live"); this.connect(); window.addEventListener("resize", this.resizeHandler); } unmount() { window.removeEventListener("resize", this.resizeHandler); if (this.socket) { this.socket.close(); this.socket = null; } if (this.term) { this.term.dispose(); this.term = null; } this.fitAddon = null; this.commandBuffer = ""; this.currentPrompt = "PS>"; this.footerStateEl = null; this.connectionStateEl = null; } updateTranslations(t) { this.t = t; if (this.socket?.readyState === WebSocket.OPEN) { this.setConnectionState("socket.connected", "status-ok"); return; } this.setConnectionState("socket.connecting", "status-live"); } handleResize() { this.fitAddon?.fit(); } bindTerminalInput() { this.term.onData((data) => { if (!this.socket || this.socket.readyState !== WebSocket.OPEN) { return; } if (data === "\r") { const command = this.commandBuffer.trim(); this.term.write("\r\n"); if (command === "clear" || command === "cls") { this.commandBuffer = ""; this.term.clear(); this.writePrompt(); return; } this.socket.send(JSON.stringify({ type: "input", data: this.commandBuffer, })); this.commandBuffer = ""; return; } if (data === "\u007f") { if (this.commandBuffer.length > 0) { this.commandBuffer = this.commandBuffer.slice(0, -1); this.term.write("\b \b"); } return; } if (data === "\u0003") { this.commandBuffer = ""; this.term.write("^C\r\n"); this.writePrompt(); return; } if (data < " ") { return; } this.commandBuffer += data; this.term.write(data); }); } writePrompt() { this.term.write(this.currentPrompt + " "); } setConnectionState(labelKey, className) { const label = this.t(labelKey); if (this.connectionStateEl) { this.connectionStateEl.textContent = label; } if (this.footerStateEl) { this.footerStateEl.textContent = label; this.footerStateEl.className = className; } } connect() { const protocol = window.location.protocol === "https:" ? "wss" : "ws"; this.socket = new WebSocket(protocol + "://" + window.location.host + "/legacy/ws"); this.socket.addEventListener("open", () => { this.setConnectionState("socket.connected", "status-ok"); }); this.socket.addEventListener("message", (event) => { const message = JSON.parse(event.data); if (message.prompt) { this.currentPrompt = message.prompt; } if (message.type === "welcome") { this.term.write(message.data || ""); this.writePrompt(); return; } if (message.type === "output") { if (message.data) { this.term.write(message.data); } this.writePrompt(); return; } if (message.type === "error") { this.term.writeln(""); this.term.writeln(`${this.t("socket.errorPrefix")}: ${message.data || "unknown websocket error"}`); this.writePrompt(); } }); this.socket.addEventListener("close", () => { this.setConnectionState("socket.disconnected", "status-bad"); this.term?.writeln(""); this.term?.writeln(this.t("socket.closed")); }); this.socket.addEventListener("error", () => { this.setConnectionState("socket.error", "status-bad"); }); } }