← Commit history

Delete fusion-360/widget-test.html

John Lauer ·a1d3324542 ·10d ago ·parent 9a12cbd
1 file changed −129
fusion-360/widget-test.htmldeleted−129
@@ -1,129 +0,0 @@-<!doctype html><html><head><meta charset="utf-8"></head><body>-<adom-3d src="/john/buck-12v5v-molecule/render/fusion-360/buck-fusion-360.glb" poster="/john/buck-12v5v-molecule/render/fusion-360/img/3d-poster.jpg" heading="The finished board, exported from Fusion 360" caption="test"></adom-3d>-<script>/*!- * <adom-3d> - an interactive 3D view of any GLB on a wiki page, in the canonical Adom 3D Viewer.- *- * Drop-in (wiki README):- *   ::adom-3d{src="/OWNER/SLUG/render/board.glb" poster="/OWNER/SLUG/render/board.jpg" heading="The finished board"}- *- * - src: the GLB (absolute path on the wiki, usually the page's own render route). Publish it WITHOUT Draco:- *   the widget frame's connect-src does not reach Babylon's decoder CDN, so a Draco GLB cannot decode here.- * - poster: optional still shown until the reader asks for 3D (a render or screenshot of the same model).- * - heading: optional title over the view; caption: optional line under it.- *- * Nothing heavy loads on the page path (adom/wiki#1292): the wiki puts every widget in a click-to-load slot, so- * this frame only exists once the reader asked for it, and the model then loads straight away (one click, not- * two). Unload, scrolling it well offscreen or leaving the page disposes the- * viewer and releases the WebGL context; Load 3D model brings it back. The model is fetched as bytes and handed to loadModelBytes, and the- * GLB's embedded textures are given out as data: URLs, because the frame's img-src forbids blob:.- */-(() => {-  const VIEWER = 'https://wiki.adom.inc/static/vendor/adom-3d-viewer-babylon9/adom-3d-viewer-babylon9.esm.js';-  const esc = s => String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));--  // The glTF loader turns embedded images into blob: URLs, which this frame's CSP blocks. Remember each Blob's-  // bytes and hand out an equivalent data: URL instead. Installed once, only when a reader loads a model.-  let shimmed = false;-  function shimBlobUrls() {-    if (shimmed) return; shimmed = true;-    const NativeBlob = window.Blob;-    function TrackedBlob(parts, opts) { const b = new NativeBlob(parts || [], opts); try { b.__parts = parts; b.__type = (opts && opts.type) || ''; } catch (e) {} return b; }-    TrackedBlob.prototype = NativeBlob.prototype; Object.setPrototypeOf(TrackedBlob, NativeBlob); window.Blob = TrackedBlob;-    const create = URL.createObjectURL.bind(URL), revoke = URL.revokeObjectURL.bind(URL);-    URL.createObjectURL = o => {-      if (!o || !o.__parts) return create(o);-      let s = '';-      for (const p of o.__parts) {-        const u = typeof p === 'string' ? new TextEncoder().encode(p) : p instanceof ArrayBuffer ? new Uint8Array(p) : ArrayBuffer.isView(p) ? new Uint8Array(p.buffer, p.byteOffset, p.byteLength) : new Uint8Array(0);-        for (let i = 0; i < u.length; i += 0x8000) s += String.fromCharCode.apply(null, u.subarray(i, i + 0x8000));-      }-      return 'data:' + (o.__type || 'application/octet-stream') + ';base64,' + btoa(s);-    };-    URL.revokeObjectURL = u => { if (typeof u === 'string' && u.startsWith('blob:')) revoke(u); };-  }-  // the frame is an opaque origin: storage throws, and the viewer reads its preferences on init-  function shimStorage() {-    try { window.localStorage; } catch (e) {-      let m = {};-      Object.defineProperty(window, 'localStorage', { value: { getItem: k => (k in m ? m[k] : null), setItem: (k, v) => { m[k] = String(v); }, removeItem: k => { delete m[k]; }, clear: () => { m = {}; } } });-    }-    window.process = window.process || { env: { NODE_ENV: 'production' } };-    window.global = window.global || window;-  }--  class Adom3D extends HTMLElement {-    connectedCallback() {-      if (this._init) return; this._init = true;-      this.attachShadow({ mode: 'open' });-      this._src = this.getAttribute('src');-      this._poster = this.getAttribute('poster');-      this._idle();-      this._io = new IntersectionObserver(es => {-        for (const e of es) if (!e.isIntersecting && this._viewer && !this._loading) this._unload('offscreen');-      }, { rootMargin: '600px 0px' });-      this._io.observe(this);-      if (this._src) this._load();   // the wiki slot already was the reader's click-    }-    disconnectedCallback() { this._io && this._io.disconnect(); this._unload('removed'); }--    _frame(stage, foot) {-      const h = this.getAttribute('heading'), c = this.getAttribute('caption');-      this.shadowRoot.innerHTML = `<style>-        :host { display: block; font: 13px/1.5 Satoshi, -apple-system, 'Segoe UI', sans-serif; color: #e6edf3; }-        .card { background: #0d1117; border: 1px solid #30363d; border-radius: 10px; overflow: hidden; }-        .h { padding: 10px 14px; font: 600 14px/1.3 'Familjen Grotesk', -apple-system, sans-serif; border-bottom: 1px solid #30363d; }-        .stage { position: relative; aspect-ratio: 16 / 9; min-height: 320px; background: linear-gradient(180deg, #1e2a3d 0%, #1e2a3d 20%, #0a121f 100%); }-        .stage img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }-        .over { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }-        button { font: 600 14px/1 inherit; padding: 11px 18px; border-radius: 8px; border: 1px solid #00b8b1; background: rgba(13, 17, 23, 0.82); color: #00b8b1; cursor: pointer; transition: background .12s ease, color .12s ease, transform .06s ease; }-        button:hover { background: #00b8b1; color: #0d1117; }-        button:active { transform: translateY(1px) scale(.98); }-        button:focus-visible { outline: 2px solid #00b8b1; outline-offset: 3px; }-        button:disabled { opacity: .7; cursor: progress; }-        .foot { display: flex; align-items: center; gap: 10px; padding: 8px 14px; border-top: 1px solid #30363d; color: #8b949e; font-size: 12px; }-        .foot .sp { flex: 1; }-        .foot button { font-size: 12px; padding: 5px 10px; }-        .err { color: #f85149; }-        ::slotted([slot=v]) { position: absolute; inset: 0; }-      </style>-      <div class="card">${h ? `<div class="h">${esc(h)}</div>` : ''}<div class="stage">${stage}</div>-      <div class="foot"><span>${esc(c || '')}</span><span class="sp"></span>${foot || ''}</div></div>`;-    }-    _idle(note) {-      this._frame(`${this._poster ? `<img src="${esc(new URL(this._poster, location.href).href)}" alt="${esc(this.getAttribute('heading') || '3D model')}" loading="lazy">` : ''}-        <div class="over"><button id="go" type="button">Load 3D model</button></div>`, note ? `<span class="err">${esc(note)}</span>` : '');-      const go = this.shadowRoot.getElementById('go');-      if (!this._src) { go.disabled = true; go.textContent = 'No src: point me at a GLB'; return; }-      go.addEventListener('click', () => this._load());-    }-    async _load() {-      const go = this.shadowRoot.getElementById('go'); go.disabled = true; go.textContent = 'Loading...';-      this._loading = true;-      try {-        shimStorage(); shimBlobUrls();-        const res = await fetch(new URL(this._src, location.href).href);-        if (!res.ok) throw new Error('could not fetch the model (' + res.status + ')');-        const bytes = new Uint8Array(await res.arrayBuffer());-        await import(VIEWER);-        this._frame('<slot name="v"></slot>', '<button id="off" type="button">Unload</button>');-        this._host = document.createElement('div'); this._host.slot = 'v'; this.appendChild(this._host);-        this._viewer = window.Adom3DViewerBabylon9.init(this._host, { zUp: true, showViewCube: true });-        await this._viewer.loadModelBytes(bytes, { extension: '.glb' });-        this._viewer.frameModel();-        this.shadowRoot.getElementById('off').addEventListener('click', () => this._unload());-        this.dispatchEvent(new CustomEvent('modelready', { detail: { bytes: bytes.length }, bubbles: true, composed: true }));-      } catch (e) {-        console.warn(e); this._unload(); this._idle(String(e && e.message || e).slice(0, 160));-      } finally { this._loading = false; }-    }-    _unload(why) {-      if (this._viewer) { try { this._viewer.destroy(); } catch (e) {} this._viewer = null; }-      if (this._host) { this._host.remove(); this._host = null; }-      if (why !== 'removed' && this.shadowRoot && !this.shadowRoot.getElementById('go')) this._idle();-    }-    // for AIs and tests: what the widget is showing-    selfCheck() { return { src: this._src, loaded: !!this._viewer, report: this._viewer && this._viewer.getReadyReport ? this._viewer.getReadyReport() : null }; }-  }-  if (!customElements.get('adom-3d')) customElements.define('adom-3d', Adom3D);-})();-</script></body></html>\ No newline at end of file