diff --git a/public/bundle.js b/public/bundle.js index 05c35b4..18b242e 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -1,7 +1,8 @@ //// -// version: 5933c50 +// version: c1faf0e // src/js/dom.ts +var content2 = $("content"); var cmdLine = $("command-line"); var cmdInput = $("command-textbox"); var scrollback = $("scrollback"); @@ -24,7 +25,7 @@ var $$ = (tag, innerHTML = "") => { }; // src/js/resize.ts -var content2 = document.getElementById("content"); +var content3 = document.getElementById("content"); function initResize() { window.addEventListener("resize", resize); resize(); @@ -38,13 +39,13 @@ function resize() { } function resizeTall() { const scale = Math.min(1, window.innerWidth / 960); - content2.style.transformOrigin = "top center"; - content2.style.transform = `scaleX(${scale})`; + content3.style.transformOrigin = "top center"; + content3.style.transform = `scaleX(${scale})`; } function resizeCinema() { const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540); - content2.style.transformOrigin = "center center"; - content2.style.transform = `scale(${scale})`; + content3.style.transformOrigin = "center center"; + content3.style.transform = `scale(${scale})`; } // src/shared/utils.ts @@ -63,8 +64,8 @@ function insert(node) { function addInput(id, input) { const parent = $$("li.input"); const status = $$("span.status.yellow", "•"); - const content3 = $$("span.content", input); - parent.append(status, content3); + const content4 = $$("span.content", input); + parent.append(status, content4); parent.dataset.id = id; insert(parent); scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight; @@ -86,11 +87,11 @@ function addOutput(id, output2) { const item = $$("li"); item.classList.add("output"); item.dataset.id = id || randomId(); - const [format, content3] = processOutput(output2); + const [format, content4] = processOutput(output2); if (format === "html") - item.innerHTML = content3; + item.innerHTML = content4; else - item.textContent = content3; + item.textContent = content4; const input = document.querySelector(`[data-id="${id}"].input`); if (input instanceof HTMLLIElement) { input.parentNode.insertBefore(item, input.nextSibling); @@ -108,11 +109,11 @@ function appendOutput(id, output2) { console.error(`output id ${id} not found`); return; } - const [format, content3] = processOutput(output2); + const [format, content4] = processOutput(output2); if (format === "html") - item.innerHTML += content3; + item.innerHTML += content4; else - item.textContent += content3; + item.textContent += content4; autoScroll(); } function replaceOutput(id, output2) { @@ -121,11 +122,11 @@ function replaceOutput(id, output2) { console.error(`output id ${id} not found`); return; } - const [format, content3] = processOutput(output2); + const [format, content4] = processOutput(output2); if (format === "html") - item.innerHTML = content3; + item.innerHTML = content4; else - item.textContent = content3; + item.textContent = content4; autoScroll(); } function processOutput(output) { @@ -159,150 +160,32 @@ function handleInputClick(e) { cmdInput.value = target.textContent; } } +function handleOutput(msg) { + const result = msg.data; + setStatus(msg.id, result.status); + addOutput(msg.id, result.output); +} // src/js/session.ts var sessionId = randomId(); -// src/js/commands.ts -var commands = []; -var browserCommands = { - "browser-session": () => sessionId, - clear: () => scrollback.innerHTML = "", - commands: () => commands.join(" "), - fullscreen: () => document.body.requestFullscreen(), - mode: () => { - document.body.dataset.mode = document.body.dataset.mode === "tall" ? "cinema" : "tall"; - resize(); - autoScroll(); - }, - reload: () => window.location.reload() -}; -function cacheCommands(cmds) { - commands.length = 0; - commands.push(...cmds); - commands.push(...Object.keys(browserCommands)); - commands.sort(); -} - -// src/js/completion.ts -function initCompletion() { - cmdInput.addEventListener("keydown", handleCompletion); -} -function handleCompletion(e) { - if (e.key !== "Tab") +// src/js/stream.ts +function handleStreamStart(msg) { + const id = msg.id; + const status = document.querySelector(`[data-id="${id}"].input .status`); + if (!status) return; - e.preventDefault(); - const input = cmdInput.value; - for (const command of commands) { - if (command.startsWith(input)) { - cmdInput.value = command; - return; - } - } + addOutput(id, msg.data); + status.classList.remove("yellow"); + status.classList.add("purple"); } - -// src/js/cursor.ts -var cursor = "Û"; -var cmdCursor; -var enabled = true; -function initCursor() { - cmdCursor = $("command-cursor"); - cmdInput.addEventListener("keydown", showCursor); - document.addEventListener("focus", cursorEnablerHandler, true); - showCursor(); +function handleStreamAppend(msg) { + appendOutput(msg.id, msg.data); } -function showCursor(e = {}) { - if (!enabled) { - cmdCursor.value = ""; - return; - } - if (e.key === "Enter" && !e.shiftKey) { - cmdCursor.value = cursor; - return; - } - requestAnimationFrame(() => cmdCursor.value = buildBlankCursorLine() + cursor); -} -function cursorEnablerHandler(e) { - if (!e.target) - return; - const target = e.target; - enabled = target.id === "command-textbox"; - showCursor(); -} -function buildBlankCursorLine() { - let line = ""; - for (const char of cmdInput.value.slice(0, cmdInput.selectionEnd)) { - line += char === ` -` ? char : " "; - } - return line; -} - -// src/js/drop.ts -function initDrop() { - ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { - document.body.addEventListener(eventName, preventDefaults, false); - }); - document.body.addEventListener("drop", handleDrop); -} -function preventDefaults(e) { - e.preventDefault(); - e.stopPropagation(); -} -function handleDrop(e) { - const fileInput = document.querySelector("input[type=file]"); - const files = e.dataTransfer?.files ?? []; - if (files.length > 0) { - const dt = new DataTransfer; - Array.from(files).forEach((f) => dt.items.add(f)); - fileInput.files = dt.files; - fileInput.dispatchEvent(new Event("change", { bubbles: true })); - } -} - -// src/js/history.ts -var history = ["one", "two", "three"]; -var idx = -1; -var savedInput = ""; -function initHistory() { - cmdInput.addEventListener("keydown", navigateHistory); -} -function addToHistory(input) { - if (history.length === 0 || history[0] === input) - return; - history.unshift(input); - resetHistory(); -} -function resetHistory() { - idx = -1; - savedInput = ""; -} -function navigateHistory(e) { - if (cmdLine.dataset.extended) - return; - if (e.key === "ArrowUp" || e.ctrlKey && e.key === "p") { - e.preventDefault(); - if (idx >= history.length - 1) - return; - if (idx === -1) - savedInput = cmdInput.value; - cmdInput.value = history[++idx] || ""; - if (idx >= history.length) - idx = history.length - 1; - } else if (e.key === "ArrowDown" || e.ctrlKey && e.key === "n") { - e.preventDefault(); - if (idx <= 0) { - cmdInput.value = savedInput; - idx = -1; - return; - } - cmdInput.value = history[--idx] || ""; - if (idx < -1) - idx = -1; - } else if (idx !== -1) { - resetHistory(); - } +function handleStreamReplace(msg) { + replaceOutput(msg.id, msg.data); } +function handleStreamEnd(_msg) {} // src/shared/game.ts class GameContext { @@ -624,28 +507,8 @@ function endGame() { focusInput(); } -// src/js/shell.ts -function runCommand(input) { - if (!input.trim()) - return; - if (input.includes(";")) { - input.split(";").forEach((cmd2) => runCommand(cmd2.trim())); - return; - } - const id = randomId(); - addToHistory(input); - addInput(id, input); - const [cmd = "", ..._args] = input.split(" "); - if (browserCommands[cmd]) { - const result = browserCommands[cmd](); - if (typeof result === "string") - addOutput(id, result); - setStatus(id, "ok"); - } else { - send({ id, type: "input", data: input }); - } -} -async function handleMessage(msg) { +// src/js/dispatch.ts +async function dispatchMessage(msg) { switch (msg.type) { case "output": handleOutput(msg); @@ -671,31 +534,13 @@ async function handleMessage(msg) { case "game:start": await handleGameStart(msg); break; + case "ui:mode": + browserCommands.mode?.(msg.data); + break; default: console.error("unknown message type", msg); } } -function handleOutput(msg) { - const result = msg.data; - setStatus(msg.id, result.status); - addOutput(msg.id, result.output); -} -function handleStreamStart(msg) { - const id = msg.id; - const status = document.querySelector(`[data-id="${id}"].input .status`); - if (!status) - return; - addOutput(id, msg.data); - status.classList.remove("yellow"); - status.classList.add("purple"); -} -function handleStreamAppend(msg) { - appendOutput(msg.id, msg.data); -} -function handleStreamReplace(msg) { - replaceOutput(msg.id, msg.data); -} -function handleStreamEnd(_msg) {} // src/js/websocket.ts var MAX_RETRIES = 5; @@ -704,7 +549,7 @@ var connected = false; var msgQueue = []; var ws = null; function startConnection() { - const url = new URL("/ws", location.href); + const url = new URL(`/ws?session=${sessionId}`, location.href); url.protocol = url.protocol.replace("http", "ws"); ws = new WebSocket(url); ws.onmessage = receive; @@ -730,7 +575,7 @@ function send(msg) { async function receive(e) { const data = JSON.parse(e.data); console.log("<- receive", data); - await handleMessage(data); + await dispatchMessage(data); } function retryConnection() { connected = false; @@ -745,6 +590,112 @@ function retryConnection() { setTimeout(startConnection, 2000); } +// src/js/commands.ts +var commands = []; +var browserCommands = { + "browser-session": () => sessionId, + clear: () => scrollback.innerHTML = "", + commands: () => { + return { html: "
" + commands.map((cmd) => `${cmd}`).join("") + "
" }; + }, + fullscreen: () => document.body.requestFullscreen(), + mode: (mode) => { + if (!mode) { + mode = document.body.dataset.mode === "tall" ? "cinema" : "tall"; + send({ type: "ui:mode", data: mode }); + } + content2.style.display = ""; + document.body.dataset.mode = mode; + resize(); + autoScroll(); + focusInput(); + }, + reload: () => window.location.reload() +}; +function cacheCommands(cmds) { + commands.length = 0; + commands.push(...cmds); + commands.push(...Object.keys(browserCommands)); + commands.sort(); + console.log("CMDS", commands); +} + +// src/js/completion.ts +function initCompletion() { + cmdInput.addEventListener("keydown", handleCompletion); +} +function handleCompletion(e) { + if (e.key !== "Tab") + return; + e.preventDefault(); + const input = cmdInput.value; + for (const command of commands) { + if (command.startsWith(input)) { + cmdInput.value = command; + return; + } + } +} + +// src/js/cursor.ts +var cursor = "Û"; +var cmdCursor; +var enabled = true; +function initCursor() { + cmdCursor = $("command-cursor"); + cmdInput.addEventListener("keydown", showCursor); + document.addEventListener("focus", cursorEnablerHandler, true); + showCursor(); +} +function showCursor(e = {}) { + if (!enabled) { + cmdCursor.value = ""; + return; + } + if (e.key === "Enter" && !e.shiftKey) { + cmdCursor.value = cursor; + return; + } + requestAnimationFrame(() => cmdCursor.value = buildBlankCursorLine() + cursor); +} +function cursorEnablerHandler(e) { + if (!e.target) + return; + const target = e.target; + enabled = target.id === "command-textbox"; + showCursor(); +} +function buildBlankCursorLine() { + let line = ""; + for (const char of cmdInput.value.slice(0, cmdInput.selectionEnd)) { + line += char === ` +` ? char : " "; + } + return line; +} + +// src/js/drop.ts +function initDrop() { + ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { + document.body.addEventListener(eventName, preventDefaults, false); + }); + document.body.addEventListener("drop", handleDrop); +} +function preventDefaults(e) { + e.preventDefault(); + e.stopPropagation(); +} +function handleDrop(e) { + const fileInput = document.querySelector("input[type=file]"); + const files = e.dataTransfer?.files ?? []; + if (files.length > 0) { + const dt = new DataTransfer; + Array.from(files).forEach((f) => dt.items.add(f)); + fileInput.files = dt.files; + fileInput.dispatchEvent(new Event("change", { bubbles: true })); + } +} + // src/js/editor.ts var INDENT_SIZE = 2; function initEditor() { @@ -1008,11 +959,77 @@ function handleGamepad() { requestAnimationFrame(handleGamepad); } +// src/js/history.ts +var history = ["one", "two", "three"]; +var idx = -1; +var savedInput = ""; +function initHistory() { + cmdInput.addEventListener("keydown", navigateHistory); +} +function addToHistory(input) { + if (history.length === 0 || history[0] === input) + return; + history.unshift(input); + resetHistory(); +} +function resetHistory() { + idx = -1; + savedInput = ""; +} +function navigateHistory(e) { + if (cmdLine.dataset.extended) + return; + if (e.key === "ArrowUp" || e.ctrlKey && e.key === "p") { + e.preventDefault(); + if (idx >= history.length - 1) + return; + if (idx === -1) + savedInput = cmdInput.value; + cmdInput.value = history[++idx] || ""; + if (idx >= history.length) + idx = history.length - 1; + } else if (e.key === "ArrowDown" || e.ctrlKey && e.key === "n") { + e.preventDefault(); + if (idx <= 0) { + cmdInput.value = savedInput; + idx = -1; + return; + } + cmdInput.value = history[--idx] || ""; + if (idx < -1) + idx = -1; + } else if (idx !== -1) { + resetHistory(); + } +} + +// src/js/shell.ts +async function runCommand(input) { + if (!input.trim()) + return; + if (input.includes(";")) { + input.split(";").forEach(async (cmd2) => await runCommand(cmd2.trim())); + return; + } + const id = randomId(); + addToHistory(input); + addInput(id, input); + const [cmd = "", ...args] = input.split(" "); + if (browserCommands[cmd]) { + const result = await browserCommands[cmd](...args); + if (result) + addOutput(id, result); + setStatus(id, "ok"); + } else { + send({ id, type: "input", data: input }); + } +} + // src/js/hyperlink.ts function initHyperlink() { window.addEventListener("click", handleClick); } -function handleClick(e) { +async function handleClick(e) { const target = e.target; if (!(target instanceof HTMLElement)) return; @@ -1024,7 +1041,7 @@ function handleClick(e) { return; if (href.startsWith("#")) { e.preventDefault(); - runCommand(href.slice(1)); + await runCommand(href.slice(1)); focusInput(); } } @@ -1034,7 +1051,7 @@ function initInput() { cmdInput.addEventListener("keydown", inputHandler); cmdInput.addEventListener("paste", pasteHandler); } -function inputHandler(event) { +async function inputHandler(event) { const target = event.target; if (target?.id !== cmdInput.id) return; @@ -1048,7 +1065,7 @@ function inputHandler(event) { cmdLine.dataset.extended = "true"; } else if (event.key === "Enter") { event.preventDefault(); - runCommand(cmdInput.value); + await runCommand(cmdInput.value); clearInput(); } setTimeout(() => {