전략편집기 수정
This commit is contained in:
@@ -129,6 +129,12 @@ function MultiSideHandles({
|
|||||||
function usePaletteDropHandlers(id: string, d: StrategyFlowNodeData) {
|
function usePaletteDropHandlers(id: string, d: StrategyFlowNodeData) {
|
||||||
const { screenToFlowPosition } = useReactFlow();
|
const { screenToFlowPosition } = useReactFlow();
|
||||||
|
|
||||||
|
const onDragEnter = useCallback((e: React.DragEvent) => {
|
||||||
|
if (!isPaletteHtmlDrag(e)) return;
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const onDragOver = useCallback((e: React.DragEvent) => {
|
const onDragOver = useCallback((e: React.DragEvent) => {
|
||||||
if (!isPaletteHtmlDrag(e)) return;
|
if (!isPaletteHtmlDrag(e)) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -154,13 +160,13 @@ function usePaletteDropHandlers(id: string, d: StrategyFlowNodeData) {
|
|||||||
d.onDropTarget?.(id, payload, flowPos);
|
d.onDropTarget?.(id, payload, flowPos);
|
||||||
}, [d, id, screenToFlowPosition]);
|
}, [d, id, screenToFlowPosition]);
|
||||||
|
|
||||||
return { onDragOver, onDragLeave, onDrop };
|
return { onDragEnter, onDragOver, onDragLeave, onDrop };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StartNode = memo(function StartNode({ id, data }: NodeProps) {
|
export const StartNode = memo(function StartNode({ id, data }: NodeProps) {
|
||||||
const d = data as StrategyFlowNodeData;
|
const d = data as StrategyFlowNodeData;
|
||||||
const isSell = d.signalTab === 'sell';
|
const isSell = d.signalTab === 'sell';
|
||||||
const { onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
const { onDragEnter, onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
||||||
const candleTypes = d.candleTypes?.length
|
const candleTypes = d.candleTypes?.length
|
||||||
? d.candleTypes
|
? d.candleTypes
|
||||||
: getStartCandleTypes({ candleType: d.candleType ?? '1m' });
|
: getStartCandleTypes({ candleType: d.candleType ?? '1m' });
|
||||||
@@ -171,6 +177,7 @@ export const StartNode = memo(function StartNode({ id, data }: NodeProps) {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`se-flow-node se-flow-node--start${isSell ? ' se-flow-node--sell-mode' : ''} ${d.dragOver ? 'se-flow-node--drop' : ''}`}
|
className={`se-flow-node se-flow-node--start${isSell ? ' se-flow-node--sell-mode' : ''} ${d.dragOver ? 'se-flow-node--drop' : ''}`}
|
||||||
|
onDragEnter={onDragEnter}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onDragLeave={onDragLeave}
|
onDragLeave={onDragLeave}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
@@ -221,13 +228,14 @@ export const LogicGateNode = memo(function LogicGateNode({ id, data, selected }:
|
|||||||
const isPriceExtremePair = isPriceExtremeBreakoutPairRoot(node);
|
const isPriceExtremePair = isPriceExtremeBreakoutPairRoot(node);
|
||||||
const isInflection33Pair = isInflection33PairRoot(node);
|
const isInflection33Pair = isInflection33PairRoot(node);
|
||||||
const isStableStrategyPair = isStableStrategyPairRoot(node);
|
const isStableStrategyPair = isStableStrategyPairRoot(node);
|
||||||
const { onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
const { onDragEnter, onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`se-flow-node se-flow-node--logic${isStochPair ? ' se-flow-node--stoch-pair' : ''}${isPriceExtremePair ? ' se-flow-node--price-extreme-pair' : ''}${isInflection33Pair ? ' se-flow-node--inflection33-pair' : ''}${isStableStrategyPair ? ' se-flow-node--stable-strategy-pair' : ''}${isSell ? ' se-flow-node--sell-mode' : ''}${d.isOrphan ? ' se-flow-node--orphan' : ''} ${selected ? 'se-flow-node--selected' : ''} ${d.dragOver ? ' se-flow-node--drop' : ''}${settingsOpen ? ' se-flow-node--settings-open' : ''}`}
|
className={`se-flow-node se-flow-node--logic${isStochPair ? ' se-flow-node--stoch-pair' : ''}${isPriceExtremePair ? ' se-flow-node--price-extreme-pair' : ''}${isInflection33Pair ? ' se-flow-node--inflection33-pair' : ''}${isStableStrategyPair ? ' se-flow-node--stable-strategy-pair' : ''}${isSell ? ' se-flow-node--sell-mode' : ''}${d.isOrphan ? ' se-flow-node--orphan' : ''} ${selected ? 'se-flow-node--selected' : ''} ${d.dragOver ? ' se-flow-node--drop' : ''}${settingsOpen ? ' se-flow-node--settings-open' : ''}`}
|
||||||
style={{ '--se-gate-color': color } as React.CSSProperties}
|
style={{ '--se-gate-color': color } as React.CSSProperties}
|
||||||
|
onDragEnter={onDragEnter}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onDragLeave={onDragLeave}
|
onDragLeave={onDragLeave}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
@@ -398,7 +406,7 @@ export const ConditionFlowNode = memo(function ConditionFlowNode({ id, data, sel
|
|||||||
const isCross = ['CROSS_UP', 'CROSS_DOWN'].includes(condType);
|
const isCross = ['CROSS_UP', 'CROSS_DOWN'].includes(condType);
|
||||||
const tone = isCross ? 'cross' : 'value';
|
const tone = isCross ? 'cross' : 'value';
|
||||||
const isSell = d.signalTab === 'sell';
|
const isSell = d.signalTab === 'sell';
|
||||||
const { onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
const { onDragEnter, onDragOver, onDragLeave, onDrop } = usePaletteDropHandlers(id, d);
|
||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const showSettings = cond && hasNodeSettings(cond);
|
const showSettings = cond && hasNodeSettings(cond);
|
||||||
|
|
||||||
@@ -409,6 +417,7 @@ export const ConditionFlowNode = memo(function ConditionFlowNode({ id, data, sel
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`se-flow-node se-flow-node--cond se-flow-node--${tone}${isSell ? ' se-flow-node--sell-mode' : ''}${d.isOrphan ? ' se-flow-node--orphan' : ''} ${selected ? 'se-flow-node--selected' : ''} ${d.dragOver ? 'se-flow-node--drop' : ''}${settingsOpen ? ' se-flow-node--settings-open' : ''}`}
|
className={`se-flow-node se-flow-node--cond se-flow-node--${tone}${isSell ? ' se-flow-node--sell-mode' : ''}${d.isOrphan ? ' se-flow-node--orphan' : ''} ${selected ? 'se-flow-node--selected' : ''} ${d.dragOver ? 'se-flow-node--drop' : ''}${settingsOpen ? ' se-flow-node--settings-open' : ''}`}
|
||||||
|
onDragEnter={onDragEnter}
|
||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onDragLeave={onDragLeave}
|
onDragLeave={onDragLeave}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
handlePaletteMouseDown,
|
handlePaletteMouseDown,
|
||||||
handlePalettePointerDown,
|
handlePalettePointerDown,
|
||||||
handlePaletteTouchStart,
|
handlePaletteTouchStart,
|
||||||
|
markPaletteHtmlDragEnd,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
shouldSuppressPaletteClick,
|
shouldSuppressPaletteClick,
|
||||||
writePaletteHtmlDragData,
|
writePaletteHtmlDragData,
|
||||||
@@ -53,6 +54,10 @@ export default function PaletteChip({
|
|||||||
writePaletteHtmlDragData(e, buildPayload());
|
writePaletteHtmlDragData(e, buildPayload());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
markPaletteHtmlDragEnd();
|
||||||
|
};
|
||||||
|
|
||||||
const onPointerDown = (e: React.PointerEvent) => {
|
const onPointerDown = (e: React.PointerEvent) => {
|
||||||
handlePalettePointerDown(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
handlePalettePointerDown(e, buildPayload(), composite ? `${label} + ${label}` : label);
|
||||||
};
|
};
|
||||||
@@ -92,6 +97,7 @@ export default function PaletteChip({
|
|||||||
draggable={!needsPointerPaletteDrag()}
|
draggable={!needsPointerPaletteDrag()}
|
||||||
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`}
|
className={`se-palette-card ${color ? `se-palette-card--${color}` : 'se-palette-card--ind'}${composite ? ' se-palette-card--composite' : ''}${selected ? ' se-palette-card--selected' : ''}`}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
onMouseDown={onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
onTouchStart={onTouchStart}
|
onTouchStart={onTouchStart}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type { DefType } from '../../utils/strategyEditorShared';
|
|||||||
import {
|
import {
|
||||||
handlePalettePointerDown,
|
handlePalettePointerDown,
|
||||||
handlePaletteTouchStart,
|
handlePaletteTouchStart,
|
||||||
|
markPaletteHtmlDragEnd,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
writePaletteHtmlDragData,
|
writePaletteHtmlDragData,
|
||||||
type PaletteDragPayload,
|
type PaletteDragPayload,
|
||||||
@@ -85,6 +86,11 @@ function SidewaysFilterChip({
|
|||||||
writePaletteHtmlDragData(e, payload as PaletteDragPayload);
|
writePaletteHtmlDragData(e, payload as PaletteDragPayload);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDragEnd = () => {
|
||||||
|
hideHint();
|
||||||
|
markPaletteHtmlDragEnd();
|
||||||
|
};
|
||||||
|
|
||||||
const onPointerDown = (e: React.PointerEvent) => {
|
const onPointerDown = (e: React.PointerEvent) => {
|
||||||
hideHint();
|
hideHint();
|
||||||
const payload: PaletteDragPayload = {
|
const payload: PaletteDragPayload = {
|
||||||
@@ -123,6 +129,7 @@ function SidewaysFilterChip({
|
|||||||
draggable={!needsPointerPaletteDrag()}
|
draggable={!needsPointerPaletteDrag()}
|
||||||
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`}
|
className={`se-palette-card se-palette-card--range se-palette-card--range-${item.mode}${selected ? ' se-palette-card--selected' : ''}`}
|
||||||
onDragStart={onDragStart}
|
onDragStart={onDragStart}
|
||||||
|
onDragEnd={onDragEnd}
|
||||||
onPointerDown={onPointerDown}
|
onPointerDown={onPointerDown}
|
||||||
onTouchStart={onTouchStart}
|
onTouchStart={onTouchStart}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import {
|
|||||||
import { buildSidewaysFilterNode } from '../../utils/sidewaysFilterPaletteStorage';
|
import { buildSidewaysFilterNode } from '../../utils/sidewaysFilterPaletteStorage';
|
||||||
import {
|
import {
|
||||||
isPaletteHtmlDrag,
|
isPaletteHtmlDrag,
|
||||||
|
isPaletteHtmlDragActive,
|
||||||
|
markPaletteHtmlDragEnd,
|
||||||
needsPointerPaletteDrag,
|
needsPointerPaletteDrag,
|
||||||
readPaletteHtmlDragData,
|
readPaletteHtmlDragData,
|
||||||
setPaletteDragDropHandler,
|
setPaletteDragDropHandler,
|
||||||
@@ -1282,6 +1284,40 @@ function StrategyEditorCanvasInner({
|
|||||||
const lastPreviewAnchorRef = useRef<string | null>(null);
|
const lastPreviewAnchorRef = useRef<string | null>(null);
|
||||||
const lastPreviewKeyRef = useRef<string | null>(null);
|
const lastPreviewKeyRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (needsPointerPaletteDrag()) return;
|
||||||
|
const onDocDragOver = (e: DragEvent) => {
|
||||||
|
if (!isPaletteHtmlDragActive()) return;
|
||||||
|
e.preventDefault();
|
||||||
|
const { flowPos, anchorId } = resolveDropFromClientRef.current(e.clientX, e.clientY);
|
||||||
|
if (anchorId) {
|
||||||
|
const sideKey = `${anchorId}:${Math.round(flowPos.x / 8)}:${Math.round(flowPos.y / 8)}`;
|
||||||
|
if (sideKey === lastPreviewKeyRef.current) return;
|
||||||
|
lastPreviewKeyRef.current = sideKey;
|
||||||
|
lastPreviewAnchorRef.current = anchorId;
|
||||||
|
updatePreviewRef.current(anchorId, flowPos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lastPreviewAnchorRef.current != null) {
|
||||||
|
lastPreviewAnchorRef.current = null;
|
||||||
|
lastPreviewKeyRef.current = null;
|
||||||
|
clearPreviewRef.current();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onDocDragEnd = () => {
|
||||||
|
lastPreviewAnchorRef.current = null;
|
||||||
|
lastPreviewKeyRef.current = null;
|
||||||
|
clearPreviewRef.current();
|
||||||
|
markPaletteHtmlDragEnd();
|
||||||
|
};
|
||||||
|
document.addEventListener('dragover', onDocDragOver, { passive: false });
|
||||||
|
document.addEventListener('dragend', onDocDragEnd);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('dragover', onDocDragOver);
|
||||||
|
document.removeEventListener('dragend', onDocDragEnd);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!needsPointerPaletteDrag()) return;
|
if (!needsPointerPaletteDrag()) return;
|
||||||
return subscribePaletteDrag((ev: PaletteDragEvent) => {
|
return subscribePaletteDrag((ev: PaletteDragEvent) => {
|
||||||
@@ -1320,6 +1356,11 @@ function StrategyEditorCanvasInner({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const onPaneDragEnter = useCallback((e: React.DragEvent) => {
|
||||||
|
if (!isPaletteDrag(e)) return;
|
||||||
|
e.preventDefault();
|
||||||
|
}, [isPaletteDrag]);
|
||||||
|
|
||||||
const onPaneDragOver = useCallback((e: React.DragEvent) => {
|
const onPaneDragOver = useCallback((e: React.DragEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!isPaletteDrag(e)) return;
|
if (!isPaletteDrag(e)) return;
|
||||||
@@ -1378,6 +1419,7 @@ function StrategyEditorCanvasInner({
|
|||||||
isValidConnection={isValidConnection}
|
isValidConnection={isValidConnection}
|
||||||
onReconnect={onReconnect}
|
onReconnect={onReconnect}
|
||||||
onDrop={onPaneDrop}
|
onDrop={onPaneDrop}
|
||||||
|
onDragEnter={onPaneDragEnter}
|
||||||
onDragOver={onPaneDragOver}
|
onDragOver={onPaneDragOver}
|
||||||
onDragLeave={onPaneDragLeave}
|
onDragLeave={onPaneDragLeave}
|
||||||
connectionMode={ConnectionMode.Loose}
|
connectionMode={ConnectionMode.Loose}
|
||||||
|
|||||||
@@ -54,6 +54,26 @@ let moveRafId: number | null = null;
|
|||||||
let pendingMove: { x: number; y: number } | null = null;
|
let pendingMove: { x: number; y: number } | null = null;
|
||||||
let unbindGesture: (() => void) | null = null;
|
let unbindGesture: (() => void) | null = null;
|
||||||
let lastDragClientXY: { x: number; y: number } | null = null;
|
let lastDragClientXY: { x: number; y: number } | null = null;
|
||||||
|
let htmlPaletteDragActive = false;
|
||||||
|
|
||||||
|
function installHtmlPaletteDragGlobals() {
|
||||||
|
if (typeof window === 'undefined') return;
|
||||||
|
const w = window as Window & { __gcPaletteHtmlDragGlobals?: boolean };
|
||||||
|
if (w.__gcPaletteHtmlDragGlobals) return;
|
||||||
|
w.__gcPaletteHtmlDragGlobals = true;
|
||||||
|
|
||||||
|
window.addEventListener('dragover', (e) => {
|
||||||
|
if (!htmlPaletteDragActive) return;
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}, { passive: false });
|
||||||
|
|
||||||
|
window.addEventListener('dragend', () => {
|
||||||
|
htmlPaletteDragActive = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
installHtmlPaletteDragGlobals();
|
||||||
|
|
||||||
function hasCoarsePointer(): boolean {
|
function hasCoarsePointer(): boolean {
|
||||||
if (typeof window === 'undefined') return false;
|
if (typeof window === 'undefined') return false;
|
||||||
@@ -306,7 +326,20 @@ export function handlePaletteTouchStart(
|
|||||||
startPaletteDragGesture(touch.clientX, touch.clientY, touch.identifier, payload);
|
startPaletteDragGesture(touch.clientX, touch.clientY, touch.identifier, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function markPaletteHtmlDragStart(): void {
|
||||||
|
htmlPaletteDragActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markPaletteHtmlDragEnd(): void {
|
||||||
|
htmlPaletteDragActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPaletteHtmlDragActive(): boolean {
|
||||||
|
return htmlPaletteDragActive;
|
||||||
|
}
|
||||||
|
|
||||||
export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDragPayload): void {
|
export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDragPayload): void {
|
||||||
|
markPaletteHtmlDragStart();
|
||||||
const json = JSON.stringify(payload);
|
const json = JSON.stringify(payload);
|
||||||
e.dataTransfer.setData('application/json', json);
|
e.dataTransfer.setData('application/json', json);
|
||||||
e.dataTransfer.setData('text/plain', json);
|
e.dataTransfer.setData('text/plain', json);
|
||||||
@@ -314,13 +347,16 @@ export function writePaletteHtmlDragData(e: React.DragEvent, payload: PaletteDra
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isPaletteHtmlDrag(e: React.DragEvent): boolean {
|
export function isPaletteHtmlDrag(e: React.DragEvent): boolean {
|
||||||
return e.dataTransfer.types.includes('application/json')
|
if (htmlPaletteDragActive) return true;
|
||||||
|| e.dataTransfer.types.includes('text/plain');
|
const types = e.dataTransfer?.types;
|
||||||
|
if (!types) return false;
|
||||||
|
return Array.from(types).includes('application/json')
|
||||||
|
|| Array.from(types).includes('text/plain');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readPaletteHtmlDragData(e: React.DragEvent): PaletteDragPayload | null {
|
export function readPaletteHtmlDragData(e: React.DragEvent): PaletteDragPayload | null {
|
||||||
const raw = e.dataTransfer.getData('application/json')
|
const raw = e.dataTransfer.getData('text/plain')
|
||||||
|| e.dataTransfer.getData('text/plain');
|
|| e.dataTransfer.getData('application/json');
|
||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
try {
|
try {
|
||||||
return JSON.parse(raw) as PaletteDragPayload;
|
return JSON.parse(raw) as PaletteDragPayload;
|
||||||
|
|||||||
Reference in New Issue
Block a user