import React, { ReactNode, useEffect, useRef, useState } from "react"; import { createPortal } from "react-dom"; type NewWindowProps = { children: ReactNode; title?: string; width?: number; height?: number; left?: number; top?: number; center?: boolean; features?: Partial<{ toolbar: boolean; menubar: boolean; scrollbars: boolean; resizable: boolean; location: boolean; status: boolean; }>; onClose?: () => void; copyStyles?: boolean; noopener?: boolean; className?: string; theme?: string | null; }; export const RenderInNewWindow: React.FC = ({ children, title = "3D Viewer", width = 900, height = 700, left, top, center = true, features, onClose, copyStyles = true, noopener = true, className, theme = localStorage.getItem("theme") ?? "light", }) => { const [mounted, setMounted] = useState(false); const childWindowRef = useRef(null); const containerElRef = useRef(null); useEffect(() => { if (typeof window === "undefined") return; const screenLeft = window.screenLeft ?? window.screenX ?? 0; const screenTop = window.screenTop ?? window.screenY ?? 0; const availWidth = window.outerWidth ?? window.innerWidth; const availHeight = window.outerHeight ?? window.innerHeight; const finalLeft = center && availWidth ? Math.max(0, screenLeft + (availWidth - width) / 2) : left ?? 100; const finalTop = center && availHeight ? Math.max(0, screenTop + (availHeight - height) / 2) : top ?? 100; const baseFeatures = [ `width=${Math.floor(width)}`, `height=${Math.floor(height)}`, `left=${Math.floor(finalLeft)}`, `top=${Math.floor(finalTop)}`, ]; const featureFlags = features ?? { toolbar: false, menubar: false, scrollbars: true, resizable: true, location: false, status: false, }; Object.entries(featureFlags).forEach(([k, v]) => baseFeatures.push(`${k}=${v ? "yes" : "no"}`) ); const newWin = window.open("", "_blank", baseFeatures.join(",")); if (!newWin) { console.warn("Popup blocked or failed to open window."); onClose?.(); return; } if (noopener) { try { newWin.opener = null; } catch {} } newWin.document.open(); newWin.document.write(` ${title}
`); newWin.document.close(); if (copyStyles) { const head = newWin.document.head; Array.from(document.styleSheets).forEach((styleSheet) => { try { if ((styleSheet as CSSStyleSheet).cssRules) { const newStyleEl = newWin.document.createElement("style"); const rules = Array.from((styleSheet as CSSStyleSheet).cssRules).map( (r) => r.cssText ); newStyleEl.appendChild(newWin.document.createTextNode(rules.join("\n"))); head.appendChild(newStyleEl); } } catch { const ownerNode = styleSheet.ownerNode as HTMLElement | null; if (ownerNode && ownerNode.tagName === "LINK") { const link = ownerNode as HTMLLinkElement; const newLink = newWin.document.createElement("link"); newLink.rel = link.rel; newLink.href = link.href; newLink.media = link.media; head.appendChild(newLink); } } }); } const container = newWin.document.getElementById( "three-container" ) as HTMLDivElement | null; if (!container) return; if (className) container.className = className; newWin.document.title = title; // ✅ CRITICAL FIX: Wait for window to be fully ready then trigger resize const initializeWindow = () => { // Force maximize handling setTimeout(() => { // Get actual window dimensions (might be maximized) const actualWidth = newWin.innerWidth; const actualHeight = newWin.innerHeight; // console.log("Window dimensions:", actualWidth, actualHeight); // Trigger resize event for Three.js newWin.dispatchEvent(new Event("resize")); // Additional safety: trigger again after a short delay setTimeout(() => { newWin.dispatchEvent(new Event("resize")); }, 200); }, 100); }; // Handle both load and focus events newWin.addEventListener("load", initializeWindow); newWin.addEventListener("focus", initializeWindow); // Also initialize immediately if window is already loaded if (newWin.document.readyState === "complete") { initializeWindow(); } else { newWin.addEventListener("DOMContentLoaded", initializeWindow); } // Handle child window close const handleChildUnload = () => { onClose?.(); }; newWin.addEventListener("beforeunload", handleChildUnload); // Handle parent refresh/close → auto close child const handleParentUnload = () => { try { newWin.close(); } catch {} }; window.addEventListener("beforeunload", handleParentUnload); childWindowRef.current = newWin; containerElRef.current = container; setMounted(true); return () => { newWin.removeEventListener("load", initializeWindow); newWin.removeEventListener("focus", initializeWindow); newWin.removeEventListener("DOMContentLoaded", initializeWindow); newWin.removeEventListener("beforeunload", handleChildUnload); window.removeEventListener("beforeunload", handleParentUnload); try { newWin.close(); } catch {} childWindowRef.current = null; containerElRef.current = null; }; }, []); useEffect(() => { const w = childWindowRef.current; if (w && !w.closed) { w.document.title = title; } }, [title]); if (!mounted || !containerElRef.current) return null; return createPortal(children, containerElRef.current); };