desktop 앱 적용
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import './forceServerApi';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window';
|
||||
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 '@frontend/styles/paperDashboard.css';
|
||||
import '@frontend/styles/widgetDashboard.css';
|
||||
import '@frontend/styles/floatingWidget.css';
|
||||
|
||||
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<FloatingWidgetInstance | null>(initial);
|
||||
const sync = readDesktopSync();
|
||||
const [theme, setTheme] = useState<Theme>((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<StrategyDto[]>([]);
|
||||
const [summary, setSummary] = useState<PaperSummaryDto | null>(null);
|
||||
const [trades, setTrades] = useState<PaperTradeDto[]>([]);
|
||||
|
||||
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(() => {
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
}, [theme]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
void getCurrentWindow().close();
|
||||
}, []);
|
||||
|
||||
if (!instance) {
|
||||
return <p className="wd-widget-empty">위젯 정보를 불러올 수 없습니다.</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<WidgetDashboardProvider
|
||||
theme={theme}
|
||||
tickers={tickers}
|
||||
marketInfos={marketInfos}
|
||||
marketLoading={marketLoading}
|
||||
usdRate={usdRate}
|
||||
defaultMarket={defaultMarket}
|
||||
strategies={strategies}
|
||||
summary={summary}
|
||||
trades={trades}
|
||||
refreshPaperData={() => { void refreshPaperData(); }}
|
||||
paperTradingEnabled
|
||||
paperAutoTradeEnabled={false}
|
||||
chartRealtimeSource="BACKEND_STOMP"
|
||||
>
|
||||
<div className="fw-widget-native-root">
|
||||
<FloatingWidgetWindow
|
||||
instance={instance}
|
||||
focused
|
||||
onClose={handleClose}
|
||||
onFocus={() => {}}
|
||||
onUpdateSlots={slots => setInstance(prev => (prev ? { ...prev, slots } : prev))}
|
||||
onUpdateSize={(width, height) => setInstance(prev => (prev ? { ...prev, width, height } : prev))}
|
||||
onUpdateGridFr={(rowFr, colFr) => setInstance(prev => (prev ? { ...prev, rowFr, colFr } : prev))}
|
||||
/>
|
||||
</div>
|
||||
</WidgetDashboardProvider>
|
||||
);
|
||||
};
|
||||
|
||||
async function bootstrap() {
|
||||
await initStorage();
|
||||
ensureDesktopApiBase();
|
||||
refreshApiBaseFromStorage();
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<WidgetApp />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
}
|
||||
|
||||
void bootstrap();
|
||||
Reference in New Issue
Block a user