목록형 전략평가 수정

This commit is contained in:
Macbook
2026-06-18 02:05:09 +09:00
parent b96fc64c91
commit bde65b6ef8
3 changed files with 51 additions and 22 deletions
+23 -17
View File
@@ -34,6 +34,7 @@ import {
normalizeStartCombineOp, normalizeStartCombineOp,
updateStartCombineOp, updateStartCombineOp,
updateStartCandleTypes, updateStartCandleTypes,
updateBranchRoot,
type EditorConditionState, type EditorConditionState,
} from '../utils/strategyConditionSerde'; } from '../utils/strategyConditionSerde';
import { migrateLogicRootForEditor } from '../utils/strategyConditionNormalize'; import { migrateLogicRootForEditor } from '../utils/strategyConditionNormalize';
@@ -778,6 +779,7 @@ export default function StrategyEditorPage({
const resolved = typeof next === 'function' const resolved = typeof next === 'function'
? next(currentEditorStateRef.current) ? next(currentEditorStateRef.current)
: next; : next;
currentEditorStateRef.current = resolved;
setCurrentRoot(resolved.root); setCurrentRoot(resolved.root);
setCurrentLayout(prev => ({ setCurrentLayout(prev => ({
...prev, ...prev,
@@ -790,12 +792,12 @@ export default function StrategyEditorPage({
}, [setCurrentRoot, setCurrentLayout, scheduleStrategyPersist]); }, [setCurrentRoot, setCurrentLayout, scheduleStrategyPersist]);
const handleStartCandleTypesChange = useCallback((startId: string, candleTypes: string[]) => { const handleStartCandleTypesChange = useCallback((startId: string, candleTypes: string[]) => {
handleEditorStateChange(updateStartCandleTypes(currentEditorState, startId, candleTypes)); handleEditorStateChange(prev => updateStartCandleTypes(prev, startId, candleTypes));
}, [handleEditorStateChange, currentEditorState]); }, [handleEditorStateChange]);
const handleStartCombineOpChange = useCallback((op: StartCombineOp) => { const handleStartCombineOpChange = useCallback((op: StartCombineOp) => {
handleEditorStateChange(updateStartCombineOp(currentEditorState, op)); handleEditorStateChange(prev => updateStartCombineOp(prev, op));
}, [handleEditorStateChange, currentEditorState]); }, [handleEditorStateChange]);
const switchSignalTab = useCallback((tab: 'buy' | 'sell') => { const switchSignalTab = useCallback((tab: 'buy' | 'sell') => {
if (editorMode === 'graph') layoutFlushRef.current?.(); if (editorMode === 'graph') layoutFlushRef.current?.();
@@ -1328,9 +1330,9 @@ export default function StrategyEditorPage({
}, []); }, []);
const handleAddStartSection = useCallback(() => { const handleAddStartSection = useCallback(() => {
handleEditorStateChange(addExtraStartSection(currentEditorState)); handleEditorStateChange(prev => addExtraStartSection(prev));
bumpLayoutSeed(layoutStrategyKey, signalTab); bumpLayoutSeed(layoutStrategyKey, signalTab);
}, [handleEditorStateChange, currentEditorState, bumpLayoutSeed, layoutStrategyKey, signalTab]); }, [handleEditorStateChange, bumpLayoutSeed, layoutStrategyKey, signalTab]);
const applyPalette = useCallback((type: string, value: string, _label: string, composite = false) => { const applyPalette = useCallback((type: string, value: string, _label: string, composite = false) => {
if (type === 'start') { if (type === 'start') {
@@ -1338,12 +1340,15 @@ export default function StrategyEditorPage({
return; return;
} }
const newNode = makeNode(type, value, signalTab, DEF, composite ? { composite: true } : undefined); const newNode = makeNode(type, value, signalTab, DEF, composite ? { composite: true } : undefined);
const root = currentRoot; handleEditorStateChange(prev => {
if (!root) setCurrentRoot(newNode); const root = prev.root;
else if (type === 'operator') setCurrentRoot(mergeAtRoot(root, newNode, true)); if (!root) return updateBranchRoot(prev, START_NODE_ID, newNode);
else setCurrentRoot(mergeAtRoot(root, newNode, false)); if (type === 'operator') {
scheduleStrategyPersist(); return updateBranchRoot(prev, START_NODE_ID, mergeAtRoot(root, newNode, true));
}, [signalTab, DEF, currentRoot, setCurrentRoot, handleAddStartSection, scheduleStrategyPersist]); }
return updateBranchRoot(prev, START_NODE_ID, mergeAtRoot(root, newNode, false));
});
}, [signalTab, DEF, handleEditorStateChange, handleAddStartSection]);
const applyPaletteItem = useCallback((item: PaletteItem) => { const applyPaletteItem = useCallback((item: PaletteItem) => {
const stochPair = isStochOverboughtPairPaletteValue(item.value); const stochPair = isStochOverboughtPairPaletteValue(item.value);
@@ -1361,11 +1366,12 @@ export default function StrategyEditorPage({
? buildStableStrategyTree(item.value, DEF) ? buildStableStrategyTree(item.value, DEF)
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined); : makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
if (!newNode) return; if (!newNode) return;
const root = currentRoot; handleEditorStateChange(prev => {
if (!root) setCurrentRoot(newNode); const root = prev.root;
else setCurrentRoot(mergeAtRoot(root, newNode, false)); if (!root) return updateBranchRoot(prev, START_NODE_ID, newNode);
scheduleStrategyPersist(); return updateBranchRoot(prev, START_NODE_ID, mergeAtRoot(root, newNode, false));
}, [signalTab, DEF, currentRoot, setCurrentRoot, scheduleStrategyPersist]); });
}, [signalTab, DEF, handleEditorStateChange]);
const applySidewaysFilter = useCallback((filterId: string) => { const applySidewaysFilter = useCallback((filterId: string) => {
const newNode = buildSidewaysFilterNode(filterId, DEF, sidewaysPalette); const newNode = buildSidewaysFilterNode(filterId, DEF, sidewaysPalette);
@@ -41,6 +41,7 @@ import {
setHtmlPaletteDropRouter, setHtmlPaletteDropRouter,
setPaletteDragDropHandler, setPaletteDragDropHandler,
subscribePaletteDrag, subscribePaletteDrag,
wasPaletteHtmlDropCommitted,
type PaletteDragPayload, type PaletteDragPayload,
} from '../../utils/paletteDragSession'; } from '../../utils/paletteDragSession';
@@ -108,12 +109,18 @@ const TreeNodeComp: React.FC<TreeNodeProps> = ({
e.stopPropagation(); e.stopPropagation();
setDragOverKey?.(nodeDropKey); setDragOverKey?.(nodeDropKey);
}; };
const handleDragLeave = () => setDragOverKey?.(null); const handleDragLeave = (e: React.DragEvent) => {
const rel = e.relatedTarget as Node | null;
if (!rel) return;
if (e.currentTarget.contains(rel)) return;
setDragOverKey?.(null);
};
const handleDrop = (e: React.DragEvent) => { const handleDrop = (e: React.DragEvent) => {
if (!isLogic) return; if (!isLogic) return;
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setDragOverKey?.(null); setDragOverKey?.(null);
if (wasPaletteHtmlDropCommitted()) return;
try { try {
const data = JSON.parse(e.dataTransfer.getData('application/json')); const data = JSON.parse(e.dataTransfer.getData('application/json'));
if (data.type === 'start') { if (data.type === 'start') {
@@ -338,6 +345,7 @@ function StartSectionBlock({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setDragOverKey(null); setDragOverKey(null);
if (wasPaletteHtmlDropCommitted()) return;
const data = readPaletteHtmlDragData(e); const data = readPaletteHtmlDragData(e);
if (!data) return; if (!data) return;
applyDrop(null, data); applyDrop(null, data);
@@ -375,7 +383,12 @@ function StartSectionBlock({
e.stopPropagation(); e.stopPropagation();
setDragOverKey(sectionDropKey); setDragOverKey(sectionDropKey);
}} }}
onDragLeave={() => setDragOverKey(null)} onDragLeave={e => {
const rel = e.relatedTarget as Node | null;
if (!rel) return;
if (e.currentTarget.contains(rel)) return;
setDragOverKey(null);
}}
onDrop={handleSectionDrop} onDrop={handleSectionDrop}
> >
{!root ? ( {!root ? (
@@ -561,13 +574,14 @@ export default function StrategyListEditor({
const handleListDragOver = (e: React.DragEvent) => { const handleListDragOver = (e: React.DragEvent) => {
e.preventDefault(); e.preventDefault();
e.dataTransfer.dropEffect = 'copy'; e.dataTransfer.dropEffect = 'copy';
setDragOverKey(LIST_DROP_KEY); setDragOverKeyThrottled(LIST_DROP_KEY);
}; };
const handleListDrop = (e: React.DragEvent) => { const handleListDrop = (e: React.DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setDragOverKey(null); setDragOverKey(null);
if (wasPaletteHtmlDropCommitted()) return;
const data = readPaletteHtmlDragData(e); const data = readPaletteHtmlDragData(e);
if (!data) return; if (!data) return;
applyDropRef.current(findPaletteDropKeyAtPoint(e.clientX, e.clientY) ?? LIST_DROP_KEY, data); applyDropRef.current(findPaletteDropKeyAtPoint(e.clientX, e.clientY) ?? LIST_DROP_KEY, data);
@@ -578,7 +592,12 @@ export default function StrategyListEditor({
className={`se-list-editor sp-dropzone${dragOverKey === LIST_DROP_KEY ? ' se-list-editor--over' : ''}`} className={`se-list-editor sp-dropzone${dragOverKey === LIST_DROP_KEY ? ' se-list-editor--over' : ''}`}
data-palette-drop-key={LIST_DROP_KEY} data-palette-drop-key={LIST_DROP_KEY}
onDragOver={handleListDragOver} onDragOver={handleListDragOver}
onDragLeave={() => setDragOverKey(null)} onDragLeave={e => {
const rel = e.relatedTarget as Node | null;
if (!rel) return;
if (e.currentTarget.contains(rel)) return;
setDragOverKey(null);
}}
onDrop={handleListDrop} onDrop={handleListDrop}
> >
{sections.map(section => ( {sections.map(section => (
@@ -591,7 +610,7 @@ export default function StrategyListEditor({
signalTab={signalTab} signalTab={signalTab}
def={def} def={def}
dragOverKey={dragOverKey} dragOverKey={dragOverKey}
setDragOverKey={setDragOverKey} setDragOverKey={setDragOverKeyThrottled}
onRootChange={root => handleRootChange(section.startId, root)} onRootChange={root => handleRootChange(section.startId, root)}
onCandleTypesChange={types => handleCandleTypesChange(section.startId, types)} onCandleTypesChange={types => handleCandleTypesChange(section.startId, types)}
onDeleteStart={section.canDelete ? () => handleDeleteStart(section.startId) : undefined} onDeleteStart={section.canDelete ? () => handleDeleteStart(section.startId) : undefined}
+4
View File
@@ -78,6 +78,10 @@ type HtmlPaletteDropRouter = (
let htmlDropRouter: HtmlPaletteDropRouter | null = null; let htmlDropRouter: HtmlPaletteDropRouter | null = null;
let htmlDropCommitted = false; let htmlDropCommitted = false;
export function wasPaletteHtmlDropCommitted(): boolean {
return htmlDropCommitted;
}
export function setHtmlPaletteDropRouter(router: HtmlPaletteDropRouter | null): void { export function setHtmlPaletteDropRouter(router: HtmlPaletteDropRouter | null): void {
htmlDropRouter = router; htmlDropRouter = router;
} }