You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
208 lines
5.2 KiB
208 lines
5.2 KiB
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");
|
|
});
|
|
}
|
|
}
|
|
|