모의투자, 백테스팅 레이아웃 수정
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user