custom 툴발 적용

This commit is contained in:
Macbook
2026-06-03 01:09:06 +09:00
parent 76e5b12d19
commit 88fe92d8c3
8 changed files with 1110 additions and 22 deletions
+102 -1
View File
@@ -54,6 +54,15 @@ import {
DEFAULT_CHART_OVERLAY_VISIBILITY,
listChartOverlayToggleItems,
} from './utils/chartOverlayVisibility';
import {
type ChartCustomOverlaySelection,
type ChartCustomOverlaySlotId,
createDefaultChartCustomOverlaySelection,
isCustomOverlaySlotActive,
resolveActiveCustomOverlaySlot,
syncChartManagerCustomOverlaysByActiveSlot,
} from './utils/chartCustomOverlay';
import ChartCustomOverlaySettingsModal from './components/ChartCustomOverlaySettingsModal';
import { isUpbitMarket, UPBIT_MARKETS } from './utils/upbitApi';
import { getFavorites, saveFavorites, initFavoritesFromDb, initHoldingsFromDb, FAVORITES_CHANGED_EVENT } from './utils/marketStorage';
import { useChartRealtimeData, type WsStatus } from './hooks/useChartRealtimeData';
@@ -1455,6 +1464,73 @@ function App() {
[overlayVisibility],
);
const [customOverlaySelection, setCustomOverlaySelection] = useState<ChartCustomOverlaySelection>(
() => createDefaultChartCustomOverlaySelection(),
);
const [custom1OverlaySelection, setCustom1OverlaySelection] = useState<ChartCustomOverlaySelection>(
() => createDefaultChartCustomOverlaySelection(),
);
const [activeCustomOverlaySlot, setActiveCustomOverlaySlot] = useState<ChartCustomOverlaySlotId | null>(null);
const [customOverlaySettingsSlot, setCustomOverlaySettingsSlot] = useState<ChartCustomOverlaySlotId | null>(null);
const customOverlayActive = isCustomOverlaySlotActive(activeCustomOverlaySlot, 'custom');
const custom1OverlayActive = isCustomOverlaySlotActive(activeCustomOverlaySlot, 'custom1');
const syncCustomOverlaysToManager = useCallback((mgr: import('./utils/ChartManager').ChartManager) => {
syncChartManagerCustomOverlaysByActiveSlot(
mgr,
activeCustomOverlaySlot,
customOverlaySelection,
custom1OverlaySelection,
);
}, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]);
const handleCustomOverlayActiveChange = useCallback((active: boolean) => {
const nextSlot = resolveActiveCustomOverlaySlot('custom', active, activeCustomOverlaySlot);
setActiveCustomOverlaySlot(nextSlot);
const mgr = managerRef.current;
if (mgr) {
syncChartManagerCustomOverlaysByActiveSlot(
mgr,
nextSlot,
customOverlaySelection,
custom1OverlaySelection,
);
}
}, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]);
const handleCustom1OverlayActiveChange = useCallback((active: boolean) => {
const nextSlot = resolveActiveCustomOverlaySlot('custom1', active, activeCustomOverlaySlot);
setActiveCustomOverlaySlot(nextSlot);
const mgr = managerRef.current;
if (mgr) {
syncChartManagerCustomOverlaysByActiveSlot(
mgr,
nextSlot,
customOverlaySelection,
custom1OverlaySelection,
);
}
}, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]);
const handleSaveCustomOverlaySelection = useCallback((
slot: ChartCustomOverlaySlotId,
selection: ChartCustomOverlaySelection,
) => {
const nextCustomSel = slot === 'custom' ? selection : customOverlaySelection;
const nextCustom1Sel = slot === 'custom1' ? selection : custom1OverlaySelection;
if (slot === 'custom') setCustomOverlaySelection(selection);
else setCustom1OverlaySelection(selection);
setCustomOverlaySettingsSlot(null);
const mgr = managerRef.current;
if (!mgr) return;
syncChartManagerCustomOverlaysByActiveSlot(
mgr,
activeCustomOverlaySlot,
nextCustomSel,
nextCustom1Sel,
);
}, [activeCustomOverlaySlot, customOverlaySelection, custom1OverlaySelection]);
const handleToggleCandleOverlay = useCallback((key: ChartOverlayToggleKey) => {
setOverlayVisibility(prev => {
const next = { ...prev, [key]: !prev[key] };
@@ -2451,7 +2527,9 @@ function App() {
onCrosshair={setLegend}
onTradeOrderRequest={applyTradeFill}
onDataLoaded={() => {
managerRef.current?.setChartOverlayVisibility(overlayVisibilityRef.current);
const mgr = managerRef.current;
mgr?.setChartOverlayVisibility(overlayVisibilityRef.current);
if (mgr) syncCustomOverlaysToManager(mgr);
syncBacktestMarkersRef.current();
}}
onCandlesReady={handleCandlesReady}
@@ -2474,6 +2552,7 @@ function App() {
mgr.setPaneSeparatorOptions(chartPaneSeparator);
mgr.setDisplayTimezone(displayTimezone, timeframe, chartTimeFormat);
mgr.setChartOverlayVisibility(overlayVisibilityRef.current);
syncCustomOverlaysToManager(mgr);
syncBacktestMarkersRef.current();
// 왼쪽 스크롤 감지 → 과거 데이터 추가 로드
mgr.subscribeVisibleLogicalRange(r => {
@@ -2520,8 +2599,30 @@ function App() {
paneSeparatorOptions={chartPaneSeparator}
candleOverlayToggles={candleOverlayToggles}
onToggleCandleOverlay={handleToggleCandleOverlay}
customOverlayActive={customOverlayActive}
onCustomOverlayActiveChange={handleCustomOverlayActiveChange}
onOpenCustomOverlaySettings={() => setCustomOverlaySettingsSlot('custom')}
custom1OverlayActive={custom1OverlayActive}
onCustom1OverlayActiveChange={handleCustom1OverlayActiveChange}
onOpenCustom1OverlaySettings={() => setCustomOverlaySettingsSlot('custom1')}
showHoverToolbar={chartHoverToolbarVisible}
/>
{customOverlaySettingsSlot === 'custom' && (
<ChartCustomOverlaySettingsModal
title="Custom 표시 설정"
selection={customOverlaySelection}
onSave={sel => handleSaveCustomOverlaySelection('custom', sel)}
onCancel={() => setCustomOverlaySettingsSlot(null)}
/>
)}
{customOverlaySettingsSlot === 'custom1' && (
<ChartCustomOverlaySettingsModal
title="Custom1 표시 설정"
selection={custom1OverlaySelection}
onSave={sel => handleSaveCustomOverlaySelection('custom1', sel)}
onCancel={() => setCustomOverlaySettingsSlot(null)}
/>
)}
{isSingleLoadingMore && (
<div className="chart-history-loading">
<div className="loading-spinner" style={{ width: 14, height: 14 }} />