From abc2b9f2fa8e392eb415a4216b5f7c6a487f0df8 Mon Sep 17 00:00:00 2001 From: Chris Wanstrath Date: Mon, 6 Oct 2025 20:43:42 -0700 Subject: [PATCH] update build.js --- public/bundle.js | 336 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 231 insertions(+), 105 deletions(-) diff --git a/public/bundle.js b/public/bundle.js index 6af5f8b..edadc2d 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -1,5 +1,5 @@ //// -// version: 7d7febe +// version: 384a0bd // src/js/dom.ts var content2 = $("content"); @@ -24,28 +24,26 @@ var $$ = (tag, innerHTML = "") => { return el; }; -// src/js/resize.ts -var content3 = document.getElementById("content"); -function initResize() { - window.addEventListener("resize", resize); - resize(); +// src/js/focus.ts +function initFocus() { + window.addEventListener("click", focusHandler); + focusInput(); } -function resize() { - if (document.body.dataset.mode === "tall") { - resizeTall(); - } else { - resizeCinema(); +function focusInput() { + cmdInput.focus(); +} +function focusHandler(e) { + const target = e.target; + if (!(target instanceof HTMLElement)) { + focusInput(); + return; } -} -function resizeTall() { - const scale = Math.min(1, window.innerWidth / 960); - content3.style.transformOrigin = "top center"; - content3.style.transform = `scaleX(${scale})`; -} -function resizeCinema() { - const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540); - content3.style.transformOrigin = "center center"; - content3.style.transform = `scale(${scale})`; + if (["INPUT", "TEXTAREA", "CANVAS", "A"].includes(target.tagName)) + return false; + const selection = window.getSelection() || ""; + if (selection.toString() === "") + focusInput(); + e.preventDefault(); } // src/shared/utils.ts @@ -54,18 +52,23 @@ function randomId() { } // src/js/scrollback.ts +var statusColors = { + waiting: "yellow", + streaming: "purple", + ok: "green", + error: "red" +}; function initScrollback() { window.addEventListener("click", handleInputClick); } -function autoScroll() {} function insert(node) { scrollback.append(node); } -function addInput(id, input) { +function addInput(id, input, status) { const parent = $$("li.input"); - const status = $$("span.status.yellow", "•"); - const content4 = $$("span.content", input); - parent.append(status, content4); + const statusSpan = $$(`span.status.${statusColors[status || "waiting"]}`, "•"); + const content3 = $$("span.content", input); + parent.append(statusSpan, content3); parent.dataset.id = id; insert(parent); scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight; @@ -74,31 +77,24 @@ function setStatus(id, status) { const statusEl = document.querySelector(`[data-id="${id}"].input .status`); if (!statusEl) return; - const colors = { - waiting: "yellow", - streaming: "purple", - ok: "green", - error: "red" - }; - statusEl.classList.remove(...Object.values(colors)); - statusEl.classList.add(colors[status]); + statusEl.classList.remove(...Object.values(statusColors)); + statusEl.classList.add(statusColors[status]); } function addOutput(id, output2) { const item = $$("li"); item.classList.add("output"); item.dataset.id = id || randomId(); - const [format, content4] = processOutput(output2); + const [format, content3] = processOutput(output2); if (format === "html") - item.innerHTML = content4; + item.innerHTML = content3; else - item.textContent = content4; + item.textContent = content3; const input = document.querySelector(`[data-id="${id}"].input`); if (input instanceof HTMLLIElement) { input.parentNode.insertBefore(item, input.nextSibling); } else { insert(item); } - autoScroll(); } function addErrorMessage(message) { addOutput("", { html: `${message}` }); @@ -109,12 +105,11 @@ function appendOutput(id, output2) { console.error(`output id ${id} not found`); return; } - const [format, content4] = processOutput(output2); + const [format, content3] = processOutput(output2); if (format === "html") - item.innerHTML += content4; + item.innerHTML += content3; else - item.textContent += content4; - autoScroll(); + item.textContent += content3; } function replaceOutput(id, output2) { const item = document.querySelector(`[data-id="${id}"].output`); @@ -122,12 +117,11 @@ function replaceOutput(id, output2) { console.error(`output id ${id} not found`); return; } - const [format, content4] = processOutput(output2); + const [format, content3] = processOutput(output2); if (format === "html") - item.innerHTML = content4; + item.innerHTML = content3; else - item.textContent = content4; - autoScroll(); + item.textContent = content3; } function processOutput(output) { let content = ""; @@ -162,8 +156,181 @@ function handleInputClick(e) { } function handleOutput(msg) { const result = msg.data; - setStatus(msg.id, result.status); - addOutput(msg.id, result.output); + const id = "id" in msg ? msg.id || "" : ""; + setStatus(id, result.status); + addOutput(id, result.output); +} + +// src/js/browser.ts +var HEIGHT = 540; +var WIDTH = 960; +var controls = $("browser-controls"); +var address = $("browser-address"); +var iframe; +var realUrl = ""; +var showInput = true; +function isBrowsing() { + return document.querySelector("iframe.browser.active") !== null; +} +function openBrowser(url, openedVia = "click") { + showInput = openedVia === "click"; + iframe = $$("iframe.browser.active"); + iframe.src = url; + iframe.sandbox.add("allow-scripts", "allow-same-origin", "allow-forms"); + iframe.height = String(HEIGHT); + iframe.width = String(WIDTH); + iframe.tabIndex = 0; + window.addEventListener("message", handleAppMessage); + window.addEventListener("keydown", handleBrowserKeydown); + controls.addEventListener("click", handleClick); + iframe.addEventListener("load", handlePageLoad); + controls.style.display = ""; + const main = document.querySelector("#content"); + main?.prepend(iframe); + setAddress(url); +} +function closeBrowser() { + window.removeEventListener("keydown", handleBrowserKeydown); + window.removeEventListener("message", handleAppMessage); + controls.removeEventListener("click", handleClick); + iframe.removeEventListener("load", handlePageLoad); + const id = randomId(); + if (showInput) + addInput(id, "browse " + realUrl, "ok"); + iframe.height = String(HEIGHT / 2); + iframe.width = String(WIDTH / 2); + iframe.style.pointerEvents = "none"; + iframe.tabIndex = -1; + iframe.classList.remove("fullscreen", "active"); + scrollback.append(iframe); + controls.style.display = "none"; + focusInput(); +} +function handleAppMessage(event) { + const origin = event.origin; + if (!origin.includes("localhost") && !origin.match(/\.local$/)) { + return; + } + const { type, data } = event.data; + switch (type) { + case "NAV_READY": + break; + case "URL_CHANGED": + setAddress(data.url); + break; + case "TITLE_CHANGED": + console.log("Page title:", data.title); + break; + case "NAV_BLOCKED": + showNavigationError(data.url, data.reason); + break; + case "KEYDOWN": + const keyEvent = new KeyboardEvent("keydown", { + key: data.key, + ctrlKey: data.ctrlKey, + shiftKey: data.shiftKey, + altKey: data.altKey, + metaKey: data.metaKey, + bubbles: true + }); + window.dispatchEvent(keyEvent); + break; + } +} +function handleBrowserKeydown(e) { + if (e.key === "Escape" || e.ctrlKey && e.key === "c") { + e.preventDefault(); + closeBrowser(); + } +} +function handleClick(e) { + const target = e.target; + if (!(target instanceof HTMLElement)) + return; + switch (target.id) { + case "back-button": + navigateBack(); + break; + case "forward-button": + navigateForward(); + break; + case "stop-button": + stopLoading(); + break; + case "reload-button": + reloadBrowser(); + break; + case "fullscreen-button": + fullscreenBrowser(); + break; + case "close-button": + closeBrowser(); + break; + default: + return; + } + e.preventDefault(); +} +function handlePageLoad() {} +function setAddress(url) { + realUrl = url; + address.textContent = url.replace(/https?:\/\//, ""); +} +function navigateBack() { + sendNavCommand("back"); +} +function navigateForward() { + sendNavCommand("forward"); +} +function reloadBrowser() { + sendNavCommand("reload"); +} +function stopLoading() { + sendNavCommand("stop"); +} +function fullscreenBrowser() { + controls.style.display = "none"; + iframe.classList.add("fullscreen"); + document.body.append(iframe); +} +function sendNavCommand(action) { + if (!iframe.contentWindow) + return; + iframe.contentWindow.postMessage({ + type: "NAV_COMMAND", + action + }, "*"); +} +function showNavigationError(url, reason) { + alert(`NAVIGATION BLOCKED + +${url} + +${reason}`); +} + +// src/js/resize.ts +var content3 = document.getElementById("content"); +function initResize() { + window.addEventListener("resize", resize); + resize(); +} +function resize() { + if (document.body.dataset.mode === "tall") { + resizeTall(); + } else { + resizeCinema(); + } +} +function resizeTall() { + const scale = Math.min(1, window.innerWidth / 960); + content3.style.transformOrigin = "top center"; + content3.style.transform = `scaleX(${scale})`; +} +function resizeCinema() { + const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540); + content3.style.transformOrigin = "center center"; + content3.style.transform = `scale(${scale})`; } // src/js/session.ts @@ -391,32 +558,10 @@ class GameContext { } } -// src/js/focus.ts -function initFocus() { - window.addEventListener("click", focusHandler); - focusInput(); -} -function focusInput() { - cmdInput.focus(); -} -function focusHandler(e) { - const target = e.target; - if (!(target instanceof HTMLElement)) { - focusInput(); - return; - } - if (["INPUT", "TEXTAREA", "CANVAS", "A"].includes(target.tagName)) - return false; - const selection = window.getSelection() || ""; - if (selection.toString() === "") - focusInput(); - e.preventDefault(); -} - // src/js/game.ts var FPS = 30; -var HEIGHT = 540; -var WIDTH = 980; +var HEIGHT2 = 540; +var WIDTH2 = 960; var oldMode = "cinema"; var running = false; var canvas; @@ -458,8 +603,8 @@ async function handleGameStart(msg) { function createCanvas() { const canvas2 = $$("canvas.game.active"); canvas2.id = randomId(); - canvas2.height = HEIGHT; - canvas2.width = WIDTH; + canvas2.height = HEIGHT2; + canvas2.width = WIDTH2; canvas2.tabIndex = 0; const main = document.querySelector("main"); main?.classList.add("game"); @@ -530,8 +675,8 @@ function endGame() { const main = document.querySelector("main"); main?.classList.remove("game"); canvas.classList.remove("active"); - canvas.style.height = HEIGHT / 2 + "px"; - canvas.style.width = WIDTH / 2 + "px"; + canvas.style.height = HEIGHT2 / 2 + "px"; + canvas.style.width = WIDTH2 / 2 + "px"; const output2 = $$("li.output"); output2.append(canvas); insert(output2); @@ -627,6 +772,7 @@ function retryConnection() { // src/js/commands.ts var commands = []; var browserCommands = { + browse: (url) => openBrowser(url, "command"), "browser-session": () => sessionId, clear: () => scrollback.innerHTML = "", commands: () => { @@ -636,12 +782,11 @@ var browserCommands = { mode: (mode) => { if (!mode) { mode = document.body.dataset.mode === "tall" ? "cinema" : "tall"; - send({ type: "ui:mode", data: mode }); + send({ type: "session:update", data: { "ui:mode": mode } }); } content2.style.display = ""; document.body.dataset.mode = mode; resize(); - autoScroll(); focusInput(); }, reload: () => window.location.reload() @@ -770,7 +915,7 @@ function keydownHandler(e) { } else if (e.ctrlKey && e.key === "s" || e.ctrlKey && e.key === "Enter") { e.preventDefault(); send({ - id: editor.dataset.path, + id: editor.dataset.path || "/tmp.txt", type: "save-file", data: editor.value }); @@ -1058,30 +1203,11 @@ async function runCommand(input) { } } -// src/js/statusbar.ts -var STATUS_MSG_LENGTH = 3000; -var statusbar = $("statusbar"); -var statusbarMsg = $("statusbar-msg"); -var timer; -function status(msg) { - showStatusMsg(); - statusbarMsg.textContent = msg; - if (timer) - clearTimeout(timer); - timer = setTimeout(hideStatusMsg, STATUS_MSG_LENGTH); -} -function showStatusMsg() { - statusbar.classList.add("showing-msg"); -} -function hideStatusMsg() { - statusbar.className = ""; -} - // src/js/hyperlink.ts function initHyperlink() { - window.addEventListener("click", handleClick); + window.addEventListener("click", handleClick2); } -async function handleClick(e) { +async function handleClick2(e) { const target = e.target; if (!(target instanceof HTMLElement)) return; @@ -1091,13 +1217,13 @@ async function handleClick(e) { const href = a.getAttribute("href"); if (!href) return; - if (href.startsWith("#")) { + if (href.startsWith("#") && href.length > 1) { e.preventDefault(); await runCommand(href.slice(1)); focusInput(); - } else { + } else if (!isBrowsing()) { e.preventDefault(); - status(href); + openBrowser(href); } } @@ -1155,7 +1281,7 @@ function clearInput() { // src/js/vram.ts var vramCounter = $("vram-size"); var startVramCounter = () => { - const timer2 = setInterval(() => { + const timer = setInterval(() => { const count = parseInt(vramCounter.textContent) + 1; let val = count + "KB"; if (count < 10) @@ -1163,7 +1289,7 @@ var startVramCounter = () => { vramCounter.textContent = val; if (count >= 64) { vramCounter.textContent += " OK"; - clearInterval(timer2); + clearInterval(timer); } }, 15); };