fix(desktop): Windows 위젯 추가 팝업 즉시 표시
창 크기 조절 완료를 기다리지 않고 picker를 먼저 열고, body portal· resize 타임아웃으로 WebView2에서 팝업이 뜨지 않던 문제를 해결합니다. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -139,6 +139,7 @@ const WidgetApp: React.FC = () => {
|
|||||||
|
|
||||||
const handlePickerOpenChange = useCallback(async (open: boolean): Promise<void> => {
|
const handlePickerOpenChange = useCallback(async (open: boolean): Promise<void> => {
|
||||||
const win = getCurrentWindow();
|
const win = getCurrentWindow();
|
||||||
|
try {
|
||||||
if (open) {
|
if (open) {
|
||||||
const size = await win.innerSize();
|
const size = await win.innerSize();
|
||||||
sizeBeforePickerRef.current = { width: size.width, height: size.height };
|
sizeBeforePickerRef.current = { width: size.width, height: size.height };
|
||||||
@@ -148,7 +149,8 @@ const WidgetApp: React.FC = () => {
|
|||||||
const h = Math.min(NATIVE_WIDGET_PICKER_WINDOW.height, maxH);
|
const h = Math.min(NATIVE_WIDGET_PICKER_WINDOW.height, maxH);
|
||||||
await win.setSize(new LogicalSize(w, h));
|
await win.setSize(new LogicalSize(w, h));
|
||||||
await win.center();
|
await win.center();
|
||||||
await new Promise<void>(resolve => {
|
await Promise.race([
|
||||||
|
new Promise<void>(resolve => {
|
||||||
let settled = false;
|
let settled = false;
|
||||||
const finish = () => {
|
const finish = () => {
|
||||||
if (settled) return;
|
if (settled) return;
|
||||||
@@ -158,14 +160,18 @@ const WidgetApp: React.FC = () => {
|
|||||||
};
|
};
|
||||||
let unlisten: (() => void) | undefined;
|
let unlisten: (() => void) | undefined;
|
||||||
void win.onResized(finish).then(fn => { unlisten = fn; });
|
void win.onResized(finish).then(fn => { unlisten = fn; });
|
||||||
window.setTimeout(finish, 150);
|
}),
|
||||||
});
|
new Promise<void>(resolve => { window.setTimeout(resolve, 400); }),
|
||||||
|
]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const prev = sizeBeforePickerRef.current;
|
const prev = sizeBeforePickerRef.current;
|
||||||
if (!prev) return;
|
if (!prev) return;
|
||||||
sizeBeforePickerRef.current = null;
|
sizeBeforePickerRef.current = null;
|
||||||
await win.setSize(new LogicalSize(prev.width, prev.height));
|
await win.setSize(new LogicalSize(prev.width, prev.height));
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[widget-main] picker window resize failed', err);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!instance) {
|
if (!instance) {
|
||||||
|
|||||||
@@ -40,29 +40,36 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
|||||||
onPickerOpenChange,
|
onPickerOpenChange,
|
||||||
}) => {
|
}) => {
|
||||||
const [pickSlotId, setPickSlotId] = useState<string | null>(null);
|
const [pickSlotId, setPickSlotId] = useState<string | null>(null);
|
||||||
const [pickerOpening, setPickerOpening] = useState(false);
|
|
||||||
|
|
||||||
const openPicker = useCallback(async (slotId: string) => {
|
const openPicker = useCallback((slotId: string) => {
|
||||||
if (nativeShell && onPickerOpenChange) {
|
|
||||||
setPickerOpening(true);
|
|
||||||
try {
|
|
||||||
await onPickerOpenChange(true);
|
|
||||||
await new Promise<void>(resolve => {
|
|
||||||
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setPickerOpening(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setPickSlotId(slotId);
|
setPickSlotId(slotId);
|
||||||
|
if (nativeShell) {
|
||||||
|
document.documentElement.classList.add('fw-native-picker-open');
|
||||||
|
}
|
||||||
|
if (nativeShell && onPickerOpenChange) {
|
||||||
|
void onPickerOpenChange(true).catch(err => {
|
||||||
|
console.warn('[FloatingWidgetWindow] picker window resize failed', err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}, [nativeShell, onPickerOpenChange]);
|
}, [nativeShell, onPickerOpenChange]);
|
||||||
|
|
||||||
const closePicker = useCallback(async () => {
|
const closePicker = useCallback(async () => {
|
||||||
setPickSlotId(null);
|
setPickSlotId(null);
|
||||||
|
if (nativeShell) {
|
||||||
|
document.documentElement.classList.remove('fw-native-picker-open');
|
||||||
|
}
|
||||||
if (nativeShell && onPickerOpenChange) {
|
if (nativeShell && onPickerOpenChange) {
|
||||||
|
try {
|
||||||
await onPickerOpenChange(false);
|
await onPickerOpenChange(false);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[FloatingWidgetWindow] picker window restore failed', err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [nativeShell, onPickerOpenChange]);
|
}, [nativeShell, onPickerOpenChange]);
|
||||||
|
useEffect(() => () => {
|
||||||
|
document.documentElement.classList.remove('fw-native-picker-open');
|
||||||
|
}, []);
|
||||||
|
|
||||||
const bodyWrapRef = useRef<HTMLDivElement>(null);
|
const bodyWrapRef = useRef<HTMLDivElement>(null);
|
||||||
const bodyRef = useRef<HTMLDivElement>(null);
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
const [bodySize, setBodySize] = useState({ width: 0, height: 0 });
|
const [bodySize, setBodySize] = useState({ width: 0, height: 0 });
|
||||||
@@ -312,23 +319,35 @@ const FloatingWidgetWindow: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
{(pickerOpening || pickSlotId != null) && (
|
const pickerModal = pickSlotId != null ? (
|
||||||
<WidgetPickerModal
|
<WidgetPickerModal
|
||||||
open={pickSlotId != null}
|
open
|
||||||
category="all"
|
category="all"
|
||||||
nativeCompact={nativeShell}
|
nativeCompact={nativeShell}
|
||||||
onClose={() => { void closePicker(); }}
|
onClose={() => { void closePicker(); }}
|
||||||
onSelect={applyWidgetType}
|
onSelect={applyWidgetType}
|
||||||
/>
|
/>
|
||||||
)}
|
) : null;
|
||||||
{pickerOpening && pickSlotId == null && nativeShell && (
|
|
||||||
<div className="wd-picker-native-loading" aria-live="polite">위젯 목록 준비 중…</div>
|
if (nativeShell) {
|
||||||
)}
|
return (
|
||||||
|
<>
|
||||||
|
{content}
|
||||||
|
{pickerModal && createPortal(pickerModal, document.body)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return nativeShell ? content : createPortal(content, document.body);
|
return createPortal(
|
||||||
|
<>
|
||||||
|
{content}
|
||||||
|
{pickerModal}
|
||||||
|
</>,
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FloatingWidgetWindow;
|
export default FloatingWidgetWindow;
|
||||||
|
|||||||
@@ -1329,6 +1329,16 @@ html.fw-native-widget .wd-picker-native-loading {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.fw-native-widget.fw-native-picker-open .fw-widget-native-root .fw-window {
|
||||||
|
visibility: hidden;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.fw-native-widget.fw-native-picker-open .wd-picker-native-overlay {
|
||||||
|
padding: 0;
|
||||||
|
background: var(--bg, #1a1b26);
|
||||||
|
}
|
||||||
|
|
||||||
.bps-page--wd-no-left .bps-side-wrap--left,
|
.bps-page--wd-no-left .bps-side-wrap--left,
|
||||||
.bps-page--wd-no-left .bps-side-wrap--left + .bps-splitter {
|
.bps-page--wd-no-left .bps-side-wrap--left + .bps-splitter {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|||||||
Reference in New Issue
Block a user