#pragma once inline const char* scripts_js = R"rawliteral( let socket = null; let pendingEchoLines = 0; let reconnectInterval = 1000; // ms let responseTimeout = null; let responseTimeoutDelay = 6000; // ms let bridgeMode = false; let fsTotalBytes = 0; let fsUsedBytes = 0; let isUploading = false; const filePanel = document.getElementById("file-panel"); const filePanelOverlay = document.getElementById("file-panel-overlay"); /* ========================= WebSocket / Terminal ========================= */ function connectSocket() { socket = new WebSocket("ws://" + window.location.host + "/ws"); socket.onopen = function () { hideWsLostPopup(); bridgeMode = false; pendingEchoLines = 0; console.log("[WebSocket] Connected"); }; socket.onmessage = function (event) { const output = document.getElementById("output"); const lines = event.data.split("\n"); if (event.data.includes("Bridge: Stopped by user.")) { bridgeMode = false; console.log("[WebSocket] Bridge mode exited."); } clearTimeout(responseTimeout); hideWsLostPopup(); if (pendingEchoLines > 0) { pendingEchoLines -= 1; return; } output.value += lines.join("\n"); output.scrollTop = output.scrollHeight; console.log("[WebSocket] Recv:", event.data); }; socket.onerror = function (error) { console.error("[WebSocket] Error:", error); }; socket.onclose = function () { console.warn("[WebSocket] Disconnected. Retrying in 1s..."); showWsLostPopup(); setTimeout(connectSocket, reconnectInterval); }; } function showWsLostPopup() { if (bridgeMode) return; const popup = document.getElementById("ws-lost-popup"); if (popup) popup.style.display = "block"; } function hideWsLostPopup() { const popup = document.getElementById("ws-lost-popup"); if (popup) popup.style.display = "none"; } function sendCommand() { const input = document.getElementById("command"); const output = document.getElementById("output"); const cmd = input.value.trim(); if (!socket || socket.readyState !== WebSocket.OPEN) return; if (cmd === "bridge" || cmd === "keyboard") { bridgeMode = true; clearTimeout(responseTimeout); hideWsLostPopup(); console.log("[WebSocket] Bridge Mode"); pendingEchoLines = cmd.length socket.send(cmd + "\n"); input.value = ""; addToHistory(cmd); output.value += cmd + "\n"; return; } clearTimeout(responseTimeout); responseTimeout = setTimeout(() => { console.warn("[WebSocket] No response after command."); showWsLostPopup(); }, responseTimeoutDelay); socket.send(cmd + "\n"); input.value = ""; if (!bridgeMode && !/^\d+$/.test(cmd)) { output.value += cmd; addToHistory(cmd); pendingEchoLines = cmd.length; } else { pendingEchoLines = 0; } } window.addEventListener("DOMContentLoaded", function () { const input = document.getElementById("command"); const output = document.getElementById("output"); output.value = ` ____ _ _ | __ ) _ _ ___ _ __ (_)_ __ __ _| |_ ___ | _ \\| | | / __| | '_ \\| | '__/ _\` | __/ _ \\ | |_) | |_| \\__ \\ | |_) | | | | (_| | || __/ |____/ \\__,_|___/ | .__/|_|_| \\__,_|\\__\\___| |_| Version 1.3 Ready to board Type 'mode' to start or 'help' for commands HIZ> `; input.addEventListener("keydown", function (event) { if (event.key === "Enter") { event.preventDefault(); sendCommand(); return; } if (event.key === "Escape") { if (bridgeMode) { event.preventDefault(); socket.send("\x1B"); } return; } if (event.ctrlKey && event.key.toLowerCase() === "c") { if (bridgeMode) { event.preventDefault(); socket.send("\x03"); } return; } if (event.key === "Tab") { if (bridgeMode) { event.preventDefault(); socket.send("\x09"); } return; } if (event.ctrlKey && event.key.toLowerCase() === "d") { if (bridgeMode) { event.preventDefault(); socket.send("\x04"); } return; } if (event.ctrlKey && event.key.toLowerCase() === "z") { if (bridgeMode) { event.preventDefault(); socket.send("\x1A"); } return; } if (event.ctrlKey && event.key.toLowerCase() === "x") { if (bridgeMode) { event.preventDefault(); socket.send("\x18"); } return; } }); // Files button const filesBtn = document.getElementById("files-btn"); if (filesBtn) filesBtn.addEventListener("click", openFilePanel); connectSocket(); }); /* ========================= Command History ========================= */ function addToHistory(cmd) { if (!isValidCommand(cmd)) return; const history = document.getElementById("history"); const last = history.firstChild; if (last && last.textContent === cmd) return; const btn = document.createElement("button"); const maxLength = 15; const displayText = cmd.length > maxLength ? cmd.slice(0, maxLength - 3) + "..." : cmd; btn.textContent = displayText; btn.title = cmd; btn.onclick = () => { document.getElementById("command").value = cmd; document.getElementById("command").focus(); }; history.insertBefore(btn, history.firstChild); } function isValidCommand(cmd) { if (!cmd) return false; if (cmd.length < 2) return false; if (/^\d+$/.test(cmd)) return false; return true; } /* ========================= File Panel ========================= */ function onOverlayClick(e) { if (e.target === filePanelOverlay && !isUploading) closeFilePanel(); } function openFilePanel() { document.getElementById("file-panel-overlay").style.display = "block"; document.getElementById("file-panel").style.display = "flex"; filePanelOverlay.addEventListener("click", onOverlayClick); refreshFileList(); } function closeFilePanel() { document.getElementById("file-panel-overlay").style.display = "none"; document.getElementById("file-panel").style.display = "none"; } async function refreshFileList() { const listEl = document.getElementById("file-list"); listEl.innerHTML = "