import './forceServerApi'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import ReactDOM from 'react-dom/client'; import { getCurrentWindow } from '@tauri-apps/api/window'; 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 { defaultGridFr, type FloatingWidgetInstance, } from '@frontend/types/floatingWidget'; import type { Theme } from '@frontend/types'; import type { WidgetSlot } from '@frontend/types/widgetDashboard'; import { useMarketTicker } from '@frontend/hooks/useMarketTicker'; import { loadPaperSummary, loadPaperTrades, loadStrategies, type PaperSummaryDto, type PaperTradeDto, type StrategyDto, } from '@frontend/utils/backendApi'; import { PAPER_TRADES_CHANGED_EVENT } from '@frontend/utils/paperTradeEvents'; import { WidgetDashboardProvider } from '@frontend/widgets/WidgetDashboardContext'; import { readDesktopSync, subscribeDesktopSync } from '@frontend/utils/desktopSync'; import { syncDocumentTheme } from '@frontend/utils/documentTheme'; import '@frontend/App.css'; import '@frontend/styles/appPopup.css'; import '@frontend/styles/paperDashboard.css'; import '@frontend/styles/widgetDashboard.css'; import '@frontend/styles/floatingWidget.css'; document.documentElement.classList.add('fw-native-widget'); function parseInstanceFromUrl(): FloatingWidgetInstance | null { const params = new URLSearchParams(window.location.search); const id = params.get('id'); if (!id) return null; const rows = Number(params.get('rows') ?? 1); const cols = Number(params.get('cols') ?? 1); const width = Number(params.get('width') ?? 640); const height = Number(params.get('height') ?? 480); let slots: WidgetSlot[] = []; try { slots = JSON.parse(params.get('slots') ?? '[]') as WidgetSlot[]; } catch { slots = []; } const rowFrRaw = params.get('rowFr'); const colFrRaw = params.get('colFr'); const rowFr = rowFrRaw ? rowFrRaw.split(',').map(Number) : defaultGridFr(rows); const colFr = colFrRaw ? colFrRaw.split(',').map(Number) : defaultGridFr(cols); return { id, rows, cols, slots, rowFr, colFr, position: { x: 0, y: 0 }, width, height, zIndex: 1, }; } const WidgetApp: React.FC = () => { const initial = useMemo(() => parseInstanceFromUrl(), []); const [instance, setInstance] = useState(initial); const sync = readDesktopSync(); const [theme, setTheme] = useState((sync?.theme as Theme | undefined) ?? 'dark'); const [defaultMarket, setDefaultMarket] = useState(sync?.selectedMarket ?? 'KRW-BTC'); const { tickers, marketInfos, loading: marketLoading, usdRate } = useMarketTicker( useMemo(() => ({ enabled: true, loadFull: true }), []), ); const [strategies, setStrategies] = useState([]); const [summary, setSummary] = useState(null); const [trades, setTrades] = useState([]); const refreshPaperData = useCallback(async () => { try { const [sum, tr] = await Promise.all([loadPaperSummary(), loadPaperTrades()]); setSummary(sum); setTrades(tr ?? []); } catch { /* ignore */ } }, []); useEffect(() => { void loadStrategies().then(list => setStrategies(list ?? [])); void refreshPaperData(); }, [refreshPaperData]); useEffect(() => { const onChanged = () => { void refreshPaperData(); }; window.addEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged); return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged); }, [refreshPaperData]); useEffect(() => { return subscribeDesktopSync(state => { if (state.theme) setTheme(state.theme as Theme); if (state.selectedMarket) setDefaultMarket(state.selectedMarket); }); }, []); useEffect(() => { syncDocumentTheme(theme); }, [theme]); useEffect(() => { if (!instance) return; const title = `GoldenChart — 위젯 ${instance.rows}×${instance.cols}`; void getCurrentWindow().setTitle(title); }, [instance?.rows, instance?.cols, instance]); useEffect(() => { if (!instance) return; let unlisten: (() => void) | undefined; void (async () => { unlisten = await getCurrentWindow().onResized(async () => { const size = await getCurrentWindow().innerSize(); setInstance(prev => ( prev ? { ...prev, width: size.width, height: size.height } : prev )); }); })(); return () => { unlisten?.(); }; }, [instance?.id]); const handleClose = useCallback(() => { void getCurrentWindow().close(); }, []); if (!instance) { return

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

; } return ( { void refreshPaperData(); }} paperTradingEnabled paperAutoTradeEnabled={false} chartRealtimeSource="BACKEND_STOMP" >
{}} 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))} />
); }; async function bootstrap() { await initStorage(); ensureDesktopApiBase(); refreshApiBaseFromStorage(); ReactDOM.createRoot(document.getElementById('root')!).render( , ); } void bootstrap();