You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
3.4 KiB
TypeScript
125 lines
3.4 KiB
TypeScript
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 }; |