전략편집기 병합
This commit is contained in:
@@ -30,7 +30,13 @@ import {
|
||||
} from '../utils/strategyEditorLayoutStorage';
|
||||
import LogicExpressionPreview from './strategyEditor/LogicExpressionPreview';
|
||||
import StrategyEditorCanvas, { type FlowLayoutSeed } from './strategyEditor/StrategyEditorCanvas';
|
||||
import StrategyListEditor from './strategyEditor/StrategyListEditor';
|
||||
import { layoutFlushRef } from './strategyEditor/strategyEditorCallbacks';
|
||||
import {
|
||||
loadEditorMode,
|
||||
saveEditorMode,
|
||||
type StrategyEditorMode,
|
||||
} from '../utils/strategyEditorModeStorage';
|
||||
import PaletteChip from './strategyEditor/PaletteChip';
|
||||
import { readStoredSize, storeSize, usePanelResize } from './strategyEditor/usePanelResize';
|
||||
import '../styles/strategyEditor.css';
|
||||
@@ -105,6 +111,7 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
|
||||
terminalHeightRef.current = terminalHeight;
|
||||
|
||||
const [layoutSeedKey, setLayoutSeedKey] = useState('draft:buy:0');
|
||||
const [editorMode, setEditorMode] = useState<StrategyEditorMode>(() => loadEditorMode());
|
||||
|
||||
const onLeftSplitter = usePanelResize(
|
||||
'vertical',
|
||||
@@ -216,13 +223,27 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
|
||||
}, [layoutStrategyKey, schedulePersistFlowLayout]);
|
||||
|
||||
const switchSignalTab = useCallback((tab: 'buy' | 'sell') => {
|
||||
layoutFlushRef.current?.();
|
||||
if (editorMode === 'graph') layoutFlushRef.current?.();
|
||||
persistFlowLayout(layoutStrategyKey);
|
||||
layoutRevisionRef.current += 1;
|
||||
setLayoutSeedKey(`${layoutStrategyKey}:${tab}:${layoutRevisionRef.current}`);
|
||||
setSignalTab(tab);
|
||||
setSelectedNodeId(null);
|
||||
}, [layoutStrategyKey, persistFlowLayout]);
|
||||
}, [layoutStrategyKey, persistFlowLayout, editorMode]);
|
||||
|
||||
const handleEditorModeChange = useCallback((mode: StrategyEditorMode) => {
|
||||
if (mode === editorMode) return;
|
||||
if (editorMode === 'graph') {
|
||||
layoutFlushRef.current?.();
|
||||
persistFlowLayout(layoutStrategyKey);
|
||||
}
|
||||
if (mode === 'list' && (buyOrphans.length > 0 || sellOrphans.length > 0)) {
|
||||
showSnack('목록 방식에서는 미연결(고아) 노드가 표시되지 않습니다', false);
|
||||
}
|
||||
setEditorMode(mode);
|
||||
saveEditorMode(mode);
|
||||
setSelectedNodeId(null);
|
||||
}, [editorMode, layoutStrategyKey, persistFlowLayout, buyOrphans.length, sellOrphans.length]);
|
||||
|
||||
const layoutSeed = useMemo((): FlowLayoutSeed => {
|
||||
const source = signalTab === 'buy' ? buyLayout : sellLayout;
|
||||
@@ -444,6 +465,22 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
|
||||
<span className="se-subtitle">Strategy Builder</span>
|
||||
</div>
|
||||
<div className="se-header-actions">
|
||||
<div className="se-editor-mode" role="group" aria-label="편집 방식">
|
||||
<button
|
||||
type="button"
|
||||
className={`se-editor-mode-btn${editorMode === 'graph' ? ' se-editor-mode-btn--on' : ''}`}
|
||||
onClick={() => handleEditorModeChange('graph')}
|
||||
>
|
||||
그래프 방식
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`se-editor-mode-btn${editorMode === 'list' ? ' se-editor-mode-btn--on' : ''}`}
|
||||
onClick={() => handleEditorModeChange('list')}
|
||||
>
|
||||
목록 방식
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" className="se-btn se-btn--ghost" onClick={handleNew}>+ 새 전략</button>
|
||||
<button type="button" className="se-btn se-btn--gold" onClick={() => setSaveOpen(true)}>저장하기</button>
|
||||
</div>
|
||||
@@ -539,43 +576,54 @@ export default function StrategyEditorPage({ theme, activeIndicators = [] }: Pro
|
||||
{stratName && <span className="se-editing-name">{stratName}</span>}
|
||||
</div>
|
||||
|
||||
<ReactFlowProvider>
|
||||
<StrategyEditorCanvas
|
||||
theme={theme}
|
||||
root={currentRoot}
|
||||
orphans={currentOrphans}
|
||||
onOrphansChange={setCurrentOrphans}
|
||||
def={DEF}
|
||||
signalTab={signalTab}
|
||||
onChange={setCurrentRoot}
|
||||
selectedNodeId={selectedNodeId}
|
||||
onSelectNode={setSelectedNodeId}
|
||||
layoutSeed={layoutSeed}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
{editorMode === 'graph' ? (
|
||||
<>
|
||||
<ReactFlowProvider>
|
||||
<StrategyEditorCanvas
|
||||
theme={theme}
|
||||
root={currentRoot}
|
||||
orphans={currentOrphans}
|
||||
onOrphansChange={setCurrentOrphans}
|
||||
def={DEF}
|
||||
signalTab={signalTab}
|
||||
onChange={setCurrentRoot}
|
||||
selectedNodeId={selectedNodeId}
|
||||
onSelectNode={setSelectedNodeId}
|
||||
layoutSeed={layoutSeed}
|
||||
onLayoutChange={handleLayoutChange}
|
||||
/>
|
||||
</ReactFlowProvider>
|
||||
|
||||
{selectedLogicNode?.type === 'CONDITION' && selectedLogicNode.condition && (
|
||||
<div className="se-node-config-bar">
|
||||
<span className="se-node-config-label">{selectedLogicNode.condition.indicatorType}</span>
|
||||
<CondEditor
|
||||
cond={selectedLogicNode.condition}
|
||||
signalType={signalTab}
|
||||
def={DEF}
|
||||
onChange={c => {
|
||||
if (!selectedNodeId) return;
|
||||
const inOrphans = currentOrphans.some(o => o.id === selectedNodeId);
|
||||
if (inOrphans) {
|
||||
setCurrentOrphans(currentOrphans.map(o => (
|
||||
o.id === selectedNodeId ? updateNode(o, selectedNodeId, n => ({ ...n, condition: c })) : o
|
||||
)));
|
||||
} else if (currentRoot) {
|
||||
setCurrentRoot(updateNode(currentRoot, selectedNodeId, n => ({ ...n, condition: c })));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="se-sync-tip">차트 설정 동기화 · 기간 {getIndicatorPeriodLabel(selectedLogicNode.condition.indicatorType, DEF) || '—'}</span>
|
||||
</div>
|
||||
{selectedLogicNode?.type === 'CONDITION' && selectedLogicNode.condition && (
|
||||
<div className="se-node-config-bar">
|
||||
<span className="se-node-config-label">{selectedLogicNode.condition.indicatorType}</span>
|
||||
<CondEditor
|
||||
cond={selectedLogicNode.condition}
|
||||
signalType={signalTab}
|
||||
def={DEF}
|
||||
onChange={c => {
|
||||
if (!selectedNodeId) return;
|
||||
const inOrphans = currentOrphans.some(o => o.id === selectedNodeId);
|
||||
if (inOrphans) {
|
||||
setCurrentOrphans(currentOrphans.map(o => (
|
||||
o.id === selectedNodeId ? updateNode(o, selectedNodeId, n => ({ ...n, condition: c })) : o
|
||||
)));
|
||||
} else if (currentRoot) {
|
||||
setCurrentRoot(updateNode(currentRoot, selectedNodeId, n => ({ ...n, condition: c })));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span className="se-sync-tip">차트 설정 동기화 · 기간 {getIndicatorPeriodLabel(selectedLogicNode.condition.indicatorType, DEF) || '—'}</span>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<StrategyListEditor
|
||||
root={currentRoot}
|
||||
signalTab={signalTab}
|
||||
def={DEF}
|
||||
onChange={setCurrentRoot}
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user