fix: open detail popup on right monitor using Window Management API

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-06-05 10:27:14 +09:00
parent 7c600b2176
commit 0da56b505c
2 changed files with 68 additions and 15 deletions

View File

@@ -11,7 +11,7 @@ interface DashboardHeaderProps {
stats: Stats; stats: Stats;
activeStatus: string; activeStatus: string;
onStatusChange: (status: string) => void; onStatusChange: (status: string) => void;
onOpenDetailWindow: () => void; onOpenDetailWindow: () => void | Promise<void>;
onOpenTaskManager: () => void; onOpenTaskManager: () => void;
} }

View File

@@ -14,6 +14,63 @@ type DualMonitorEvent =
let channel: BroadcastChannel | null = null; let channel: BroadcastChannel | null = null;
let detailWindow: Window | null = null; let detailWindow: Window | null = null;
interface ScreenDetailed {
left: number;
top: number;
width: number;
height: number;
availLeft?: number;
availTop?: number;
availWidth?: number;
availHeight?: number;
}
interface ScreenDetails {
currentScreen: ScreenDetailed;
screens: ScreenDetailed[];
}
interface WindowWithScreenDetails extends Window {
getScreenDetails?: () => Promise<ScreenDetails>;
}
/** 참조 대시보드와 동일: 우측 모니터 좌표·크기 계산 */
async function getDetailWindowFeatures(): Promise<string> {
let left = window.screenX + window.outerWidth;
let top = window.screenY;
let width = window.screen.availWidth;
let height = window.screen.availHeight;
try {
const win = window as WindowWithScreenDetails;
if (win.getScreenDetails) {
const details = await win.getScreenDetails();
const current = details.currentScreen;
let target = details.screens.find((s) => s.left >= current.left + current.width);
target ||= details.screens.find((s) => s !== current);
if (target) {
left = target.availLeft ?? target.left;
top = target.availTop ?? target.top;
width = target.availWidth ?? target.width;
height = target.availHeight ?? target.height;
} else {
left = window.screenX + window.outerWidth;
width = window.screen.availWidth - left;
if (width < 800) {
width = 1920;
left = window.screen.availWidth;
}
height = window.screen.availHeight;
}
}
} catch (err) {
console.warn('Window Management API failed or denied, using fallback', err);
}
return `left=${left},top=${top},width=${width},height=${height},menubar=no,toolbar=no,location=no,status=no,resizable=yes,scrollbars=yes`;
}
function getChannel(): BroadcastChannel { function getChannel(): BroadcastChannel {
if (!channel) { if (!channel) {
channel = new BroadcastChannel(CHANNEL_NAME); channel = new BroadcastChannel(CHANNEL_NAME);
@@ -27,33 +84,29 @@ export function isDetailWindowOpen(): boolean {
} }
/** 상세 창 토글 — 열려 있으면 닫고, 닫혀 있으면 오른쪽 모니터에 열기 */ /** 상세 창 토글 — 열려 있으면 닫고, 닫혀 있으면 오른쪽 모니터에 열기 */
export function openDetailWindow(): Window | null { export async function openDetailWindow(): Promise<Window | null> {
if (isDetailWindowOpen()) { if (isDetailWindowOpen()) {
detailWindow!.close(); detailWindow!.close();
detailWindow = null; detailWindow = null;
return null; return null;
} }
// 현재 창이 왼쪽 모니터에 있다고 가정하고
// 오른쪽 모니터의 시작 X 좌표 = 현재 창 X + 현재 화면 너비
const screenW = window.screen.width;
const screenH = window.screen.height;
const rightMonitorLeft = window.screenX + screenW;
const detailUrl = `${window.location.origin}/detail`; const detailUrl = `${window.location.origin}/detail`;
const features = await getDetailWindowFeatures();
detailWindow = window.open( detailWindow = window.open(detailUrl, DETAIL_WINDOW_NAME, features);
detailUrl, try {
DETAIL_WINDOW_NAME, detailWindow?.focus();
`width=${screenW},height=${screenH},left=${rightMonitorLeft},top=0,resizable=yes,scrollbars=yes`, } catch {
); // popup-blocked 등
}
return detailWindow; return detailWindow;
} }
/** 좌측 → 우측: 업무 선택 이벤트 전송 (창이 닫혀 있으면 열고 전송) */ /** 좌측 → 우측: 업무 선택 이벤트 전송 (창이 닫혀 있으면 열고 전송) */
export function sendTaskSelected(taskId: string): void { export async function sendTaskSelected(taskId: string): Promise<void> {
if (!isDetailWindowOpen()) { if (!isDetailWindowOpen()) {
openDetailWindow(); await openDetailWindow();
// 창이 로드될 때까지 잠시 대기 후 전송 // 창이 로드될 때까지 잠시 대기 후 전송
setTimeout(() => { setTimeout(() => {
getChannel().postMessage({ type: 'TASK_SELECTED', taskId } satisfies DualMonitorEvent); getChannel().postMessage({ type: 'TASK_SELECTED', taskId } satisfies DualMonitorEvent);