Skip to content

Commit

Permalink
feat: add FlowHeader and delete edge (infiniflow#959)
Browse files Browse the repository at this point in the history
### What problem does this PR solve?
feat: add FlowHeader and delete edge infiniflow#918 

### Type of change

- [x] New Feature (non-breaking change which adds functionality)
  • Loading branch information
cike8899 authored May 29, 2024
1 parent c89d665 commit 98d326d
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 18 deletions.
6 changes: 6 additions & 0 deletions web/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"classnames": "^2.5.1",
"dagre": "^0.8.5",
"dayjs": "^1.11.10",
"elkjs": "^0.9.3",
"eventsource-parser": "^1.1.2",
"i18next": "^23.7.16",
"i18next-browser-languagedetector": "^8.0.0",
Expand Down
2 changes: 1 addition & 1 deletion web/src/locales/zh-traditional.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default {
copied: '複製成功',
comingSoon: '即將推出',
download: '下載',
close: '关闭',
close: '關閉',
preview: '預覽',
},
login: {
Expand Down
3 changes: 0 additions & 3 deletions web/src/pages/flow/canvas/context-menu/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,6 @@ export const useHandleNodeContextMenu = (sideWidth: number) => {
// event.clientY >= pane.height - 200 ? pane.height - event.clientY : 0,
// });

console.info('clientX:', event.clientX);
console.info('clientY:', event.clientY);

setMenu({
id: node.id,
top: event.clientY - 72,
Expand Down
25 changes: 15 additions & 10 deletions web/src/pages/flow/canvas/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ import 'reactflow/dist/style.css';
import { NodeContextMenu, useHandleNodeContextMenu } from './context-menu';

import FlowDrawer from '../flow-drawer';
import { useHandleDrop, useShowDrawer } from '../hooks';
import { initialEdges, initialNodes } from '../mock';
import { getLayoutedElements } from '../utils';
import {
useHandleDrop,
useHandleKeyUp,
useHandleSelectionChange,
useShowDrawer,
} from '../hooks';
import { dsl } from '../mock';
import { TextUpdaterNode } from './node';

const nodeTypes = { textUpdater: TextUpdaterNode };
Expand All @@ -29,13 +33,11 @@ interface IProps {
}

function FlowCanvas({ sideWidth }: IProps) {
const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(
initialNodes,
initialEdges,
'LR',
);
const [nodes, setNodes] = useState<Node[]>(layoutedNodes);
const [edges, setEdges] = useState<Edge[]>(layoutedEdges);
const [nodes, setNodes] = useState<Node[]>(dsl.graph.nodes);
const [edges, setEdges] = useState<Edge[]>(dsl.graph.edges);

const { selectedEdges, selectedNodes } = useHandleSelectionChange();

const { ref, menu, onNodeContextMenu, onPaneClick } =
useHandleNodeContextMenu(sideWidth);
const { drawerVisible, hideDrawer, showDrawer } = useShowDrawer();
Expand All @@ -60,6 +62,8 @@ function FlowCanvas({ sideWidth }: IProps) {

const { onDrop, onDragOver, setReactFlowInstance } = useHandleDrop(setNodes);

const { handleKeyUp } = useHandleKeyUp(selectedEdges, selectedNodes);

useEffect(() => {
console.info('nodes:', nodes);
console.info('edges:', edges);
Expand All @@ -82,6 +86,7 @@ function FlowCanvas({ sideWidth }: IProps) {
onDragOver={onDragOver}
onNodeClick={onNodeClick}
onInit={setReactFlowInstance}
onKeyUp={handleKeyUp}
>
<Background />
<Controls />
Expand Down
35 changes: 35 additions & 0 deletions web/src/pages/flow/elk-hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { useCallback, useLayoutEffect } from 'react';
import { getLayoutedElements } from './elk-utils';

export const elkOptions = {
'elk.algorithm': 'layered',
'elk.layered.spacing.nodeNodeBetweenLayers': '100',
'elk.spacing.nodeNode': '80',
};

export const useLayoutGraph = (
initialNodes,
initialEdges,
setNodes,
setEdges,
) => {
const onLayout = useCallback(({ direction, useInitialNodes = false }) => {
const opts = { 'elk.direction': direction, ...elkOptions };
const ns = initialNodes;
const es = initialEdges;

getLayoutedElements(ns, es, opts).then(
({ nodes: layoutedNodes, edges: layoutedEdges }) => {
setNodes(layoutedNodes);
setEdges(layoutedEdges);

// window.requestAnimationFrame(() => fitView());
},
);
}, []);

// Calculate the initial layout on mount.
useLayoutEffect(() => {
onLayout({ direction: 'RIGHT', useInitialNodes: true });
}, [onLayout]);
};
42 changes: 42 additions & 0 deletions web/src/pages/flow/elk-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import ELK from 'elkjs/lib/elk.bundled.js';
import { Edge, Node } from 'reactflow';

const elk = new ELK();

export const getLayoutedElements = (
nodes: Node[],
edges: Edge[],
options = {},
) => {
const isHorizontal = options?.['elk.direction'] === 'RIGHT';
const graph = {
id: 'root',
layoutOptions: options,
children: nodes.map((node) => ({
...node,
// Adjust the target and source handle positions based on the layout
// direction.
targetPosition: isHorizontal ? 'left' : 'top',
sourcePosition: isHorizontal ? 'right' : 'bottom',

// Hardcode a width and height for elk to use when layouting.
width: 150,
height: 50,
})),
edges: edges,
};

return elk
.layout(graph)
.then((layoutedGraph) => ({
nodes: layoutedGraph.children.map((node) => ({
...node,
// React Flow expects a position property on the node instead of `x`
// and `y` fields.
position: { x: node.x, y: node.y },
})),

edges: layoutedGraph.edges,
}))
.catch(console.error);
};
3 changes: 3 additions & 0 deletions web/src/pages/flow/header/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.flowHeader {
padding: 20px;
}
26 changes: 26 additions & 0 deletions web/src/pages/flow/header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Button, Flex } from 'antd';

import { useSaveGraph } from '../hooks';
import styles from './index.less';

const FlowHeader = () => {
const { saveGraph } = useSaveGraph();

return (
<Flex
align="center"
justify="end"
gap={'large'}
className={styles.flowHeader}
>
<Button>
<b>Debug</b>
</Button>
<Button type="primary" onClick={saveGraph}>
<b>Save</b>
</Button>
</Flex>
);
};

export default FlowHeader;
65 changes: 63 additions & 2 deletions web/src/pages/flow/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import { useSetModalState } from '@/hooks/commonHooks';
import React, { Dispatch, SetStateAction, useCallback, useState } from 'react';
import { Node, Position, ReactFlowInstance } from 'reactflow';
import React, {
Dispatch,
KeyboardEventHandler,
SetStateAction,
useCallback,
useState,
} from 'react';
import {
Node,
Position,
ReactFlowInstance,
useOnSelectionChange,
useReactFlow,
} from 'reactflow';
import { v4 as uuidv4 } from 'uuid';

export const useHandleDrag = () => {
Expand Down Expand Up @@ -75,3 +87,52 @@ export const useShowDrawer = () => {
showDrawer,
};
};

export const useHandleSelectionChange = () => {
const [selectedNodes, setSelectedNodes] = useState<string[]>([]);
const [selectedEdges, setSelectedEdges] = useState<string[]>([]);

useOnSelectionChange({
onChange: ({ nodes, edges }) => {
setSelectedNodes(nodes.map((node) => node.id));
setSelectedEdges(edges.map((edge) => edge.id));
},
});

return { selectedEdges, selectedNodes };
};

export const useDeleteEdge = (selectedEdges: string[]) => {
const { setEdges } = useReactFlow();

const deleteEdge = useCallback(() => {
setEdges((edges) =>
edges.filter((edge) => selectedEdges.every((x) => x !== edge.id)),
);
}, [setEdges, selectedEdges]);

return deleteEdge;
};

export const useHandleKeyUp = (
selectedEdges: string[],
selectedNodes: string[],
) => {
const deleteEdge = useDeleteEdge(selectedEdges);
const handleKeyUp: KeyboardEventHandler = useCallback(
(e) => {
if (e.code === 'Delete') {
deleteEdge();
}
},
[deleteEdge],
);

return { handleKeyUp };
};

export const useSaveGraph = () => {
const saveGraph = useCallback(() => {}, []);

return { saveGraph };
};
2 changes: 2 additions & 0 deletions web/src/pages/flow/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useState } from 'react';
import { ReactFlowProvider } from 'reactflow';
import FlowCanvas from './canvas';
import Sider from './flow-sider';
import FlowHeader from './header';

const { Content } = Layout;

Expand All @@ -14,6 +15,7 @@ function RagFlow() {
<ReactFlowProvider>
<Sider setCollapsed={setCollapsed} collapsed={collapsed}></Sider>
<Layout>
<FlowHeader></FlowHeader>
<Content style={{ margin: '0 16px' }}>
<FlowCanvas sideWidth={collapsed ? 0 : 200}></FlowCanvas>
</Content>
Expand Down
Loading

0 comments on commit 98d326d

Please sign in to comment.