← Commit history

viewers: one srcdoc iframe per board (viewer engine is a singleton per document)

Caleb ·899faaddf9 ·2mo ago ·parent 095f270
1 file changed +77−68
README.html+77−68
@@ -4,8 +4,6 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Base Scaffold Control Panel</title>-<link rel="stylesheet" href="/static/vendor/adom-3d-viewer-babylon9/style.css">-<script type="module" src="/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260717e"></script> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body {@@ -33,7 +31,7 @@ img { max-width: 100%; border-radius: 6px; display: block; margin: 0 0 16px; }   border-radius: 8px; overflow: hidden; background: #0d1117;   border: 1px solid #21262d; }-.viewer-slot .mount { position: absolute; inset: 0; }+.viewer-slot .vframe { position: absolute; inset: 0; width: 100%; height: 100%; border: none; } .viewer-slot .fallback { position: absolute; inset: 0; object-fit: contain; width: 100%; height: 100%; margin: 0; } .viewer-slot .vctl {   position: absolute; left: 12px; bottom: 12px; z-index: 10; display: none; gap: 6px;@@ -67,27 +65,87 @@ img { max-width: 100%; border-radius: 6px; display: block; margin: 0 0 16px; }  <h3>Workcell side</h3> -<div class="viewer-slot" data-glb="/api/pages/adom/base-scaffold-control-panel/files/models/workcell-q.glb"-     data-img="/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-workcell.png">-  <img class="fallback" src="/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-workcell.png" alt="Control panel — workcell side">-  <div class="mount"></div>-  <div class="vctl">-    <button type="button" data-act="ground">Ground</button>-    <button type="button" data-act="light">Bottom light</button>-  </div>+<div class="viewer-slot">+  <iframe class="vframe" srcdoc="&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;+&lt;link rel=&quot;stylesheet&quot; href=&quot;https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/style.css&quot;&gt;+&lt;script type=&quot;module&quot; src=&quot;https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260717e&quot;&gt;&lt;/script&gt;+&lt;style&gt;+* { margin:0; padding:0; box-sizing:border-box }+html,body { width:100%; height:100%; overflow:hidden; background:#0d1117 }+#wrap { position:absolute; inset:0 }+#fb { position:absolute; inset:0; width:100%; height:100%; object-fit:contain }+#ctl { position:absolute; left:12px; bottom:12px; z-index:10; display:none; gap:6px; font-family:-apple-system,BlinkMacSystemFont,&#x27;Segoe UI&#x27;,sans-serif }+#ctl button { background:rgba(22,27,34,.85); border:1px solid rgba(240,246,252,.12); color:#8b949e; font-size:12px; line-height:1; padding:7px 11px; border-radius:14px; cursor:pointer }+#ctl button.on { color:#e6edf3; border-color:#2f81f7; background:rgba(31,111,235,.18) }+&lt;/style&gt;&lt;/head&gt;&lt;body&gt;+&lt;img id=&quot;fb&quot; src=&quot;https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-workcell.png&quot; alt=&quot;board render&quot;&gt;+&lt;div id=&quot;wrap&quot;&gt;&lt;/div&gt;+&lt;div id=&quot;ctl&quot;&gt;&lt;button id=&quot;g&quot; type=&quot;button&quot;&gt;Ground&lt;/button&gt;&lt;button id=&quot;l&quot; type=&quot;button&quot;&gt;Bottom light&lt;/button&gt;&lt;/div&gt;+&lt;script type=&quot;module&quot;&gt;+const V = window.Adom3DViewerBabylon9;+const wrap = document.getElementById(&#x27;wrap&#x27;);+if (typeof V === &#x27;undefined&#x27;) { wrap.remove(); }+else {+  try {+    const viewer = V.init(wrap, { zUp: true, showViewCube: true, showGround: true });+    viewer.loadModel(&#x27;https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/models/workcell-q.glb&#x27;).then(() =&gt; {+      viewer.frameModel();+      try { if (viewer.toggleAxes) viewer.toggleAxes(&#x27;world&#x27;, false); } catch (e) {}+      document.getElementById(&#x27;fb&#x27;).remove();+      const ctl = document.getElementById(&#x27;ctl&#x27;);+      const g = document.getElementById(&#x27;g&#x27;), l = document.getElementById(&#x27;l&#x27;);+      let groundOn = true;+      const paint = () =&gt; { g.classList.toggle(&#x27;on&#x27;, groundOn); l.classList.toggle(&#x27;on&#x27;, viewer.getBottomLightState()); };+      g.onclick = () =&gt; { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); };+      l.onclick = () =&gt; { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); };+      paint(); ctl.style.display = &#x27;flex&#x27;;+    }).catch(() =&gt; wrap.remove());+  } catch (e) { wrap.remove(); }+}+&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;" loading="lazy" title="3D viewer"></iframe> </div> <p class="viewer-note">Drag to orbit &middot; wheel to zoom &middot; right-drag to pan &middot; the cube snaps the view.</p>  <h3>Scaffold side</h3> -<div class="viewer-slot" data-glb="/api/pages/adom/base-scaffold-control-panel/files/models/scaffold-q.glb"-     data-img="/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-scaffold.png">-  <img class="fallback" src="/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-scaffold.png" alt="Control panel — scaffold side">-  <div class="mount"></div>-  <div class="vctl">-    <button type="button" data-act="ground">Ground</button>-    <button type="button" data-act="light">Bottom light</button>-  </div>+<div class="viewer-slot">+  <iframe class="vframe" srcdoc="&lt;!doctype html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;+&lt;link rel=&quot;stylesheet&quot; href=&quot;https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/style.css&quot;&gt;+&lt;script type=&quot;module&quot; src=&quot;https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js?v=20260717e&quot;&gt;&lt;/script&gt;+&lt;style&gt;+* { margin:0; padding:0; box-sizing:border-box }+html,body { width:100%; height:100%; overflow:hidden; background:#0d1117 }+#wrap { position:absolute; inset:0 }+#fb { position:absolute; inset:0; width:100%; height:100%; object-fit:contain }+#ctl { position:absolute; left:12px; bottom:12px; z-index:10; display:none; gap:6px; font-family:-apple-system,BlinkMacSystemFont,&#x27;Segoe UI&#x27;,sans-serif }+#ctl button { background:rgba(22,27,34,.85); border:1px solid rgba(240,246,252,.12); color:#8b949e; font-size:12px; line-height:1; padding:7px 11px; border-radius:14px; cursor:pointer }+#ctl button.on { color:#e6edf3; border-color:#2f81f7; background:rgba(31,111,235,.18) }+&lt;/style&gt;&lt;/head&gt;&lt;body&gt;+&lt;img id=&quot;fb&quot; src=&quot;https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/diagrams/viewer-scaffold.png&quot; alt=&quot;board render&quot;&gt;+&lt;div id=&quot;wrap&quot;&gt;&lt;/div&gt;+&lt;div id=&quot;ctl&quot;&gt;&lt;button id=&quot;g&quot; type=&quot;button&quot;&gt;Ground&lt;/button&gt;&lt;button id=&quot;l&quot; type=&quot;button&quot;&gt;Bottom light&lt;/button&gt;&lt;/div&gt;+&lt;script type=&quot;module&quot;&gt;+const V = window.Adom3DViewerBabylon9;+const wrap = document.getElementById(&#x27;wrap&#x27;);+if (typeof V === &#x27;undefined&#x27;) { wrap.remove(); }+else {+  try {+    const viewer = V.init(wrap, { zUp: true, showViewCube: true, showGround: true });+    viewer.loadModel(&#x27;https://wiki.adom.inc/api/pages/adom/base-scaffold-control-panel/files/models/scaffold-q.glb&#x27;).then(() =&gt; {+      viewer.frameModel();+      try { if (viewer.toggleAxes) viewer.toggleAxes(&#x27;world&#x27;, false); } catch (e) {}+      document.getElementById(&#x27;fb&#x27;).remove();+      const ctl = document.getElementById(&#x27;ctl&#x27;);+      const g = document.getElementById(&#x27;g&#x27;), l = document.getElementById(&#x27;l&#x27;);+      let groundOn = true;+      const paint = () =&gt; { g.classList.toggle(&#x27;on&#x27;, groundOn); l.classList.toggle(&#x27;on&#x27;, viewer.getBottomLightState()); };+      g.onclick = () =&gt; { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); };+      l.onclick = () =&gt; { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); };+      paint(); ctl.style.display = &#x27;flex&#x27;;+    }).catch(() =&gt; wrap.remove());+  } catch (e) { wrap.remove(); }+}+&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;" loading="lazy" title="3D viewer"></iframe> </div> <p class="viewer-note">Drag to orbit &middot; wheel to zoom &middot; right-drag to pan &middot; the cube snaps the view.</p> @@ -194,54 +252,5 @@ img { max-width: 100%; border-radius: 6px; display: block; margin: 0 0 16px; }  </div> -<script type="module">-// Mount Colby's Babylon viewer (the wiki's own 3D viewer bundle) on each slot.-// Load chain per slot: draco GLB -> quantized GLB (no wasm needed) -> static image.-const Viewer = window.Adom3DViewerBabylon9;-try {-  if (window.BABYLON && window.BABYLON.DracoCompression) {-    window.BABYLON.DracoCompression.Configuration = { decoder: {-      wasmUrl: "/static/vendor/adom-3d-viewer-babylon9/draco/draco_wasm_wrapper_gltf.js",-      wasmBinaryUrl: "/static/vendor/adom-3d-viewer-babylon9/draco/draco_decoder_gltf.wasm",-      fallbackUrl: "/static/vendor/adom-3d-viewer-babylon9/draco/draco_decoder_gltf.js",-    } };-  }-} catch (e) {}--document.querySelectorAll('.viewer-slot').forEach((slot) => {-  const mount = slot.querySelector('.mount');-  const fallbackImg = slot.querySelector('.fallback');-  const ctl = slot.querySelector('.vctl');-  function showStatic() {-    mount.remove();-    if (ctl) ctl.remove();-  }-  if (typeof Viewer === 'undefined') { showStatic(); return; }-  let viewer;-  try {-    viewer = Viewer.init(mount, { zUp: true, showViewCube: true, showGround: true });-  } catch (e) { showStatic(); return; }-  const tryLoad = (url) => viewer.loadModel(url).then(() => {-    viewer.frameModel();-    try { if (viewer.toggleAxes) viewer.toggleAxes('world', false); } catch (e) {}-    fallbackImg.remove();-    // Ground + bottom light toggles, matching the component-page viewer chrome.-    try {-      let groundOn = true;-      const gBtn = ctl.querySelector('[data-act="ground"]');-      const lBtn = ctl.querySelector('[data-act="light"]');-      const paint = () => {-        gBtn.classList.toggle('on', groundOn);-        lBtn.classList.toggle('on', viewer.getBottomLightState());-      };-      gBtn.addEventListener('click', () => { groundOn = !groundOn; viewer.setGroundVisible(groundOn); paint(); });-      lBtn.addEventListener('click', () => { viewer.setBottomLight(!viewer.getBottomLightState()); paint(); });-      paint();-      ctl.style.display = 'flex';-    } catch (e) {}-  });-  tryLoad(slot.dataset.glb).catch(() => showStatic());-});-</script> </body> </html>