Spaces:
Running
Running
File size: 3,343 Bytes
653b61d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 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(); },
};
}
|