summaryrefslogtreecommitdiff
path: root/static/script
diff options
context:
space:
mode:
authorMorgana <Morgana@icolotl.com>2026-09-15 11:28:53 -0500
committerMorgana <Morgana@icolotl.com>2026-09-15 11:28:53 -0500
commit2c76015a7a3f3a08ba205b04729f29df9fb7389f (patch)
treee12b09312ab70253d32fb5576f5dd2d76ceda196 /static/script
Initial Commit
Diffstat (limited to 'static/script')
-rw-r--r--static/script/main.js197
1 files changed, 197 insertions, 0 deletions
diff --git a/static/script/main.js b/static/script/main.js
new file mode 100644
index 0000000..171645b
--- /dev/null
+++ b/static/script/main.js
@@ -0,0 +1,197 @@
+BANNER = "\
+/\\\\ I N D E X\n\
+\\\\/ Terminal\n\
+===============\n\n"
+
+/*
+Index Virtual Terminal (IVT) Webclient JS
+Copyright (C) 2026 Morgana <morgana@icolotl.com>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+function newElement(tag, opt, children) {
+ var elem = document.createElement(tag);
+ if (opt) {
+ if (opt.id) { elem.id = opt.id; }
+ if (opt.cls) { elem.className = opt.cls; }
+ if (opt.clss) { elem.className = opt.clss.join(" "); }
+ if (opt.txt) { elem.append(document.createTextNode(opt.txt)); }
+ if (opt.val) { elem.value = opt.val; }
+ if (opt.name) { elem.name = opt.name; }
+ if (opt.label) { elem.htmlFor = opt.label; }
+ if (opt.href) { elem.href = opt.href; }
+ if (opt.type) { elem.type = opt.type; }
+ if (opt.autocomplete) { elem.autocomplete = opt.autocomplete; }
+ if (opt.term) {
+ elem.title = opt.term;
+ elem.addEventListener("click", (event) => {
+ event.preventDefault();
+ conn.process(opt.term);
+ return false;
+ })
+ }
+ }
+ if (children) {
+ for (child of children) {
+ elem.append(opt.list ? newElement("li", {}, [child]) : child);
+ }
+ }
+ return elem;
+}
+
+function newElementFull(opt) {
+ if (opt.raw) {
+ return document.createTextNode(opt.raw);
+ }
+ var tag = opt.tag ?? "span"
+ var children = opt.children ?? []
+ return newElement(tag, opt, children)
+}
+
+class IVTClient {
+
+ constructor() {
+ this.stdout = document.getElementById("stdout");
+ this.stdin = document.getElementById("stdin");
+ this.stdinBox = document.getElementById("stdin-box");
+ this.stdinBtn = document.getElementById("stdin-btn");
+ this.stdinPrompt = document.getElementById("stdin-prompt");
+ this.socket = null;
+ this.username = null;
+ this.pwdreturn = false;
+ this.oldprompt = null;
+ this.prelude = null;
+ this.printTextCLS(BANNER, "term-bold term-centered");
+ this.stdinBox.addEventListener("submit", (event) => {
+ event.preventDefault();
+ if (this.pwdreturn) {
+ this.socket.send(this.stdin.value);
+ this.stdin.type = "text";
+ this.pwdreturn = false;
+ this.stdin.value = "";
+ this.stdinPrompt.innerText = this.oldprompt;
+ return false;
+ }
+ this.process(this.stdin.value);
+ this.stdin.value = "";
+ return false;
+ });
+ this.socket = new WebSocket("ws://localhost:5417");
+ this.socket.addEventListener("open", (event) => {
+ this.printText("Attempting to connect to INDEX server...");
+ this.stdinPrompt.innerText = "CONNECTING>";
+ });
+ this.socket.addEventListener("error", (event) => {
+ this.printText("*** A network error has occurred. Please try again later. ***");
+ })
+ this.socket.addEventListener("close", (event) => {
+ this.printText("Disconnected from INDEX server.");
+ this.stdinPrompt.innerText = "DISCONNECTED>";
+ this.stdinBox.dataset.offline = "";
+ this.stdin.disabled = true;
+ this.stdinBtn.disabled = true;
+ })
+ this.socket.addEventListener("message", (event) => {
+ if (this.prelude) {
+ var fxn = this["on_" + this.prelude + "_prelude"];
+ if (fxn) {
+ fxn.call(this, event.data)
+ }
+ this.prelude = null;
+ return
+ }
+ var data = JSON.parse(event.data);
+ var fxn = this["on_" + data.act + "_msg"];
+ if (fxn) {
+ fxn.call(this, data)
+ }
+ });
+ }
+
+ on_text_msg(data) {
+ this.printText(data.text);
+ }
+
+ on_display_msg(data) {
+ this.printTextFULL(data.msg);
+ }
+
+ on_activate_msg(data) {
+ this.username = data.username;
+ this.stdin.disabled = false;
+ this.stdinBtn.disabled = false;
+ this.stdinPrompt.innerText = `${data.username}@INDEX>`;
+ delete this.stdinBox.dataset.offline;
+ this.stdin.focus();
+ }
+
+ on_userchange_msg(data) {
+ this.username = data.username;
+ this.stdinPrompt.innerText = `${data.username}@INDEX>`;
+ this.stdin.focus();
+ }
+
+ on_password_msg(data) {
+ this.stdin.type = "password";
+ this.pwdreturn = true;
+ this.oldprompt = this.stdinPrompt.innerText;
+ this.stdinPrompt.innerText = data.prompt;
+ this.printText(data.text);
+ }
+
+ on_action_msg(data) {
+ if (data.action == "clear") {
+ this.stdout.innerHTML = "";
+ }
+ }
+
+ on_prelude_msg(data) {
+ this.prelude = data.action;
+ }
+
+ async on_music_prelude(data) {
+ var elem = document.createElement("audio");
+ elem.src = window.URL.createObjectURL(data);
+ elem.controls = true;
+ elem.autoplay = true;
+ this.stdout.append(elem);
+ }
+
+ process(text) {
+ if (this.pwdreturn) {
+ return;
+ }
+ this.printTextCLS(`${this.stdinPrompt.innerText} ${text}`, "history");
+ this.socket.send(text);
+ }
+
+ printText(text) {
+ this.stdout.append(newElement("div", { txt: text }));
+ this.stdout.scrollTop = this.stdout.scrollHeight;
+ }
+
+ printTextCLS(text, cls) {
+ this.stdout.append(newElement("div", { txt: text, cls: cls }));
+ this.stdout.scrollTop = this.stdout.scrollHeight;
+ }
+
+ printTextFULL(txtobj) {
+ this.stdout.append(newElement("div", {}, txtobj.map(newElementFull)));
+ this.stdout.scrollTop = this.stdout.scrollHeight;
+ }
+
+}
+
+let conn = new IVTClient(); \ No newline at end of file