fix(edges): use derived store for edge handling

This commit is contained in:
moklick
2023-02-01 15:25:14 +01:00
parent 2af833aee1
commit 71bd05b7e7
10 changed files with 243 additions and 215 deletions
@@ -5,9 +5,6 @@
export let sourceY: number = 0;
export let targetX: number = 0;
export let targetY: number = 0;
const path = `M ${sourceX},${sourceY}L ${targetX},${targetY}`;
</script>
<g
@@ -15,7 +12,7 @@
data-id={id}
>
<path
d={path}
d={`M ${sourceX},${sourceY}L ${targetX},${targetY}`}
fill="none"
class="react-flow__edge-path"
/>
@@ -44,7 +44,7 @@
.react-flow__node {
padding: 10px;
border-radius: 3px;
width: var(--node-width);
width: 50px;
font-size: 12px;
color: #222;
text-align: center;
@@ -10,6 +10,6 @@
</script>
<Handle type="target" position={targetPosition} />
{data?.label}
{data?.label}
<Handle type="source" position={sourcePosition} />
@@ -1,171 +0,0 @@
<script lang="ts">
type EdgePositions = {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
}
import Edge from '$lib/components/edges/BaseEdge.svelte';
import { useStore } from '$lib/store';
import { type NodeHandleBounds, type Rect, type Node, type HandleElement, Position, type XYPosition } from '@reactflow/core';
import { internalsSymbol } from '@reactflow/core';
const { edgesStore, widthStore, heightStore, nodesStore } = useStore();
function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
const isValid =
handleBounds &&
node?.width &&
node?.height &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
return [
{
x: node?.positionAbsolute?.x || 0,
y: node?.positionAbsolute?.y || 0,
width: node?.width || 0,
height: node?.height || 0,
},
handleBounds,
!!isValid,
];
}
function getHandlePosition(position: Position, nodeRect: Rect, handle: HandleElement | null = null): XYPosition {
const x = (handle?.x || 0) + nodeRect.x;
const y = (handle?.y || 0) + nodeRect.y;
const width = handle?.width || nodeRect.width;
const height = handle?.height || nodeRect.height;
switch (position) {
case Position.Top:
return {
x: x + width / 2,
y,
};
case Position.Right:
return {
x: x + width,
y: y + height / 2,
};
case Position.Bottom:
return {
x: x + width / 2,
y: y + height,
};
case Position.Left:
return {
x,
y: y + height / 2,
};
}
}
function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null {
if (!bounds) {
return null;
}
if (handleId) {
return bounds.find((d) => d.id === handleId)!;
} else if (bounds.length === 1) {
return bounds[0];
}
return null;
}
function getEdgePositions(
sourceNodeRect: Rect,
sourceHandle: HandleElement,
sourcePosition: Position,
targetNodeRect: Rect,
targetHandle: HandleElement,
targetPosition: Position
): EdgePositions {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
return {
sourceX: sourceHandlePos.x,
sourceY: sourceHandlePos.y,
targetX: targetHandlePos.x,
targetY: targetHandlePos.y,
};
};
let edgesWithData: any = [];
$: {
$nodesStore
edgesWithData = $edgesStore.map((edge) => {
const sourceNode = $nodesStore.find((node) => node.id === edge.source);
const targetNode = $nodesStore.find((node) => node.id === edge.target);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
if (!sourceIsValid || !targetIsValid) {
return null;
}
let edgeType = edge.type || 'default';
const targetNodeHandles = targetHandleBounds!.target;
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
if (!sourceHandle || !targetHandle) {
return null;
}
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNodeRect,
sourceHandle,
sourcePosition,
targetNodeRect,
targetHandle,
targetPosition
);
return {
id: edge.id,
type: edgeType,
sourceX,
sourceY,
targetX,
targetY
};
})
}
</script>
<svg
width={$widthStore}
height={$heightStore}
class="react-flow__edges react-flow__container"
>
{#each edgesWithData as edge}
{#if edge}
<Edge {...edge} />
{/if}
{/each}
</svg>
<style>
.react-flow__edges {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
@@ -0,0 +1,28 @@
<script lang="ts">
import Edge from '$lib/components/edges/BaseEdge.svelte';
import { useStore } from '$lib/store';
const { edgesWithDataStore, widthStore, heightStore } = useStore();
</script>
<svg
width={$widthStore}
height={$heightStore}
class="react-flow__edges react-flow__container"
>
{#each $edgesWithDataStore as edge}
<Edge {...edge} />
{/each}
</svg>
<style>
.react-flow__edges {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
pointer-events: none;
overflow: visible;
}
@@ -0,0 +1,105 @@
import {
type NodeHandleBounds,
type Rect,
type Node,
type HandleElement,
type XYPosition,
Position,
internalsSymbol
} from '@reactflow/core';
export function getNodeData(node?: Node): [Rect, NodeHandleBounds | null, boolean] {
const handleBounds = node?.[internalsSymbol]?.handleBounds || null;
const isValid =
handleBounds &&
node?.width &&
node?.height &&
typeof node?.positionAbsolute?.x !== 'undefined' &&
typeof node?.positionAbsolute?.y !== 'undefined';
return [
{
x: node?.positionAbsolute?.x || 0,
y: node?.positionAbsolute?.y || 0,
width: node?.width || 0,
height: node?.height || 0
},
handleBounds,
!!isValid
];
}
export function getHandlePosition(
position: Position,
nodeRect: Rect,
handle: HandleElement | null = null
): XYPosition {
const x = (handle?.x || 0) + nodeRect.x;
const y = (handle?.y || 0) + nodeRect.y;
const width = handle?.width || nodeRect.width;
const height = handle?.height || nodeRect.height;
switch (position) {
case Position.Top:
return {
x: x + width / 2,
y
};
case Position.Right:
return {
x: x + width,
y: y + height / 2
};
case Position.Bottom:
return {
x: x + width / 2,
y: y + height
};
case Position.Left:
return {
x,
y: y + height / 2
};
}
}
export function getHandle(bounds: HandleElement[], handleId?: string | null): HandleElement | null {
if (!bounds) {
return null;
}
if (handleId) {
return bounds.find((d) => d.id === handleId)!;
} else if (bounds.length === 1) {
return bounds[0];
}
return null;
}
export type EdgePosition = {
sourceX: number;
sourceY: number;
targetX: number;
targetY: number;
};
export function getEdgePositions(
sourceNodeRect: Rect,
sourceHandle: HandleElement,
sourcePosition: Position,
targetNodeRect: Rect,
targetHandle: HandleElement,
targetPosition: Position
): EdgePosition {
const sourceHandlePos = getHandlePosition(sourcePosition, sourceNodeRect, sourceHandle);
const targetHandlePos = getHandlePosition(targetPosition, targetNodeRect, targetHandle);
return {
sourceX: sourceHandlePos.x,
sourceY: sourceHandlePos.y,
targetX: targetHandlePos.x,
targetY: targetHandlePos.y
};
}
@@ -6,7 +6,7 @@
import { key, createStore } from '$lib/store';
import Viewport from '$lib/container/Viewport.svelte';
import NodeRenderer from '$lib/container/NodeRenderer.svelte';
import EdgeRenderer from '$lib/container/EdgeRenderer.svelte';
import EdgeRenderer from '$lib/container/EdgeRenderer/index.svelte';
export let nodes: Node[];
export let edges: Edge[];
@@ -56,7 +56,4 @@
z-index: 0;
}
:root {
--node-width: 150px;
}
</style>
+97 -28
View File
@@ -1,9 +1,22 @@
import { getContext } from 'svelte';
import { get, writable, type Writable } from 'svelte/store';
import type { Node, Transform, NodeDragItem, NodeDimensionUpdate, Edge } from '@reactflow/core';
import { derived, writable, type Readable, type Writable } from 'svelte/store';
import {
type Node,
type Transform,
type NodeDragItem,
type NodeDimensionUpdate,
type Edge,
Position
} from '@reactflow/core';
import { internalsSymbol } from '@reactflow/core';
import { getDimensions, getHandleBounds, updateAbsoluteNodePositions } from '../../utils';
import { getDimensions, getHandleBounds } from '../../utils';
import {
getEdgePositions,
getHandle,
getNodeData,
type EdgePosition
} from '$lib/container/EdgeRenderer/utils';
export const key = Symbol();
@@ -13,12 +26,18 @@ type CreateStoreProps = {
transform?: Transform;
};
export type EdgeWithData = EdgePosition & {
id: string;
type: string;
};
type SvelteFlowStore = {
nodesStore: Writable<CreateStoreProps['nodes']>;
edgesStore: Writable<CreateStoreProps['edges']>;
heightStore: Writable<number>;
widthStore: Writable<number>;
transformStore: Writable<Transform>;
edgesWithDataStore: Readable<EdgeWithData[]>;
updateNodePositions: (
nodeDragItems: NodeDragItem[],
positionChanged?: boolean,
@@ -37,6 +56,51 @@ export function createStore({
const heightStore = writable(500);
const widthStore = writable(500);
const edgesWithDataStore = derived([edgesStore, nodesStore], ([$edges, $nodes]) => {
return $edges
.map((edge) => {
const sourceNode = $nodes.find((node) => node.id === edge.source);
const targetNode = $nodes.find((node) => node.id === edge.target);
const [sourceNodeRect, sourceHandleBounds, sourceIsValid] = getNodeData(sourceNode);
const [targetNodeRect, targetHandleBounds, targetIsValid] = getNodeData(targetNode);
if (!sourceIsValid || !targetIsValid) {
return null;
}
const edgeType = edge.type || 'default';
const targetNodeHandles = targetHandleBounds!.target;
const sourceHandle = getHandle(sourceHandleBounds!.source!, edge.sourceHandle);
const targetHandle = getHandle(targetNodeHandles!, edge.targetHandle);
const sourcePosition = sourceHandle?.position || Position.Bottom;
const targetPosition = targetHandle?.position || Position.Top;
if (!sourceHandle || !targetHandle) {
return null;
}
const { sourceX, sourceY, targetX, targetY } = getEdgePositions(
sourceNodeRect,
sourceHandle,
sourcePosition,
targetNodeRect,
targetHandle,
targetPosition
);
return {
id: edge.id,
type: edgeType,
sourceX,
sourceY,
targetX,
targetY
};
})
.filter((e) => e !== null) as EdgeWithData[];
});
const transformStore = writable(transform);
function updateNodePositions(nodeDragItems: NodeDragItem[], dragging = false) {
@@ -67,36 +131,40 @@ export function createStore({
const style = window.getComputedStyle(viewportNode);
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const nds = get(nodesStore);
updates.forEach((update) => {
const node = nds.find((n) => n.id === update.id);
nodesStore.update((nds) => {
const nextNodes = nds.map((node) => {
const update = updates.find((u) => u.id === node.id);
if (node) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate = !!(
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width ||
node.height !== dimensions.height ||
update.forceUpdate)
);
if (update) {
const dimensions = getDimensions(update.nodeElement);
if (doUpdate) {
node[internalsSymbol] = {
...node[internalsSymbol],
handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom),
target: getHandleBounds('.target', update.nodeElement, zoom)
}
};
node.width = dimensions.width;
node.height = dimensions.height;
const doUpdate = !!(
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width ||
node.height !== dimensions.height ||
update.forceUpdate)
);
if (doUpdate) {
node[internalsSymbol] = {
...node[internalsSymbol],
handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom),
target: getHandleBounds('.target', update.nodeElement, zoom)
}
};
node.width = dimensions.width;
node.height = dimensions.height;
}
}
}
});
updateAbsoluteNodePositions(nds);
return node;
});
return nextNodes;
});
}
return {
@@ -105,6 +173,7 @@ export function createStore({
transformStore,
heightStore,
widthStore,
edgesWithDataStore,
updateNodePositions,
updateNodeDimensions
};
+7
View File
@@ -48,6 +48,13 @@
// data: { label: 'Input Node 2' },
// position: { x: 150, y: 250 },
// }]
// const edges = [{
// id: '1-2',
// type: 'default',
// source: '1',
// target: '2'
// }]
</script>
<SvelteFlow {nodes} {edges} />
+2 -6
View File
@@ -82,10 +82,6 @@ export const getHandleBounds = (
const handlesArray = Array.from(handles) as HTMLDivElement[];
const nodeBounds = nodeElement.getBoundingClientRect();
const nodeOffset = {
x: nodeBounds.width,
y: nodeBounds.height
};
return handlesArray.map((handle): HandleElement => {
const handleBounds = handle.getBoundingClientRect();
@@ -93,8 +89,8 @@ export const getHandleBounds = (
return {
id: handle.getAttribute('data-handleid'),
position: handle.getAttribute('data-handlepos') as unknown as Position,
x: (handleBounds.left - nodeBounds.left - nodeOffset.x) / zoom,
y: (handleBounds.top - nodeBounds.top - nodeOffset.y) / zoom,
x: (handleBounds.left - nodeBounds.left) / zoom,
y: (handleBounds.top - nodeBounds.top) / zoom,
...getDimensions(handle)
};
});