Commit 7417de74 by heheer Committed by GitHub

feat: add workflow helperline (#2356)

* feat: add workflow helperline

* del console

* across zoom & adjust distance
parent fe9cb437
......@@ -80,3 +80,17 @@ export type SystemPluginTemplateItemType = WorkflowTemplateType & {
workflow: WorkflowTemplateBasicType;
};
export type THelperLine = {
position: number;
nodes: {
left: number;
right: number;
top: number;
bottom: number;
width: number;
height: number;
centerX: number;
centerY: number;
}[];
};
import { THelperLine } from '@fastgpt/global/core/workflow/type';
import { CSSProperties, useEffect, useRef } from 'react';
import { ReactFlowState, useStore, useViewport } from 'reactflow';
const canvasStyle: CSSProperties = {
width: '100%',
height: '100%',
position: 'absolute',
zIndex: 10,
pointerEvents: 'none'
};
const storeSelector = (state: ReactFlowState) => ({
width: state.width,
height: state.height,
transform: state.transform
});
export type HelperLinesProps = {
horizontal?: THelperLine;
vertical?: THelperLine;
};
function HelperLinesRenderer({ horizontal, vertical }: HelperLinesProps) {
const { width, height, transform } = useStore(storeSelector);
const { zoom } = useViewport();
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas?.getContext('2d');
if (!ctx || !canvas) {
return;
}
const dpi = window.devicePixelRatio;
canvas.width = width * dpi;
canvas.height = height * dpi;
ctx.scale(dpi, dpi);
ctx.clearRect(0, 0, width, height);
ctx.strokeStyle = '#D92D20';
const drawCross = (x: number, y: number, size: number) => {
ctx.beginPath();
ctx.moveTo(x - size, y - size);
ctx.lineTo(x + size, y + size);
ctx.moveTo(x + size, y - size);
ctx.lineTo(x - size, y + size);
ctx.stroke();
};
if (vertical) {
const x = vertical.position * transform[2] + transform[0];
ctx.beginPath();
ctx.moveTo(
x,
Math.min(...vertical.nodes.map((node) => node.top)) * transform[2] + transform[1]
);
ctx.lineTo(
x,
Math.max(...vertical.nodes.map((node) => node.bottom)) * transform[2] + transform[1]
);
ctx.stroke();
vertical.nodes.forEach((node) => {
drawCross(x, node.top * transform[2] + transform[1], 5 * zoom);
drawCross(x, node.bottom * transform[2] + transform[1], 5 * zoom);
});
}
if (horizontal) {
const y = horizontal.position * transform[2] + transform[1];
ctx.beginPath();
ctx.moveTo(
Math.min(...horizontal.nodes.map((node) => node.left)) * transform[2] + transform[0],
y
);
ctx.lineTo(
Math.max(...horizontal.nodes.map((node) => node.right)) * transform[2] + transform[0],
y
);
ctx.stroke();
horizontal.nodes.forEach((node) => {
drawCross(node.left * transform[2] + transform[0], y, 5 * zoom);
drawCross(node.right * transform[2] + transform[0], y, 5 * zoom);
});
}
}, [width, height, transform, horizontal, vertical]);
return <canvas ref={canvasRef} style={canvasStyle} />;
}
export default HelperLinesRenderer;
import React, { useCallback, useMemo } from 'react';
import { Connection, NodeChange, OnConnectStartParams, addEdge, EdgeChange, Edge } from 'reactflow';
import {
Connection,
NodeChange,
OnConnectStartParams,
addEdge,
EdgeChange,
Edge,
applyNodeChanges,
Node
} from 'reactflow';
import { EDGE_TYPE } from '@fastgpt/global/core/workflow/node/constant';
import 'reactflow/dist/style.css';
import { useToast } from '@fastgpt/web/hooks/useToast';
......@@ -8,6 +17,7 @@ import { useConfirm } from '@fastgpt/web/hooks/useConfirm';
import { useKeyboard } from './useKeyboard';
import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../../context';
import { useWorkflowUtils } from './useUtils';
export const useWorkflow = () => {
const { toast } = useToast();
......@@ -18,12 +28,47 @@ export const useWorkflow = () => {
});
const { isDowningCtrl } = useKeyboard();
const { setConnectingEdge, nodes, onNodesChange, setEdges, onEdgesChange, setHoverEdgeId } =
useContextSelector(WorkflowContext, (v) => v);
const {
setConnectingEdge,
nodes,
setNodes,
onNodesChange,
setEdges,
onEdgesChange,
setHoverEdgeId,
setHelperLineHorizontal,
setHelperLineVertical
} = useContextSelector(WorkflowContext, (v) => v);
const { getHelperLines } = useWorkflowUtils();
const customApplyNodeChanges = useCallback((changes: NodeChange[], nodes: Node[]): Node[] => {
setHelperLineHorizontal(undefined);
setHelperLineVertical(undefined);
if (
changes.length === 1 &&
changes[0].type === 'position' &&
changes[0].dragging &&
changes[0].position
) {
const helperLines = getHelperLines(changes[0], nodes);
changes[0].position.x = helperLines.snapPosition.x ?? changes[0].position.x;
changes[0].position.y = helperLines.snapPosition.y ?? changes[0].position.y;
setHelperLineHorizontal(helperLines.horizontal);
setHelperLineVertical(helperLines.vertical);
}
return applyNodeChanges(changes, nodes);
}, []);
/* node */
const handleNodesChange = useCallback(
(changes: NodeChange[]) => {
setNodes((nodes) => customApplyNodeChanges(changes, nodes));
for (const change of changes) {
if (change.type === 'remove') {
const node = nodes.find((n) => n.id === change.id);
......
......@@ -26,6 +26,7 @@ import { useContextSelector } from 'use-context-selector';
import { WorkflowContext } from '../context';
import { useWorkflow } from './hooks/useWorkflow';
import { t } from 'i18next';
import HelperLines from './components/HelperLines';
const NodeSimple = dynamic(() => import('./nodes/NodeSimple'));
const nodeTypes: Record<FlowNodeTypeEnum, any> = {
......@@ -62,7 +63,8 @@ const edgeTypes = {
};
const Workflow = () => {
const { nodes, edges, reactFlowWrapper } = useContextSelector(WorkflowContext, (v) => v);
const { nodes, edges, reactFlowWrapper, helperLineHorizontal, helperLineVertical } =
useContextSelector(WorkflowContext, (v) => v);
const {
ConfirmDeleteModal,
......@@ -135,6 +137,7 @@ const Workflow = () => {
onEdgeMouseLeave={onEdgeMouseLeave}
>
<FlowController />
<HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
</ReactFlow>
</Box>
......
......@@ -48,6 +48,7 @@ import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { formatTime2HM, formatTime2YMDHMW } from '@fastgpt/global/common/string/time';
import type { InitProps } from '@/pages/app/detail/components/PublishHistoriesSlider';
import { cloneDeep } from 'lodash';
import { THelperLine } from '@fastgpt/global/core/workflow/type';
type OnChange<ChangesType> = (changes: ChangesType[]) => void;
......@@ -135,6 +136,12 @@ type WorkflowContextType = {
historiesDefaultData?: InitProps;
setHistoriesDefaultData: React.Dispatch<React.SetStateAction<undefined | InitProps>>;
// helper line
helperLineHorizontal?: THelperLine;
setHelperLineHorizontal: React.Dispatch<React.SetStateAction<THelperLine | undefined>>;
helperLineVertical?: THelperLine;
setHelperLineVertical: React.Dispatch<React.SetStateAction<THelperLine | undefined>>;
// chat test
setWorkflowTestData: React.Dispatch<
React.SetStateAction<
......@@ -260,6 +267,14 @@ export const WorkflowContext = createContext<WorkflowContextType>({
setHistoriesDefaultData: function (value: React.SetStateAction<InitProps | undefined>): void {
throw new Error('Function not implemented.');
},
helperLineHorizontal: undefined,
setHelperLineHorizontal: function (value: React.SetStateAction<THelperLine | undefined>): void {
throw new Error('Function not implemented.');
},
helperLineVertical: undefined,
setHelperLineVertical: function (value: React.SetStateAction<THelperLine | undefined>): void {
throw new Error('Function not implemented.');
},
getNodeDynamicInputs: function (nodeId: string): FlowNodeInputItemType[] {
throw new Error('Function not implemented.');
}
......@@ -728,6 +743,11 @@ const WorkflowContextProvider = ({
/* Version histories */
const [historiesDefaultData, setHistoriesDefaultData] = useState<InitProps>();
/* helper line */
const [helperLineHorizontal, setHelperLineHorizontal] = useState<THelperLine | undefined>(
undefined
);
const [helperLineVertical, setHelperLineVertical] = useState<THelperLine | undefined>(undefined);
/* event bus */
useEffect(() => {
eventBus.on(EventNameEnum.requestWorkflowStore, () => {
......@@ -797,6 +817,12 @@ const WorkflowContextProvider = ({
historiesDefaultData,
setHistoriesDefaultData,
// helper line
helperLineHorizontal,
setHelperLineHorizontal,
helperLineVertical,
setHelperLineVertical,
// chat test
setWorkflowTestData
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment