// import { panicProxy } from "@graphite/utility-functions/panic-proxy"; import { type JsMessageType } from "@graphite/messages"; import { createSubscriptionRouter, type SubscriptionRouter } from "@graphite/subscription-router"; import init, { setRandomSeed, wasmMemory, EditorHandle } from "@graphite-frontend/wasm/pkg/graphite_wasm.js"; export type Editor = { raw: WebAssembly.Memory; handle: EditorHandle; subscriptions: SubscriptionRouter; }; // `wasmImport` starts uninitialized because its initialization needs to occur asynchronously, and thus needs to occur by manually calling and awaiting `initWasm()` let wasmImport: WebAssembly.Memory | undefined; // Should be called asynchronously before `createEditor()`. export async function initWasm() { // Skip if the WASM module is already initialized if (wasmImport !== undefined) return; // Import the WASM module JS bindings and wrap them in the panic proxy // eslint-disable-next-line import/no-cycle const wasm = await init(); for (const [name, f] of Object.entries(wasm)) { if (name.startsWith("__node_registry")) f(); } wasmImport = await wasmMemory(); // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).imageCanvases = {}; // Provide a random starter seed which must occur after initializing the WASM module, since WASM can't generate its own random numbers const randomSeedFloat = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER); const randomSeed = BigInt(randomSeedFloat); setRandomSeed(randomSeed); } // Should be called after running `initWasm()` and its promise resolving. export function createEditor(): Editor { // Raw: object containing several callable functions from `editor_api.rs` defined directly on the WASM module, not the `EditorHandle` struct (generated by wasm-bindgen) if (!wasmImport) throw new Error("Editor WASM backend was not initialized at application startup"); const raw: WebAssembly.Memory = wasmImport; // Handle: object containing many functions from `editor_api.rs` that are part of the `EditorHandle` struct (generated by wasm-bindgen) const handle: EditorHandle = new EditorHandle((messageType: JsMessageType, messageData: Record) => { // This callback is called by WASM when a FrontendMessage is received from the WASM wrapper `EditorHandle` // We pass along the first two arguments then add our own `raw` and `handle` context for the last two arguments subscriptions.handleJsMessage(messageType, messageData, raw, handle); }); // Subscriptions: allows subscribing to messages in JS that are sent from the WASM backend const subscriptions: SubscriptionRouter = createSubscriptionRouter(); // Check if the URL hash fragment has any demo artwork to be loaded (async () => { const demoArtwork = window.location.hash.trim().match(/#demo\/(.*)/)?.[1]; if (!demoArtwork) return; try { const url = new URL(`/${demoArtwork}.graphite`, document.location.href); const data = await fetch(url); if (!data.ok) throw new Error(); const filename = url.pathname.split("/").pop() || "Untitled"; const content = await data.text(); handle.openDocumentFile(filename, content); // Remove the hash fragment from the URL history.replaceState("", "", `${window.location.pathname}${window.location.search}`); } catch { // Do nothing } })(); return { raw, handle, subscriptions }; }