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 = "New Window", // 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.createElement("div"); // if (className) container.className = className; // newWin.document.body.appendChild(container); // newWin.document.title = title; // // 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("beforeunload", handleChildUnload); // window.removeEventListener("beforeunload", handleParentUnload); // try { // newWin.close(); // } catch {} // childWindowRef.current = null; // containerElRef.current = null; // }; // // eslint-disable-next-line react-hooks/exhaustive-deps // }, []); // 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); // }; 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); };