feat(ui): copy/paste input edges when copying node

- Copy edges to selected nodes on copy
- If pasted with `ctrl/meta-shift-v`, also paste the input edges
This commit is contained in:
psychedelicious 2024-05-17 18:41:59 +10:00
parent 575ecb4028
commit 32dff2c4e3
3 changed files with 21 additions and 2 deletions

View File

@ -231,6 +231,15 @@ export const Flow = memo(() => {
); );
useHotkeys(['Ctrl+v', 'Meta+v'], onPasteHotkey); useHotkeys(['Ctrl+v', 'Meta+v'], onPasteHotkey);
const onPasteWithEdgesToNodesHotkey = useCallback(
(e: KeyboardEvent) => {
e.preventDefault();
pasteSelection(true);
},
[pasteSelection]
);
useHotkeys(['Ctrl+shift+v', 'Meta+shift+v'], onPasteWithEdgesToNodesHotkey);
const onUndoHotkey = useCallback(() => { const onUndoHotkey = useCallback(() => {
if (mayUndo) { if (mayUndo) {
dispatch(undo()); dispatch(undo());

View File

@ -4,10 +4,12 @@ import {
$copiedEdges, $copiedEdges,
$copiedNodes, $copiedNodes,
$cursorPos, $cursorPos,
$edgesToCopiedNodes,
selectionPasted, selectionPasted,
selectNodesSlice, selectNodesSlice,
} from 'features/nodes/store/nodesSlice'; } from 'features/nodes/store/nodesSlice';
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition'; import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
import { isEqual, uniqWith } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
const copySelection = () => { const copySelection = () => {
@ -16,17 +18,24 @@ const copySelection = () => {
const { nodes, edges } = selectNodesSlice(getState()); const { nodes, edges } = selectNodesSlice(getState());
const selectedNodes = nodes.filter((node) => node.selected); const selectedNodes = nodes.filter((node) => node.selected);
const selectedEdges = edges.filter((edge) => edge.selected); const selectedEdges = edges.filter((edge) => edge.selected);
const edgesToSelectedNodes = edges.filter((edge) => selectedNodes.some((node) => node.id === edge.target));
$copiedNodes.set(selectedNodes); $copiedNodes.set(selectedNodes);
$copiedEdges.set(selectedEdges); $copiedEdges.set(selectedEdges);
$edgesToCopiedNodes.set(edgesToSelectedNodes);
}; };
const pasteSelection = () => { const pasteSelection = (withEdgesToCopiedNodes?: boolean) => {
const { getState, dispatch } = getStore(); const { getState, dispatch } = getStore();
const currentNodes = selectNodesSlice(getState()).nodes; const currentNodes = selectNodesSlice(getState()).nodes;
const cursorPos = $cursorPos.get(); const cursorPos = $cursorPos.get();
const copiedNodes = deepClone($copiedNodes.get()); const copiedNodes = deepClone($copiedNodes.get());
const copiedEdges = deepClone($copiedEdges.get()); let copiedEdges = deepClone($copiedEdges.get());
if (withEdgesToCopiedNodes) {
const edgesToCopiedNodes = deepClone($edgesToCopiedNodes.get());
copiedEdges = uniqWith([...copiedEdges, ...edgesToCopiedNodes], isEqual);
}
// Calculate an offset to reposition nodes to surround the cursor position, maintaining relative positioning // Calculate an offset to reposition nodes to surround the cursor position, maintaining relative positioning
const xCoords = copiedNodes.map((node) => node.position.x); const xCoords = copiedNodes.map((node) => node.position.x);

View File

@ -498,6 +498,7 @@ export const $cursorPos = atom<XYPosition | null>(null);
export const $templates = atom<Templates>({}); export const $templates = atom<Templates>({});
export const $copiedNodes = atom<AnyNode[]>([]); export const $copiedNodes = atom<AnyNode[]>([]);
export const $copiedEdges = atom<InvocationNodeEdge[]>([]); export const $copiedEdges = atom<InvocationNodeEdge[]>([]);
export const $edgesToCopiedNodes = atom<InvocationNodeEdge[]>([]);
export const $pendingConnection = atom<PendingConnection | null>(null); export const $pendingConnection = atom<PendingConnection | null>(null);
export const $isUpdatingEdge = atom(false); export const $isUpdatingEdge = atom(false);
export const $viewport = atom<Viewport>({ x: 0, y: 0, zoom: 1 }); export const $viewport = atom<Viewport>({ x: 0, y: 0, zoom: 1 });