전략편집기 수정
This commit is contained in:
@@ -1,8 +1,30 @@
|
||||
import { BaseEdge, EdgeLabelRenderer, getSmoothStepPath, type EdgeProps } from '@xyflow/react';
|
||||
import { edgeDisconnectRef } from './strategyEditorCallbacks';
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
getSmoothStepPath,
|
||||
useReactFlow,
|
||||
type EdgeProps,
|
||||
} from '@xyflow/react';
|
||||
import { bindWindowDrag } from '../../utils/pointerDrag';
|
||||
import type { EdgeHandleBinding } from '../../utils/strategyFlowLayout';
|
||||
import {
|
||||
buildPositionsFromNodes,
|
||||
getEdgeDragZone,
|
||||
pickSourceHandleAtPoint,
|
||||
pickTargetHandleAtPoint,
|
||||
type EdgeDragZone,
|
||||
} from '../../utils/strategyEdgeInteraction';
|
||||
import { edgeBindingUpdateRef, edgeDisconnectRef } from './strategyEditorCallbacks';
|
||||
|
||||
type StrategyEdgeData = {
|
||||
binding?: EdgeHandleBinding;
|
||||
};
|
||||
|
||||
export function StrategyFlowEdge({
|
||||
id,
|
||||
source,
|
||||
target,
|
||||
sourceX,
|
||||
sourceY,
|
||||
targetX,
|
||||
@@ -10,7 +32,12 @@ export function StrategyFlowEdge({
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
selected,
|
||||
data,
|
||||
}: EdgeProps) {
|
||||
const { screenToFlowPosition, getNodes } = useReactFlow();
|
||||
const binding = (data as StrategyEdgeData | undefined)?.binding;
|
||||
const hasCenter = binding?.manualCenter && binding.centerX != null && binding.centerY != null;
|
||||
|
||||
const [edgePath, labelX, labelY] = getSmoothStepPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
@@ -18,15 +45,147 @@ export function StrategyFlowEdge({
|
||||
targetY,
|
||||
sourcePosition,
|
||||
targetPosition,
|
||||
...(hasCenter ? { centerX: binding!.centerX!, centerY: binding!.centerY! } : {}),
|
||||
});
|
||||
|
||||
const dragRef = useRef<{
|
||||
zone: EdgeDragZone;
|
||||
startCenterX: number;
|
||||
startCenterY: number;
|
||||
} | null>(null);
|
||||
const unbindRef = useRef<(() => void) | null>(null);
|
||||
const [dragZone, setDragZone] = useState<EdgeDragZone | null>(null);
|
||||
|
||||
const commitBinding = useCallback((patch: EdgeHandleBinding) => {
|
||||
edgeBindingUpdateRef.current?.(id, patch);
|
||||
}, [id]);
|
||||
|
||||
const positionsFromFlow = useCallback(() => {
|
||||
return buildPositionsFromNodes(getNodes());
|
||||
}, [getNodes]);
|
||||
|
||||
const onHitPointerDown = useCallback((e: React.PointerEvent<SVGPathElement>) => {
|
||||
if (e.button !== 0) return;
|
||||
if ((e.target as Element).closest('.se-flow-edge-del')) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const flow = screenToFlowPosition({ x: e.clientX, y: e.clientY });
|
||||
const zone = getEdgeDragZone(sourceX, sourceY, targetX, targetY, flow.x, flow.y);
|
||||
setDragZone(zone);
|
||||
|
||||
try {
|
||||
(e.currentTarget as SVGPathElement).setPointerCapture(e.pointerId);
|
||||
} catch { /* ignore */ }
|
||||
|
||||
const positions = positionsFromFlow();
|
||||
const startCenterX = binding?.centerX ?? labelX;
|
||||
const startCenterY = binding?.centerY ?? labelY;
|
||||
|
||||
dragRef.current = { zone, startCenterX, startCenterY };
|
||||
|
||||
if (zone === 'source') {
|
||||
commitBinding({
|
||||
sourceHandle: pickSourceHandleAtPoint(source, flow, positions),
|
||||
manualSource: true,
|
||||
});
|
||||
} else if (zone === 'target') {
|
||||
commitBinding({
|
||||
targetHandle: pickTargetHandleAtPoint(target, flow, positions),
|
||||
manualTarget: true,
|
||||
});
|
||||
} else {
|
||||
commitBinding({
|
||||
centerX: flow.x,
|
||||
centerY: flow.y,
|
||||
manualCenter: true,
|
||||
});
|
||||
}
|
||||
|
||||
unbindRef.current?.();
|
||||
unbindRef.current = bindWindowDrag(
|
||||
({ x, y }) => {
|
||||
const d = dragRef.current;
|
||||
if (!d) return;
|
||||
const pos = screenToFlowPosition({ x, y });
|
||||
const positions = positionsFromFlow();
|
||||
|
||||
if (d.zone === 'source') {
|
||||
commitBinding({
|
||||
sourceHandle: pickSourceHandleAtPoint(source, pos, positions),
|
||||
manualSource: true,
|
||||
});
|
||||
} else if (d.zone === 'target') {
|
||||
commitBinding({
|
||||
targetHandle: pickTargetHandleAtPoint(target, pos, positions),
|
||||
manualTarget: true,
|
||||
});
|
||||
} else {
|
||||
commitBinding({
|
||||
centerX: pos.x,
|
||||
centerY: pos.y,
|
||||
manualCenter: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
() => {
|
||||
dragRef.current = null;
|
||||
setDragZone(null);
|
||||
unbindRef.current = null;
|
||||
},
|
||||
);
|
||||
}, [
|
||||
binding?.centerX,
|
||||
binding?.centerY,
|
||||
commitBinding,
|
||||
labelX,
|
||||
labelY,
|
||||
positionsFromFlow,
|
||||
screenToFlowPosition,
|
||||
source,
|
||||
sourceX,
|
||||
sourceY,
|
||||
target,
|
||||
targetX,
|
||||
targetY,
|
||||
]);
|
||||
|
||||
const onHitPointerUp = useCallback((e: React.PointerEvent<SVGPathElement>) => {
|
||||
dragRef.current = null;
|
||||
setDragZone(null);
|
||||
unbindRef.current?.();
|
||||
unbindRef.current = null;
|
||||
try {
|
||||
(e.currentTarget as SVGPathElement).releasePointerCapture(e.pointerId);
|
||||
} catch { /* ignore */ }
|
||||
}, []);
|
||||
|
||||
const hitCursor = dragZone === 'source'
|
||||
? 'grab'
|
||||
: dragZone === 'target'
|
||||
? 'grab'
|
||||
: dragZone === 'middle'
|
||||
? 'move'
|
||||
: 'pointer';
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseEdge
|
||||
id={id}
|
||||
path={edgePath}
|
||||
className={`se-flow-edge${selected ? ' se-flow-edge--selected' : ''}`}
|
||||
style={{ strokeWidth: selected ? 3 : 2 }}
|
||||
style={{ strokeWidth: selected ? 3 : 2, pointerEvents: 'none' }}
|
||||
/>
|
||||
<path
|
||||
d={edgePath}
|
||||
fill="none"
|
||||
stroke="transparent"
|
||||
strokeWidth={22}
|
||||
className={`se-flow-edge-hit${dragZone ? ` se-flow-edge-hit--${dragZone}` : ''}`}
|
||||
style={{ cursor: hitCursor }}
|
||||
onPointerDown={onHitPointerDown}
|
||||
onPointerUp={onHitPointerUp}
|
||||
onPointerCancel={onHitPointerUp}
|
||||
/>
|
||||
{selected && edgeDisconnectRef.current && (
|
||||
<EdgeLabelRenderer>
|
||||
@@ -39,8 +198,8 @@ export function StrategyFlowEdge({
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`,
|
||||
pointerEvents: 'all',
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
edgeDisconnectRef.current?.(id);
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user