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
@@ -0,0 +1,267 @@
import React, { useState, useCallback, useRef, useEffect } from 'react';
import {
type ChartCustomOverlaySelection,
type CustomOverlaySectionId,
cloneChartCustomOverlaySelection,
smaMaLabel,
BB_CUSTOM_LABELS,
ICHIMOKU_CUSTOM_ROWS,
isCustomSectionAllSelected,
setCustomSectionAllSelected,
} from '../utils/chartCustomOverlay';
import { SMA_MA_COUNT, smaPlotId } from '../utils/smaConfig';
import { useDraggablePanel } from '../hooks/useDraggablePanel';
interface ChartCustomOverlaySettingsModalProps {
title?: string;
selection: ChartCustomOverlaySelection;
onSave: (selection: ChartCustomOverlaySelection) => void;
onCancel: () => void;
}
const SECTION_META: Array<{
id: CustomOverlaySectionId;
ko: string;
en: string;
cols: 2 | 3;
}> = [
{ id: 'sma', ko: '이동평균선', en: 'Simple Moving Average (SMA)', cols: 3 },
{ id: 'bollinger', ko: '볼린저 밴드', en: 'Bollinger Bands', cols: 2 },
{ id: 'ichimoku', ko: '일목균형표', en: 'Ichimoku Cloud', cols: 3 },
{ id: 'candle', ko: '캔들차트', en: 'Candlestick', cols: 2 },
];
const ALL_SECTIONS = new Set<CustomOverlaySectionId>(SECTION_META.map(s => s.id));
const GridToggle: React.FC<{
label: string;
checked: boolean;
onChange: (v: boolean) => void;
}> = ({ label, checked, onChange }) => (
<div className="ccos-grid-item">
<label className="ibsm-toggle ccos-grid-toggle" onClick={e => e.stopPropagation()}>
<input
type="checkbox"
checked={checked}
onChange={e => onChange(e.target.checked)}
/>
<span className="ism-toggle-slider" />
</label>
<span className="ccos-grid-label">{label}</span>
</div>
);
const ChartCustomOverlaySettingsModal: React.FC<ChartCustomOverlaySettingsModalProps> = ({
title = 'Custom 표시 설정',
selection,
onSave,
onCancel,
}) => {
const draftRef = useRef(cloneChartCustomOverlaySelection(selection));
const [, bump] = useState(0);
const refresh = useCallback(() => bump(n => n + 1), []);
const [expanded, setExpanded] = useState<Set<CustomOverlaySectionId>>(
() => new Set(ALL_SECTIONS),
);
const {
panelRef,
dragging,
onHeaderPointerDown,
headerTouchStyle,
panelStyle,
headerCursor,
} = useDraggablePanel({ centerOnMount: true });
useEffect(() => {
draftRef.current = cloneChartCustomOverlaySelection(selection);
setExpanded(new Set(ALL_SECTIONS));
refresh();
}, [selection, refresh]);
const draft = draftRef.current;
const patch = useCallback((patchFn: (d: ChartCustomOverlaySelection) => void) => {
patchFn(draftRef.current);
refresh();
}, [refresh]);
const toggleSectionExpand = useCallback((id: CustomOverlaySectionId) => {
setExpanded(prev => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
}, []);
const toggleSectionAll = useCallback((id: CustomOverlaySectionId) => {
patch(d => {
const allOn = isCustomSectionAllSelected(d, id);
setCustomSectionAllSelected(d, id, !allOn);
});
}, [patch]);
const renderSectionItems = (sectionId: CustomOverlaySectionId) => {
switch (sectionId) {
case 'sma':
return Array.from({ length: SMA_MA_COUNT }, (_, i) => {
const pid = smaPlotId(i);
return (
<GridToggle
key={pid}
label={smaMaLabel(i)}
checked={draft.smaPlots[pid] !== false}
onChange={v => patch(d => { d.smaPlots[pid] = v; })}
/>
);
});
case 'bollinger':
return (
<>
<GridToggle
label={BB_CUSTOM_LABELS.plot0}
checked={draft.bollinger.basis}
onChange={v => patch(d => { d.bollinger.basis = v; })}
/>
<GridToggle
label={BB_CUSTOM_LABELS.plot1}
checked={draft.bollinger.upper}
onChange={v => patch(d => { d.bollinger.upper = v; })}
/>
<GridToggle
label={BB_CUSTOM_LABELS.plot2}
checked={draft.bollinger.lower}
onChange={v => patch(d => { d.bollinger.lower = v; })}
/>
<GridToggle
label="백그라운드 그리기"
checked={draft.bollinger.background}
onChange={v => patch(d => { d.bollinger.background = v; })}
/>
</>
);
case 'ichimoku':
return ICHIMOKU_CUSTOM_ROWS.map(row => (
<GridToggle
key={row.key}
label={row.label}
checked={draft.ichimoku[row.key]}
onChange={v => patch(d => { d.ichimoku[row.key] = v; })}
/>
));
case 'candle':
return (
<GridToggle
label="캔들차트"
checked={draft.candle}
onChange={v => patch(d => { d.candle = v; })}
/>
);
default:
return null;
}
};
return (
<div
className="ibsm-overlay"
onMouseDown={e => { if (e.target === e.currentTarget) onCancel(); }}
>
<div
ref={panelRef}
className="ibsm-dialog ibsm-dialog-wide ccos-dialog"
onMouseDown={e => e.stopPropagation()}
style={{
...panelStyle,
zIndex: 5002,
cursor: dragging ? 'grabbing' : undefined,
}}
>
<div
className="gc-popup-header ibsm-header"
onPointerDown={onHeaderPointerDown}
style={{ cursor: headerCursor, ...headerTouchStyle }}
>
<span className="gc-popup-title ibsm-header-title">{title}</span>
<div className="ibsm-header-actions">
<button type="button" className="gc-popup-close ibsm-icon-btn" onClick={onCancel} title="닫기">
</button>
</div>
</div>
<p className="ibsm-hint ccos-hint">
Custom <strong></strong> . on/off와는 ,
·.
</p>
<div className="ibsm-body ccos-body">
{SECTION_META.map(meta => {
const isExpanded = expanded.has(meta.id);
const allOn = isCustomSectionAllSelected(draft, meta.id);
return (
<div
key={meta.id}
className={`ibsm-card ibsm-main-row${allOn ? ' ibsm-main-row--active' : ''}${isExpanded ? ' ibsm-card-expanded' : ''}`}
>
<div className="ibsm-card-main ibsm-main-row-head">
<div className="ibsm-main-row-info">
<span className="ibsm-main-row-ko">{meta.ko}</span>
<span className="ibsm-main-row-en">{meta.en}</span>
</div>
<div className="ibsm-main-row-badges">
<span className={allOn ? 'ibsm-main-badge ibsm-main-badge--on' : 'ibsm-main-badge'}>
{allOn ? '전체 선택' : '일부 선택'}
</span>
</div>
<label
className="ibsm-toggle"
title={allOn ? '전체 해제' : '전체 선택'}
onClick={e => e.stopPropagation()}
>
<input
type="checkbox"
checked={allOn}
onChange={() => toggleSectionAll(meta.id)}
/>
<span className="ism-toggle-slider" />
</label>
<button
type="button"
className="ibsm-expand"
onClick={() => toggleSectionExpand(meta.id)}
aria-expanded={isExpanded}
title={isExpanded ? '접기' : '펼치기'}
>
{isExpanded ? '▲' : '▼'}
</button>
</div>
{isExpanded && (
<div className={`ibsm-card-expand ccos-item-grid ccos-item-grid--${meta.cols}`}>
{renderSectionItems(meta.id)}
</div>
)}
</div>
);
})}
</div>
<div className="ibsm-footer">
<button type="button" className="ism-btn-cancel" onClick={onCancel}>
</button>
<button
type="button"
className="ism-btn-ok"
onClick={() => onSave(cloneChartCustomOverlaySelection(draftRef.current))}
>
</button>
</div>
</div>
</div>
);
};
export default ChartCustomOverlaySettingsModal;
+146 -1
View File
@@ -3,6 +3,7 @@
* pane 하단: 드래그 핸들 + 펼침메뉴(분리·복사·전체보기·종료)
*/
import React, { useState, useEffect, useCallback, useRef } from 'react';
import { createPortal } from 'react-dom';
import type { IndicatorConfig } from '../types';
import { ChartManager } from '../utils/ChartManager';
import { getPaneHostId } from '../utils/indicatorPaneMerge';
@@ -35,6 +36,13 @@ export interface ChartRightToolbarProps {
onToggleHidden?: (id: string) => void;
/** 지표명 툴바 — 설정 모달 */
onSettings?: (id: string) => void;
/** Custom / Custom1 오버레이 — 적용 여부 (동시 적용 불가, 설정은 슬롯별 분리) */
customOverlayActive?: boolean;
onCustomOverlayActiveChange?: (active: boolean) => void;
onOpenCustomOverlaySettings?: () => void;
custom1OverlayActive?: boolean;
onCustom1OverlayActiveChange?: (active: boolean) => void;
onOpenCustom1OverlaySettings?: () => void;
}
const BTN_SIZE = 32;
@@ -159,6 +167,24 @@ const IconEye = ({ hidden }: { hidden?: boolean }) => hidden ? (
</svg>
);
const CUSTOM_MENU_ID = '__custom_overlay__';
const CUSTOM1_MENU_ID = '__custom1_overlay__';
const IconCustom = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="1.5" strokeLinecap="round">
<path d="M2 4h10M2 7h10M2 10h6"/>
<circle cx="10.5" cy="10" r="1.2" fill="currentColor" stroke="none"/>
</svg>
);
const IconCheck = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"
stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<polyline points="2,7.5 5.5,11 12,3"/>
</svg>
);
const IconGear = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.4">
<circle cx="7" cy="7" r="2.3"/>
@@ -166,6 +192,62 @@ const IconGear = () => (
</svg>
);
const CustomOverlayFlyoutMenu: React.FC<{
title: string;
flyoutPos: { top: number; left: number };
overlayActive: boolean;
onToggleActive: () => void;
onOpenSettings: () => void;
onCancelClose: () => void;
onScheduleClose: () => void;
onCloseFlyout: () => void;
}> = ({
title,
flyoutPos,
overlayActive,
onToggleActive,
onOpenSettings,
onCancelClose,
onScheduleClose,
onCloseFlyout,
}) => createPortal(
<div
className="tv-side-flyout tv-side-flyout--right-anchor tv-side-flyout--custom-overlay"
style={{ top: flyoutPos.top, left: flyoutPos.left }}
onMouseEnter={onCancelClose}
onMouseLeave={onScheduleClose}
>
<div className="tv-side-flyout-header">{title}</div>
<button
type="button"
className={`tv-side-flyout-item${overlayActive ? ' active' : ''}`}
onClick={e => {
e.stopPropagation();
onToggleActive();
onCloseFlyout();
}}
>
<span className="tv-side-flyout-icon"><IconCheck /></span>
<span className="tv-side-flyout-title">
{overlayActive ? '미적용' : '적용'}
</span>
</button>
<button
type="button"
className="tv-side-flyout-item"
onClick={e => {
e.stopPropagation();
onOpenSettings();
onCloseFlyout();
}}
>
<span className="tv-side-flyout-icon"><IconGear /></span>
<span className="tv-side-flyout-title"></span>
</button>
</div>,
document.body,
);
interface MenuItemDef {
key: string;
title: string;
@@ -266,6 +348,12 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
manager, indicators, chartHeight, paused = false,
candleOverlayToggles,
onToggleCandleOverlay,
customOverlayActive = false,
onCustomOverlayActiveChange,
onOpenCustomOverlaySettings,
custom1OverlayActive = false,
onCustom1OverlayActiveChange,
onOpenCustom1OverlaySettings,
onSplit, onRemove, onDuplicate,
onExpand, onRestore, focusedId,
onDragStart, onToggleHidden, onSettings,
@@ -341,6 +429,10 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
const candlePane = paneLayouts.find(l => l.paneIndex === 0);
const showOverlayIcons = !!(candleOverlayToggles?.length && candlePane && onToggleCandleOverlay);
const showCustomBtn = !!(showOverlayIcons && onCustomOverlayActiveChange && onOpenCustomOverlaySettings);
const showCustom1Btn = !!(showOverlayIcons && onCustom1OverlayActiveChange && onOpenCustom1OverlaySettings);
const customMenuOpen = openMenuId === CUSTOM_MENU_ID;
const custom1MenuOpen = openMenuId === CUSTOM1_MENU_ID;
const focusedLayoutId = focusedId
? resolveFocusedPaneLayoutId(focusedId, indicators)
@@ -395,6 +487,33 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
{OVERLAY_ICONS[item.key]}
</button>
))}
{showCustomBtn && (
<button
type="button"
className={`tv-side-btn tv-side-group-btn chart-right-toolbar-btn chart-right-toolbar-btn--custom${customOverlayActive ? ' active' : ''}${customMenuOpen ? ' active' : ''}`}
title="Custom 표시"
style={{ marginTop: BTN_GAP }}
onMouseEnter={e => openFlyout(CUSTOM_MENU_ID, e.currentTarget)}
onMouseLeave={scheduleClose}
>
<IconCustom />
<span className="tv-side-expand-arrow" />
</button>
)}
{showCustom1Btn && (
<button
type="button"
className={`tv-side-btn tv-side-group-btn chart-right-toolbar-btn chart-right-toolbar-btn--custom1${custom1OverlayActive ? ' active' : ''}${custom1MenuOpen ? ' active' : ''}`}
title="Custom1 표시"
style={{ marginTop: BTN_GAP }}
onMouseEnter={e => openFlyout(CUSTOM1_MENU_ID, e.currentTarget)}
onMouseLeave={scheduleClose}
>
<IconCustom />
<span className="chart-right-toolbar-custom1-badge">1</span>
<span className="tv-side-expand-arrow" />
</button>
)}
</div>
)}
@@ -457,7 +576,33 @@ const ChartRightToolbar: React.FC<ChartRightToolbarProps> = ({
})}
</div>
{openItem && openMenuId && (() => {
{customMenuOpen && showCustomBtn && (
<CustomOverlayFlyoutMenu
title="Custom"
flyoutPos={flyoutPos}
overlayActive={customOverlayActive}
onToggleActive={() => onCustomOverlayActiveChange?.(!customOverlayActive)}
onOpenSettings={() => onOpenCustomOverlaySettings?.()}
onCancelClose={cancelClose}
onScheduleClose={scheduleClose}
onCloseFlyout={closeFlyout}
/>
)}
{custom1MenuOpen && showCustom1Btn && (
<CustomOverlayFlyoutMenu
title="Custom1"
flyoutPos={flyoutPos}
overlayActive={custom1OverlayActive}
onToggleActive={() => onCustom1OverlayActiveChange?.(!custom1OverlayActive)}
onOpenSettings={() => onOpenCustom1OverlaySettings?.()}
onCancelClose={cancelClose}
onScheduleClose={scheduleClose}
onCloseFlyout={closeFlyout}
/>
)}
{openItem && openMenuId && openMenuId !== CUSTOM_MENU_ID && openMenuId !== CUSTOM1_MENU_ID && (() => {
const menuItems = buildMenuItems(openItem, indicators, paneItems, {
focusedId, onSplit, onDuplicate, onExpand, onRestore, onRemove,
onToggleHidden, onSettings,
+101
View File
@@ -51,6 +51,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 './ChartCustomOverlaySettingsModal';
import { saveSlot } from '../utils/backendApi';
import type {
OHLCVBar, ChartType, Theme, Timeframe,
@@ -672,6 +681,73 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
[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: 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] };
@@ -937,6 +1013,7 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
mgr.setVolumeVisible(chartVolumeVisible);
if (chartPaneSeparator) mgr.setPaneSeparatorOptions(chartPaneSeparator);
mgr.setChartOverlayVisibility(overlayVisibilityRef.current);
syncCustomOverlaysToManager(mgr);
// Time sync: 가시 시간 범위 변화 구독
mgr.subscribeVisibleTimeRange(r => {
if (!r || isSyncingTimeRef.current) return;
@@ -962,6 +1039,7 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
const mgr = managerRef.current;
if (!mgr) return;
mgr.setChartOverlayVisibility(overlayVisibilityRef.current);
syncCustomOverlaysToManager(mgr);
if (skipSyncApplyRef.current) {
skipSyncApplyRef.current = false;
const fb = mgr.hasIchimoku() ? 28 : 0;
@@ -1016,8 +1094,31 @@ const ChartSlot = forwardRef<ChartSlotHandle, ChartSlotProps>(function ChartSlot
onFullView={onFullView ? () => onFullView(symbolRef.current) : undefined}
candleOverlayToggles={compactMode ? undefined : candleOverlayToggles}
onToggleCandleOverlay={compactMode ? undefined : handleToggleCandleOverlay}
customOverlayActive={compactMode ? undefined : customOverlayActive}
onCustomOverlayActiveChange={compactMode ? undefined : handleCustomOverlayActiveChange}
onOpenCustomOverlaySettings={compactMode ? undefined : () => setCustomOverlaySettingsSlot('custom')}
custom1OverlayActive={compactMode ? undefined : custom1OverlayActive}
onCustom1OverlayActiveChange={compactMode ? undefined : handleCustom1OverlayActiveChange}
onOpenCustom1OverlaySettings={compactMode ? undefined : () => setCustomOverlaySettingsSlot('custom1')}
/>
{customOverlaySettingsSlot === 'custom' && !compactMode && (
<ChartCustomOverlaySettingsModal
title="Custom 표시 설정"
selection={customOverlaySelection}
onSave={sel => handleSaveCustomOverlaySelection('custom', sel)}
onCancel={() => setCustomOverlaySettingsSlot(null)}
/>
)}
{customOverlaySettingsSlot === 'custom1' && !compactMode && (
<ChartCustomOverlaySettingsModal
title="Custom1 표시 설정"
selection={custom1OverlaySelection}
onSave={sel => handleSaveCustomOverlaySelection('custom1', sel)}
onCancel={() => setCustomOverlaySettingsSlot(null)}
/>
)}
{/* 컨텍스트 툴바 */}
{ctxToolbar && (() => {
const cfg = ctxToolbar.entryId !== '__main__'
+18
View File
@@ -165,6 +165,12 @@ interface TradingChartProps {
/** 캔들 오버레이(이동평균·일목·볼린저) 표시 토글 — 우측 툴바 상단 */
candleOverlayToggles?: CandleOverlayToggleItem[];
onToggleCandleOverlay?: (key: import('../utils/chartOverlayVisibility').ChartOverlayToggleKey) => void;
customOverlayActive?: boolean;
onCustomOverlayActiveChange?: (active: boolean) => void;
onOpenCustomOverlaySettings?: () => void;
custom1OverlayActive?: boolean;
onCustom1OverlayActiveChange?: (active: boolean) => void;
onOpenCustom1OverlaySettings?: () => void;
}
const TradingChart: React.FC<TradingChartProps> = ({
@@ -209,6 +215,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
showCandlePaneControls = true,
candleOverlayToggles,
onToggleCandleOverlay,
customOverlayActive,
onCustomOverlayActiveChange,
onOpenCustomOverlaySettings,
custom1OverlayActive,
onCustom1OverlayActiveChange,
onOpenCustom1OverlaySettings,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const wrapperRef = useRef<HTMLDivElement>(null); // 스크롤 래퍼
@@ -1609,6 +1621,12 @@ const TradingChart: React.FC<TradingChartProps> = ({
paused={paneLegendPaused}
candleOverlayToggles={candleOverlayToggles}
onToggleCandleOverlay={onToggleCandleOverlay}
customOverlayActive={customOverlayActive}
onCustomOverlayActiveChange={onCustomOverlayActiveChange}
onOpenCustomOverlaySettings={onOpenCustomOverlaySettings}
custom1OverlayActive={custom1OverlayActive}
onCustom1OverlayActiveChange={onCustom1OverlayActiveChange}
onOpenCustom1OverlaySettings={onOpenCustom1OverlaySettings}
onSplit={onSplitIndicatorPane}
onRemove={onRemoveIndicator}
onDuplicate={onDuplicateIndicator}