WIP: migrated store and useSvelteFlow hook to signals & solved state propagation for provider

This commit is contained in:
peterkogo
2024-12-06 13:53:48 +01:00
parent ed1ead7b5a
commit f29898f0bd
49 changed files with 1616 additions and 1137 deletions
+132 -203
View File
@@ -1,14 +1,11 @@
import { getContext, setContext } from 'svelte';
import { derived, get, writable } from 'svelte/store';
import { get } from 'svelte/store';
import {
createMarkerIds,
fitView as fitViewSystem,
panBy as panBySystem,
updateNodeInternals as updateNodeInternalsSystem,
addEdge as addEdgeUtil,
initialConnection,
errorMessages,
pointToRendererPoint,
type UpdateNodePositions,
type InternalNodeUpdate,
type ViewportHelperFunctionOptions,
@@ -17,60 +14,37 @@ import {
type CoordinateExtent,
type UpdateConnection,
type ConnectionState,
type NodeOrigin,
getFitViewNodes,
updateAbsolutePositions,
getDimensions
} from '@xyflow/system';
import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store';
import type { SvelteFlowStore } from './types';
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store.svelte';
import { type StoreSignals, type SvelteFlowStore, type SvelteFlowStoreActions } from './types';
import { syncNodeStores, syncEdgeStores, syncViewportStores } from './utils';
import { getVisibleEdges } from './visible-edges';
import { getVisibleNodes } from './visible-nodes';
// import { getVisibleEdges } from './visible-edges';
// import { getVisibleNodes } from './visible-nodes';
export const key = Symbol();
export function createStore({
nodes,
edges,
width,
height,
fitView: fitViewOnCreate,
nodeOrigin,
nodeExtent
}: {
nodes?: Node[];
edges?: Edge[];
width?: number;
height?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}): SvelteFlowStore {
const store = getInitialStore({
nodes,
edges,
width,
height,
fitView: fitViewOnCreate,
nodeOrigin,
nodeExtent
});
export { useStore } from './hook.svelte';
export function createStore(signals: StoreSignals): SvelteFlowStore {
const store = getInitialStore(signals);
function setNodeTypes(nodeTypes: NodeTypes) {
store.nodeTypes.set({
store.nodeTypes = {
...initialNodeTypes,
...nodeTypes
});
};
}
function setEdgeTypes(edgeTypes: EdgeTypes) {
store.edgeTypes.set({
store.edgeTypes = {
...initialEdgeTypes,
...edgeTypes
});
};
}
function addEdge(edgeParams: Edge | Connection) {
@@ -79,10 +53,8 @@ export function createStore({
}
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
const nodeLookup = get(store.nodeLookup);
for (const [id, dragItem] of nodeDragItems) {
const node = nodeLookup.get(id)?.internals.userNode;
const node = store.nodeLookup.get(id)?.internals.userNode;
if (!node) {
continue;
@@ -96,33 +68,33 @@ export function createStore({
};
function updateNodeInternals(updates: Map<string, InternalNodeUpdate>) {
const nodeLookup = get(store.nodeLookup);
const parentLookup = get(store.parentLookup);
const { changes, updatedInternals } = updateNodeInternalsSystem(
updates,
nodeLookup,
get(store.parentLookup),
get(store.domNode),
get(store.nodeOrigin)
store.nodeLookup,
store.parentLookup,
store.domNode,
store.nodeOrigin
);
if (!updatedInternals) {
return;
}
updateAbsolutePositions(nodeLookup, parentLookup, { nodeOrigin, nodeExtent });
updateAbsolutePositions(store.nodeLookup, store.parentLookup, {
nodeOrigin: store.nodeOrigin,
nodeExtent: store.nodeExtent
});
if (!get(store.fitViewOnInitDone) && get(store.fitViewOnInit)) {
const fitViewOptions = get(store.fitViewOptions);
if (!store.fitViewOnInitDone && store.fitViewOnInit) {
const fitViewOnInitDone = fitViewSync({
...fitViewOptions,
nodes: fitViewOptions?.nodes
...store.fitViewOptions,
nodes: store.fitViewOptions?.nodes
});
store.fitViewOnInitDone.set(fitViewOnInitDone);
store.fitViewOnInitDone = fitViewOnInitDone;
}
for (const change of changes) {
const node = nodeLookup.get(change.id)?.internals.userNode;
const node = store.nodeLookup.get(change.id)?.internals.userNode;
if (!node) {
continue;
@@ -149,14 +121,14 @@ export function createStore({
store.nodes.update((nds) => nds);
if (!get(store.nodesInitialized)) {
store.nodesInitialized.set(true);
if (!store.nodesInitialized) {
store.nodesInitialized = true;
}
}
function fitView(options?: FitViewOptions) {
const panZoom = get(store.panZoom);
const domNode = get(store.domNode);
const panZoom = store.panZoom;
const domNode = store.domNode;
if (!panZoom || !domNode) {
return Promise.resolve(false);
@@ -164,15 +136,15 @@ export function createStore({
const { width, height } = getDimensions(domNode);
const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
return fitViewSystem(
{
nodes: fitViewNodes,
width,
height,
minZoom: get(store.minZoom),
maxZoom: get(store.maxZoom),
minZoom: store.minZoom,
maxZoom: store.maxZoom,
panZoom
},
options
@@ -180,21 +152,21 @@ export function createStore({
}
function fitViewSync(options?: FitViewOptions) {
const panZoom = get(store.panZoom);
const panZoom = store.panZoom;
if (!panZoom) {
return false;
}
const fitViewNodes = getFitViewNodes(get(store.nodeLookup), options);
const fitViewNodes = getFitViewNodes(store.nodeLookup, options);
fitViewSystem(
{
nodes: fitViewNodes,
width: get(store.width),
height: get(store.height),
minZoom: get(store.minZoom),
maxZoom: get(store.maxZoom),
width: store.width,
height: store.height,
minZoom: store.minZoom,
maxZoom: store.maxZoom,
panZoom
},
options
@@ -204,7 +176,7 @@ export function createStore({
}
function zoomBy(factor: number, options?: ViewportHelperFunctionOptions) {
const panZoom = get(store.panZoom);
const panZoom = store.panZoom;
if (!panZoom) {
return Promise.resolve(false);
}
@@ -221,29 +193,29 @@ export function createStore({
}
function setMinZoom(minZoom: number) {
const panZoom = get(store.panZoom);
const panZoom = store.panZoom;
if (panZoom) {
panZoom.setScaleExtent([minZoom, get(store.maxZoom)]);
store.minZoom.set(minZoom);
panZoom.setScaleExtent([minZoom, store.maxZoom]);
store.minZoom = minZoom;
}
}
function setMaxZoom(maxZoom: number) {
const panZoom = get(store.panZoom);
const panZoom = store.panZoom;
if (panZoom) {
panZoom.setScaleExtent([get(store.minZoom), maxZoom]);
store.maxZoom.set(maxZoom);
panZoom.setScaleExtent([store.minZoom, maxZoom]);
store.maxZoom = maxZoom;
}
}
function setTranslateExtent(extent: CoordinateExtent) {
const panZoom = get(store.panZoom);
const panZoom = store.panZoom;
if (panZoom) {
panZoom.setTranslateExtent(extent);
store.translateExtent.set(extent);
store.translateExtent = extent;
}
}
@@ -259,7 +231,7 @@ export function createStore({
}
function setPaneClickDistance(distance: number) {
get(store.panZoom)?.setClickDistance(distance);
store.panZoom?.setClickDistance(distance);
}
function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) {
@@ -270,39 +242,8 @@ export function createStore({
if (resetEdges) store.edges.set(get(store.edges));
}
// store.deleteKeyPressed.subscribe(async (deleteKeyPressed) => {
// if (deleteKeyPressed) {
// const nodes = get(store.nodes);
// const edges = get(store.edges);
// const selectedNodes = nodes.filter((node) => node.selected);
// const selectedEdges = edges.filter((edge) => edge.selected);
// const { nodes: matchingNodes, edges: matchingEdges } = await getElementsToRemove({
// nodesToRemove: selectedNodes,
// edgesToRemove: selectedEdges,
// nodes,
// edges,
// onBeforeDelete: get(store.onbeforedelete)
// });
// if (matchingNodes.length || matchingEdges.length) {
// store.nodes.update((nds) =>
// nds.filter((node) => !matchingNodes.some((mN) => mN.id === node.id))
// );
// store.edges.update((eds) =>
// eds.filter((edge) => !matchingEdges.some((mE) => mE.id === edge.id))
// );
// get(store.ondelete)?.({
// nodes: matchingNodes,
// edges: matchingEdges
// });
// }
// }
// });
function addSelectedNodes(ids: string[]) {
const isMultiSelection = get(store.multiselectionKeyPressed);
const isMultiSelection = store.multiselectionKeyPressed;
store.nodes.update((ns) =>
ns.map((node) => {
@@ -329,7 +270,7 @@ export function createStore({
}
function addSelectedEdges(ids: string[]) {
const isMultiSelection = get(store.multiselectionKeyPressed);
const isMultiSelection = store.multiselectionKeyPressed;
store.edges.update((edges) =>
edges.map((edge) => {
@@ -362,12 +303,12 @@ export function createStore({
return;
}
store.selectionRect.set(null);
store.selectionRectMode.set(null);
store.selectionRect = null;
store.selectionRectMode = null;
if (!node.selected) {
addSelectedNodes([id]);
} else if (node.selected && get(store.multiselectionKeyPressed)) {
} else if (node.selected && store.multiselectionKeyPressed) {
unselectNodesAndEdges({ nodes: [node], edges: [] });
}
}
@@ -376,78 +317,33 @@ export function createStore({
const viewport = get(store.viewport);
return panBySystem({
delta,
panZoom: get(store.panZoom),
panZoom: store.panZoom,
transform: [viewport.x, viewport.y, viewport.zoom],
translateExtent: get(store.translateExtent),
width: get(store.width),
height: get(store.height)
translateExtent: store.translateExtent,
width: store.width,
height: store.height
});
}
const _connection = writable<ConnectionState>(initialConnection);
// const _connection = writable<ConnectionState>(initialConnection);
const updateConnection: UpdateConnection = (newConnection: ConnectionState) => {
_connection.set({ ...newConnection });
store.rawConnection = { ...newConnection };
};
function cancelConnection() {
_connection.set(initialConnection);
store.rawConnection = initialConnection;
}
function reset() {
store.fitViewOnInitDone.set(false);
store.selectionRect.set(null);
store.selectionRectMode.set(null);
store.snapGrid.set(null);
store.isValidConnection.set(() => true);
store.fitViewOnInitDone = false;
store.selectionRect = null;
store.selectionRectMode = null;
unselectNodesAndEdges();
cancelConnection();
}
return {
// state
...store,
// derived state
visibleEdges: getVisibleEdges(store),
visibleNodes: getVisibleNodes(store),
connection: derived([_connection, store.viewport], ([connection, viewport]) => {
return connection.inProgress
? {
...connection,
to: pointToRendererPoint(connection.to, [viewport.x, viewport.y, viewport.zoom])
}
: { ...connection };
}),
markers: derived(
[store.edges, store.defaultMarkerColor, store.flowId],
([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
),
initialized: (() => {
let initialized = false;
const initialNodesLength = get(store.nodes).length;
const initialEdgesLength = get(store.edges).length;
return derived(
[store.nodesInitialized, store.edgesInitialized, store.viewportInitialized],
([nodesInitialized, edgesInitialized, viewportInitialized]) => {
// If it was already initialized, return true from then on
if (initialized) return initialized;
// if it hasn't been initialised check if it's now
if (initialNodesLength === 0) {
initialized = viewportInitialized;
} else if (initialEdgesLength === 0) {
initialized = viewportInitialized && nodesInitialized;
} else {
initialized = viewportInitialized && nodesInitialized && edgesInitialized;
}
return initialized;
}
);
})(),
// actions
const storeWithActions = Object.assign(store, {
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
@@ -471,43 +367,76 @@ export function createStore({
updateConnection,
cancelConnection,
reset
};
}
} satisfies SvelteFlowStoreActions);
export function useStore(): SvelteFlowStore {
const store = getContext<{ getStore: () => SvelteFlowStore }>(key);
return storeWithActions;
if (!store) {
throw new Error(
'In order to use useStore you need to wrap your component in a <SvelteFlowProvider />'
);
}
// return {
// // state
// ...store,
return store.getStore();
}
// // derived state
// // visibleEdges: getVisibleEdges(store),
// // visibleNodes: getVisibleNodes(store),
// // connection: derived([_connection, store.viewport], ([connection, viewport]) => {
// // return connection.inProgress
// // ? {
// // ...connection,
// // to: pointToRendererPoint(connection.to, [viewport.x, viewport.y, viewport.zoom])
// // }
// // : { ...connection };
// // }),
// // markers: derived(
// // [store.edges, store.defaultMarkerColor, store.flowId],
// // ([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
// // ),
// // initialized: (() => {
// // let initialized = false;
// // const initialNodesLength = get(store.nodes).length;
// // const initialEdgesLength = get(store.edges).length;
// // return derived(
// // [store.nodesInitialized, store.edgesInitialized, store.viewportInitialized],
// // ([nodesInitialized, edgesInitialized, viewportInitialized]) => {
// // // If it was already initialized, return true from then on
// // if (initialized) return initialized;
export function createStoreContext({
nodes,
edges,
width,
height,
fitView,
nodeOrigin,
nodeExtent
}: {
nodes?: Node[];
edges?: Edge[];
width?: number;
height?: number;
fitView?: boolean;
nodeOrigin?: NodeOrigin;
nodeExtent?: CoordinateExtent;
}) {
const store = createStore({ nodes, edges, width, height, fitView, nodeOrigin, nodeExtent });
// // // if it hasn't been initialised check if it's now
// // if (initialNodesLength === 0) {
// // initialized = viewportInitialized;
// // } else if (initialEdgesLength === 0) {
// // initialized = viewportInitialized && nodesInitialized;
// // } else {
// // initialized = viewportInitialized && nodesInitialized && edgesInitialized;
// // }
setContext(key, {
getStore: () => store
});
// // return initialized;
// // }
// // );
// // })(),
return store;
// // actions
// syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
// syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
// // syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
// setNodeTypes,
// setEdgeTypes,
// addEdge,
// updateNodePositions,
// updateNodeInternals,
// zoomIn,
// zoomOut,
// fitView: (options?: FitViewOptions) => fitView(options),
// setMinZoom,
// setMaxZoom,
// setTranslateExtent,
// setPaneClickDistance,
// unselectNodesAndEdges,
// addSelectedNodes,
// addSelectedEdges,
// handleNodeSelection,
// panBy,
// updateConnection,
// cancelConnection,
// reset
// };
}