import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; import { Icon } from "./icons"; import { hold } from "./scroll"; import { mode, useMode } from "./theme"; const SPIN_AFTER = 400; const GIVE_UP = 60000; type State = "starting" | "slow" | "ready" | "failed"; /** One story live from the gallery, in the site's mode. */ export function Live({ page, story, title }: { page: string; story: string; title: string }) { const theme = useMode(); const frame = useRef(null); const [state, setState] = useState("starting"); const [full, setFull] = useState(false); const [run, setRun] = useState(0); const after = useRef(null); // Each new frame opens in the current mode. const start = useMemo(() => mode(), [run]); const src = (t: string) => `/gallery/?page=${page}&story=${story}&theme=${t}`; useEffect(() => { setState("starting"); const slow = window.setTimeout(() => setState((s) => (s === "starting" ? "slow" : s)), SPIN_AFTER); const failed = window.setTimeout(() => { setState((s) => (s === "ready" ? s : "failed")); console.error(`live: ${page}/${story} sent no ready in ${GIVE_UP} ms`); }, GIVE_UP); const ready = (event: MessageEvent) => { if (event.source !== frame.current?.contentWindow) return; if (event.data?.ely === "failed") { clearTimeout(slow); clearTimeout(failed); setState("failed"); console.error(`live: ${page}/${story} failed to start: ${event.data.error}`); return; } if (event.data?.ely !== "ready") return; clearTimeout(slow); clearTimeout(failed); setState("ready"); frame.current?.contentWindow?.postMessage({ ely: "theme", theme: mode() }, location.origin); }; addEventListener("message", ready); return () => { clearTimeout(slow); clearTimeout(failed); removeEventListener("message", ready); }; }, [page, story, run]); useEffect(() => { frame.current?.contentWindow?.postMessage({ ely: "theme", theme }, location.origin); }, [theme]); // Before paint, so no glide frame lands after the press. useLayoutEffect(() => { if (!full) return; document.documentElement.style.overflow = "hidden"; hold(true); const escape = (event: KeyboardEvent) => event.key === "Escape" && setFull(false); addEventListener("keydown", escape); return () => { document.documentElement.style.overflow = ""; hold(false); removeEventListener("keydown", escape); }; }, [full]); return ( <>