Files
effectcraft/apps/effectcraft-web/web/index.html
T
Brandon ThomasandClaude Opus 5.5 31f192e2a9 M15.1: web build (eframe wasm) + xtask web
- apps/effectcraft-web (L6): eframe web runner booting the shared EffectcraftApp with a browser
  session: in-memory file table, <input type=file> pickers and drag-and-drop for open/import,
  downloads for Save and Render Queue outputs (several files as one stored .zip), and the
  window.effectcraft promise API over the control channel. Demo project on start.
- wasm32 runtime fixes: web-time for Instant/SystemTime (render, engine, export, ui-egui);
  Frames renders queued jobs on the UI thread (Frames::pump) instead of a thread pool;
  Render Queue runs inline; relative outputs resolve under / ; control PNGs inline.
- effectcraft-export: per-job output Sink (files to memory instead of disk); host FileExporter
  carries it; test that sink exports touch no file.
- xtask web [--dev] [--serve PORT] (wasm-bindgen 0.2.129, optional wasm-opt, COOP/COEP server);
  effectcraft-web in WEB_CRATES. docs/web.md, headless Chrome smoke test, favicon (+attribution).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 00:19:35 -04:00

65 lines
2.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="dark">
<title>EffectCraft</title>
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<style>
html, body { margin: 0; height: 100%; overflow: hidden; background: #1b1c1f; }
canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; outline: none; }
#loading {
position: absolute; inset: 0; display: flex; flex-direction: column; gap: 14px;
align-items: center; justify-content: center; color: #c9c9cc;
font: 14px/1.4 system-ui, sans-serif; pointer-events: none;
}
#loading img { width: 56px; height: 56px; }
#loading .err { color: #ff8a7a; white-space: pre-wrap; max-width: 70ch; pointer-events: auto; }
</style>
</head>
<body>
<canvas id="effectcraft_canvas"></canvas>
<div id="loading"><img src="favicon.svg" alt=""><div id="status">Loading EffectCraft…</div></div>
<script type="module">
import init, * as ec from "./effectcraft_web.js";
const t0 = performance.now();
window.effectcraftLoad = {};
// The agent / test API: the desktop control channel as promises (docs/web.md).
window.effectcraft = {
request: (method, params) => ec.request(method, params ?? {}),
execute: (command, params) => ec.request("engine.execute", { command, params: params ?? {} }),
commands: () => ec.request("engine.commands", {}),
inspect: () => ec.request("ui.inspect", {}),
screenshot: (params) => ec.request("ui.screenshot", params ?? {}),
renderFrame: (params) => ec.request("render.frame", { base64: true, ...(params ?? {}) }),
files: () => ec.files(),
readFile: (path) => ec.readFile(path),
addFile: async (fileOrUrl, name) => {
let bytes;
if (typeof fileOrUrl === "string") {
bytes = new Uint8Array(await (await fetch(fileOrUrl)).arrayBuffer());
name = name ?? fileOrUrl.split("/").pop();
} else {
bytes = new Uint8Array(await fileOrUrl.arrayBuffer());
name = name ?? fileOrUrl.name;
}
return ec.addFile(name, bytes);
},
info: () => ({ ...ec.info(), crossOriginIsolated: self.crossOriginIsolated, load: window.effectcraftLoad }),
};
try {
await init();
window.effectcraftLoad.wasmMs = performance.now() - t0;
await ec.start("effectcraft_canvas");
window.effectcraftLoad.readyMs = performance.now() - t0;
} catch (e) {
const s = document.getElementById("status");
if (s) { s.className = "err"; s.textContent = "EffectCraft failed to start:\n" + (e && e.stack || e); }
window.effectcraftLoad.error = String(e);
throw e;
}
</script>
</body>
</html>