"use client"; import { parseScopedThreadKey } from "@t3tools/client-runtime/environment"; import { FILL_PREVIEW_VIEWPORT } from "@t3tools/contracts"; import { useEffect, useMemo } from "react"; import { isElectron } from "~/env"; import { useTheme } from "~/hooks/useTheme"; import { useActivePreviewSessions } from "~/previewStateStore"; import { readPreviewAnnotationTheme } from "./annotationTheme"; import { useBrowserPointerStore } from "./browserPointerStore"; import { HostedBrowserWebview } from "./HostedBrowserWebview"; import { previewRuntimeTabId } from "./previewRuntimeTabId"; export function ElectronBrowserHost() { const { resolvedTheme } = useTheme(); const previewByThreadKey = useActivePreviewSessions(); const sessions = useMemo( () => Object.entries(previewByThreadKey).flatMap(([threadKey, previewState]) => { const threadRef = parseScopedThreadKey(threadKey); return threadRef ? Object.values(previewState.sessions).map((snapshot) => ({ threadRef, snapshot, runtimeTabId: previewRuntimeTabId( threadRef, previewState.serverEpoch, snapshot.tabId, ), pictureInPicture: previewState.desktopByTabId[snapshot.tabId]?.pictureInPicture ?? false, zoomFactor: previewState.desktopByTabId[snapshot.tabId]?.zoomFactor ?? 1, })) : []; }), [previewByThreadKey], ); useEffect(() => { const preview = window.desktopBridge?.preview; if (!preview) return; let lastSerializedTheme = ""; const syncTheme = () => { const theme = readPreviewAnnotationTheme(); const serializedTheme = JSON.stringify(theme); if (serializedTheme === lastSerializedTheme) return; lastSerializedTheme = serializedTheme; void preview.setAnnotationTheme(theme).catch(() => { lastSerializedTheme = ""; }); }; const frameId = window.requestAnimationFrame(syncTheme); const observer = new MutationObserver(syncTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class", "style"], }); const headObserver = new MutationObserver(syncTheme); headObserver.observe(document.head, { childList: true, subtree: true, characterData: true, }); return () => { window.cancelAnimationFrame(frameId); observer.disconnect(); headObserver.disconnect(); }; }, [resolvedTheme]); useEffect(() => { const preview = window.desktopBridge?.preview; if (!preview) return; return preview.onPointerEvent((event) => { useBrowserPointerStore.getState().apply(event); }); }, []); if (!isElectron) return null; return (
{sessions.map(({ threadRef, snapshot, runtimeTabId, pictureInPicture, zoomFactor }) => { const url = snapshot.navStatus._tag === "Idle" ? null : snapshot.navStatus.url; return ( ); })}
); }