Skip to content

Commit

Permalink
feat: add clipboard feature
Browse files Browse the repository at this point in the history
NewByVector committed Nov 9, 2023

Unverified

The email in this signature doesn’t match the committer email.
1 parent 7509e81 commit 25bbd38
Showing 17 changed files with 197 additions and 25 deletions.
4 changes: 3 additions & 1 deletion apps/basic/src/pages/basic/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { XFlow, XFlowGraph, Grid, Background } from '@antv/xflow';
import { XFlow, XFlowGraph, Grid, Background, Clipboard } from '@antv/xflow';

import styles from './index.less';
import { JSONCode } from './json';
import { ToolsButton } from './tools';
@@ -26,6 +27,7 @@ const Page = () => {
/>
<Grid type="mesh" options={{ color: '#ccc', thickness: 1 }} />
<JSONCode />
<Clipboard />
</div>
</XFlow>
</div>
4 changes: 3 additions & 1 deletion apps/basic/src/pages/basic/json.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { useEffect, useRef } from 'react';
import { useGraphStore } from '@antv/xflow';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
import { useEffect, useRef } from 'react';

import 'highlight.js/styles/github.css';
import styles from './index.less';

@@ -21,6 +22,7 @@ const JSONCode = () => {

useEffect(() => {
parse();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nodes, edges]);

return (
28 changes: 20 additions & 8 deletions apps/basic/src/pages/basic/tools.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styles from './index.less';
import { useGraphStore } from '@antv/xflow';
import { useGraphStore, useClipboard } from '@antv/xflow';
import { Button } from 'antd';

import styles from './index.less';
import { REACT_NODE } from './shape';

const initialData = {
@@ -110,6 +111,7 @@ const ToolsButton = () => {
const addEdges = useGraphStore((state) => state.addEdges);
const removeEdges = useGraphStore((state) => state.removeEdges);
const updateEdge = useGraphStore((state) => state.updateEdge);
const { copy, paste } = useClipboard();

const onInit = () => {
initData(initialData);
@@ -197,21 +199,29 @@ const ToolsButton = () => {
updateEdge('edge-1', { selected: false });
};

const changePosition = () => {
const onChangePosition = () => {
updateNode('1', {
x: Math.floor(Math.random() * 900),
y: Math.floor(Math.random() * 600),
});
};

const animateEdge = () => {
const onAnimateEdge = () => {
updateEdge('edge-1', { animated: true });
};

const unAnimateEdge = () => {
const onUnAnimateEdge = () => {
updateEdge('edge-1', { animated: false });
};

const onCopy = () => {
copy(['1']);
};

const onPaste = () => {
paste();
};

return (
<div className={styles.tools}>
<Button onClick={onInit}>initData</Button>
@@ -226,9 +236,11 @@ const ToolsButton = () => {
<Button onClick={onUnSelectNode}>UnSelectNode</Button>
<Button onClick={onSelectEdge}>SelectEdge</Button>
<Button onClick={onUnSelectEdge}>UnSelectEdge</Button>
<Button onClick={changePosition}>changePosition</Button>
<Button onClick={animateEdge}>animateEdge</Button>
<Button onClick={unAnimateEdge}>unAnimateEdge</Button>
<Button onClick={onChangePosition}>changePosition</Button>
<Button onClick={onAnimateEdge}>animateEdge</Button>
<Button onClick={onUnAnimateEdge}>unAnimateEdge</Button>
<Button onClick={onCopy}>copy</Button>
<Button onClick={onPaste}>paste</Button>
</div>
);
};
1 change: 1 addition & 0 deletions apps/basic/src/pages/dnd/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { XFlow, XFlowGraph, Background } from '@antv/xflow';

import { Dnd } from './dnd';
import styles from './index.less';

8 changes: 5 additions & 3 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -27,11 +27,13 @@
},
"dependencies": {
"@antv/x6": "^2.15.3",
"@antv/x6-plugin-selection": "^2.2.1",
"@antv/x6-plugin-clipboard": "^2.1.6",
"@antv/x6-plugin-dnd": "^2.1.1",
"@antv/x6-plugin-keyboard": "^2.2.1",
"@antv/x6-plugin-selection": "^2.2.1",
"@antv/x6-react-shape": "^2.2.2",
"zustand": "^4.4.3",
"immer": "^10.0.3"
"immer": "^10.0.3",
"zustand": "^4.4.3"
},
"devDependencies": {
"@antv/config-tsconfig": "workspace:^",
26 changes: 26 additions & 0 deletions packages/core/src/components/Clipboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Clipboard as C } from '@antv/x6-plugin-clipboard';
import { useEffect } from 'react';

import { useGraphInstance } from '../hooks/useGraphInstance';

const Clipboard = (props: Omit<C.Options, 'enabled'>) => {
const graph = useGraphInstance();

useEffect(() => {
if (graph) {
if (graph.getPlugin('clipboard')) {
graph.disposePlugins('clipboard');
}
graph.use(
new C({
enabled: true,
...props,
}),
);
}
}, [graph, props]);

return null;
};

export { Clipboard };
2 changes: 1 addition & 1 deletion packages/core/src/components/Grid.tsx
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ interface GridProps<T extends GridTypes> {
options: Registry.Grid.OptionsMap[T];
}

const Grid: <T extends GridTypes>(props: GridProps<T>) => null = (props) => {
const Grid = <T extends GridTypes>(props: GridProps<T>) => {
const graph = useGraphInstance();

useEffect(() => {
1 change: 1 addition & 0 deletions packages/core/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -3,3 +3,4 @@ export * from './XFlow';
export * from './Graph';
export * from './Grid';
export * from './Background';
export * from './Clipboard';
1 change: 1 addition & 0 deletions packages/core/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -2,3 +2,4 @@ export * from './useGraphInstance';
export * from './useGraphStore';
export * from './useGraphEvent';
export * from './useDnd';
export * from './useClipboard';
57 changes: 57 additions & 0 deletions packages/core/src/hooks/useClipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { Clipboard } from '@antv/x6-plugin-clipboard';
import { useCallback } from 'react';

import { useGraphInstance } from './useGraphInstance';
import { useGraphStore } from './useGraphStore';

export const useClipboard = () => {
const graph = useGraphInstance();
const addNodes = useGraphStore((state) => state.addNodes);
const addEdges = useGraphStore((state) => state.addEdges);

const isLoaded = useCallback(() => {
const loaded = graph && graph.getPlugin('clipboard');
if (!loaded) {
console.warn('clipboard is not loaded, please use clipboard component first');
}
return loaded;
}, [graph]);

const copy = useCallback(
(ids: string[], copyOptions?: Clipboard.CopyOptions) => {
if (graph && isLoaded()) {
const cells = ids.map((id) => graph?.getCellById(id)).filter(Boolean);
graph.copy(cells, copyOptions);
}
},
[graph, isLoaded],
);

const cut = useCallback(
(ids: string[], cutOptions?: Clipboard.CopyOptions) => {
if (graph && isLoaded()) {
const cells = ids.map((id) => graph?.getCellById(id)).filter(Boolean);
graph.cut(cells, cutOptions);
}
},
[graph, isLoaded],
);

const paste = useCallback(
(pasteOptions?: Clipboard.PasteOptions) => {
if (graph && isLoaded()) {
const cells = graph.paste(pasteOptions);
cells.forEach((cell) => {
if (cell.isNode()) {
addNodes([cell.toJSON()], { silent: true });
} else if (cell.isEdge()) {
addEdges([cell.toJSON()], { silent: true });
}
});
}
},
[graph, isLoaded, addNodes, addEdges],
);

return { copy, cut, paste };
};
7 changes: 4 additions & 3 deletions packages/core/src/hooks/useDnd.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Dnd } from '@antv/x6-plugin-dnd';
import { useCallback, useContext, useEffect, useRef } from 'react';
import { useCallback, useEffect, useRef } from 'react';

import { GraphContext } from '../context/GraphContext';
import type { NodeOptions } from '../types';

import { useGraphInstance } from './useGraphInstance';

export const useDnd = (
options?: Omit<Dnd.Options, 'target' | 'getDragNode' | 'getDropNode'>,
) => {
const { graph } = useContext(GraphContext);
const graph = useGraphInstance();
const ref = useRef<Dnd>();

useEffect(() => {
7 changes: 3 additions & 4 deletions packages/core/src/hooks/useGraphEvent.ts
Original file line number Diff line number Diff line change
@@ -17,10 +17,9 @@ export const useGraphEvent = <T extends keyof EventArgs>(
if (ref.current) {
graph?.off(name, ref.current);
}
if (callback) {
ref.current = callback;
graph?.on(name, ref.current);
}

ref.current = callback;
graph?.on(name, ref.current);

return () => {
if (ref.current) {
21 changes: 21 additions & 0 deletions packages/core/src/hooks/useKeyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Keyboard } from '@antv/x6-plugin-keyboard';
import { useEffect, useRef } from 'react';

import { useGraphInstance } from './useGraphInstance';

export const useBindKey = () => {
const graph = useGraphInstance();
const ref = useRef<Keyboard>();

graph?.bindKey();
useEffect(() => {
if (graph && !ref.current) {
ref.current = new Keyboard({
enabled: true,
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [graph]);

return { clipboard: ref.current };
};
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Graph } from '@antv/x6';

export * from './components';
export * from './hooks';
export * from './util';
export * from './types';

export * from '@antv/x6-react-shape';
export { Graph };
4 changes: 1 addition & 3 deletions packages/core/src/util/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
import * as Util from './main';

export { Util };
export * from './algorithm';
1 change: 0 additions & 1 deletion packages/core/src/util/main.ts

This file was deleted.

47 changes: 47 additions & 0 deletions pnpm-lock.yaml

0 comments on commit 25bbd38

Please sign in to comment.