전략지표 설정 삭제버튼

This commit is contained in:
Macbook
2026-06-24 01:06:36 +09:00
parent e46ffe838f
commit e5086b2c92
3 changed files with 119 additions and 16 deletions
+64 -1
View File
@@ -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">
<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">
<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">
<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">
<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">
<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} />;
});
+33 -1
View File
@@ -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;