nose-pluto/public/bundle.js
Chris Wanstrath 1d6a7bc0c1 bundle js
2025-10-01 10:12:05 -07:00

1111 lines
29 KiB
JavaScript

// src/js/dom.ts
var cmdLine = $("command-line");
var cmdInput = $("command-textbox");
var scrollback = $("scrollback");
function $(id) {
return document.getElementById(id);
}
var $$ = (tag, innerHTML = "") => {
let name = tag;
let classList = [];
if (tag.startsWith("."))
tag = "div" + tag;
if (tag.includes("."))
[name, ...classList] = tag.split(".").map((x) => x.trim());
const el = document.createElement(name);
if (innerHTML)
el.innerHTML = innerHTML;
if (classList.length)
el.classList.add(...classList);
return el;
};
// src/js/resize.ts
var content2 = 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);
content2.style.transformOrigin = "top center";
content2.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})`;
}
// src/shared/utils.ts
function randomId() {
return Math.random().toString(36).slice(7);
}
// src/js/scrollback.ts
function initScrollback() {
window.addEventListener("click", handleInputClick);
}
function autoScroll() {}
function insert(node) {
scrollback.append(node);
}
function addInput(id, input) {
const parent = $$("li.input");
const status = $$("span.status.yellow", "•");
const content3 = $$("span.content", input);
parent.append(status, content3);
parent.dataset.id = id;
insert(parent);
scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight;
}
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]);
}
function addOutput(id, output2) {
const item = $$("li");
item.classList.add("output");
item.dataset.id = id || randomId();
const [format, content3] = processOutput(output2);
if (format === "html")
item.innerHTML = content3;
else
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: `<span class="red">${message}</span>` });
}
function appendOutput(id, output2) {
const item = document.querySelector(`[data-id="${id}"].output`);
if (!item) {
console.error(`output id ${id} not found`);
return;
}
const [format, content3] = processOutput(output2);
if (format === "html")
item.innerHTML += content3;
else
item.textContent += content3;
autoScroll();
}
function replaceOutput(id, output2) {
const item = document.querySelector(`[data-id="${id}"].output`);
if (!item) {
console.error(`output id ${id} not found`);
return;
}
const [format, content3] = processOutput(output2);
if (format === "html")
item.innerHTML = content3;
else
item.textContent = content3;
autoScroll();
}
function processOutput(output) {
let content = "";
let html = false;
if (typeof output === "string") {
content = output;
} else if (Array.isArray(output)) {
content = output.join(" ");
} else if (typeof output === "object" && "html" in output) {
html = true;
content = output.html;
if (output.script)
eval(output.script);
} else if (typeof output === "object" && "text" in output) {
content = output.text;
if (output.script)
eval(output.script);
} else if (typeof output === "object" && "script" in output) {
eval(output.script);
} else {
content = JSON.stringify(output);
}
return [html ? "html" : "text", content];
}
function handleInputClick(e) {
const target = e.target;
if (!(target instanceof HTMLElement))
return;
if (target.matches(".input .content")) {
cmdInput.value = target.textContent;
}
}
// 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")
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/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/shared/game.ts
class GameContext {
ctx;
constructor(ctx) {
this.ctx = ctx;
}
width = 960;
height = 540;
clear(color) {
if (color)
this.rectfill(0, 0, this.width, this.height, color);
else
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
}
text(msg, x, y, color = "black", size = 16, font = "C64ProMono") {
const c = this.ctx;
c.save();
c.fillStyle = color;
c.font = `${size}px ${font}`;
c.textBaseline = "top";
c.fillText(msg, x, y);
c.restore();
}
centerText(msg, color = "black", size = 16, font = "C64ProMono") {
const c = this.ctx;
c.save();
c.fillStyle = color;
c.font = `${size}px ${font}`;
c.textBaseline = "middle";
c.textAlign = "center";
c.fillText(msg, this.width / 2, this.height / 2);
c.restore();
}
centerTextX(msg, y, color = "black", size = 16, font = "C64ProMono") {
const c = this.ctx;
c.save();
c.fillStyle = color;
c.font = `${size}px ${font}`;
c.textBaseline = "middle";
c.textAlign = "center";
c.fillText(msg, this.width / 2, y);
c.restore();
}
centerTextY(msg, x, color = "black", size = 16, font = "C64ProMono") {
const c = this.ctx;
c.save();
c.fillStyle = color;
c.font = `${size}px ${font}`;
c.textBaseline = "middle";
c.textAlign = "center";
c.fillText(msg, x, this.height / 2);
c.restore();
}
circ(x, y, r, color = "black") {
const c = this.ctx;
c.save();
c.beginPath();
c.strokeStyle = color;
c.arc(x, y, r, 0, Math.PI * 2);
c.stroke();
c.restore();
}
circfill(x, y, r, color = "black") {
const c = this.ctx;
c.save();
c.beginPath();
c.fillStyle = color;
c.arc(x, y, r, 0, Math.PI * 2);
c.fill();
c.restore();
}
line(x0, y0, x1, y1, color = "black") {
const c = this.ctx;
c.save();
c.beginPath();
c.strokeStyle = color;
c.moveTo(x0, y0);
c.lineTo(x1, y1);
c.stroke();
c.restore();
}
oval(x0, y0, x1, y1, color = "black") {
const c = this.ctx;
const w = x1 - x0;
const h = y1 - y0;
c.save();
c.beginPath();
c.strokeStyle = color;
c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
c.stroke();
c.restore();
}
ovalfill(x0, y0, x1, y1, color = "black") {
const c = this.ctx;
const w = x1 - x0;
const h = y1 - y0;
c.save();
c.beginPath();
c.fillStyle = color;
c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
c.fill();
c.restore();
}
rect(x0, y0, x1, y1, color = "black") {
const c = this.ctx;
c.save();
c.beginPath();
c.strokeStyle = color;
c.rect(x0, y0, x1 - x0, y1 - y0);
c.stroke();
c.restore();
}
rectfill(x0, y0, x1, y1, color = "black") {
const c = this.ctx;
c.save();
this.ctx.fillStyle = color;
this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
c.restore();
}
rrect(x, y, w, h, r, color = "black") {
const c = this.ctx;
c.save();
c.beginPath();
c.strokeStyle = color;
this.roundRectPath(x, y, w, h, r);
c.stroke();
c.restore();
}
rrectfill(x, y, w, h, r, color = "black") {
const c = this.ctx;
c.save();
c.beginPath();
c.fillStyle = color;
this.roundRectPath(x, y, w, h, r);
c.fill();
c.restore();
}
trianglefill(x0, y0, x1, y1, x2, y2, color = "black") {
return this.polygonfill([
[x0, y0],
[x1, y1],
[x2, y2]
], color);
}
polygonfill(points, color = "black") {
if (points.length < 3)
return;
const c = this.ctx;
c.save();
c.beginPath();
c.fillStyle = color;
c.moveTo(points[0][0], points[0][1]);
for (let i = 1;i < points.length; i++) {
c.lineTo(points[i][0], points[i][1]);
}
c.closePath();
c.fill();
c.restore();
}
roundRectPath(x, y, w, h, r) {
const c = this.ctx;
c.moveTo(x + r, y);
c.lineTo(x + w - r, y);
c.quadraticCurveTo(x + w, y, x + w, y + r);
c.lineTo(x + w, y + h - r);
c.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
c.lineTo(x + r, y + h);
c.quadraticCurveTo(x, y + h, x, y + h - r);
c.lineTo(x, y + r);
c.quadraticCurveTo(x, y, x + r, y);
}
}
// 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 oldMode = "cinema";
var running = false;
var canvas;
var pressedStack = new Set;
var pressed = {
key: "",
shift: false,
ctrl: false,
meta: false,
pressed: pressedStack,
prevPressed: new Set,
justPressed: new Set,
justReleased: new Set
};
async function handleGameStart(msg) {
const msgId = msg.id;
const name = msg.data;
let game;
try {
game = await import(`/source/${name}`);
} catch (err) {
setStatus(msgId, "error");
addOutput(msgId, `Error: ${err.message ? err.message : err}`);
return;
}
if (document.body.dataset.mode === "tall") {
browserCommands.mode?.();
oldMode = "tall";
}
canvas = createCanvas();
canvas.focus();
setStatus(msgId, "ok");
window.addEventListener("keydown", handleKeydown);
window.addEventListener("keyup", handleKeyup);
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
gameLoop(new GameContext(canvas.getContext("2d")), game);
}
function createCanvas() {
const canvas2 = $$("canvas.game.active");
canvas2.id = randomId();
canvas2.height = HEIGHT;
canvas2.width = WIDTH;
canvas2.tabIndex = 0;
const main = document.querySelector("main");
main?.classList.add("game");
main?.parentNode?.insertBefore(canvas2, main);
return canvas2;
}
function handleKeydown(e) {
e.preventDefault();
if (e.key === "Escape" || e.ctrlKey && e.key === "c") {
endGame();
} else {
pressedStack.add(e.key);
pressed.key = e.key;
pressed.ctrl = e.ctrlKey;
pressed.shift = e.shiftKey;
pressed.meta = e.metaKey;
}
}
function handleKeyup(e) {
pressedStack.delete(e.key);
if (pressedStack.size === 0) {
pressed.key = "";
pressed.ctrl = false;
pressed.shift = false;
pressed.meta = false;
}
}
function updateInputState() {
pressed.justPressed = new Set([...pressed.pressed].filter((k) => !pressed.prevPressed.has(k)));
pressed.justReleased = new Set([...pressed.prevPressed].filter((k) => !pressed.pressed.has(k)));
pressed.prevPressed = new Set(pressed.pressed);
}
function resizeCanvas() {
const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540);
canvas.width = 960 * scale;
canvas.height = 540 * scale;
const ctx = canvas.getContext("2d");
ctx.setTransform(scale, 0, 0, scale, 0, 0);
}
function gameLoop(ctx, game) {
running = true;
let last = 0;
if (game.init)
game.init();
function loop(ts) {
if (!running)
return;
const delta = ts - last;
if (delta >= 1000 / FPS) {
updateInputState();
if (game.update)
game.update(delta, pressed);
if (game.draw)
game.draw(ctx);
last = ts;
}
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function endGame() {
running = false;
window.removeEventListener("keydown", handleKeydown);
window.removeEventListener("keyup", handleKeyup);
window.removeEventListener("resize", resizeCanvas);
if (oldMode === "tall")
browserCommands.mode?.();
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";
const output2 = $$("li.output");
output2.append(canvas);
insert(output2);
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) {
switch (msg.type) {
case "output":
handleOutput(msg);
break;
case "commands":
cacheCommands(msg.data);
break;
case "error":
console.error(msg.data);
break;
case "stream:start":
handleStreamStart(msg);
break;
case "stream:end":
handleStreamEnd(msg);
break;
case "stream:append":
handleStreamAppend(msg);
break;
case "stream:replace":
handleStreamReplace(msg);
break;
case "game:start":
await handleGameStart(msg);
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;
var retries = 0;
var connected = false;
var msgQueue = [];
var ws = null;
function startConnection() {
const url = new URL("/ws", location.href);
url.protocol = url.protocol.replace("http", "ws");
ws = new WebSocket(url);
ws.onmessage = receive;
ws.onclose = retryConnection;
ws.onerror = () => ws?.close();
ws.onopen = () => {
connected = true;
msgQueue.forEach((msg) => send(msg));
msgQueue.length = 0;
};
}
function send(msg) {
if (!connected) {
msgQueue.push(msg);
startConnection();
return;
}
if (!msg.session)
msg.session = sessionId;
ws?.readyState === 1 && ws.send(JSON.stringify(msg));
console.log("-> send", msg);
}
async function receive(e) {
const data = JSON.parse(e.data);
console.log("<- receive", data);
await handleMessage(data);
}
function retryConnection() {
connected = false;
if (retries >= MAX_RETRIES) {
addErrorMessage(`Failed to reconnect ${retries} times. Server is down.`);
if (ws)
ws.onclose = () => {};
return;
}
retries++;
addErrorMessage(`Connection lost. Retrying...`);
setTimeout(startConnection, 2000);
}
// src/js/editor.ts
var INDENT_SIZE = 2;
function initEditor() {
document.addEventListener("input", handleAdjustHeight);
focusTextareaOnCreation();
}
function handleAdjustHeight(e) {
const target = e.target;
if (target?.matches(".editor"))
adjustHeight(target);
}
function adjustHeight(editor) {
editor.style.height = "auto";
editor.style.height = editor.scrollHeight + "px";
}
function focusTextareaOnCreation() {
const observer = new MutationObserver((mutations) => {
for (const m of mutations)
for (const node of Array.from(m.addedNodes))
if (node instanceof HTMLElement && node.childNodes[0] instanceof HTMLElement && node.childNodes[0].matches("textarea")) {
const editor = node.childNodes[0];
editor.focus();
editor.addEventListener("keydown", keydownHandler);
return;
}
});
observer.observe(scrollback, { childList: true });
}
function keydownHandler(e) {
const editor = e.target;
if (e.key === "Tab") {
e.preventDefault();
if (e.shiftKey)
removeTab(editor);
else
insertTab(editor);
} else if (e.ctrlKey && e.key === "c") {
focusInput();
} else if (e.ctrlKey && e.key === "s" || e.ctrlKey && e.key === "Enter") {
e.preventDefault();
send({
id: editor.dataset.path,
type: "save-file",
data: editor.value
});
} else if (e.key === "{") {
if (editor.selectionStart !== editor.selectionEnd) {
insertAroundSelection(editor, "{", "}");
e.preventDefault();
} else {
setTimeout(() => insertAfterCaret(editor, "}"), 0);
}
} else if (e.key === "}" && isNextChar(editor, "}")) {
moveOneRight(editor);
e.preventDefault();
} else if (e.key === "[") {
if (editor.selectionStart !== editor.selectionEnd) {
insertAroundSelection(editor, "[", "]");
e.preventDefault();
} else {
setTimeout(() => insertAfterCaret(editor, "]"), 0);
}
} else if (e.key === "]" && isNextChar(editor, "]")) {
moveOneRight(editor);
e.preventDefault();
} else if (e.key === "(") {
if (editor.selectionStart !== editor.selectionEnd) {
insertAroundSelection(editor, "(", ")");
e.preventDefault();
} else {
setTimeout(() => insertAfterCaret(editor, ")"), 0);
}
} else if (e.key === ")" && isNextChar(editor, ")")) {
moveOneRight(editor);
e.preventDefault();
} else if (e.key === '"') {
if (isNextChar(editor, '"')) {
moveOneRight(editor);
e.preventDefault();
} else if (editor.selectionStart !== editor.selectionEnd) {
insertAroundSelection(editor, '"', '"');
e.preventDefault();
} else {
setTimeout(() => insertAfterCaret(editor, '"'), 0);
}
} else if (e.key === "Enter") {
indentNewlineForBraces(e, editor);
}
}
function moveOneRight(editor) {
const pos = editor.selectionStart;
editor.selectionStart = editor.selectionEnd = pos + 1;
}
function insertTab(editor) {
const start = editor.selectionStart;
const end = editor.selectionEnd;
const lineStart = editor.value.lastIndexOf(`
`, start - 1) + 1;
editor.value = editor.value.slice(0, lineStart) + " " + editor.value.slice(lineStart);
editor.selectionStart = start + INDENT_SIZE;
editor.selectionEnd = end + INDENT_SIZE;
}
function removeTab(editor) {
const start = editor.selectionStart;
const end = editor.selectionEnd;
const lineStart = editor.value.lastIndexOf(`
`, start - 1) + 1;
if (editor.value.slice(lineStart, lineStart + 2) === " ") {
editor.value = editor.value.slice(0, lineStart) + editor.value.slice(lineStart + 2);
editor.selectionStart = start - INDENT_SIZE;
editor.selectionEnd = end - INDENT_SIZE;
}
}
function insertAfterCaret(editor, char) {
const pos = editor.selectionStart;
editor.value = editor.value.slice(0, pos) + char + editor.value.slice(pos);
editor.selectionStart = editor.selectionEnd = pos;
}
function isNextChar(editor, char) {
return editor.value[editor.selectionStart] === char;
}
function indentNewlineForBraces(e, editor) {
e.preventDefault();
if (isBetween(editor, "{", "}") || isBetween(editor, "[", "]")) {
setTimeout(() => insertMoreIndentedNewline(editor), 0);
} else {
setTimeout(() => insertIndentedNewline(editor), 0);
}
}
function isBetween(editor, start, end) {
const pos = editor.selectionStart;
const val = editor.value;
if (pos <= 0 || pos >= val.length)
return false;
return val[pos - 1] === start && val[pos] === end;
}
function insertIndentedNewline(editor) {
const pos = editor.selectionStart;
const before = editor.value.slice(0, pos);
const prevLineStart = before.lastIndexOf(`
`, pos - 1) + 1;
const prevLine = before.slice(prevLineStart);
let leading = 0;
while (prevLine[leading] === " ")
leading++;
const indent = " ".repeat(leading);
const insert2 = `
` + indent;
editor.value = editor.value.slice(0, pos) + insert2 + editor.value.slice(pos);
const newPos = pos + insert2.length;
editor.selectionStart = editor.selectionEnd = newPos;
adjustHeight(editor);
}
function insertAroundSelection(editor, before, after) {
const start = editor.selectionStart;
const end = editor.selectionEnd;
editor.value = editor.value.slice(0, start) + before + editor.value.slice(start, end) + after + editor.value.slice(end);
}
function insertMoreIndentedNewline(editor) {
const pos = editor.selectionStart;
const before = editor.value.slice(0, pos);
const prevLineStart = before.lastIndexOf(`
`, pos - 1) + 1;
const prevLine = before.slice(prevLineStart);
let leading = 0;
while (prevLine[leading] === " ")
leading++;
const oldIndent = " ".repeat(leading);
const newIndent = " ".repeat(leading + INDENT_SIZE);
const insert2 = `
` + newIndent + `
` + oldIndent;
editor.value = editor.value.slice(0, pos) + insert2 + editor.value.slice(pos);
const newPos = pos + insert2.length;
editor.selectionStart = editor.selectionEnd = newPos - 1 - oldIndent.length;
adjustHeight(editor);
}
// src/js/form.ts
function initForm() {
document.addEventListener("submit", submitHandler);
}
var submitHandler = async (e) => {
e.preventDefault();
const form = e.target;
if (!(form instanceof HTMLFormElement))
return;
const li = form.closest(".output");
if (!(li instanceof HTMLLIElement))
return;
const id = li.dataset.id;
if (!id)
return;
let output2;
let error = false;
try {
const fd = new FormData(form);
const data = await fetch("/cmd" + new URL(form.action).pathname, {
method: "POST",
headers: { "X-Session": sessionId },
body: fd
}).then((r) => r.json());
output2 = data.output;
error = data.status === "error";
} catch (e2) {
output2 = e2.message || e2.toString();
error = true;
}
if (error)
setStatus(id, "error");
replaceOutput(id, output2);
focusInput();
};
// src/js/gamepad.ts
var BUTTONS_TO_KEYS = {
0: "z",
1: "x",
2: "c",
3: "v",
12: "ArrowUp",
13: "ArrowDown",
14: "ArrowLeft",
15: "ArrowRight",
9: "Enter",
8: "Escape"
};
var activePads = new Set;
var prevState = {};
function initGamepad() {
window.addEventListener("gamepadconnected", (e) => {
console.log("Gamepad connected:", e.gamepad);
activePads.add(e.gamepad.index);
requestAnimationFrame(handleGamepad);
});
window.addEventListener("gamepaddisconnected", (e) => {
console.log("Gamepad disconnected:", e.gamepad);
activePads.delete(e.gamepad.index);
});
}
function handleGamepad() {
const pads = navigator.getGamepads();
for (const gp of pads) {
if (!gp)
continue;
if (!prevState[gp.index])
prevState[gp.index] = gp.buttons.map(() => false);
gp.buttons.forEach((btn, i) => {
const was = prevState[gp.index][i];
const is = btn.pressed;
if (was !== is) {
const key = BUTTONS_TO_KEYS[i];
if (key) {
const type = is ? "keydown" : "keyup";
window.dispatchEvent(new KeyboardEvent(type, { code: key, key }));
}
prevState[gp.index][i] = is;
}
});
}
requestAnimationFrame(handleGamepad);
}
// src/js/hyperlink.ts
function initHyperlink() {
window.addEventListener("click", handleClick);
}
function handleClick(e) {
const target = e.target;
if (!(target instanceof HTMLElement))
return;
const a = target.closest("a");
if (!a)
return;
const href = a.getAttribute("href");
if (!href)
return;
if (href.startsWith("#")) {
e.preventDefault();
runCommand(href.slice(1));
focusInput();
}
}
// src/js/input.ts
function initInput() {
cmdInput.addEventListener("keydown", inputHandler);
cmdInput.addEventListener("paste", pasteHandler);
}
function inputHandler(event) {
const target = event.target;
if (target?.id !== cmdInput.id)
return;
if (event.key === "Escape" || event.ctrlKey && event.key === "c") {
clearInput();
resetHistory();
} else if (event.key === "Tab") {
event.preventDefault();
} else if (event.shiftKey && event.key === "Enter") {
cmdInput.rows += 1;
cmdLine.dataset.extended = "true";
} else if (event.key === "Enter") {
event.preventDefault();
runCommand(cmdInput.value);
clearInput();
}
setTimeout(() => {
if (cmdLine.dataset.extended && !cmdInput.value.includes(`
`)) {
cmdInput.rows = 1;
cmdInput.style.height = "auto";
delete cmdLine.dataset.extended;
}
}, 0);
}
function pasteHandler(event) {
const text = event.clipboardData?.getData("text") || "";
if (!text.includes(`
`)) {
delete cmdLine.dataset.extended;
return;
}
setTimeout(() => {
cmdInput.style.height = "auto";
cmdInput.style.height = cmdInput.scrollHeight + "px";
cmdLine.dataset.extended = "true";
}, 0);
}
function clearInput() {
cmdInput.value = "";
cmdInput.rows = 1;
cmdInput.style.height = "auto";
delete cmdLine.dataset.extended;
}
// src/js/vram.ts
var vramCounter = $("vram-size");
var startVramCounter = () => {
const timer = setInterval(() => {
const count = parseInt(vramCounter.textContent) + 1;
let val = count + "KB";
if (count < 10)
val = "0" + val;
vramCounter.textContent = val;
if (count >= 64) {
vramCounter.textContent += " OK";
clearInterval(timer);
}
}, 15);
};
// src/js/main.ts
initCompletion();
initCursor();
initDrop();
initFocus();
initForm();
initEditor();
initGamepad();
initHistory();
initHyperlink();
initInput();
initResize();
initScrollback();
startConnection();
startVramCounter();