Files
healer-man/src/platform/BottomDisplayApp.tsx
T
2026-09-14 13:20:20 -04:00

86 lines
3.3 KiB
TypeScript

import { useCallback, useEffect, useRef, useState } from "react";
import {
getControllerMovement,
subscribeControllerMovement,
subscribeControllerToken,
} from "../input/controller";
import { CompanionPanel } from "../ui/CompanionPanel";
import {
createDualScreenChannel,
currentCompanionSnapshot,
type CompanionCommand,
type CompanionSnapshot,
type DualScreenMessage,
} from "./dualScreenSync";
import { normalizeInputPreferences } from "../input/keybinds";
import { publishInputPreferences } from "../input/keybindRuntime";
import { useForcedThorDisplays } from "./useThorDualScreen";
import { RemoteDungeonHud } from "./RemoteDungeonHud";
export function BottomDisplayApp() {
useForcedThorDisplays();
const channelRef = useRef<BroadcastChannel | null>(null);
const [snapshot, setSnapshot] = useState<CompanionSnapshot>(() => ({
...currentCompanionSnapshot(),
notice: "Linking the upper display...",
}));
const send = useCallback((command: CompanionCommand) => {
channelRef.current?.postMessage({ type: "command", command } satisfies DualScreenMessage);
}, []);
useEffect(() => {
const channel = createDualScreenChannel();
if (!channel) return;
channelRef.current = channel;
const role = new URLSearchParams(window.location.search).get("role");
const forwardsController = role === "activity";
const ready = () => channel.postMessage({ type: "companion-ready" } satisfies DualScreenMessage);
const closing = () => channel.postMessage({ type: "companion-closing" } satisfies DualScreenMessage);
const unsubscribeToken = forwardsController
? subscribeControllerToken((event) => channel.postMessage({ type: "controller-token", event } satisfies DualScreenMessage))
: () => undefined;
const unsubscribeMovement = forwardsController
? subscribeControllerMovement((movement) => channel.postMessage({
type: "controller-motion",
movement: { ...movement },
} satisfies DualScreenMessage))
: () => undefined;
channel.onmessage = (event: MessageEvent<DualScreenMessage>) => {
if (event.data.type === "app-state") {
setSnapshot(event.data.snapshot);
if (event.data.snapshot.keybinds) publishInputPreferences(normalizeInputPreferences(event.data.snapshot.keybinds));
}
else if (event.data.type === "authoritative-ready") ready();
};
window.addEventListener("pageshow", ready);
window.addEventListener("pagehide", closing);
ready();
if (forwardsController) {
channel.postMessage({
type: "controller-motion",
movement: { ...getControllerMovement() },
} satisfies DualScreenMessage);
}
return () => {
window.removeEventListener("pageshow", ready);
window.removeEventListener("pagehide", closing);
unsubscribeToken();
unsubscribeMovement();
if (forwardsController) {
channel.postMessage({
type: "controller-motion",
movement: { moveX: 0, moveY: 0, lookX: 0, lookY: 0 },
} satisfies DualScreenMessage);
}
closing();
channelRef.current = null;
channel.close();
};
}, []);
return <main className="bottom-display-root">{snapshot.phase === "game"
? <RemoteDungeonHud snapshot={snapshot} send={send} />
: <CompanionPanel snapshot={snapshot} send={send} preferControllerBinds />}</main>;
}