import { useCallback, useEffect, useRef, useState, type ReactNode } from "react"; import { requestDisplaySurface, subscribeDisplaySurface, type DisplaySurface } from "../platform/displayRouting"; import { resolveDisplayLayout } from "../platform/displayLayout"; import { subscribeControllerToken } from "../input/controller"; import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs"; export function DualDisplayFrame({ top, bottom, contextLabel = "Context" }: { top: ReactNode; bottom: ReactNode; contextLabel?: string }) { const params = new URLSearchParams(window.location.search); const dedicatedSurface = params.get("display"); const layout = resolveDisplayLayout({ display: dedicatedSurface, layout: params.get("layout") }); const [activeSurface, setActiveSurface] = useState(() => dedicatedSurface === "bottom" ? "bottom" : "top"); const activeSurfaceRef = useRef(activeSurface); activeSurfaceRef.current = activeSurface; const showSurface = useCallback((surface: DisplaySurface) => { activeSurfaceRef.current = surface; setActiveSurface(surface); }, []); const toggleSurface = useCallback(() => { requestDisplaySurface(activeSurfaceRef.current === "top" ? "bottom" : "top"); }, []); useEffect(() => { if (dedicatedSurface === "top" || dedicatedSurface === "bottom") return; if (layout === "thor-preview") return; requestDisplaySurface("top"); const unsubscribeSurface = subscribeDisplaySurface(showSurface); const unsubscribeController = subscribeControllerToken(({ token, repeat }) => { if (token === "Button8" && !repeat) toggleSurface(); }); const onKeyDown = (event: KeyboardEvent) => { if (event.repeat) return; if (event.key === "Escape" && activeSurfaceRef.current === "bottom") { event.preventDefault(); requestDisplaySurface("top"); return; } if (event.key !== "Tab") return; event.preventDefault(); toggleSurface(); }; window.addEventListener("keydown", onKeyDown); return () => { window.removeEventListener("keydown", onKeyDown); unsubscribeSurface(); unsubscribeController(); requestDisplaySurface("top"); }; }, [dedicatedSurface, layout, showSurface, toggleSurface]); if (dedicatedSurface === "top" || dedicatedSurface === "bottom") { return
{dedicatedSurface === "top" ? top : bottom}
; } if (layout === "single") { const contextOpen = activeSurface === "bottom"; return (
{top}
{contextOpen && (
)}
); } return (
Main viewport960 × 540 CSS · 1920 × 1080 · 120Hz
{top}
Context display620 × 540 CSS · 1240 × 1080 · 60Hz
{bottom}
); }