fix: restore popup opening on user click for detail window

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-06-05 22:29:27 +09:00
parent 41feabec04
commit 0ee52cf35f
2 changed files with 68 additions and 32 deletions

View File

@@ -35,7 +35,19 @@ interface WindowWithScreenDetails extends Window {
getScreenDetails?: () => Promise<ScreenDetails>; getScreenDetails?: () => Promise<ScreenDetails>;
} }
/** 우측 모니터 좌표·크기 계산 */ /** 클릭 직후 동기적으로 쓸 기본 창 위치 (await 없음) */
function buildSyncWindowFeatures(): string {
const left = window.screenX + window.outerWidth;
const top = window.screenY;
let width = window.screen.availWidth - left;
if (width < 800) {
width = 1280;
}
const height = window.screen.availHeight;
return `left=${left},top=${top},width=${width},height=${height},menubar=no,toolbar=no,location=no,status=no,resizable=yes,scrollbars=yes`;
}
/** 우측 모니터 좌표·크기 계산 (열린 뒤 위치 조정용) */
export async function getRightMonitorWindowFeatures(): Promise<string> { export async function getRightMonitorWindowFeatures(): Promise<string> {
let left = window.screenX + window.outerWidth; let left = window.screenX + window.outerWidth;
let top = window.screenY; let top = window.screenY;
@@ -116,59 +128,73 @@ function postTaskSelected(taskId: string) {
getChannel().postMessage({ type: 'TASK_SELECTED', taskId } satisfies DualMonitorEvent); getChannel().postMessage({ type: 'TASK_SELECTED', taskId } satisfies DualMonitorEvent);
} }
/** 상세 창 토글 — 열려 있으면 닫고, 닫혀 있으면 오른쪽 모니터에 열기 */ function scheduleTaskSelected(taskId: string) {
export async function openDetailWindow(): Promise<Window | null> { postTaskSelected(taskId);
if (isDetailWindowOpen()) { setTimeout(() => postTaskSelected(taskId), 500);
detailWindow!.close(); setTimeout(() => postTaskSelected(taskId), 1500);
detailWindow = null; }
return null;
}
/** 상세 창 열기 — 반드시 사용자 클릭 직후 동기 호출 */
function openDetailWindowSync(): Window | null {
const detailUrl = `${window.location.origin}/detail`; const detailUrl = `${window.location.origin}/detail`;
// 사용자 클릭 직후 동기적으로 열어야 팝업 차단을 피할 수 있음 const features = buildSyncWindowFeatures();
detailWindow = window.open(detailUrl, DETAIL_WINDOW_NAME, 'noopener,noreferrer,width=1280,height=900');
detailWindow = window.open(detailUrl, DETAIL_WINDOW_NAME, features);
if (!detailWindow) { if (!detailWindow) {
alert('팝업이 차단되었습니다. 브라우저에서 이 사이트의 팝업 허용해 주세요.'); console.warn('상세 창을 열지 못했습니다. 브라우저 팝업 허용을 확인해 주세요.');
return null; return null;
} }
try { try {
detailWindow.focus(); detailWindow.focus();
} catch { } catch {
// popup-blocked 등 // ignore
} }
void getRightMonitorWindowFeatures().then((features) => { // 창을 연 뒤 비동기로 위치만 보정 (팝업 차단과 무관)
const { left, top, width, height } = parseWindowFeatures(features); void getRightMonitorWindowFeatures().then((f) => {
const { left, top, width, height } = parseWindowFeatures(f);
if (detailWindow && !detailWindow.closed && left != null && top != null && width && height) { if (detailWindow && !detailWindow.closed && left != null && top != null && width && height) {
applyWindowPlacement(detailWindow, left, top, width, height); applyWindowPlacement(detailWindow, left, top, width, height);
} }
}); });
const savedTaskId = getPersistedTaskId();
if (savedTaskId) {
postTaskSelected(savedTaskId);
setTimeout(() => postTaskSelected(savedTaskId), 500);
}
return detailWindow; return detailWindow;
} }
/** 상세 창 토글 — 열려 있으면 닫고, 닫혀 있으면 오른쪽 모니터에 열기 */
export function openDetailWindow(): Window | null {
if (isDetailWindowOpen()) {
detailWindow!.close();
detailWindow = null;
return null;
}
const win = openDetailWindowSync();
const savedTaskId = getPersistedTaskId();
if (win && savedTaskId) {
scheduleTaskSelected(savedTaskId);
}
return win;
}
/** 좌측 → 우측: 업무 선택 이벤트 전송 (창이 닫혀 있으면 열고 전송) */ /** 좌측 → 우측: 업무 선택 이벤트 전송 (창이 닫혀 있으면 열고 전송) */
export async function sendTaskSelected(taskId: string): Promise<void> { export function sendTaskSelected(taskId: string): void {
persistSelectedTask(taskId); persistSelectedTask(taskId);
if (!isDetailWindowOpen()) { if (!isDetailWindowOpen()) {
const win = await openDetailWindow(); const win = openDetailWindowSync();
if (!win) return; if (!win) return;
postTaskSelected(taskId); scheduleTaskSelected(taskId);
setTimeout(() => postTaskSelected(taskId), 500);
setTimeout(() => postTaskSelected(taskId), 1500);
return; return;
} }
detailWindow!.focus(); try {
postTaskSelected(taskId); detailWindow!.focus();
} catch {
// ignore
}
scheduleTaskSelected(taskId);
} }
/** 좌측 → 우측: 업무 선택 해제 */ /** 좌측 → 우측: 업무 선택 해제 */
@@ -193,14 +219,24 @@ export function closeChannel(): void {
channel = null; channel = null;
} }
/** 웹 링크를 우측 모니터 새 창에서 열기 (같은 URL이면 기존 창 포커스) */ /** 웹 링크를 우측 모니터 새 창에서 열기 */
export async function openLinkOnRightMonitor(url: string, windowName: string): Promise<Window | null> { export function openLinkOnRightMonitor(url: string, windowName: string): Window | null {
const features = await getRightMonitorWindowFeatures(); const features = buildSyncWindowFeatures();
const win = window.open(url, windowName, features); const win = window.open(url, windowName, features);
try { try {
win?.focus(); win?.focus();
} catch { } catch {
// popup-blocked 등 // ignore
} }
if (win) {
void getRightMonitorWindowFeatures().then((f) => {
const { left, top, width, height } = parseWindowFeatures(f);
if (win && !win.closed && left != null && top != null && width && height) {
applyWindowPlacement(win, left, top, width, height);
}
});
}
return win; return win;
} }

View File

@@ -216,7 +216,7 @@ export default function DashboardPage() {
stats={stats} stats={stats}
activeStatus={activeStatus} activeStatus={activeStatus}
onStatusChange={setActiveStatus} onStatusChange={setActiveStatus}
onOpenDetailWindow={() => void openDetailWindow()} onOpenDetailWindow={() => { openDetailWindow(); }}
onOpenTaskManager={() => setShowTaskManager(true)} onOpenTaskManager={() => setShowTaskManager(true)}
/> />