fix: open detail popup on right monitor using Window Management API
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user