전략지표 설정 삭제버튼
This commit is contained in:
@@ -42,6 +42,7 @@ import { persistStrategyEditorState } from '../utils/strategyTimeframeSync';
|
||||
import {
|
||||
START_NODE_ID,
|
||||
defaultStartMeta,
|
||||
isStartNodeId,
|
||||
type StartCombineOp,
|
||||
} from '../utils/strategyStartNodes';
|
||||
import IndicatorPaletteTab from './strategyEditor/IndicatorPaletteTab';
|
||||
@@ -114,7 +115,7 @@ import {
|
||||
} from '../utils/strategyEditorLayoutStorage';
|
||||
import LogicExpressionPreview from './strategyEditor/LogicExpressionPreview';
|
||||
import LogicExpressionNarrative from './strategyEditor/LogicExpressionNarrative';
|
||||
import StrategyEditorCanvas, { type FlowLayoutSeed } from './strategyEditor/StrategyEditorCanvas';
|
||||
import StrategyEditorCanvas, { type FlowLayoutSeed, type StrategyEditorCanvasHandle } from './strategyEditor/StrategyEditorCanvas';
|
||||
import StrategyListEditor from './strategyEditor/StrategyListEditor';
|
||||
import StartCombineOpControl from './strategyEditor/StartCombineOpControl';
|
||||
import { layoutFlushRef } from './strategyEditor/strategyEditorCallbacks';
|
||||
@@ -333,6 +334,7 @@ export default function StrategyEditorPage({
|
||||
const [descOpen, setDescOpen] = useState(false);
|
||||
const [deleteId, setDeleteId] = useState<number | null>(null);
|
||||
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
|
||||
const canvasRef = useRef<StrategyEditorCanvasHandle>(null);
|
||||
const [buyOrphans, setBuyOrphans] = useState<LogicNode[]>([]);
|
||||
const [sellOrphans, setSellOrphans] = useState<LogicNode[]>([]);
|
||||
const [snack, setSnack] = useState<{ msg: string; ok: boolean } | null>(null);
|
||||
@@ -460,6 +462,11 @@ export default function StrategyEditorPage({
|
||||
);
|
||||
}, [selectedNodeId, currentRoot, currentOrphans, currentLayout.extraRoots]);
|
||||
|
||||
const handleDeleteSelectedNode = useCallback(() => {
|
||||
if (!selectedNodeId || isStartNodeId(selectedNodeId)) return;
|
||||
canvasRef.current?.deleteNode(selectedNodeId);
|
||||
}, [selectedNodeId]);
|
||||
|
||||
const stochPairForest = useMemo(
|
||||
() => [
|
||||
currentRoot,
|
||||
@@ -2041,6 +2048,7 @@ export default function StrategyEditorPage({
|
||||
<>
|
||||
<ReactFlowProvider>
|
||||
<StrategyEditorCanvas
|
||||
ref={canvasRef}
|
||||
theme={theme}
|
||||
root={currentRoot}
|
||||
orphans={currentOrphans}
|
||||
@@ -2064,11 +2072,22 @@ export default function StrategyEditorPage({
|
||||
|
||||
{selectedLogicNode?.type === 'CONDITION' && selectedLogicNode.condition && (
|
||||
<div className="se-node-config-bar">
|
||||
<span className="se-node-config-label">
|
||||
{stochPairEditForSelected && !stochPairEditForSelected.stochLocked
|
||||
? 'Stoch 과열×보조'
|
||||
: selectedLogicNode.condition.indicatorType}
|
||||
</span>
|
||||
<div className="se-node-config-head">
|
||||
<span className="se-node-config-label">
|
||||
{stochPairEditForSelected && !stochPairEditForSelected.stochLocked
|
||||
? 'Stoch 과열×보조'
|
||||
: selectedLogicNode.condition.indicatorType}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
className="se-node-config-del"
|
||||
title="삭제"
|
||||
aria-label="지표 삭제"
|
||||
onClick={handleDeleteSelectedNode}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<CondEditor
|
||||
cond={selectedLogicNode.condition}
|
||||
signalType={signalTab}
|
||||
@@ -2106,7 +2125,18 @@ export default function StrategyEditorPage({
|
||||
|
||||
{selectedLogicNode && isStochOverboughtPairRoot(selectedLogicNode) && selectedLogicNode.stochPair && (
|
||||
<div className="se-node-config-bar se-node-config-bar--stoch-pair">
|
||||
<span className="se-node-config-label">Stoch 과열×보조</span>
|
||||
<div className="se-node-config-head">
|
||||
<span className="se-node-config-label">Stoch 과열×보조</span>
|
||||
<button
|
||||
type="button"
|
||||
className="se-node-config-del"
|
||||
title="삭제"
|
||||
aria-label="지표 삭제"
|
||||
onClick={handleDeleteSelectedNode}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<StochPairNodeSettings
|
||||
variant="inline"
|
||||
secondaryIndicator={selectedLogicNode.stochPair.secondaryIndicatorType}
|
||||
@@ -2118,7 +2148,18 @@ export default function StrategyEditorPage({
|
||||
|
||||
{selectedLogicNode && isPriceExtremeBreakoutPairRoot(selectedLogicNode) && selectedLogicNode.priceExtremePair && (
|
||||
<div className="se-node-config-bar se-node-config-bar--price-extreme">
|
||||
<span className="se-node-config-label">9·20 신고가/신저가 필터</span>
|
||||
<div className="se-node-config-head">
|
||||
<span className="se-node-config-label">9·20 신고가/신저가 필터</span>
|
||||
<button
|
||||
type="button"
|
||||
className="se-node-config-del"
|
||||
title="삭제"
|
||||
aria-label="지표 삭제"
|
||||
onClick={handleDeleteSelectedNode}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<PriceExtremePairNodeSettings
|
||||
variant="inline"
|
||||
mode={selectedLogicNode.priceExtremePair.mode}
|
||||
@@ -2132,7 +2173,18 @@ export default function StrategyEditorPage({
|
||||
)}
|
||||
{selectedLogicNode && isInflection33PairRoot(selectedLogicNode) && selectedLogicNode.inflection33Pair && (
|
||||
<div className="se-node-config-bar se-node-config-bar--inflection33">
|
||||
<span className="se-node-config-label">33변곡 필터</span>
|
||||
<div className="se-node-config-head">
|
||||
<span className="se-node-config-label">33변곡 필터</span>
|
||||
<button
|
||||
type="button"
|
||||
className="se-node-config-del"
|
||||
title="삭제"
|
||||
aria-label="지표 삭제"
|
||||
onClick={handleDeleteSelectedNode}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<Inflection33PairNodeSettings
|
||||
variant="inline"
|
||||
mode={selectedLogicNode.inflection33Pair.mode}
|
||||
@@ -2145,7 +2197,18 @@ export default function StrategyEditorPage({
|
||||
)}
|
||||
{selectedLogicNode && isStableStrategyPairRoot(selectedLogicNode) && selectedLogicNode.stableStrategyPair && (
|
||||
<div className="se-node-config-bar se-node-config-bar--stable-strategy">
|
||||
<span className="se-node-config-label">추천 전략 필터</span>
|
||||
<div className="se-node-config-head">
|
||||
<span className="se-node-config-label">추천 전략 필터</span>
|
||||
<button
|
||||
type="button"
|
||||
className="se-node-config-del"
|
||||
title="삭제"
|
||||
aria-label="지표 삭제"
|
||||
onClick={handleDeleteSelectedNode}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<StableStrategyPairNodeSettings
|
||||
variant="inline"
|
||||
strategyId={selectedLogicNode.stableStrategyPair.strategyId as StableStrategyId}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from 'react';
|
||||
import {
|
||||
ReactFlow,
|
||||
Background,
|
||||
@@ -148,6 +148,10 @@ interface Props {
|
||||
onExtraRootsChange?: (roots: Record<string, LogicNode | null>) => void;
|
||||
}
|
||||
|
||||
export interface StrategyEditorCanvasHandle {
|
||||
deleteNode: (id: string) => void;
|
||||
}
|
||||
|
||||
function collectTreeIds(node: LogicNode | null): string[] {
|
||||
if (!node) return [];
|
||||
const ids = [node.id];
|
||||
@@ -310,7 +314,7 @@ function StrategyEditorCanvasInner({
|
||||
onStartCandleTypesChange,
|
||||
onExtraStartIdsChange,
|
||||
onExtraRootsChange,
|
||||
}: Props) {
|
||||
}: Props, ref: React.ForwardedRef<StrategyEditorCanvasHandle>) {
|
||||
const { fitView } = useReactFlow();
|
||||
const canvasWrapRef = useRef<HTMLDivElement>(null);
|
||||
const reactFlowRef = useRef<ReactFlowInstance | null>(null);
|
||||
@@ -500,6 +504,8 @@ function StrategyEditorCanvasInner({
|
||||
onSelectNode, selectedNodeId, scheduleLayoutEmit,
|
||||
]);
|
||||
|
||||
useImperativeHandle(ref, () => ({ deleteNode: handleDelete }), [handleDelete]);
|
||||
|
||||
const deleteSelectedNodes = useCallback(() => {
|
||||
const ids = nodesRef.current
|
||||
.filter(n => n.selected && !isStartNodeId(n.id))
|
||||
@@ -1586,6 +1592,8 @@ function StrategyEditorCanvasInner({
|
||||
);
|
||||
}
|
||||
|
||||
export default function StrategyEditorCanvas(props: Props) {
|
||||
return <StrategyEditorCanvasInner {...props} />;
|
||||
}
|
||||
const StrategyEditorCanvasInnerForwarded = forwardRef(StrategyEditorCanvasInner);
|
||||
|
||||
export default forwardRef<StrategyEditorCanvasHandle, Props>(function StrategyEditorCanvas(props, ref) {
|
||||
return <StrategyEditorCanvasInnerForwarded {...props} ref={ref} />;
|
||||
});
|
||||
|
||||
@@ -1517,6 +1517,13 @@
|
||||
background: var(--se-toolbar-bg);
|
||||
box-shadow: 0 0 24px color-mix(in srgb, var(--se-accent) 8%, transparent);
|
||||
}
|
||||
.se-node-config-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.se-node-config-label {
|
||||
display: inline-block;
|
||||
font-size: 0.68rem;
|
||||
@@ -1525,7 +1532,29 @@
|
||||
padding: 2px 8px;
|
||||
border: 1px solid color-mix(in srgb, var(--se-accent) 35%, transparent);
|
||||
border-radius: 999px;
|
||||
margin-bottom: 8px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.se-node-config-del {
|
||||
border: 1px solid color-mix(in srgb, var(--se-danger) 45%, transparent);
|
||||
background: color-mix(in srgb, var(--se-danger) 24%, transparent);
|
||||
color: color-mix(in srgb, var(--se-danger) 88%, #fff 12%);
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
transition: background 0.12s, border-color 0.12s, color 0.12s;
|
||||
}
|
||||
.se-node-config-del:hover {
|
||||
background: color-mix(in srgb, var(--se-danger) 42%, transparent);
|
||||
border-color: color-mix(in srgb, var(--se-danger) 72%, transparent);
|
||||
color: #fff;
|
||||
}
|
||||
.se-sync-tip {
|
||||
display: block;
|
||||
@@ -1551,6 +1580,9 @@
|
||||
align-items: flex-start;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
.se-node-config-bar--stoch-pair .se-node-config-head {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
.se-stoch-pair-inline-settings {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
Reference in New Issue
Block a user