desktop 앱 적용
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { isDesktop } from '../utils/platform';
|
||||
import {
|
||||
checkDesktopUpdates,
|
||||
getDesktopAppVersion,
|
||||
} from '../utils/desktopBridge';
|
||||
|
||||
/** PC(Tauri) 앱 — 버전 표시 및 업데이트 확인 */
|
||||
const DesktopUpdatePanel: React.FC = () => {
|
||||
const [version, setVersion] = useState<string>('—');
|
||||
const [status, setStatus] = useState<string>('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktop()) return;
|
||||
void getDesktopAppVersion().then(v => {
|
||||
if (v) setVersion(v);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleCheck = useCallback(async () => {
|
||||
setBusy(true);
|
||||
setStatus('업데이트 확인 중…');
|
||||
try {
|
||||
const res = await checkDesktopUpdates();
|
||||
setStatus(res.message ?? (res.available ? `새 버전 ${res.version}` : '최신 버전입니다.'));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!isDesktop()) return null;
|
||||
|
||||
return (
|
||||
<div className="stg-section">
|
||||
<h3 className="stg-section-title">PC 앱 (GoldenChart Desktop)</h3>
|
||||
<div className="stg-row">
|
||||
<div className="stg-row-label">
|
||||
<strong>현재 버전</strong>
|
||||
<p className="stg-row-desc">설치된 데스크톱 클라이언트 버전입니다.</p>
|
||||
</div>
|
||||
<div className="stg-row-control">
|
||||
<span>{version}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="stg-row">
|
||||
<div className="stg-row-label">
|
||||
<strong>업데이트</strong>
|
||||
<p className="stg-row-desc">서버에서 새 버전을 확인하고 다운로드합니다.</p>
|
||||
</div>
|
||||
<div className="stg-row-control">
|
||||
<button type="button" className="stg-btn-secondary" disabled={busy} onClick={() => { void handleCheck(); }}>
|
||||
{busy ? '확인 중…' : '업데이트 확인'}
|
||||
</button>
|
||||
{status && <span className="stg-hint" style={{ marginLeft: 8 }}>{status}</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DesktopUpdatePanel;
|
||||
@@ -17,6 +17,7 @@ import type { IndicatorConfig } from '../types';
|
||||
import type { PlotDef, HLineDef } from '../utils/indicatorRegistry';
|
||||
import type { IchimokuCloudColors } from '../utils/ichimokuConfig';
|
||||
import ChartTimeFormatPicker from './ChartTimeFormatPicker';
|
||||
import DesktopUpdatePanel from './DesktopUpdatePanel';
|
||||
import {
|
||||
TRADE_ALERT_SOUND_OPTIONS,
|
||||
normalizeTradeAlertSoundId,
|
||||
@@ -911,6 +912,8 @@ const GeneralPanel: React.FC<{
|
||||
</label>
|
||||
</SettingRow>
|
||||
</SettingSection>
|
||||
|
||||
<DesktopUpdatePanel />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
/**
|
||||
* 플로팅 위젯 레이어 — 메뉴바에서 다중 팝업 위젯 실행
|
||||
*/
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import type { Theme } from '../../types';
|
||||
@@ -24,6 +21,9 @@ import {
|
||||
import type { WidgetSlot } from '../../types/widgetDashboard';
|
||||
import FloatingWidgetLayoutPicker from './FloatingWidgetLayoutPicker';
|
||||
import FloatingWidgetWindow from './FloatingWidgetWindow';
|
||||
import { isDesktop } from '../../utils/platform';
|
||||
import { getDesktopBridge } from '../../utils/desktopBridge';
|
||||
import { writeDesktopSync } from '../../utils/desktopSync';
|
||||
import '../../styles/paperDashboard.css';
|
||||
import '../../styles/widgetDashboard.css';
|
||||
import '../../styles/floatingWidget.css';
|
||||
@@ -40,6 +40,7 @@ interface Props {
|
||||
onPaperOrderFilled?: () => void;
|
||||
chartRealtimeSource?: ChartRealtimeSource;
|
||||
onInstancesChange?: (count: number) => void;
|
||||
selectedStrategyId?: number | null;
|
||||
}
|
||||
|
||||
const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
@@ -52,7 +53,9 @@ const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
onPaperOrderFilled,
|
||||
chartRealtimeSource = 'BACKEND_STOMP',
|
||||
onInstancesChange,
|
||||
selectedStrategyId = null,
|
||||
}) => {
|
||||
const desktopMode = isDesktop();
|
||||
const [instances, setInstances] = useState<FloatingWidgetInstance[]>([]);
|
||||
const [focusedId, setFocusedId] = useState<string | null>(null);
|
||||
const zCounterRef = useRef(BASE_Z + 100);
|
||||
@@ -83,14 +86,28 @@ const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
return () => window.removeEventListener(PAPER_TRADES_CHANGED_EVENT, onChanged);
|
||||
}, [refreshPaperData]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!desktopMode) return;
|
||||
writeDesktopSync({
|
||||
selectedMarket: defaultMarket,
|
||||
selectedStrategyId,
|
||||
theme,
|
||||
});
|
||||
}, [desktopMode, defaultMarket, selectedStrategyId, theme]);
|
||||
|
||||
const bringToFront = useCallback((id: string) => {
|
||||
if (desktopMode) {
|
||||
void getDesktopBridge()?.focusWidgetWindow(id);
|
||||
setFocusedId(id);
|
||||
return;
|
||||
}
|
||||
zCounterRef.current += 1;
|
||||
const nextZ = zCounterRef.current;
|
||||
setFocusedId(id);
|
||||
setInstances(prev => prev.map(inst =>
|
||||
inst.id === id ? { ...inst, zIndex: nextZ } : inst,
|
||||
));
|
||||
}, []);
|
||||
}, [desktopMode]);
|
||||
|
||||
const handleLayoutSelect = useCallback((preset: FloatingWidgetLayoutPreset) => {
|
||||
zCounterRef.current += 1;
|
||||
@@ -102,14 +119,25 @@ const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
zCounterRef.current,
|
||||
);
|
||||
inst.zIndex = zCounterRef.current;
|
||||
|
||||
if (desktopMode) {
|
||||
void getDesktopBridge()?.openWidgetWindow(inst);
|
||||
setInstances(prev => [...prev, inst]);
|
||||
setFocusedId(inst.id);
|
||||
return;
|
||||
}
|
||||
|
||||
setInstances(prev => [...prev, inst]);
|
||||
setFocusedId(inst.id);
|
||||
}, [instances.length]);
|
||||
}, [instances.length, desktopMode]);
|
||||
|
||||
const handleClose = useCallback((id: string) => {
|
||||
if (desktopMode) {
|
||||
void getDesktopBridge()?.closeWidgetWindow(id);
|
||||
}
|
||||
setInstances(prev => prev.filter(i => i.id !== id));
|
||||
setFocusedId(prev => (prev === id ? null : prev));
|
||||
}, []);
|
||||
}, [desktopMode]);
|
||||
|
||||
const handleUpdateSlots = useCallback((id: string, slots: WidgetSlot[]) => {
|
||||
setInstances(prev => prev.map(inst => (inst.id === id ? { ...inst, slots } : inst)));
|
||||
@@ -122,9 +150,9 @@ const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
}, []);
|
||||
|
||||
const handleUpdateGridFr = useCallback((id: string, rowFr: number[], colFr: number[]) => {
|
||||
setInstances(prev => prev.map(inst => (
|
||||
inst.id === id ? { ...inst, rowFr, colFr } : inst
|
||||
)));
|
||||
setInstances(prev => prev.map(inst =>
|
||||
inst.id === id ? { ...inst, rowFr, colFr } : inst,
|
||||
));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -133,6 +161,42 @@ const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
|
||||
if (instances.length === 0 && !layoutPickerOpen) return null;
|
||||
|
||||
const picker = (
|
||||
<FloatingWidgetLayoutPicker
|
||||
open={layoutPickerOpen}
|
||||
onClose={() => onLayoutPickerOpenChange(false)}
|
||||
onSelect={handleLayoutSelect}
|
||||
/>
|
||||
);
|
||||
|
||||
if (desktopMode) {
|
||||
return (
|
||||
<WidgetDashboardProvider
|
||||
theme={theme}
|
||||
tickers={tickers}
|
||||
marketInfos={marketInfos}
|
||||
marketLoading={marketLoading}
|
||||
usdRate={usdRate}
|
||||
defaultMarket={defaultMarket}
|
||||
strategies={strategies}
|
||||
summary={summary}
|
||||
trades={trades}
|
||||
refreshPaperData={() => { void refreshPaperData(); }}
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={onPaperOrderFilled}
|
||||
chartRealtimeSource={chartRealtimeSource}
|
||||
>
|
||||
{layoutPickerOpen && createPortal(
|
||||
<div className="fw-layer fw-layer--picker-only" aria-label="위젯 레이아웃 선택">
|
||||
{picker}
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
</WidgetDashboardProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<WidgetDashboardProvider
|
||||
theme={theme}
|
||||
@@ -165,11 +229,7 @@ const FloatingWidgetLayer: React.FC<Props> = ({
|
||||
/>
|
||||
))}
|
||||
|
||||
<FloatingWidgetLayoutPicker
|
||||
open={layoutPickerOpen}
|
||||
onClose={() => onLayoutPickerOpenChange(false)}
|
||||
onSelect={handleLayoutSelect}
|
||||
/>
|
||||
{picker}
|
||||
</div>,
|
||||
document.body,
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user