streamlined connections

This commit is contained in:
peterkogo
2024-06-26 15:03:12 +02:00
parent 44254648c8
commit 5493f045ff
25 changed files with 355 additions and 474 deletions
@@ -1,130 +0,0 @@
import { derived, type Writable } from 'svelte/store';
import {
getBezierPath,
getSmoothStepPath,
getStraightPath,
ConnectionLineType,
ConnectionMode,
Position,
type HandleElement
} from '@xyflow/system';
import type { SvelteFlowStoreState } from './types';
import type { ConnectionData } from '$lib/types';
export type ConnectionProps = {
path: string | null;
sourceX: number | null;
sourceY: number | null;
sourcePosition: Position | undefined | null;
targetX: number | null;
targetY: number | null;
targetPosition: Position | undefined | null;
pointerPosition: ConnectionData['connectionPosition'] | null;
startHandle: ConnectionData['connectionStartHandle'] | null;
endHandle: ConnectionData['connectionEndHandle'] | null;
status: ConnectionData['connectionStatus'] | null;
};
export const initConnectionProps = {
path: null,
sourceX: null,
sourceY: null,
sourcePosition: null,
targetX: null,
targetY: null,
targetPosition: null,
pointerPosition: null,
startHandle: null,
endHandle: null,
status: null
};
const oppositePosition = {
[Position.Left]: Position.Right,
[Position.Right]: Position.Left,
[Position.Top]: Position.Bottom,
[Position.Bottom]: Position.Top
};
export function getDerivedConnectionProps(
store: SvelteFlowStoreState,
currentConnection: Writable<ConnectionData>
) {
return derived(
[
currentConnection,
store.connectionLineType,
store.connectionMode,
store.nodeLookup,
store.viewport
],
([connection, connectionLineType, connectionMode, nodeLookup, viewport]) => {
if (!connection.connectionStartHandle?.nodeId) {
return initConnectionProps;
}
// TODO: it should bail out if the node is not found
const fromNode = nodeLookup.get(connection.connectionStartHandle?.nodeId);
const fromHandleBounds = fromNode?.internals.handleBounds;
const handleBoundsStrict =
fromHandleBounds?.[connection.connectionStartHandle.type || 'source'] || [];
const handleBoundsLoose: HandleElement[] | undefined | null = handleBoundsStrict
? handleBoundsStrict
: fromHandleBounds?.[
connection?.connectionStartHandle?.type === 'source' ? 'target' : 'source'
];
const handleBounds =
connectionMode === ConnectionMode.Strict ? handleBoundsStrict : handleBoundsLoose;
const fromHandle = connection.connectionStartHandle?.handleId
? handleBounds?.find((d) => d.id === connection.connectionStartHandle?.handleId)
: handleBounds?.[0];
const fromHandleX = fromHandle
? fromHandle.x + fromHandle.width / 2
: (fromNode?.measured.width ?? 0) / 2;
const fromHandleY = fromHandle
? fromHandle.y + fromHandle.height / 2
: fromNode?.measured.height ?? 0;
const fromX = (fromNode?.internals.positionAbsolute.x ?? 0) + fromHandleX;
const fromY = (fromNode?.internals.positionAbsolute.y ?? 0) + fromHandleY;
const fromPosition = fromHandle?.position;
const toPosition =
connection.connectionEndHandle?.position ??
(fromPosition ? oppositePosition[fromPosition] : undefined);
const pathParams = {
sourceX: fromX,
sourceY: fromY,
sourcePosition: fromPosition,
targetX: ((connection.connectionPosition?.x ?? 0) - viewport.x) / viewport.zoom,
targetY: ((connection.connectionPosition?.y ?? 0) - viewport.y) / viewport.zoom,
targetPosition: toPosition
};
let path = '';
if (connectionLineType === ConnectionLineType.Bezier) {
// we assume the destination position is opposite to the source position
[path] = getBezierPath(pathParams);
} else if (connectionLineType === ConnectionLineType.Step) {
[path] = getSmoothStepPath({
...pathParams,
borderRadius: 0
});
} else if (connectionLineType === ConnectionLineType.SmoothStep) {
[path] = getSmoothStepPath(pathParams);
} else {
[path] = getStraightPath(pathParams);
}
return {
path,
...pathParams,
pointerPosition: connection.connectionPosition,
startHandle: connection.connectionStartHandle,
endHandle: connection.connectionEndHandle,
status: connection.connectionStatus
};
}
);
}
+6 -22
View File
@@ -1,5 +1,5 @@
import { getContext, setContext } from 'svelte';
import { derived, get, writable } from 'svelte/store';
import { derived, get } from 'svelte/store';
import {
createMarkerIds,
fitView as fitViewSystem,
@@ -7,6 +7,8 @@ import {
panBy as panBySystem,
updateNodeInternals as updateNodeInternalsSystem,
addEdge as addEdgeUtil,
initialConnection,
errorMessages,
type UpdateNodePositions,
type InternalNodeUpdate,
type ViewportHelperFunctionOptions,
@@ -14,17 +16,15 @@ import {
type XYPosition,
type CoordinateExtent,
type UpdateConnection,
errorMessages,
type ConnectionState
} from '@xyflow/system';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions, ConnectionData } from '$lib/types';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import type { SvelteFlowStore } from './types';
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
import { getVisibleEdges } from './visible-edges';
import { getVisibleNodes } from './visible-nodes';
import { getDerivedConnectionProps } from './derived-connection-props';
export const key = Symbol();
@@ -331,27 +331,12 @@ export function createStore({
});
}
const initConnectionUpdateData = {
connectionStartHandle: null,
connectionEndHandle: null,
connectionPosition: null,
connectionStatus: null
};
// by creating an internal, unexposed store and using a derived store
// we prevent using slow get() calls
const currentConnection = writable<ConnectionData>(initConnectionUpdateData);
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
currentConnection.set({
connectionStartHandle: newConnection.fromHandle,
connectionEndHandle: newConnection.toHandle,
connectionPosition: newConnection.position,
connectionStatus: newConnection.isValid ? 'valid' : 'invalid'
});
store.connection.set({ ...newConnection });
};
function cancelConnection() {
currentConnection.set(initConnectionUpdateData);
store.connection.set(initialConnection);
}
function reset() {
@@ -370,7 +355,6 @@ export function createStore({
...store,
// derived state
connection: getDerivedConnectionProps(store, currentConnection),
visibleEdges: getVisibleEdges(store),
visibleNodes: getVisibleNodes(store),
markers: derived(
@@ -9,6 +9,7 @@ import {
getNodesBounds,
getViewportForBounds,
updateConnectionLookup,
initialConnection,
type SelectionRect,
type SnapGrid,
type MarkerProps,
@@ -22,7 +23,8 @@ import {
type OnConnectStart,
type OnConnectEnd,
type NodeLookup,
type EdgeLookup
type EdgeLookup,
type ConnectionState
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -51,7 +53,6 @@ import type {
InternalNode
} from '$lib/types';
import { createNodesStore, createEdgesStore } from './utils';
import { initConnectionProps, type ConnectionProps } from './derived-connection-props';
export const initialNodeTypes = {
input: InputNode,
@@ -142,7 +143,7 @@ export const getInitialStore = ({
viewport: writable<Viewport>(viewport),
connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
domNode: writable<HTMLDivElement | null>(null),
connection: readable<ConnectionProps>(initConnectionProps),
connection: writable<ConnectionState>(initialConnection),
connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
connectionRadius: writable<number>(20),
isValidConnection: writable<IsValidConnection>(() => true),