모의투자, 백테스팅 레이아웃 수정

This commit is contained in:
Macbook
2026-05-24 20:13:21 +09:00
parent 958b813f3b
commit af230a4233
23 changed files with 2394 additions and 782 deletions
@@ -13,25 +13,51 @@ interface Props {
desc?: string;
color?: string;
period?: string;
selected?: boolean;
onSelect?: () => void;
onAdd: () => void;
}
export default function PaletteChip({ type, value, label, desc, color, period, onAdd }: Props) {
export default function PaletteChip({
type, value, label, desc, color, period, selected = false, onSelect, onAdd,
}: Props) {
const onDragStart = (e: React.DragEvent) => {
const payload: PaletteDragPayload = { type, value, label };
e.dataTransfer.setData('application/json', JSON.stringify(payload));
e.dataTransfer.effectAllowed = 'copy';
};
const handleClick = () => {
if (onSelect) onSelect();
else onAdd();
};
const handleDoubleClick = (e: React.MouseEvent) => {
e.preventDefault();
onAdd();
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key !== 'Enter' && e.key !== ' ') return;
e.preventDefault();
if (onSelect) {
if (selected) onAdd();
else onSelect();
} else {
onAdd();
}
};
return (
<div
draggable
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}`}
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${selected ? ' se-palette-card--selected' : ''}`}
onDragStart={onDragStart}
onClick={onAdd}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
role="button"
tabIndex={0}
onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') onAdd(); }}
onKeyDown={handleKeyDown}
>
<div className="se-palette-card-head">
<span className="se-palette-card-icon">{label.slice(0, 1)}</span>
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useRef } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
ReactFlow,
Background,
@@ -54,6 +54,11 @@ import { StrategyFlowEdge } from './StrategyFlowEdge';
import { edgeDisconnectRef, edgeBindingUpdateRef, layoutFlushRef } from './strategyEditorCallbacks';
import { MultiSelectionDeleteButton } from './MultiSelectionDeleteButton';
import { getMinimapColors } from './minimapTheme';
import {
loadCanvasInteractionMode,
saveCanvasInteractionMode,
type StrategyCanvasInteractionMode,
} from '../../utils/strategyCanvasInteractionStorage';
import type { FlowLayoutChangePayload } from '../../utils/strategyEditorLayoutStorage';
export type FlowLayoutSeed = {
@@ -190,6 +195,14 @@ function StrategyEditorCanvasInner({
const structureKeyRef = useRef<string | null>(null);
const orphanKeyRef = useRef<string | null>(null);
const layoutEmitTimerRef = useRef<number | null>(null);
const [interactionMode, setInteractionMode] = useState<StrategyCanvasInteractionMode>(
() => loadCanvasInteractionMode(),
);
const handleInteractionModeChange = useCallback((mode: StrategyCanvasInteractionMode) => {
setInteractionMode(mode);
saveCanvasInteractionMode(mode);
}, []);
const [nodes, setNodes, onNodesChangeBase] = useNodesState<Node>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([]);
@@ -839,7 +852,7 @@ function StrategyEditorCanvasInner({
return (
<div
className={`se-canvas-wrap se-canvas-wrap--${signalTab}`}
className={`se-canvas-wrap se-canvas-wrap--${signalTab} se-canvas-wrap--${interactionMode}`}
onKeyDown={onKeyDown}
tabIndex={0}
>
@@ -865,9 +878,9 @@ function StrategyEditorCanvasInner({
nodesDraggable
nodesConnectable
elementsSelectable
selectionOnDrag
selectionOnDrag={interactionMode === 'select'}
selectionMode={SelectionMode.Partial}
panOnDrag={[1, 2]}
panOnDrag={interactionMode === 'pan' ? true : [1, 2]}
selectNodesOnDrag={false}
multiSelectionKeyCode={['Meta', 'Control', 'Shift']}
deleteKeyCode={null}
@@ -893,8 +906,28 @@ function StrategyEditorCanvasInner({
/>
<Panel position="top-left" className="se-canvas-toolbar">
<span className="se-canvas-toolbar-title"> </span>
<div className="se-canvas-interaction" role="group" aria-label="캔버스 조작 모드">
<button
type="button"
className={`se-canvas-interaction-btn${interactionMode === 'pan' ? ' se-canvas-interaction-btn--on' : ''}`}
title="빈 영역 드래그로 그래프 이동"
onClick={() => handleInteractionModeChange('pan')}
>
</button>
<button
type="button"
className={`se-canvas-interaction-btn${interactionMode === 'select' ? ' se-canvas-interaction-btn--on' : ''}`}
title="드래그로 전략 요소 다중 선택"
onClick={() => handleInteractionModeChange('select')}
>
</button>
</div>
<span className="se-canvas-toolbar-hint">
× · × · Del
{interactionMode === 'pan'
? '빈 영역 드래그 → 그래프 이동 · 노드 드래그 → 위치 변경'
: '빈 영역 드래그 → 다중 선택 · Del 삭제 · 연결선 × 끊기'}
{orphans.length > 0 ? ` · 미연결 ${orphans.length}` : ''}
</span>
</Panel>