54 lines
2.7 KiB
TypeScript
54 lines
2.7 KiB
TypeScript
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||
import { subscribeDisplaySurface, type DisplaySurface } from "../platform/displayRouting";
|
||
import { subscribeControllerToken } from "../input/controller";
|
||
import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs";
|
||
|
||
export function DualDisplayFrame({ top, bottom }: { top: ReactNode; bottom: ReactNode }) {
|
||
const dedicatedSurface = new URLSearchParams(window.location.search).get("display");
|
||
const [activeSurface, setActiveSurface] = useState<DisplaySurface>(() => dedicatedSurface === "bottom" ? "bottom" : "top");
|
||
const activeSurfaceRef = useRef(activeSurface);
|
||
activeSurfaceRef.current = activeSurface;
|
||
|
||
useEffect(() => {
|
||
if (!document.documentElement.classList.contains("native-platform")) return;
|
||
if (dedicatedSurface === "top" || dedicatedSurface === "bottom") return;
|
||
const toggle = () => setActiveSurface((surface) => surface === "top" ? "bottom" : "top");
|
||
const unsubscribeSurface = subscribeDisplaySurface(setActiveSurface);
|
||
const unsubscribeController = subscribeControllerToken(({ token, repeat }) => {
|
||
if (token === "Button8" && !repeat) toggle();
|
||
});
|
||
const onKeyDown = (event: KeyboardEvent) => {
|
||
if (event.key !== "Tab" || event.repeat) return;
|
||
event.preventDefault();
|
||
toggle();
|
||
};
|
||
window.addEventListener("keydown", onKeyDown);
|
||
return () => {
|
||
window.removeEventListener("keydown", onKeyDown);
|
||
unsubscribeSurface();
|
||
unsubscribeController();
|
||
};
|
||
}, [dedicatedSurface]);
|
||
|
||
if (dedicatedSurface === "top" || dedicatedSurface === "bottom") {
|
||
return <div className={`dedicated-display-surface dedicated-${dedicatedSurface}`}>{dedicatedSurface === "top" ? top : bottom}</div>;
|
||
}
|
||
|
||
return (
|
||
<div className={`device-frame active-${activeSurface}`}>
|
||
<div className="screen-label"><span>Main viewport</span><small>960 × 540 CSS · 1920 × 1080 · 120Hz</small></div>
|
||
<div className={`surface-slot top-slot ${activeSurface === "top" ? "is-active" : ""}`}>{top}</div>
|
||
<div className="hinge" aria-hidden="true"><i /><b>AYN THOR</b><i /></div>
|
||
<div className="screen-label bottom-label"><span>Context display</span><small>620 × 540 CSS · 1240 × 1080 · 60Hz</small></div>
|
||
<div className={`surface-slot bottom-slot ${activeSurface === "bottom" ? "is-active" : ""}`}>{bottom}</div>
|
||
<button
|
||
className="native-display-switch"
|
||
onClick={() => setActiveSurface(activeSurfaceRef.current === "top" ? "bottom" : "top")}
|
||
aria-label={activeSurface === "top" ? "Open tactical display" : "Return to main display"}
|
||
>
|
||
<b>{activeSurface === "top" ? "Tactical" : "Main"}</b><small>{DEFAULT_CONTROLLER_GLYPHS.select} / TAB</small>
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|