From 002156ea331a467edd1d8ec13b364ac829677b45 Mon Sep 17 00:00:00 2001 From: moklick Date: Tue, 6 Jun 2023 14:27:40 +0200 Subject: [PATCH] refactor(svelte): simplify nodes and edges store handling --- .../SvelteFlowProvider.svelte | 21 +- .../components/SvelteFlowProvider/index.ts | 1 - .../components/SvelteFlowProvider/types.ts | 9 - .../container/SvelteFlow/SvelteFlow.svelte | 82 ++--- .../src/lib/container/SvelteFlow/types.ts | 3 + .../src/lib/container/SvelteFlow/utils.ts | 71 ++++ packages/svelte/src/lib/store/index.ts | 27 +- .../svelte/src/lib/store/initial-store.ts | 11 +- packages/svelte/src/lib/store/types.ts | 9 +- packages/svelte/src/lib/store/utils.ts | 188 +++++++++++ packages/svelte/src/lib/utils/index.ts | 132 +------- .../svelte/src/routes/customnode/+page.svelte | 30 +- .../src/routes/drag-n-drop/+page.svelte | 47 +-- .../svelte/src/routes/drag-n-drop/Flow.svelte | 42 +++ packages/svelte/src/routes/edges/+page.svelte | 280 ++++++++-------- .../src/routes/interaction/+page.svelte | 316 +++++++++--------- .../svelte/src/routes/overview/+page.svelte | 68 ++-- .../svelte/src/routes/stress/+page.svelte | 25 +- .../svelte/src/routes/subflows/+page.svelte | 31 +- .../src/routes/usesvelteflow/+page.svelte | 47 +-- .../src/routes/usesvelteflow/Flow.svelte | 40 +++ .../svelte/src/routes/validation/+page.svelte | 36 +- 22 files changed, 779 insertions(+), 737 deletions(-) delete mode 100644 packages/svelte/src/lib/components/SvelteFlowProvider/types.ts create mode 100644 packages/svelte/src/lib/container/SvelteFlow/utils.ts create mode 100644 packages/svelte/src/lib/store/utils.ts diff --git a/packages/svelte/src/lib/components/SvelteFlowProvider/SvelteFlowProvider.svelte b/packages/svelte/src/lib/components/SvelteFlowProvider/SvelteFlowProvider.svelte index bc0ae5c4..4b9388f7 100644 --- a/packages/svelte/src/lib/components/SvelteFlowProvider/SvelteFlowProvider.svelte +++ b/packages/svelte/src/lib/components/SvelteFlowProvider/SvelteFlowProvider.svelte @@ -1,22 +1,9 @@
& { id?: string; + nodes: Writable; + edges: Writable; nodeTypes?: NodeTypes; edgeTypes?: EdgeTypes; selectionKey?: KeyDefinition; diff --git a/packages/svelte/src/lib/container/SvelteFlow/utils.ts b/packages/svelte/src/lib/container/SvelteFlow/utils.ts new file mode 100644 index 00000000..6ed50181 --- /dev/null +++ b/packages/svelte/src/lib/container/SvelteFlow/utils.ts @@ -0,0 +1,71 @@ +import type { SvelteFlowStore } from '$lib/store/types'; +import type { EdgeTypes, NodeTypes } from '$lib/types'; +import type { CoordinateExtent } from '@xyflow/system'; +import type { Writable } from 'svelte/store'; + +// this is helper function for updating the store +// for props where we need to call a specific store action +export function updateStore( + store: SvelteFlowStore, + { + nodeTypes, + edgeTypes, + minZoom, + maxZoom, + translateExtent + }: { + nodeTypes?: NodeTypes; + edgeTypes?: EdgeTypes; + minZoom?: number; + maxZoom?: number; + translateExtent?: CoordinateExtent; + } +) { + if (nodeTypes !== undefined) { + store.setNodeTypes(nodeTypes); + } + + if (edgeTypes !== undefined) { + store.setEdgeTypes(edgeTypes); + } + + if (minZoom !== undefined) { + store.setMinZoom(minZoom); + } + + if (maxZoom !== undefined) { + store.setMaxZoom(maxZoom); + } + + if (translateExtent !== undefined) { + store.setTranslateExtent(translateExtent); + } +} + +const getKeys = (obj: T) => Object.keys(obj) as Array; + +type UnwrapWritable = T extends Writable ? U : T; + +// @todo there must be a better way to define the types here.. +export type UpdatableStoreProps = { + flowId?: UnwrapWritable; + connectionLineType?: UnwrapWritable; + connectionRadius?: UnwrapWritable; + selectionMode?: UnwrapWritable; + snapGrid?: UnwrapWritable; + defaultMarkerColor?: UnwrapWritable; + nodesDraggable?: UnwrapWritable; + nodesConnectable?: UnwrapWritable; + elementsSelectable?: UnwrapWritable; + isValidConnection?: UnwrapWritable; +}; + +export function updateStoreByKeys(store: SvelteFlowStore, keys: UpdatableStoreProps) { + getKeys(keys).forEach((prop) => { + const update = keys[prop]; + if (update !== undefined) { + // @ts-ignore + store[prop].set(update); + } + }); +} diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index d0b16fd0..7b4e348a 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -1,4 +1,4 @@ -import { getContext } from 'svelte'; +import { getContext, setContext } from 'svelte'; import { derived, get } from 'svelte/store'; import { internalsSymbol, @@ -25,19 +25,15 @@ import { initConnectionData, initialEdgeTypes, initialNodeTypes, - initialStoreState + getInitialStore } from './initial-store'; import type { SvelteFlowStore } from './types'; +import { syncNodeStores, syncEdgeStores } from './utils'; export const key = Symbol(); -type CreateStoreParams = Pick; - -export function createStore(params: CreateStoreParams): SvelteFlowStore { - const store = { - ...initialStoreState, - ...params - }; +export function createStore(): SvelteFlowStore { + const store = getInitialStore(); function setNodeTypes(nodeTypes: NodeTypes) { store.nodeTypes.set({ @@ -55,6 +51,7 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore { function addEdge(edgeParams: Edge | Connection) { const edges = get(store.edges); + store.edges.set(addEdgeUtil(edgeParams, edges)); } @@ -348,6 +345,8 @@ export function createStore(params: CreateStoreParams): SvelteFlowStore { ), // actions + syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes), + syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges), setNodeTypes, setEdgeTypes, addEdge, @@ -380,3 +379,13 @@ export function useStore(): SvelteFlowStore { return store.getStore(); } + +export function createStoreContext() { + const store = createStore(); + + setContext(key, { + getStore: () => store + }); + + return store; +} diff --git a/packages/svelte/src/lib/store/initial-store.ts b/packages/svelte/src/lib/store/initial-store.ts index 574de978..1df46821 100644 --- a/packages/svelte/src/lib/store/initial-store.ts +++ b/packages/svelte/src/lib/store/initial-store.ts @@ -20,7 +20,8 @@ import BezierEdge from '$lib/components/edges/BezierEdge.svelte'; 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, Edge, Node } from '$lib/types'; +import type { ConnectionData, NodeTypes, EdgeTypes, EdgeLayouted } from '$lib/types'; +import { createNodes, createEdges } from './utils'; export const initConnectionData = { connectionStartHandle: null, @@ -42,10 +43,10 @@ export const initialEdgeTypes = { step: StepEdge }; -export const initialStoreState = { +export const getInitialStore = () => ({ flowId: writable(null), - nodes: writable([]), - edges: writable([]), + nodes: createNodes([]), + edges: createEdges([]), edgesLayouted: readable([]), height: writable(500), width: writable(500), @@ -82,4 +83,4 @@ export const initialStoreState = { markers: readable([]), defaultMarkerColor: writable('#b1b1b7'), lib: readable('svelte') -}; +}); diff --git a/packages/svelte/src/lib/store/types.ts b/packages/svelte/src/lib/store/types.ts index 66735702..9156f6f0 100644 --- a/packages/svelte/src/lib/store/types.ts +++ b/packages/svelte/src/lib/store/types.ts @@ -9,10 +9,12 @@ import type { UpdateConnection } from '@xyflow/system'; -import type { initialStoreState } from './initial-store'; +import type { getInitialStore } from './initial-store'; import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/types'; export type SvelteFlowStoreActions = { + syncNodeStores: (nodesStore: Writable) => void; + syncEdgeStores: (edgeStore: Writable) => void; setNodeTypes: (nodeTypes: NodeTypes) => void; setEdgeTypes: (edgeTypes: EdgeTypes) => void; addEdge: (edge: Edge | Connection) => void; @@ -33,9 +35,6 @@ export type SvelteFlowStoreActions = { reset(): void; }; -export type SvelteFlowStoreState = typeof initialStoreState & { - nodes: Writable; - edges: Writable; -}; +export type SvelteFlowStoreState = ReturnType; export type SvelteFlowStore = SvelteFlowStoreState & SvelteFlowStoreActions; diff --git a/packages/svelte/src/lib/store/utils.ts b/packages/svelte/src/lib/store/utils.ts new file mode 100644 index 00000000..5f75947b --- /dev/null +++ b/packages/svelte/src/lib/store/utils.ts @@ -0,0 +1,188 @@ +import { + writable, + type Unsubscriber, + type Subscriber, + type Updater, + type Writable, + get +} from 'svelte/store'; +import { + isNumeric, + getNodePositionWithOrigin, + internalsSymbol, + type XYZPosition +} from '@xyflow/system'; + +import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types'; + +export function syncNodeStores( + nodesStore: ReturnType, + userNodesStore: Writable +) { + const nodesStoreSetter = nodesStore.set; + const userNodesStoreSetter = userNodesStore.set; + + let val = get(userNodesStore); + nodesStore.set(val); + + const _set = (nds: Node[]) => { + const updatedNodes = nodesStoreSetter(nds); + val = updatedNodes; + + userNodesStoreSetter(val); + + return updatedNodes; + }; + + nodesStore.set = userNodesStore.set = _set; + nodesStore.update = userNodesStore.update = (fn: (nds: Node[]) => Node[]) => _set(fn(val)); +} + +export function syncEdgeStores( + edgesStore: ReturnType, + userEdgesStore: Writable +) { + const nodesStoreSetter = edgesStore.set; + const userEdgesStoreSetter = userEdgesStore.set; + + let val = get(userEdgesStore); + edgesStore.set(val); + + const _set = (eds: Edge[]) => { + nodesStoreSetter(eds); + userEdgesStoreSetter(eds); + val = eds; + }; + + edgesStore.set = userEdgesStore.set = _set; + edgesStore.update = userEdgesStore.update = (fn: (nds: Edge[]) => Edge[]) => _set(fn(val)); +} + +export const createNodes = ( + nodes: Node[] +): { + subscribe: (this: void, run: Subscriber) => Unsubscriber; + update: (this: void, updater: Updater) => void; + set: (this: void, value: Node[]) => Node[]; + setDefaultOptions: (opts: DefaultNodeOptions) => void; +} => { + const { subscribe, set, update } = writable([]); + let value = nodes; + let defaults = {}; + + const _set = (nds: Node[]): Node[] => { + const parentNodes: Record = {}; + + 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 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; + + set(value); + + return value; + }; + + const _update: typeof update = (fn: (nds: Node[]) => Node[]) => _set(fn(value)); + + const setDefaultOptions = (options: DefaultNodeOptions) => { + defaults = options; + }; + + _set(value); + + return { + subscribe, + set: _set, + update: _update, + setDefaultOptions + }; +}; + +export const createEdges = ( + edges: Edge[], + defaultOptions?: DefaultEdgeOptions +): Writable & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => { + const { subscribe, set, update } = writable([]); + let value = edges; + let defaults = defaultOptions || {}; + + const _set: typeof set = (eds: Edge[]) => { + const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds; + value = nextEdges; + set(value); + }; + + const _update: typeof update = (fn: (eds: Edge[]) => Edge[]) => _set(fn(value)); + + const setDefaultOptions = (options: DefaultEdgeOptions) => { + defaults = options; + }; + + _set(value); + + return { + subscribe, + set: _set, + update: _update, + 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 + }); +} diff --git a/packages/svelte/src/lib/utils/index.ts b/packages/svelte/src/lib/utils/index.ts index 4f5d9a8a..23b29448 100644 --- a/packages/svelte/src/lib/utils/index.ts +++ b/packages/svelte/src/lib/utils/index.ts @@ -1,4 +1,3 @@ -import { writable, type Writable } from 'svelte/store'; import { isNodeBase, isEdgeBase, @@ -6,14 +5,10 @@ import { getOutgoersBase, getIncomersBase, updateEdgeBase, - getConnectedEdgesBase, - isNumeric, - getNodePositionWithOrigin, - internalsSymbol, - type XYZPosition + getConnectedEdgesBase } from '@xyflow/system'; -import type { DefaultEdgeOptions, DefaultNodeOptions, Edge, Node } from '$lib/types'; +import type { Edge, Node } from '$lib/types'; export const isNode = isNodeBase; export const isEdge = isEdgeBase; @@ -22,126 +17,3 @@ export const getIncomers = getIncomersBase; export const addEdge = addEdgeBase; export const updateEdge = updateEdgeBase; export const getConnectedEdges = getConnectedEdgesBase; - -export const createNodes = ( - nodes: Node[], - defaultOptions?: DefaultNodeOptions -): Writable & { setDefaultOptions: (opts: DefaultNodeOptions) => void } => { - const { subscribe, set, update } = writable([]); - let value = nodes; - let defaults = defaultOptions || {}; - - const _set: typeof set = (nds: Node[]) => { - const parentNodes: Record = {}; - - 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 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; - - set(value); - }; - - const _update: typeof update = (fn: (nds: Node[]) => Node[]) => _set(fn(value)); - - const setDefaultOptions = (options: DefaultNodeOptions) => { - defaults = options; - }; - - _set(value); - - return { - subscribe, - set: _set, - update: _update, - setDefaultOptions - }; -}; - -export const createEdges = ( - edges: Edge[], - defaultOptions?: DefaultEdgeOptions -): Writable & { setDefaultOptions: (opts: DefaultEdgeOptions) => void } => { - const { subscribe, set, update } = writable([]); - let value = edges; - let defaults = defaultOptions || {}; - - const _set: typeof set = (eds: Edge[]) => { - const nextEdges = defaults ? eds.map((edge) => ({ ...defaults, ...edge })) : eds; - value = nextEdges; - set(value); - }; - - const _update: typeof update = (fn: (eds: Edge[]) => Edge[]) => _set(fn(value)); - - const setDefaultOptions = (options: DefaultEdgeOptions) => { - defaults = options; - }; - - _set(value); - - return { - subscribe, - set: _set, - update: _update, - 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 - }); -} diff --git a/packages/svelte/src/routes/customnode/+page.svelte b/packages/svelte/src/routes/customnode/+page.svelte index 3a3338fd..7c3f5b86 100644 --- a/packages/svelte/src/routes/customnode/+page.svelte +++ b/packages/svelte/src/routes/customnode/+page.svelte @@ -2,13 +2,10 @@ import type { ChangeEventHandler } from 'svelte/elements'; import { writable } from 'svelte/store'; import SvelteFlow, { - SvelteFlowProvider, Controls, Background, BackgroundVariant, MiniMap, - createNodes, - createEdges, type NodeTypes, Position } from '../../lib/index'; @@ -23,7 +20,7 @@ const onChange: ChangeEventHandler = (event) => { nodes.update((nds) => nds.map((node) => { - if (node.id !== '2') { + if (node.type !== 'colorNode') { return node; } @@ -42,7 +39,7 @@ ); }; - const nodes = createNodes([ + const nodes = writable([ { id: '1', type: 'input', @@ -73,7 +70,7 @@ }, ]); - const edges = createEdges([ + const edges = writable([ { id: 'e1-2', source: '1', @@ -94,23 +91,20 @@ target: '4', animated: true, }, - ], { animated: true }); + ]); - - - - - - - + + + + + + + + diff --git a/packages/svelte/src/routes/interaction/+page.svelte b/packages/svelte/src/routes/interaction/+page.svelte index 6729d0a4..c5d956e5 100644 --- a/packages/svelte/src/routes/interaction/+page.svelte +++ b/packages/svelte/src/routes/interaction/+page.svelte @@ -1,10 +1,8 @@ - - - - - + + - -
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
-
\ No newline at end of file + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ diff --git a/packages/svelte/src/routes/overview/+page.svelte b/packages/svelte/src/routes/overview/+page.svelte index ce121917..dbaa81dd 100644 --- a/packages/svelte/src/routes/overview/+page.svelte +++ b/packages/svelte/src/routes/overview/+page.svelte @@ -1,15 +1,14 @@ - console.log('on node click', event)} + on:node:mouseenter={(event) => console.log('on node enter', event)} + on:node:mouseleave={(event) => console.log('on node leave', event)} + on:edge:click={(event) => console.log('edge click', event)} + on:connect:start={(event) => console.log('on connect start', event)} + on:connect={(event) => console.log('on connect', event)} + on:connect:end={(event) => console.log('on connect end', event)} + on:pane:click={(event) => console.log('on pane click', event)} + on:pane:contextmenu={(event) => { event.preventDefault(); console.log('on pane contextmenu', event); }} > - console.log('on node click', event)} - on:node:mouseenter={(event) => console.log('on node enter', event)} - on:node:mouseleave={(event) => console.log('on node leave', event)} - on:edge:click={(event) => console.log('edge click', event)} - on:connect:start={(event) => console.log('on connect start', event)} - on:connect={(event) => console.log('on connect', event)} - on:connect:end={(event) => console.log('on connect end', event)} - on:pane:click={(event) => console.log('on pane click', event)} - on:pane:contextmenu={(event) => { event.preventDefault(); console.log('on pane contextmenu', event); }} - > - - - - - - - - + + + + + + +