import { useEffect, useMemo, useState, type CSSProperties } from "react"; import { SurfView } from "./render/SurfView.js"; import type { SurfTrace } from "./render/types.js"; import { onData, onHostEvent, sendToMATLAB } from "./bridge.js"; /** Mesh payload from the numbl script: one flat (column-major) x/y/z array per * patch, each an `n x n` grid. Mirrors what refine_demo.m sends. */ interface MeshData { n: number; x: number[][]; y: number[][]; z: number[][]; npatches: number; level?: number; maxLevel?: number; } function isMeshData(d: unknown): d is MeshData { return ( !!d && typeof d === "object" && Array.isArray((d as MeshData).x) && typeof (d as MeshData).n === "number" ); } export function App() { const [mesh, setMesh] = useState(null); const [level, setLevel] = useState(0); const [busy, setBusy] = useState(false); // Initial mesh arrives via Data; refinements arrive via "mesh" events // (which update React state without remounting the iframe, so the camera / // orientation is preserved across refinements). useEffect(() => { const apply = (d: unknown) => { if (!isMeshData(d)) return; setMesh(d); if (typeof d.level === "number") setLevel(d.level); setBusy(false); }; const offData = onData(apply); const offMesh = onHostEvent("mesh", apply); return () => { offData(); offMesh(); }; }, []); const traces = useMemo(() => { if (!mesh) return []; const out: SurfTrace[] = []; for (let k = 0; k < mesh.x.length; k++) { out.push({ x: mesh.x[k], y: mesh.y[k], z: mesh.z[k], rows: mesh.n, cols: mesh.n, }); } return out; }, [mesh]); const maxLevel = mesh?.maxLevel ?? 3; const onSlider = (v: number) => { setLevel(v); setBusy(true); sendToMATLAB("refine", v); }; return (
{mesh ? ( ) : (
Waiting for mesh from the script…
)}
surfacefun mesh
patches: {mesh?.npatches ?? "—"} {busy ? " · refining…" : ""}
drag to rotate · scroll to zoom
); } const rootStyle: CSSProperties = { position: "absolute", inset: 0, background: "#ffffff", fontFamily: "system-ui, -apple-system, Arial, sans-serif", }; const waitingStyle: CSSProperties = { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", color: "#94a3b8", }; const panelStyle: CSSProperties = { position: "absolute", top: 12, left: 12, width: 220, padding: "12px 14px", background: "rgba(255,255,255,0.92)", border: "1px solid #e2e8f0", borderRadius: 8, boxShadow: "0 1px 4px rgba(0,0,0,0.1)", color: "#0f172a", };