diff --git a/public/bundle.js b/public/bundle.js index a1f2d16..ee89ad6 100644 --- a/public/bundle.js +++ b/public/bundle.js @@ -1,7 +1,33 @@ //// -// version: 49fd142 +// version: 606bcc9 + +var __defProp = Object.defineProperty; +var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { + get: all[name], + enumerable: true, + configurable: true, + set: (newValue) => all[name] = () => newValue + }); +}; + +// src/js/completion.ts +var exports_completion = {}; +__export(exports_completion, { + initCompletion: () => initCompletion +}); // src/js/dom.ts +var exports_dom = {}; +__export(exports_dom, { + scrollback: () => scrollback, + content: () => content2, + cmdLine: () => cmdLine, + cmdInput: () => cmdInput, + $$: () => $$, + $: () => $ +}); var content2 = $("content"); var cmdLine = $("command-line"); var cmdInput = $("command-textbox"); @@ -24,6 +50,35 @@ var $$ = (tag, innerHTML = "") => { return el; }; +// src/js/commands.ts +var exports_commands = {}; +__export(exports_commands, { + mode: () => mode, + commands: () => commands, + cacheCommands: () => cacheCommands +}); + +// src/js/shell.ts +var exports_shell = {}; +__export(exports_shell, { + runCommand: () => runCommand +}); + +// src/js/scrollback.ts +var exports_scrollback = {}; +__export(exports_scrollback, { + setStatus: () => setStatus, + replaceOutput: () => replaceOutput, + latestId: () => latestId, + insert: () => insert, + initScrollback: () => initScrollback, + handleOutput: () => handleOutput, + appendOutput: () => appendOutput, + addOutput: () => addOutput, + addInput: () => addInput, + addErrorMessage: () => addErrorMessage +}); + // src/shared/utils.ts function randomId() { return Math.random().toString(36).slice(7); @@ -160,7 +215,32 @@ function handleOutput(msg) { addOutput(id, result.output); } +// src/js/websocket.ts +var exports_websocket = {}; +__export(exports_websocket, { + startConnection: () => startConnection, + send: () => send, + close: () => close +}); + +// src/js/session.ts +var exports_session = {}; +__export(exports_session, { + sessionStore: () => sessionStore, + sessionId: () => sessionId, + initSession: () => initSession, + handleSessionUpdate: () => handleSessionUpdate, + handleSessionStart: () => handleSessionStart +}); + // src/js/event.ts +var exports_event = {}; +__export(exports_event, { + once: () => once, + on: () => on, + off: () => off, + fire: () => fire +}); function fire(eventName, detail) { window.dispatchEvent(new CustomEvent(eventName, { detail })); } @@ -169,6 +249,14 @@ function on(eventName, handler) { window.addEventListener(eventName, listener); return () => window.removeEventListener(eventName, listener); } +function once(eventName, handler) { + const listener = handler; + window.addEventListener(eventName, listener, { once: true }); + return () => window.removeEventListener(eventName, listener); +} +function off(eventName, handler) { + window.removeEventListener(eventName, handler); +} // src/js/session.ts var sessionId = randomId(); @@ -186,7 +274,19 @@ function handleSessionUpdate(msg) { fire("session:update", msg.data); } +// src/js/dispatch.ts +var exports_dispatch = {}; +__export(exports_dispatch, { + dispatchMessage: () => dispatchMessage +}); + // src/js/webapp.ts +var exports_webapp = {}; +__export(exports_webapp, { + currentAppUrl: () => currentAppUrl, + cacheApps: () => cacheApps, + apps: () => apps +}); var apps = []; function cacheApps(a) { apps.length = 0; @@ -194,8 +294,23 @@ function cacheApps(a) { apps.sort(); window.dispatchEvent(new CustomEvent("apps:change")); } +function currentAppUrl() { + const project = sessionStore.get("project") || "root"; + if (!apps.includes(project)) + return; + const hostname = sessionStore.get("hostname") || "localhost"; + const s = hostname.startsWith("localhost") ? "" : "s"; + return `http${s}://${project}.${hostname}`; +} // src/js/stream.ts +var exports_stream = {}; +__export(exports_stream, { + handleStreamStart: () => handleStreamStart, + handleStreamReplace: () => handleStreamReplace, + handleStreamEnd: () => handleStreamEnd, + handleStreamAppend: () => handleStreamAppend +}); function handleStreamStart(msg) { const id = msg.id; const status = document.querySelector(`[data-id="${id}"].input .status`); @@ -213,6 +328,12 @@ function handleStreamReplace(msg) { } function handleStreamEnd(_msg) {} +// src/js/game.ts +var exports_game = {}; +__export(exports_game, { + handleGameStart: () => handleGameStart +}); + // src/shared/game.ts class GameContext { ctx; @@ -387,6 +508,12 @@ class GameContext { } // src/js/focus.ts +var exports_focus = {}; +__export(exports_focus, { + initFocus: () => initFocus, + focusInput: () => focusInput, + focusHandler: () => focusHandler +}); function initFocus() { window.addEventListener("click", focusHandler); focusInput(); @@ -609,6 +736,9 @@ async function receive(e) { console.log("<- receive", data); await dispatchMessage(data); } +function close() { + ws?.close(1000, "bye"); +} function retryConnection() { connected = false; if (retries >= MAX_RETRIES) { @@ -623,6 +753,12 @@ function retryConnection() { } // src/js/history.ts +var exports_history = {}; +__export(exports_history, { + resetHistory: () => resetHistory, + initHistory: () => initHistory, + addToHistory: () => addToHistory +}); var history = ["one", "two", "three"]; var idx = -1; var savedInput = ""; @@ -724,6 +860,10 @@ function handleCompletion(e) { } // src/js/cursor.ts +var exports_cursor = {}; +__export(exports_cursor, { + initCursor: () => initCursor +}); var cursor = "Û"; var cmdCursor; var enabled = true; @@ -761,6 +901,10 @@ function buildBlankCursorLine() { } // src/js/drop.ts +var exports_drop = {}; +__export(exports_drop, { + initDrop: () => initDrop +}); function initDrop() { ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { document.body.addEventListener(eventName, preventDefaults, false); @@ -783,6 +927,10 @@ function handleDrop(e) { } // src/js/editor.ts +var exports_editor = {}; +__export(exports_editor, { + initEditor: () => initEditor +}); var INDENT_SIZE = 2; function initEditor() { document.addEventListener("input", handleAdjustHeight); @@ -961,6 +1109,11 @@ function insertMoreIndentedNewline(editor) { } // src/js/form.ts +var exports_form = {}; +__export(exports_form, { + submitHandler: () => submitHandler, + initForm: () => initForm +}); function initForm() { document.addEventListener("submit", submitHandler); } @@ -997,6 +1150,10 @@ var submitHandler = async (e) => { }; // src/js/gamepad.ts +var exports_gamepad = {}; +__export(exports_gamepad, { + initGamepad: () => initGamepad +}); var BUTTONS_TO_KEYS = { 0: "z", 1: "x", @@ -1045,7 +1202,18 @@ function handleGamepad() { requestAnimationFrame(handleGamepad); } +// src/js/hyperlink.ts +var exports_hyperlink = {}; +__export(exports_hyperlink, { + initHyperlink: () => initHyperlink +}); + // src/js/browser.ts +var exports_browser = {}; +__export(exports_browser, { + openBrowser: () => openBrowser, + isBrowsing: () => isBrowsing +}); var HEIGHT2 = 540; var WIDTH2 = 960; var controls = $("browser-controls"); @@ -1219,6 +1387,10 @@ async function handleClick2(e) { } // src/js/input.ts +var exports_input = {}; +__export(exports_input, { + initInput: () => initInput +}); function initInput() { cmdInput.addEventListener("keydown", inputHandler); cmdInput.addEventListener("paste", pasteHandler); @@ -1270,6 +1442,11 @@ function clearInput() { } // src/js/resize.ts +var exports_resize = {}; +__export(exports_resize, { + resize: () => resize, + initResize: () => initResize +}); var content3 = document.getElementById("content"); function initResize() { window.addEventListener("resize", resize); @@ -1294,11 +1471,21 @@ function resizeCinema() { } // src/js/statusbar.ts +var exports_statusbar = {}; +__export(exports_statusbar, { + status: () => status, + projectWww: () => projectWww, + projectName: () => projectName, + projectCwd: () => projectCwd, + initStatusbar: () => initStatusbar +}); var projectName = $("project-name"); var projectCwd = $("project-cwd"); var projectWww = $("project-www"); var statusbar = $("statusbar"); var statusbarMsg = $("statusbar-msg"); +var STATUS_MSG_LENGTH = 3000; +var timer; function initStatusbar() { registerEvents(); } @@ -1313,6 +1500,19 @@ function registerEvents() { updateCwd(data.cwd); }); } +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 = ""; +} function updateProjectName(project) { projectName.textContent = project; updateWww(project); @@ -1339,9 +1539,13 @@ function displayProjectPath(path) { } // src/js/vram.ts +var exports_vram = {}; +__export(exports_vram, { + startVramCounter: () => startVramCounter +}); var vramCounter = $("vram-size"); var startVramCounter = () => { - const timer = setInterval(() => { + const timer2 = setInterval(() => { const count = parseInt(vramCounter.textContent) + 1; let val = count + "KB"; if (count < 10) @@ -1349,12 +1553,12 @@ var startVramCounter = () => { vramCounter.textContent = val; if (count >= 64) { vramCounter.textContent += " OK"; - clearInterval(timer); + clearInterval(timer2); } }, 15); }; -// src/js/main.ts +// src/js/main.tmp.ts initCompletion(); initCursor(); initDrop(); @@ -1371,3 +1575,32 @@ initSession(); initStatusbar(); startConnection(); startVramCounter(); +Object.assign(window, { + __pluto_modules: { + browser: exports_browser, + commands: exports_commands, + completion: exports_completion, + cursor: exports_cursor, + dispatch: exports_dispatch, + dom: exports_dom, + drop: exports_drop, + editor: exports_editor, + event: exports_event, + focus: exports_focus, + form: exports_form, + game: exports_game, + gamepad: exports_gamepad, + history: exports_history, + hyperlink: exports_hyperlink, + input: exports_input, + resize: exports_resize, + scrollback: exports_scrollback, + session: exports_session, + shell: exports_shell, + statusbar: exports_statusbar, + stream: exports_stream, + vram: exports_vram, + webapp: exports_webapp, + websocket: exports_websocket + } +}); diff --git a/scripts/build.sh b/scripts/build.sh index 55a3dbe..41c57cb 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,11 +2,47 @@ SHA=$(git rev-parse --short HEAD) -# Build to a temporary file -bun build ./src/js/main.js \ +# Discover all modules in src/js (excluding main.ts and temp files) +MODULES=$(ls src/js/*.ts | sed 's|src/js/||' | sed 's|.ts$||' | grep -v -E '^(main|.*\.tmp)$' | sort) + +# Create temporary exports file +echo "// Auto-generated exports for dynamically loaded browser commands" > ./src/js/exports.tmp.ts + +# Generate import statements +for mod in $MODULES; do + echo "import * as $mod from \"./$mod\"" >> ./src/js/exports.tmp.ts +done + +echo "" >> ./src/js/exports.tmp.ts +echo "Object.assign(window, {" >> ./src/js/exports.tmp.ts +echo " __pluto_modules: {" >> ./src/js/exports.tmp.ts + +# Generate module list with proper formatting +FIRST=true +for mod in $MODULES; do + if [ "$FIRST" = true ]; then + printf " %s" "$mod" >> ./src/js/exports.tmp.ts + FIRST=false + else + printf ", %s" "$mod" >> ./src/js/exports.tmp.ts + fi +done +printf ",\n" >> ./src/js/exports.tmp.ts + +echo " }" >> ./src/js/exports.tmp.ts +echo "})" >> ./src/js/exports.tmp.ts + +# Create temporary main.ts that includes exports +cat ./src/js/main.ts ./src/js/exports.tmp.ts > ./src/js/main.tmp.ts + +# Build from temporary main +bun build ./src/js/main.tmp.ts \ --outfile ./public/bundle.tmp.js \ --target browser +# Clean up temporary files +rm ./src/js/exports.tmp.ts ./src/js/main.tmp.ts + # If bundle.js doesn't exist yet, fake it it if [ ! -f ./public/bundle.js ]; then touch ./public/bundle.js diff --git a/src/build.ts b/src/build.ts new file mode 100644 index 0000000..6b6a30d --- /dev/null +++ b/src/build.ts @@ -0,0 +1,12 @@ +//// +// build process helpers + +export function rewriteJsImports(code: string): string { + if (process.env.NODE_ENV === "production") { + return code + .replace(/import\s+\*\s+as\s+(\w+)\s+from\s+["']@\/js\/\w+["']/g, 'const $1 = window.__pluto_modules.$1') + .replace(/import\s+{([^}]+)}\s+from\s+["']@\/js\/(\w+)["']/g, 'const {$1} = window.__pluto_modules.$2') + } else { + return code.replace(/from (["'])@\/js\//g, "from $1../js/") + } +} \ No newline at end of file diff --git a/src/server.tsx b/src/server.tsx index b2ed009..f2780e0 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -7,6 +7,7 @@ import { prettyJSON } from "hono/pretty-json" import color from "kleur" import type { Message } from "./shared/types" +import { rewriteJsImports } from "./build" import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN, DEFAULT_PROJECT } from "./config" import { transpile, isFile, tilde, isDir } from "./utils" import { serveApp, apps, initWebapps } from "./webapp" @@ -134,11 +135,7 @@ app.get("/source/:name", async c => { const path = await sessionRun(sessionId, () => commandPath(name)) if (!path) return c.text("Command not found", 404) - let code = await transpile(path) - // rewrite imports so they work on the server and in the browser - code = code.replace(/from (["'])@\/js\//g, "from $1../js/") - - return new Response(code, { + return new Response(rewriteJsImports(await transpile(path)), { headers: { "Content-Type": "text/javascript" }