feat(flowEditor): 添加节点对齐辅助线功能
- 新增 useAlignmentGuidelines 钩子用于处理对齐逻辑 - 在 ReactFlow 组件中集成对齐辅助线 - 实现节点拖动时动态显示对齐线 - 拖动停止后清除对齐线master
parent
1c977cb4c2
commit
d7ad2a5730
@ -0,0 +1,125 @@
|
|||||||
|
import React, { useState, useCallback } from 'react';
|
||||||
|
import { Node, useReactFlow } from '@xyflow/react';
|
||||||
|
|
||||||
|
interface Guideline {
|
||||||
|
type: 'left' | 'right' | 'center' | 'top' | 'middle' | 'bottom';
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AlignmentGuidelines {
|
||||||
|
verticalLines: Array<Guideline & { x: number }>;
|
||||||
|
horizontalLines: Array<Guideline & { y: number }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOLERANCE = 2;
|
||||||
|
|
||||||
|
export const useAlignmentGuidelines = () => {
|
||||||
|
const [guidelines, setGuidelines] = useState<AlignmentGuidelines>({
|
||||||
|
verticalLines: [],
|
||||||
|
horizontalLines: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const reactFlowInstance = useReactFlow();
|
||||||
|
|
||||||
|
const clearGuidelines = useCallback(() => {
|
||||||
|
setGuidelines({ verticalLines: [], horizontalLines: [] });
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getGuidelines = useCallback((draggingNode: Node, allNodes: Node[]) => {
|
||||||
|
const { position, width = 100, height = 50 } = draggingNode;
|
||||||
|
const { x: dx, y: dy } = position;
|
||||||
|
const dWidth = width;
|
||||||
|
const dHeight = height;
|
||||||
|
|
||||||
|
const verticalLines: Array<Guideline & { x: number }> = [];
|
||||||
|
const horizontalLines: Array<Guideline & { y: number }> = [];
|
||||||
|
|
||||||
|
allNodes.forEach(node => {
|
||||||
|
if (node.id === draggingNode.id) return;
|
||||||
|
|
||||||
|
const { position: pos, measured } = node;
|
||||||
|
const { x, y } = pos;
|
||||||
|
const width2 = measured.width | 0;
|
||||||
|
const height2 = measured.height | 0;
|
||||||
|
|
||||||
|
// 垂直对齐
|
||||||
|
if (Math.abs(dx - x) < TOLERANCE) {
|
||||||
|
verticalLines.push({ type: 'left', x: x - width2 - 1, from: draggingNode.id, to: node.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 水平对齐
|
||||||
|
if (Math.abs(dy - y) < TOLERANCE) {
|
||||||
|
horizontalLines.push({ type: 'top', y: y - height2 / 2 - 3, from: draggingNode.id, to: node.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
setGuidelines({ verticalLines, horizontalLines });
|
||||||
|
return { verticalLines, horizontalLines };
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const AlignmentGuides = () => {
|
||||||
|
// 将节点坐标转换为屏幕坐标
|
||||||
|
const screenVerticalLines = guidelines.verticalLines.map(line => ({
|
||||||
|
...line,
|
||||||
|
x: reactFlowInstance.flowToScreenPosition({ x: line.x, y: 0 }).x
|
||||||
|
}));
|
||||||
|
|
||||||
|
const screenHorizontalLines = guidelines.horizontalLines.map(line => ({
|
||||||
|
...line,
|
||||||
|
y: reactFlowInstance.flowToScreenPosition({ x: 0, y: line.y }).y
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 垂直辅助线 */}
|
||||||
|
{screenVerticalLines.map((line, i) => (
|
||||||
|
<div
|
||||||
|
key={`v-${i}`}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: `${Math.round(line.x)}px`,
|
||||||
|
width: '1px',
|
||||||
|
backgroundColor: '#2196F3',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: 1000,
|
||||||
|
transform: 'translateX(-0.5px)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 水平辅助线 */}
|
||||||
|
{screenHorizontalLines.map((line, i) => (
|
||||||
|
<div
|
||||||
|
key={`h-${i}`}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: `${Math.round(line.y)}px`,
|
||||||
|
height: '1px',
|
||||||
|
backgroundColor: '#2196F3',
|
||||||
|
pointerEvents: 'none',
|
||||||
|
zIndex: 1000,
|
||||||
|
transform: 'translateY(-0.5px)'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
guidelines,
|
||||||
|
getGuidelines,
|
||||||
|
clearGuidelines,
|
||||||
|
AlignmentGuides
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type { AlignmentGuidelines, Guideline };
|
||||||
Loading…
Reference in New Issue