import { isWindowPopupKey, windowPopupDefinitions, type WindowPopupKey, } from "../model/popupDefinitions"; import type { OpenWindowPopupParams } from "../model/types"; const buildQueryString = (params: OpenWindowPopupParams = {}) => { const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value === null || value === undefined || value === "") return; searchParams.set(key, String(value)); }); const queryString = searchParams.toString(); return queryString ? `?${queryString}` : ""; }; const clamp = (value: number, min: number, max: number) => { if (max < min) return min; return Math.min(Math.max(value, min), max); }; const buildPopupFeatures = ( width: number, height: number, placement: "center" | "browser-left" = "center", ) => { const screenLeft = window.screenX ?? window.screenLeft ?? 0; const screenTop = window.screenY ?? window.screenTop ?? 0; const outerWidth = window.outerWidth || window.screen.width; const outerHeight = window.outerHeight || window.screen.height; const screen = window.screen as Screen & { availLeft?: number; availTop?: number; }; const availableLeft = screen.availLeft ?? 0; const availableTop = screen.availTop ?? 0; const availableWidth = window.screen.availWidth || outerWidth; const availableHeight = window.screen.availHeight || outerHeight; const safeWidth = Math.min(width, Math.max(360, availableWidth - 48)); const safeHeight = Math.min(height, Math.max(420, availableHeight - 72)); const minLeft = availableLeft; const minTop = availableTop + 24; const maxLeft = availableLeft + availableWidth - safeWidth - 16; const maxTop = availableTop + availableHeight - safeHeight - 24; const left = placement === "browser-left" ? clamp(screenLeft, minLeft, maxLeft) : clamp( screenLeft + Math.round((outerWidth - safeWidth) / 2), availableLeft + 16, maxLeft, ); const top = clamp( screenTop + Math.round((outerHeight - safeHeight) / 2), minTop, maxTop, ); return [ `width=${safeWidth}`, `height=${safeHeight}`, `left=${left}`, `top=${top}`, "resizable=yes", "scrollbars=yes", "status=no", "location=no", "menubar=no", "toolbar=no", ].join(","); }; export const openWindowPopup = ( popupKey: WindowPopupKey, params?: OpenWindowPopupParams, ) => { if (typeof window === "undefined") return null; if (!isWindowPopupKey(popupKey)) return null; const definition = windowPopupDefinitions[popupKey]; const popupUrl = `/popup/${popupKey}${buildQueryString(params)}`; const popupFeatures = buildPopupFeatures( definition.width, definition.height, "placement" in definition ? definition.placement : undefined, ); const popupWindow = window.open( popupUrl, `sn-popup-${popupKey}`, popupFeatures, ); popupWindow?.focus(); return popupWindow; };