update build.js

This commit is contained in:
Chris Wanstrath 2025-10-06 20:43:42 -07:00
parent 384a0bd111
commit abc2b9f2fa

View File

@ -1,5 +1,5 @@
//// ////
// version: 7d7febe // version: 384a0bd
// src/js/dom.ts // src/js/dom.ts
var content2 = $("content"); var content2 = $("content");
@ -24,28 +24,26 @@ var $$ = (tag, innerHTML = "") => {
return el; return el;
}; };
// src/js/resize.ts // src/js/focus.ts
var content3 = document.getElementById("content"); function initFocus() {
function initResize() { window.addEventListener("click", focusHandler);
window.addEventListener("resize", resize); focusInput();
resize();
} }
function resize() { function focusInput() {
if (document.body.dataset.mode === "tall") { cmdInput.focus();
resizeTall(); }
} else { function focusHandler(e) {
resizeCinema(); const target = e.target;
if (!(target instanceof HTMLElement)) {
focusInput();
return;
} }
} if (["INPUT", "TEXTAREA", "CANVAS", "A"].includes(target.tagName))
function resizeTall() { return false;
const scale = Math.min(1, window.innerWidth / 960); const selection = window.getSelection() || "";
content3.style.transformOrigin = "top center"; if (selection.toString() === "")
content3.style.transform = `scaleX(${scale})`; focusInput();
} e.preventDefault();
function resizeCinema() {
const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540);
content3.style.transformOrigin = "center center";
content3.style.transform = `scale(${scale})`;
} }
// src/shared/utils.ts // src/shared/utils.ts
@ -54,18 +52,23 @@ function randomId() {
} }
// src/js/scrollback.ts // src/js/scrollback.ts
var statusColors = {
waiting: "yellow",
streaming: "purple",
ok: "green",
error: "red"
};
function initScrollback() { function initScrollback() {
window.addEventListener("click", handleInputClick); window.addEventListener("click", handleInputClick);
} }
function autoScroll() {}
function insert(node) { function insert(node) {
scrollback.append(node); scrollback.append(node);
} }
function addInput(id, input) { function addInput(id, input, status) {
const parent = $$("li.input"); const parent = $$("li.input");
const status = $$("span.status.yellow", "•"); const statusSpan = $$(`span.status.${statusColors[status || "waiting"]}`, "•");
const content4 = $$("span.content", input); const content3 = $$("span.content", input);
parent.append(status, content4); parent.append(statusSpan, content3);
parent.dataset.id = id; parent.dataset.id = id;
insert(parent); insert(parent);
scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight; scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight;
@ -74,31 +77,24 @@ function setStatus(id, status) {
const statusEl = document.querySelector(`[data-id="${id}"].input .status`); const statusEl = document.querySelector(`[data-id="${id}"].input .status`);
if (!statusEl) if (!statusEl)
return; return;
const colors = { statusEl.classList.remove(...Object.values(statusColors));
waiting: "yellow", statusEl.classList.add(statusColors[status]);
streaming: "purple",
ok: "green",
error: "red"
};
statusEl.classList.remove(...Object.values(colors));
statusEl.classList.add(colors[status]);
} }
function addOutput(id, output2) { function addOutput(id, output2) {
const item = $$("li"); const item = $$("li");
item.classList.add("output"); item.classList.add("output");
item.dataset.id = id || randomId(); item.dataset.id = id || randomId();
const [format, content4] = processOutput(output2); const [format, content3] = processOutput(output2);
if (format === "html") if (format === "html")
item.innerHTML = content4; item.innerHTML = content3;
else else
item.textContent = content4; item.textContent = content3;
const input = document.querySelector(`[data-id="${id}"].input`); const input = document.querySelector(`[data-id="${id}"].input`);
if (input instanceof HTMLLIElement) { if (input instanceof HTMLLIElement) {
input.parentNode.insertBefore(item, input.nextSibling); input.parentNode.insertBefore(item, input.nextSibling);
} else { } else {
insert(item); insert(item);
} }
autoScroll();
} }
function addErrorMessage(message) { function addErrorMessage(message) {
addOutput("", { html: `<span class="red">${message}</span>` }); addOutput("", { html: `<span class="red">${message}</span>` });
@ -109,12 +105,11 @@ function appendOutput(id, output2) {
console.error(`output id ${id} not found`); console.error(`output id ${id} not found`);
return; return;
} }
const [format, content4] = processOutput(output2); const [format, content3] = processOutput(output2);
if (format === "html") if (format === "html")
item.innerHTML += content4; item.innerHTML += content3;
else else
item.textContent += content4; item.textContent += content3;
autoScroll();
} }
function replaceOutput(id, output2) { function replaceOutput(id, output2) {
const item = document.querySelector(`[data-id="${id}"].output`); const item = document.querySelector(`[data-id="${id}"].output`);
@ -122,12 +117,11 @@ function replaceOutput(id, output2) {
console.error(`output id ${id} not found`); console.error(`output id ${id} not found`);
return; return;
} }
const [format, content4] = processOutput(output2); const [format, content3] = processOutput(output2);
if (format === "html") if (format === "html")
item.innerHTML = content4; item.innerHTML = content3;
else else
item.textContent = content4; item.textContent = content3;
autoScroll();
} }
function processOutput(output) { function processOutput(output) {
let content = ""; let content = "";
@ -162,8 +156,181 @@ function handleInputClick(e) {
} }
function handleOutput(msg) { function handleOutput(msg) {
const result = msg.data; const result = msg.data;
setStatus(msg.id, result.status); const id = "id" in msg ? msg.id || "" : "";
addOutput(msg.id, result.output); 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 // 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 // src/js/game.ts
var FPS = 30; var FPS = 30;
var HEIGHT = 540; var HEIGHT2 = 540;
var WIDTH = 980; var WIDTH2 = 960;
var oldMode = "cinema"; var oldMode = "cinema";
var running = false; var running = false;
var canvas; var canvas;
@ -458,8 +603,8 @@ async function handleGameStart(msg) {
function createCanvas() { function createCanvas() {
const canvas2 = $$("canvas.game.active"); const canvas2 = $$("canvas.game.active");
canvas2.id = randomId(); canvas2.id = randomId();
canvas2.height = HEIGHT; canvas2.height = HEIGHT2;
canvas2.width = WIDTH; canvas2.width = WIDTH2;
canvas2.tabIndex = 0; canvas2.tabIndex = 0;
const main = document.querySelector("main"); const main = document.querySelector("main");
main?.classList.add("game"); main?.classList.add("game");
@ -530,8 +675,8 @@ function endGame() {
const main = document.querySelector("main"); const main = document.querySelector("main");
main?.classList.remove("game"); main?.classList.remove("game");
canvas.classList.remove("active"); canvas.classList.remove("active");
canvas.style.height = HEIGHT / 2 + "px"; canvas.style.height = HEIGHT2 / 2 + "px";
canvas.style.width = WIDTH / 2 + "px"; canvas.style.width = WIDTH2 / 2 + "px";
const output2 = $$("li.output"); const output2 = $$("li.output");
output2.append(canvas); output2.append(canvas);
insert(output2); insert(output2);
@ -627,6 +772,7 @@ function retryConnection() {
// src/js/commands.ts // src/js/commands.ts
var commands = []; var commands = [];
var browserCommands = { var browserCommands = {
browse: (url) => openBrowser(url, "command"),
"browser-session": () => sessionId, "browser-session": () => sessionId,
clear: () => scrollback.innerHTML = "", clear: () => scrollback.innerHTML = "",
commands: () => { commands: () => {
@ -636,12 +782,11 @@ var browserCommands = {
mode: (mode) => { mode: (mode) => {
if (!mode) { if (!mode) {
mode = document.body.dataset.mode === "tall" ? "cinema" : "tall"; 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 = ""; content2.style.display = "";
document.body.dataset.mode = mode; document.body.dataset.mode = mode;
resize(); resize();
autoScroll();
focusInput(); focusInput();
}, },
reload: () => window.location.reload() reload: () => window.location.reload()
@ -770,7 +915,7 @@ function keydownHandler(e) {
} else if (e.ctrlKey && e.key === "s" || e.ctrlKey && e.key === "Enter") { } else if (e.ctrlKey && e.key === "s" || e.ctrlKey && e.key === "Enter") {
e.preventDefault(); e.preventDefault();
send({ send({
id: editor.dataset.path, id: editor.dataset.path || "/tmp.txt",
type: "save-file", type: "save-file",
data: editor.value 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 // src/js/hyperlink.ts
function initHyperlink() { function initHyperlink() {
window.addEventListener("click", handleClick); window.addEventListener("click", handleClick2);
} }
async function handleClick(e) { async function handleClick2(e) {
const target = e.target; const target = e.target;
if (!(target instanceof HTMLElement)) if (!(target instanceof HTMLElement))
return; return;
@ -1091,13 +1217,13 @@ async function handleClick(e) {
const href = a.getAttribute("href"); const href = a.getAttribute("href");
if (!href) if (!href)
return; return;
if (href.startsWith("#")) { if (href.startsWith("#") && href.length > 1) {
e.preventDefault(); e.preventDefault();
await runCommand(href.slice(1)); await runCommand(href.slice(1));
focusInput(); focusInput();
} else { } else if (!isBrowsing()) {
e.preventDefault(); e.preventDefault();
status(href); openBrowser(href);
} }
} }
@ -1155,7 +1281,7 @@ function clearInput() {
// src/js/vram.ts // src/js/vram.ts
var vramCounter = $("vram-size"); var vramCounter = $("vram-size");
var startVramCounter = () => { var startVramCounter = () => {
const timer2 = setInterval(() => { const timer = setInterval(() => {
const count = parseInt(vramCounter.textContent) + 1; const count = parseInt(vramCounter.textContent) + 1;
let val = count + "KB"; let val = count + "KB";
if (count < 10) if (count < 10)
@ -1163,7 +1289,7 @@ var startVramCounter = () => {
vramCounter.textContent = val; vramCounter.textContent = val;
if (count >= 64) { if (count >= 64) {
vramCounter.textContent += " OK"; vramCounter.textContent += " OK";
clearInterval(timer2); clearInterval(timer);
} }
}, 15); }, 15);
}; };