Spaces:
Running
Running
| import { CONFIG } from "./config.js"; | |
| import { fetchDetail } from "./api.js"; | |
| const ALLOW = "accelerometer; autoplay; camera; microphone; clipboard-read; clipboard-write; encrypted-media; geolocation; gyroscope; midi"; | |
| const SANDBOX = "allow-scripts allow-same-origin allow-forms allow-popups allow-downloads allow-modals"; | |
| function el(tag, cls, html) { | |
| const e = document.createElement(tag); | |
| if (cls) e.className = cls; | |
| if (html != null) e.innerHTML = html; | |
| return e; | |
| } | |
| export function buildIframe(vm) { | |
| const f = document.createElement("iframe"); | |
| f.src = vm.embedUrl; | |
| f.title = vm.title; | |
| f.loading = "eager"; | |
| f.setAttribute("allow", ALLOW); | |
| f.setAttribute("sandbox", SANDBOX); | |
| f.referrerPolicy = "no-referrer-when-downgrade"; | |
| return f; | |
| } | |
| function fallbackCard(vm, message, { onSkip, countdownMs }) { | |
| const box = el("div", "fallback"); | |
| box.append(el("div", "fallback-emoji", vm.emoji)); | |
| box.append(el("strong", null, vm.title)); | |
| box.append(el("p", null, message)); | |
| const open = el("a", "btn"); | |
| open.textContent = "↗ Open on HF"; | |
| open.href = vm.hfUrl; open.target = "_blank"; open.rel = "noopener"; | |
| const skip = el("button", "btn btn-ghost"); | |
| skip.textContent = "Skip →"; | |
| skip.onclick = onSkip; | |
| const actions = el("div", "fallback-actions"); | |
| actions.append(open, skip); | |
| box.append(actions); | |
| let timer = null; | |
| if (countdownMs && onSkip) timer = setTimeout(onSkip, countdownMs); | |
| box._cleanup = () => timer && clearTimeout(timer); | |
| return box; | |
| } | |
| /** | |
| * Mounts a card into `stage`. Returns a controller with destroy(). | |
| * onState(state) receives "loading" | "waking" | "ready" | "broken". | |
| */ | |
| export function mountCard(stage, vm, { onState, onSkip } = {}) { | |
| stage.innerHTML = ""; | |
| const loader = el("div", "loader", "Loading the demo…"); | |
| stage.append(loader); | |
| onState?.("loading"); | |
| let destroyed = false; | |
| let iframe = null; | |
| let timeout = null; | |
| let fb = null; | |
| const showBroken = (msg) => { | |
| if (destroyed) return; | |
| iframe?.remove(); | |
| loader.remove(); | |
| fb?._cleanup?.(); | |
| fb = fallbackCard(vm, msg, { onSkip, countdownMs: CONFIG.autoAdvanceMs }); | |
| stage.append(fb); | |
| onState?.("broken"); | |
| }; | |
| const embed = (waking) => { | |
| if (destroyed) return; | |
| if (waking) { loader.textContent = "Waking this Space up…"; onState?.("waking"); } | |
| iframe = buildIframe(vm); | |
| iframe.addEventListener("load", () => { | |
| if (destroyed) return; | |
| clearTimeout(timeout); | |
| iframe.classList.add("ready"); // cross-fade in | |
| loader.remove(); | |
| onState?.("ready"); | |
| }); | |
| stage.append(iframe); | |
| timeout = setTimeout( | |
| () => showBroken("This one didn't wake in time. Open it on Hugging Face, or skip ahead."), | |
| CONFIG.loadTimeoutMs | |
| ); | |
| }; | |
| if (CONFIG.detailFetch) { | |
| fetchDetail(vm.id).then((d) => { | |
| if (destroyed) return; | |
| if (d.gated || d.disabled || CONFIG.skipUnhealthyStages.includes(d.stage)) { | |
| showBroken("This one isn't running right now — catch it on Hugging Face."); | |
| } else { | |
| embed(d.stage === "SLEEPING"); | |
| } | |
| }).catch(() => embed(false)); // detail failed — just try embedding | |
| } else { | |
| embed(false); | |
| } | |
| return { | |
| el: () => iframe, | |
| destroy() { destroyed = true; clearTimeout(timeout); fb?._cleanup?.(); iframe?.remove(); }, | |
| }; | |
| } | |