diff --git a/desktop/src/widget-main.tsx b/desktop/src/widget-main.tsx index 4663047..f9bdac4 100644 --- a/desktop/src/widget-main.tsx +++ b/desktop/src/widget-main.tsx @@ -6,6 +6,7 @@ import { LogicalSize } from '@tauri-apps/api/dpi'; import { initStorage, refreshApiBaseFromStorage } from '@goldenchart/shared'; import { ensureDesktopApiBase } from './ensureDesktopApiBase'; import FloatingWidgetWindow from '@frontend/components/floatingWidgets/FloatingWidgetWindow'; +import WidgetPickerModal from '@frontend/components/widgetDashboard/WidgetPickerModal'; import { defaultGridFr, NATIVE_WIDGET_PICKER_WINDOW, @@ -131,6 +132,8 @@ const WidgetApp: React.FC = () => { return () => { unlisten?.(); }; }, [instance?.id]); + const [pickSlotId, setPickSlotId] = useState(null); + const handleClose = useCallback(() => { void getCurrentWindow().close(); }, []); @@ -174,6 +177,34 @@ const WidgetApp: React.FC = () => { } }, []); + const handleNativePickRequest = useCallback((slotId: string) => { + setPickSlotId(slotId); + void Promise.resolve(handlePickerOpenChange(true)).catch(err => { + console.warn('[widget-main] picker open resize failed', err); + }); + }, [handlePickerOpenChange]); + + const handleNativePickClose = useCallback(() => { + setPickSlotId(null); + void Promise.resolve(handlePickerOpenChange(false)).catch(err => { + console.warn('[widget-main] picker close resize failed', err); + }); + }, [handlePickerOpenChange]); + + const handleNativePickSelect = useCallback((widgetType: string) => { + if (!pickSlotId) return; + setInstance(prev => { + if (!prev) return prev; + return { + ...prev, + slots: prev.slots.map(s => ( + s.id === pickSlotId ? { ...s, widgetType, config: {} } : s + )), + }; + }); + handleNativePickClose(); + }, [pickSlotId, handleNativePickClose]); + if (!instance) { return

위젯 정보를 불러올 수 없습니다.

; } @@ -194,21 +225,32 @@ const WidgetApp: React.FC = () => { paperAutoTradeEnabled={false} chartRealtimeSource="BACKEND_STOMP" > -
- {}} - onPickerOpenChange={handlePickerOpenChange} - onUpdateSlots={slots => setInstance(prev => (prev ? { ...prev, slots } : prev))} - onUpdateSize={(width, height) => { - setInstance(prev => (prev ? { ...prev, width, height } : prev)); - void getCurrentWindow().setSize(new LogicalSize(width, height)); - }} - onUpdateGridFr={(rowFr, colFr) => setInstance(prev => (prev ? { ...prev, rowFr, colFr } : prev))} - /> +
+ {pickSlotId != null ? ( + + ) : ( + {}} + onNativePickRequest={handleNativePickRequest} + onUpdateSlots={slots => setInstance(prev => (prev ? { ...prev, slots } : prev))} + onUpdateSize={(width, height) => { + setInstance(prev => (prev ? { ...prev, width, height } : prev)); + void getCurrentWindow().setSize(new LogicalSize(width, height)); + }} + onUpdateGridFr={(rowFr, colFr) => setInstance(prev => (prev ? { ...prev, rowFr, colFr } : prev))} + /> + )}
); diff --git a/frontend/src/components/floatingWidgets/FloatingWidgetWindow.tsx b/frontend/src/components/floatingWidgets/FloatingWidgetWindow.tsx index 80842f4..1cd6186 100644 --- a/frontend/src/components/floatingWidgets/FloatingWidgetWindow.tsx +++ b/frontend/src/components/floatingWidgets/FloatingWidgetWindow.tsx @@ -24,7 +24,9 @@ interface Props { onUpdateGridFr: (rowFr: number[], colFr: number[]) => void; /** Tauri OS 창 — 네이티브 타이틀·리사이즈 사용, 내부 플로팅 크롬 숨김 */ nativeShell?: boolean; - /** 위젯 선택 팝업 열림/닫힘 (네이티브 창 크기 조절 — 완료 후 picker 표시) */ + /** 네이티브 위젯 창 — picker는 부모(widget-main)에서 전환 렌더 */ + onNativePickRequest?: (slotId: string) => void; + /** 위젯 선택 팝업 열림/닫힘 (웹 플로팅 레이어용) */ onPickerOpenChange?: (open: boolean) => void | Promise; } @@ -37,27 +39,22 @@ const FloatingWidgetWindow: React.FC = ({ onUpdateSize, onUpdateGridFr, nativeShell = false, + onNativePickRequest, onPickerOpenChange, }) => { const [pickSlotId, setPickSlotId] = useState(null); + const nativePickerExternal = nativeShell && Boolean(onNativePickRequest); const openPicker = useCallback((slotId: string) => { + if (nativePickerExternal) { + onNativePickRequest!(slotId); + return; + } setPickSlotId(slotId); - if (nativeShell) { - document.documentElement.classList.add('fw-native-picker-open'); - } - if (nativeShell && onPickerOpenChange) { - void Promise.resolve(onPickerOpenChange(true)).catch(err => { - console.warn('[FloatingWidgetWindow] picker window resize failed', err); - }); - } - }, [nativeShell, onPickerOpenChange]); + }, [nativePickerExternal, onNativePickRequest]); const closePicker = useCallback(async () => { setPickSlotId(null); - if (nativeShell) { - document.documentElement.classList.remove('fw-native-picker-open'); - } if (nativeShell && onPickerOpenChange) { try { await onPickerOpenChange(false); @@ -66,9 +63,6 @@ const FloatingWidgetWindow: React.FC = ({ } } }, [nativeShell, onPickerOpenChange]); - useEffect(() => () => { - document.documentElement.classList.remove('fw-native-picker-open'); - }, []); const bodyWrapRef = useRef(null); const bodyRef = useRef(null); @@ -265,7 +259,7 @@ const FloatingWidgetWindow: React.FC = ({ { void openPicker(id); }} + onPickWidget={id => { openPicker(id); }} onClearWidget={id => clearWidget(id)} />
@@ -322,7 +316,7 @@ const FloatingWidgetWindow: React.FC = ({ ); - const pickerModal = pickSlotId != null ? ( + const pickerModal = !nativePickerExternal && pickSlotId != null ? ( = ({ ) : null; if (nativeShell) { - return ( - <> - {content} - {pickerModal && createPortal(pickerModal, document.body)} - - ); + return content; } return createPortal( diff --git a/frontend/src/components/widgetDashboard/WidgetPickerModal.tsx b/frontend/src/components/widgetDashboard/WidgetPickerModal.tsx index 6b29204..817deae 100644 --- a/frontend/src/components/widgetDashboard/WidgetPickerModal.tsx +++ b/frontend/src/components/widgetDashboard/WidgetPickerModal.tsx @@ -12,6 +12,8 @@ interface Props { onSelect: (widgetType: string) => void; /** Tauri 네이티브 위젯 창 — OS 창 전체를 쓰는 고정형 picker */ nativeCompact?: boolean; + /** 네이티브 위젯 창 루트를 picker 전용으로 쓸 때 (overlay 없음) */ + nativeFullRoot?: boolean; } function pickerMeta(category: WidgetPickerCategory) { @@ -69,36 +71,45 @@ const NativeWidgetPicker: React.FC = ({ category, onClose, onSelect, + nativeFullRoot = false, }) => { if (!open) return null; const { title } = pickerMeta(category); + const shell = ( +
+
+

+ {title} + (Widget) +

+ +
+
+ +
+
+ ); + + if (nativeFullRoot) { + return
{shell}
; + } + return (
-
-
-

- {title} - (Widget) -

- -
-
- -
-
+ {shell}
); }; @@ -109,6 +120,7 @@ const WidgetPickerModal: React.FC = ({ onClose, onSelect, nativeCompact = false, + nativeFullRoot = false, }) => { if (!open) return null; @@ -119,6 +131,7 @@ const WidgetPickerModal: React.FC = ({ category={category} onClose={onClose} onSelect={onSelect} + nativeFullRoot={nativeFullRoot} /> ); } diff --git a/frontend/src/styles/widgetDashboard.css b/frontend/src/styles/widgetDashboard.css index 141d1b2..3f6cbff 100644 --- a/frontend/src/styles/widgetDashboard.css +++ b/frontend/src/styles/widgetDashboard.css @@ -1317,6 +1317,29 @@ html.fw-native-widget .wd-picker-native-body { overflow-y: auto; } +/* 네이티브 위젯 창 — picker가 창 전체를 차지 (overlay/portal 미사용) */ +html.fw-native-widget .fw-widget-native-root--picker { + overflow: hidden; +} + +html.fw-native-widget .wd-picker-native-root { + flex: 1; + min-height: 0; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + background: var(--bg, #1a1b26); +} + +html.fw-native-widget .wd-picker-native-root .wd-picker-native-shell { + flex: 1; + min-height: 0; + border-radius: 0; + border: none; + box-shadow: none; +} + html.fw-native-widget .wd-picker-native-loading { position: fixed; inset: 0; @@ -1329,16 +1352,6 @@ html.fw-native-widget .wd-picker-native-loading { 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-splitter { display: none !important;