/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * Renders the panels that have been popped out into OS / PiP windows (#1208). * * Each open window gets a `createPortal` of the *same* panel component into * its document body — so the panel keeps running in this tab's React tree and * shares the live Zustand store. We only sync the theme class across windows * and make sure every child closes when the parent tab unloads. */ import { useEffect } from 'react'; import { createPortal } from 'react-dom'; import { useSyncExternalStore } from 'react'; import { PinOff, X, MonitorUp } from 'lucide-react'; import { useViewerStore } from '@/store'; import { getPanelDef } from '@/lib/panels/registry'; import { renderPanelBody } from '@/lib/panels/renderPanelBody'; import { PortalContainerProvider } from '@/components/ui/portal-container'; import { subscribePanelWindows, getPanelWindowsSnapshot, closePanelWindow, closeAllPanelWindows, syncPanelWindowsTheme, type PanelWindowEntry, } from '@/services/panel-windows'; export function PanelWindowHost() { const windows = useSyncExternalStore( subscribePanelWindows, getPanelWindowsSnapshot, getPanelWindowsSnapshot, ); const theme = useViewerStore((s) => s.theme); // Mirror the theme class (dark / colorful) onto every open window so toggling // the theme in the main tab updates the popped-out panels too. useEffect(() => { syncPanelWindowsTheme(document.documentElement.className); }, [theme, windows]); // Never orphan a child window: close them all when the parent tab unloads. useEffect(() => { const onUnload = () => closeAllPanelWindows(); window.addEventListener('beforeunload', onUnload); return () => { window.removeEventListener('beforeunload', onUnload); onUnload(); }; }, []); return ( <> {windows.map((entry) => createPortal(, entry.win.document.body, entry.id))} ); } function PanelWindowChrome({ entry }: { entry: PanelWindowEntry }) { const def = getPanelDef(entry.id); const Icon = def?.Icon; const dock = () => useViewerStore.getState().showWorkspacePanel(entry.id); const close = () => closePanelWindow(entry.id); return (
{Icon && } {def?.title ?? entry.id} {entry.kind === 'pip' ? 'Picture-in-picture' : 'Window'}
{renderPanelBody(entry.id, close)}
{/* Decorative hint strip — reinforces that this content is live. */}
Live · synced with the main window
); }