Refactor/vanilla store utils (#3138)

* feat(stores): use vanilla store utils

* fix(store-utils): keep dimensions if possible

* feat(utils): add nodeDimension and panBy utils

* fix(svelte): use correct updateDim func

* refactor(react): cleanup store

* chore(tests): use new imports
This commit is contained in:
Moritz Klack
2023-06-13 15:55:06 +02:00
committed by GitHub
parent 51375cd482
commit 65d1c3bbd4
27 changed files with 426 additions and 393 deletions
@@ -5,7 +5,7 @@
</script>
<div
class="svelte-flow__viewport"
class="svelte-flow__viewport xyflow__viewport"
style="transform: translate({$transform[0]}px, {$transform[1]}px) scale({$transform[2]})"
>
<slot />
+1 -6
View File
@@ -63,12 +63,7 @@ export function getEdgeTree(store: SvelteFlowStoreState, onError: OnError) {
if (edgePosition) {
res.push({
...edge,
sourceX: edgePosition.sourceX,
sourceY: edgePosition.sourceY,
targetX: edgePosition.targetX,
targetY: edgePosition.targetY,
sourcePosition: edgePosition.sourcePosition,
targetPosition: edgePosition.targetPosition
...edgePosition
});
}
+25 -81
View File
@@ -4,10 +4,9 @@ import {
internalsSymbol,
createMarkerIds,
fitView as fitViewUtil,
getDimensions,
getElementsToRemove,
getHandleBounds,
infiniteExtent,
panBy as panBySystem,
updateNodeDimensions as updateNodeDimensionsSystem,
type UpdateNodePositions,
type NodeDimensionUpdate,
type ViewportHelperFunctionOptions,
@@ -52,7 +51,6 @@ export function createStore(): SvelteFlowStore {
function addEdge(edgeParams: Edge | Connection) {
const edges = get(store.edges);
store.edges.set(addEdgeUtil(edgeParams, edges));
}
@@ -64,6 +62,7 @@ export function createStore(): SvelteFlowStore {
if (nodeDragItem) {
return {
...n,
[internalsSymbol]: n[internalsSymbol],
dragging,
positionAbsolute: nodeDragItem.positionAbsolute,
position: nodeDragItem.position
@@ -76,54 +75,19 @@ export function createStore(): SvelteFlowStore {
};
function updateNodeDimensions(updates: NodeDimensionUpdate[]) {
const viewportNode = document?.querySelector('.svelte-flow__viewport');
const nextNodes = updateNodeDimensionsSystem(
updates,
get(store.nodes),
get(store.domNode),
get(store.nodeOrigin)
);
if (!viewportNode) {
if (!nextNodes) {
return;
}
const style = window.getComputedStyle(viewportNode);
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const nextNodes = get(store.nodes).map((node) => {
const update = updates.find((u) => u.id === node.id);
if (update) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate = !!(
dimensions.width &&
dimensions.height &&
(node.width !== dimensions.width ||
node.height !== dimensions.height ||
update.forceUpdate)
);
if (doUpdate) {
const newNode = {
...node,
width: dimensions.width,
height: dimensions.height,
[internalsSymbol]: {
...node[internalsSymbol],
handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin),
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin)
}
}
};
return newNode;
}
}
return node;
});
const panZoom = get(store.panZoom);
const fitViewOnInitDone =
get(store.fitViewOnInitDone) ||
(get(store.fitViewOnInit) && !!panZoom && fitView({ nodes: nextNodes }));
get(store.fitViewOnInitDone) || (get(store.fitViewOnInit) && fitView({ nodes: nextNodes }));
store.fitViewOnInitDone.set(fitViewOnInitDone);
store.nodes.set(nextNodes);
@@ -174,22 +138,21 @@ export function createStore(): SvelteFlowStore {
function fitView(options?: FitViewOptions) {
const panZoom = get(store.panZoom);
const fitViewNodes = options?.nodes || get(store.nodes);
if (!panZoom) {
return false;
}
const fitViewNodes = options?.nodes || get(store.nodes);
return fitViewUtil(
{
nodes: fitViewNodes as Node[],
width: get(store.width),
height: get(store.height),
minZoom: 0.2,
maxZoom: 2,
minZoom: get(store.minZoom),
maxZoom: get(store.maxZoom),
panZoom,
nodeOrigin: [0, 0]
nodeOrigin: get(store.nodeOrigin)
},
{}
);
@@ -270,35 +233,14 @@ export function createStore(): SvelteFlowStore {
}
function panBy(delta: XYPosition) {
const panZoom = get(store.panZoom);
const transform = get(store.transform);
const width = get(store.width);
const height = get(store.height);
if (!panZoom || (!delta.x && !delta.y)) {
return false;
}
const nextViewport = panZoom.setViewportConstrained(
{
x: transform[0] + delta.x,
y: transform[1] + delta.y,
zoom: transform[2]
},
[
[0, 0],
[width, height]
],
infiniteExtent
);
const transformChanged =
!!nextViewport &&
(nextViewport.x !== transform[0] ||
nextViewport.y !== transform[1] ||
nextViewport.k !== transform[2]);
return transformChanged;
return panBySystem({
delta,
panZoom: get(store.panZoom),
transform: get(store.transform),
translateExtent: get(store.translateExtent),
width: get(store.width),
height: get(store.height)
});
}
const updateConnection: UpdateConnection = (update) => {
@@ -328,6 +270,8 @@ export function createStore(): SvelteFlowStore {
store.selectionRectMode.set(null);
store.snapGrid.set(null);
store.isValidConnection.set(() => true);
store.nodes.set([]);
store.edges.set([]);
unselectNodesAndEdges();
cancelConnection();
@@ -11,7 +11,8 @@ import {
type PanZoomInstance,
type CoordinateExtent,
type IsValidConnection,
type GroupedEdges
type GroupedEdges,
type NodeOrigin
} from '@xyflow/system';
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
@@ -22,7 +23,7 @@ import StraightEdge from '$lib/components/edges/StraightEdge.svelte';
import SmoothStepEdge from '$lib/components/edges/SmoothStepEdge.svelte';
import StepEdge from '$lib/components/edges/StepEdge.svelte';
import type { ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted, Node } from '$lib/types';
import { createNodes, createEdges } from './utils';
import { createNodesStore, createEdgesStore } from './utils';
export const initConnectionData = {
connectionStartHandle: null,
@@ -46,14 +47,15 @@ export const initialEdgeTypes = {
export const getInitialStore = () => ({
flowId: writable<string | null>(null),
nodes: createNodes([]),
nodes: createNodesStore([]),
visibleNodes: readable<Node[]>([]),
edges: createEdges([]),
edges: createEdgesStore([]),
edgeTree: readable<GroupedEdges<EdgeLayouted>[]>([]),
height: writable<number>(500),
width: writable<number>(500),
minZoom: writable<number>(0.5),
maxZoom: writable<number>(2),
nodeOrigin: writable<NodeOrigin>([0, 0]),
nodeExtent: writable<CoordinateExtent>(infiniteExtent),
translateExtent: writable<CoordinateExtent>(infiniteExtent),
autoPanOnNodeDrag: writable<boolean>(true),
+26 -73
View File
@@ -6,17 +6,14 @@ import {
type Writable,
get
} from 'svelte/store';
import {
isNumeric,
getNodePositionWithOrigin,
internalsSymbol,
type XYZPosition
} from '@xyflow/system';
import { updateNodes } from '@xyflow/system';
import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types';
// we need to sync the user nodes and the internal nodes so that the user can receive the updates
// made by Svelte Flow (like dragging or selecting a node).
export function syncNodeStores(
nodesStore: ReturnType<typeof createNodes>,
nodesStore: ReturnType<typeof createNodesStore>,
userNodesStore: Writable<Node[]>
) {
const nodesStoreSetter = nodesStore.set;
@@ -38,8 +35,9 @@ export function syncNodeStores(
nodesStore.update = userNodesStore.update = (fn: (nds: Node[]) => Node[]) => _set(fn(val));
}
// same for edges
export function syncEdgeStores(
edgesStore: ReturnType<typeof createEdges>,
edgesStore: ReturnType<typeof createEdgesStore>,
userEdgesStore: Writable<Edge[]>
) {
const nodesStoreSetter = edgesStore.set;
@@ -58,66 +56,33 @@ export function syncEdgeStores(
edgesStore.update = userEdgesStore.update = (fn: (nds: Edge[]) => Edge[]) => _set(fn(val));
}
export const createNodes = (
export type NodeStoreOptions = {
elevateNodesOnSelect?: boolean;
};
// we are creating a custom store for the internals nodes in order to update the zIndex and positionAbsolute.
// The user only passes in relative positions, so we need to calculate the absolute positions based on the parent nodes.
export const createNodesStore = (
nodes: Node[]
): {
subscribe: (this: void, run: Subscriber<Node[]>) => Unsubscriber;
update: (this: void, updater: Updater<Node[]>) => void;
set: (this: void, value: Node[]) => Node[];
setDefaultOptions: (opts: DefaultNodeOptions) => void;
setOptions: (opts: NodeStoreOptions) => void;
} => {
const { subscribe, set, update } = writable<Node[]>([]);
let value = nodes;
let defaults = {};
let elevateNodesOnSelect = false;
const _set = (nds: Node[]): Node[] => {
const parentNodes: Record<string, boolean> = {};
const nextNodes = nds.map((n) => {
const node: Node = { ...defaults, ...n, positionAbsolute: n.position };
const z = (isNumeric(node.zIndex) ? node.zIndex : 0) + (node.selected ? 1 : 0);
if (node.parentNode) {
parentNodes[node.parentNode] = true;
}
Object.defineProperty(node, internalsSymbol, {
value: {
handleBounds: node?.[internalsSymbol]?.handleBounds,
z
}
});
return node;
const nextNodes = updateNodes(nds, value, {
elevateNodesOnSelect,
defaults
});
const nodesWithPositions = nextNodes.map((node) => {
if (node.parentNode && !parentNodes[node.parentNode]) {
throw new Error(`Parent node ${node.parentNode} not found`);
}
if (node.parentNode || parentNodes?.[node.id]) {
const { x, y, z } = calculateXYZPosition(node, nextNodes, {
...node.position,
z: node[internalsSymbol]?.z ?? 0
});
node.positionAbsolute = {
x,
y
};
node[internalsSymbol]!.z = z;
if (parentNodes?.[node.id]) {
node[internalsSymbol]!.isParent = true;
}
}
return node;
});
value = nodesWithPositions;
value = nextNodes;
set(value);
@@ -130,17 +95,22 @@ export const createNodes = (
defaults = options;
};
const setOptions = (options: NodeStoreOptions) => {
elevateNodesOnSelect = options.elevateNodesOnSelect ?? elevateNodesOnSelect;
};
_set(value);
return {
subscribe,
set: _set,
update: _update,
setDefaultOptions
setDefaultOptions,
setOptions
};
};
export const createEdges = (
export const createEdgesStore = (
edges: Edge[],
defaultOptions?: DefaultEdgeOptions
): Writable<Edge[]> & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => {
@@ -169,20 +139,3 @@ export const createEdges = (
setDefaultOptions
};
};
function calculateXYZPosition(node: Node, nodes: Node[], result: XYZPosition): XYZPosition {
if (!node.parentNode) {
return result;
}
const parentNode = nodes.find((n) => n.id === node.parentNode)!;
const parentNodePosition = getNodePositionWithOrigin(parentNode, parentNode?.origin);
return calculateXYZPosition(parentNode, nodes, {
x: (result.x ?? 0) + parentNodePosition.x,
y: (result.y ?? 0) + parentNodePosition.y,
z:
(parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0)
? parentNode[internalsSymbol]?.z ?? 0
: result.z ?? 0
});
}
@@ -80,6 +80,10 @@
svelteFlow.nodes.update((nds) => nds.concat(newNode));
};
$: {
console.log($nodes);
}
</script>
<main>