WIP: migrated store and useSvelteFlow hook to signals & solved state propagation for provider
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { getContext } from 'svelte';
|
||||
import type { StoreContext, SvelteFlowStore } from './types';
|
||||
|
||||
import { key } from './';
|
||||
|
||||
export function useStore(): SvelteFlowStore {
|
||||
const storeContext = getContext<StoreContext>(key);
|
||||
|
||||
if (!storeContext) {
|
||||
throw new Error(
|
||||
'In order to use useStore you need to wrap your component in a <SvelteFlowProvider />'
|
||||
);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (storeContext.provider && !$effect.tracking()) {
|
||||
console.warn(
|
||||
'Use $derived(useStore()), when not calling inside a child of the <SvelteFlow /> component.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return storeContext.getStore();
|
||||
}
|
||||
@@ -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
|
||||
// };
|
||||
}
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
import { get, writable, type Writable } from 'svelte/store';
|
||||
import {
|
||||
infiniteExtent,
|
||||
SelectionMode,
|
||||
ConnectionMode,
|
||||
ConnectionLineType,
|
||||
devWarn,
|
||||
adoptUserNodes,
|
||||
getViewportForBounds,
|
||||
updateConnectionLookup,
|
||||
initialConnection,
|
||||
type SelectionRect,
|
||||
type SnapGrid,
|
||||
type MarkerProps,
|
||||
type PanZoomInstance,
|
||||
type CoordinateExtent,
|
||||
type NodeOrigin,
|
||||
type OnError,
|
||||
type Viewport,
|
||||
type OnConnect,
|
||||
type OnConnectStart,
|
||||
type OnConnectEnd,
|
||||
type NodeLookup,
|
||||
type ConnectionState,
|
||||
getInternalNodesBounds,
|
||||
type ColorMode,
|
||||
createMarkerIds,
|
||||
type EdgeLookup,
|
||||
type ConnectionLookup,
|
||||
type ParentLookup,
|
||||
pointToRendererPoint
|
||||
} from '@xyflow/system';
|
||||
|
||||
import DefaultNode from '$lib/components/nodes/DefaultNode.svelte';
|
||||
import InputNode from '$lib/components/nodes/InputNode.svelte';
|
||||
import OutputNode from '$lib/components/nodes/OutputNode.svelte';
|
||||
import GroupNode from '$lib/components/nodes/GroupNode.svelte';
|
||||
|
||||
import {
|
||||
BezierEdgeInternal,
|
||||
SmoothStepEdgeInternal,
|
||||
StraightEdgeInternal,
|
||||
StepEdgeInternal
|
||||
} from '$lib/components/edges';
|
||||
|
||||
import type {
|
||||
NodeTypes,
|
||||
EdgeTypes,
|
||||
FitViewOptions,
|
||||
OnDelete,
|
||||
OnEdgeCreate,
|
||||
OnBeforeDelete,
|
||||
IsValidConnection
|
||||
} from '$lib/types';
|
||||
import { createNodesStore, createEdgesStore } from './utils';
|
||||
import type { StoreSignals } from './types';
|
||||
|
||||
export const initialNodeTypes = {
|
||||
input: InputNode,
|
||||
output: OutputNode,
|
||||
default: DefaultNode,
|
||||
group: GroupNode
|
||||
};
|
||||
|
||||
export const initialEdgeTypes = {
|
||||
straight: StraightEdgeInternal,
|
||||
smoothstep: SmoothStepEdgeInternal,
|
||||
default: BezierEdgeInternal,
|
||||
step: StepEdgeInternal
|
||||
};
|
||||
|
||||
export const getInitialStore = (signals: StoreSignals) => {
|
||||
class SvelteFlowStore {
|
||||
nodes: ReturnType<typeof createNodesStore>;
|
||||
edges: ReturnType<typeof createEdgesStore>;
|
||||
|
||||
nodeLookup: NodeLookup = new Map();
|
||||
parentLookup: ParentLookup = new Map();
|
||||
connectionLookup: ConnectionLookup = new Map();
|
||||
edgeLookup: EdgeLookup = new Map();
|
||||
|
||||
domNode: HTMLDivElement | null = $derived(
|
||||
signals.container?.domNode ?? signals.props.domNode ?? null
|
||||
);
|
||||
width: number = $derived(signals.container?.width ?? signals.props.width ?? 0);
|
||||
height: number = $derived(signals.container?.height ?? signals.props.height ?? 0);
|
||||
|
||||
flowId: string = $derived(signals.props.id ?? '1');
|
||||
minZoom: number = $derived(signals.props.minZoom ?? 0.5);
|
||||
maxZoom: number = $derived(signals.props.maxZoom ?? 2);
|
||||
|
||||
nodeOrigin: NodeOrigin = $derived(signals.props.nodeOrigin ?? [0, 0]);
|
||||
nodeExtent: CoordinateExtent = $derived(signals.props.nodeExtent ?? infiniteExtent);
|
||||
translateExtent: CoordinateExtent = $derived(signals.props.translateExtent ?? infiniteExtent);
|
||||
|
||||
nodeDragThreshold: number = $derived(signals.props.nodeDragThreshold ?? 1);
|
||||
autoPanOnNodeDrag: boolean = $derived(signals.props.autoPanOnNodeDrag ?? true);
|
||||
autoPanOnConnect: boolean = $derived(signals.props.autoPanOnConnect ?? true);
|
||||
|
||||
fitViewOnInitDone: boolean = $state(false);
|
||||
fitViewOnInit: boolean = $derived(signals.props.fitView ?? false);
|
||||
fitViewOptions: FitViewOptions | undefined = $derived(signals.props.fitViewOptions);
|
||||
|
||||
panZoom: PanZoomInstance | null = $state(null);
|
||||
snapGrid: SnapGrid | null = $derived(signals.props.snapGrid ?? null);
|
||||
|
||||
dragging: boolean = $state(false);
|
||||
selectionRect: SelectionRect | null = $state(null);
|
||||
|
||||
selectionKeyPressed: boolean = $state(false);
|
||||
multiselectionKeyPressed: boolean = $state(false);
|
||||
deleteKeyPressed: boolean = $state(false);
|
||||
panActivationKeyPressed: boolean = $state(false);
|
||||
zoomActivationKeyPressed: boolean = $state(false);
|
||||
selectionRectMode: string | null = $state(null);
|
||||
selectionMode: SelectionMode = $derived(signals.props.selectionMode ?? SelectionMode.Partial);
|
||||
|
||||
nodeTypes: NodeTypes = $derived({ ...initialNodeTypes, ...signals.props.nodeTypes });
|
||||
edgeTypes: EdgeTypes = $derived({ ...initialEdgeTypes, ...signals.props.edgeTypes });
|
||||
|
||||
viewport: Writable<Viewport> = writable(
|
||||
signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 }
|
||||
);
|
||||
|
||||
connectionMode: ConnectionMode = $derived(
|
||||
signals.props.connectionMode ?? ConnectionMode.Strict
|
||||
);
|
||||
rawConnection: ConnectionState = $state(initialConnection);
|
||||
connection: ConnectionState = $derived.by(() => {
|
||||
if (this.rawConnection.inProgress) {
|
||||
const viewport = get(this.viewport);
|
||||
return {
|
||||
...this.rawConnection,
|
||||
to: pointToRendererPoint(this.rawConnection.to, [viewport.x, viewport.y, viewport.zoom])
|
||||
};
|
||||
} else {
|
||||
return { ...this.rawConnection };
|
||||
}
|
||||
});
|
||||
connectionLineType: ConnectionLineType = $derived(
|
||||
signals.props.connectionLineType ?? ConnectionLineType.Bezier
|
||||
);
|
||||
connectionRadius: number = $derived(signals.props.connectionRadius ?? 20);
|
||||
isValidConnection: IsValidConnection = $derived(
|
||||
signals.props.isValidConnection ?? (() => true)
|
||||
);
|
||||
nodesDraggable: boolean = $derived(signals.props.nodesDraggable ?? true);
|
||||
nodesConnectable: boolean = $derived(signals.props.nodesConnectable ?? true);
|
||||
elementsSelectable: boolean = $derived(signals.props.elementsSelectable ?? true);
|
||||
selectNodesOnDrag: boolean = $derived(signals.props.selectNodesOnDrag ?? true);
|
||||
// // [store.edges, store.defaultMarkerColor, store.flowId],
|
||||
// // ([edges, defaultColor, id]) => createMarkerIds(edges, { defaultColor, id })
|
||||
defaultMarkerColor: string = $derived(signals.props.defaultMarkerColor ?? '#b1b1b7');
|
||||
markers: MarkerProps[] = $derived.by(() => {
|
||||
const edges = signals.props.edges ?? writable([]);
|
||||
return createMarkerIds(get(edges), {
|
||||
defaultColor: this.defaultMarkerColor,
|
||||
id: this.flowId
|
||||
});
|
||||
});
|
||||
onlyRenderVisibleElements: boolean = $derived(signals.props.onlyRenderVisibleElements ?? false);
|
||||
onerror: OnError = $derived(signals.props.onerror ?? devWarn);
|
||||
ondelete?: OnDelete = $derived(signals.props.ondelete ?? undefined);
|
||||
onedgecreate?: OnEdgeCreate = $derived(signals.props.onedgecreate);
|
||||
onconnect?: OnConnect = $derived(signals.props.onconnect);
|
||||
onconnectstart?: OnConnectStart = $derived(signals.props.onconnectstart);
|
||||
onconnectend?: OnConnectEnd = $derived(signals.props.onconnectend);
|
||||
onbeforedelete?: OnBeforeDelete = $derived(signals.props.onbeforedelete);
|
||||
|
||||
nodesInitialized: boolean = $state(false);
|
||||
edgesInitialized: boolean = $state(false);
|
||||
viewportInitialized: boolean = $state(false);
|
||||
|
||||
initialNodesLength: number = signals.props.nodes ? get(signals.props.nodes)?.length : 0;
|
||||
initialEdgesLength: number = signals.props.edges ? get(signals.props.edges)?.length : 0;
|
||||
initialized: boolean = $derived.by(() => {
|
||||
let initialized = false;
|
||||
// if it hasn't been initialised check if it's now
|
||||
if (this.initialNodesLength === 0) {
|
||||
initialized = this.viewportInitialized;
|
||||
} else if (this.initialEdgesLength === 0) {
|
||||
initialized = this.viewportInitialized && this.nodesInitialized;
|
||||
} else {
|
||||
initialized = this.viewportInitialized && this.nodesInitialized && this.edgesInitialized;
|
||||
}
|
||||
|
||||
return initialized;
|
||||
});
|
||||
colorMode: ColorMode = $derived(signals.props.colorMode ?? 'light');
|
||||
|
||||
constructor() {
|
||||
const nodes = signals.props.nodes ? get(signals.props.nodes) : [];
|
||||
const edges = signals.props.edges ? get(signals.props.edges) : [];
|
||||
|
||||
this.nodes = createNodesStore(
|
||||
nodes,
|
||||
this.nodeLookup,
|
||||
this.parentLookup,
|
||||
this.nodeOrigin,
|
||||
this.nodeExtent
|
||||
);
|
||||
|
||||
adoptUserNodes(nodes, this.nodeLookup, this.parentLookup, {
|
||||
nodeExtent: this.nodeExtent,
|
||||
nodeOrigin: this.nodeOrigin,
|
||||
elevateNodesOnSelect: false,
|
||||
checkEquality: false
|
||||
});
|
||||
|
||||
this.edges = createEdgesStore(edges, this.connectionLookup, this.edgeLookup);
|
||||
updateConnectionLookup(this.connectionLookup, this.edgeLookup, edges);
|
||||
|
||||
if (signals.props.fitView && this.width && this.height) {
|
||||
const bounds = getInternalNodesBounds(this.nodeLookup, {
|
||||
filter: (node) =>
|
||||
!!((node.width || node.initialWidth) && (node.height || node.initialHeight))
|
||||
});
|
||||
this.viewport.set(getViewportForBounds(bounds, this.width, this.height, 0.5, 2, 0.1));
|
||||
}
|
||||
}
|
||||
|
||||
resetStoreValues() {
|
||||
// TODO: reset all store values here
|
||||
}
|
||||
}
|
||||
return new SvelteFlowStore();
|
||||
|
||||
// return new Store();
|
||||
// const nodeLookup: NodeLookup = new Map();
|
||||
// const parentLookup = new Map();
|
||||
// const connectionLookup = new Map();
|
||||
// const edgeLookup = new Map();
|
||||
|
||||
// const storeNodeOrigin = nodeOrigin ?? [0, 0];
|
||||
// const storeNodeExtent = nodeExtent ?? infiniteExtent;
|
||||
|
||||
// adoptUserNodes(nodes, nodeLookup, parentLookup, {
|
||||
// nodeExtent: storeNodeExtent,
|
||||
// nodeOrigin: storeNodeOrigin,
|
||||
// elevateNodesOnSelect: false,
|
||||
// checkEquality: false
|
||||
// });
|
||||
|
||||
// updateConnectionLookup(connectionLookup, edgeLookup, edges);
|
||||
|
||||
// let viewport: Viewport = { x: 0, y: 0, zoom: 1 };
|
||||
|
||||
// if (fitView && width && height) {
|
||||
// const bounds = getInternalNodesBounds(nodeLookup, {
|
||||
// filter: (node) => !!((node.width || node.initialWidth) && (node.height || node.initialHeight))
|
||||
// });
|
||||
// viewport = getViewportForBounds(bounds, width, height, 0.5, 2, 0.1);
|
||||
// }
|
||||
|
||||
// return {
|
||||
// flowId: writable<string | null>(null),
|
||||
// nodes: createNodesStore(nodes, nodeLookup, parentLookup, storeNodeOrigin, storeNodeExtent),
|
||||
// nodeLookup: readable<NodeLookup<InternalNode>>(nodeLookup),
|
||||
// parentLookup: readable<ParentLookup<InternalNode>>(parentLookup),
|
||||
// edgeLookup: readable<EdgeLookup<Edge>>(edgeLookup),
|
||||
// visibleNodes: readable<InternalNode[]>([]),
|
||||
// edges: createEdgesStore(edges, connectionLookup, edgeLookup),
|
||||
// visibleEdges: readable<EdgeLayouted[]>([]),
|
||||
// connectionLookup: readable<ConnectionLookup>(connectionLookup),
|
||||
// height: writable<number>(500),
|
||||
// width: writable<number>(500),
|
||||
// minZoom: writable<number>(0.5),
|
||||
// maxZoom: writable<number>(2),
|
||||
// nodeOrigin: writable<NodeOrigin>(storeNodeOrigin),
|
||||
// nodeDragThreshold: writable<number>(1),
|
||||
// nodeExtent: writable<CoordinateExtent>(storeNodeExtent),
|
||||
// translateExtent: writable<CoordinateExtent>(infiniteExtent),
|
||||
// autoPanOnNodeDrag: writable<boolean>(true),
|
||||
// autoPanOnConnect: writable<boolean>(true),
|
||||
// fitViewOnInit: writable<boolean>(false),
|
||||
// fitViewOnInitDone: writable<boolean>(false),
|
||||
// fitViewOptions: writable<FitViewOptions>(undefined),
|
||||
// panZoom: writable<PanZoomInstance | null>(null),
|
||||
// snapGrid: writable<SnapGrid | null>(null),
|
||||
// dragging: writable<boolean>(false),
|
||||
// selectionRect: writable<SelectionRect | null>(null),
|
||||
// selectionKeyPressed: writable<boolean>(false),
|
||||
// multiselectionKeyPressed: writable<boolean>(false),
|
||||
// deleteKeyPressed: writable<boolean>(false),
|
||||
// panActivationKeyPressed: writable<boolean>(false),
|
||||
// zoomActivationKeyPressed: writable<boolean>(false),
|
||||
// selectionRectMode: writable<string | null>(null),
|
||||
// selectionMode: writable<SelectionMode>(SelectionMode.Partial),
|
||||
// nodeTypes: writable<NodeTypes>(initialNodeTypes),
|
||||
// edgeTypes: writable<EdgeTypes>(initialEdgeTypes),
|
||||
// viewport: writable<Viewport>(viewport),
|
||||
// connectionMode: writable<ConnectionMode>(ConnectionMode.Strict),
|
||||
// domNode: writable<HTMLDivElement | null>(null),
|
||||
// connection: readable<ConnectionState>(initialConnection),
|
||||
// connectionLineType: writable<ConnectionLineType>(ConnectionLineType.Bezier),
|
||||
// connectionRadius: writable<number>(20),
|
||||
// isValidConnection: writable<IsValidConnection>(() => true),
|
||||
// nodesDraggable: writable<boolean>(true),
|
||||
// nodesConnectable: writable<boolean>(true),
|
||||
// elementsSelectable: writable<boolean>(true),
|
||||
// selectNodesOnDrag: writable<boolean>(true),
|
||||
// markers: readable<MarkerProps[]>([]),
|
||||
// defaultMarkerColor: writable<string>('#b1b1b7'),
|
||||
// lib: readable<string>('svelte'),
|
||||
// onlyRenderVisibleElements: writable<boolean>(false),
|
||||
// onerror: writable<OnError>(devWarn),
|
||||
// ondelete: writable<OnDelete>(undefined),
|
||||
// onedgecreate: writable<OnEdgeCreate>(undefined),
|
||||
// onconnect: writable<OnConnect>(undefined),
|
||||
// onconnectstart: writable<OnConnectStart>(undefined),
|
||||
// onconnectend: writable<OnConnectEnd>(undefined),
|
||||
// onbeforedelete: writable<OnBeforeDelete>(undefined),
|
||||
// nodesInitialized: writable<boolean>(false),
|
||||
// edgesInitialized: writable<boolean>(false),
|
||||
// viewportInitialized: writable<boolean>(false),
|
||||
// initialized: readable<boolean>(false)
|
||||
// };
|
||||
};
|
||||
@@ -10,8 +10,9 @@ import type {
|
||||
Viewport
|
||||
} from '@xyflow/system';
|
||||
|
||||
import type { getInitialStore } from './initial-store';
|
||||
import type { getInitialStore } from './initial-store.svelte';
|
||||
import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/types';
|
||||
import type { SvelteFlowProps } from '$lib/container/SvelteFlow';
|
||||
|
||||
export type SvelteFlowStoreActions = {
|
||||
syncNodeStores: (nodesStore: Writable<Node[]>) => void;
|
||||
@@ -39,6 +40,21 @@ export type SvelteFlowStoreActions = {
|
||||
reset(): void;
|
||||
};
|
||||
|
||||
export type ContainerSignals = { domNode?: HTMLDivElement | null; width?: number; height?: number };
|
||||
export type StoreSignals = {
|
||||
props: Partial<SvelteFlowProps> & ContainerSignals;
|
||||
container?: ContainerSignals;
|
||||
};
|
||||
|
||||
export type SvelteFlowStoreState = ReturnType<typeof getInitialStore>;
|
||||
|
||||
export type SvelteFlowStore = SvelteFlowStoreState & SvelteFlowStoreActions;
|
||||
|
||||
export type StoreContext = {
|
||||
getStore: () => SvelteFlowStore;
|
||||
provider: boolean;
|
||||
};
|
||||
|
||||
export type ProviderContext = StoreContext & {
|
||||
setStore: (store: SvelteFlowStore) => void;
|
||||
};
|
||||
|
||||
@@ -78,7 +78,7 @@ export function syncEdgeStores(
|
||||
// it is possible to pass a viewport store to SvelteFlow for having more control
|
||||
// if that's the case we need to sync the internal viewport with the user viewport
|
||||
export const syncViewportStores = (
|
||||
panZoomStore: Writable<PanZoomInstance | null>,
|
||||
panZoomStore: PanZoomInstance | null,
|
||||
viewportStore: Writable<Viewport>,
|
||||
userViewportStore?: Writable<Viewport>
|
||||
) => {
|
||||
@@ -86,7 +86,7 @@ export const syncViewportStores = (
|
||||
return;
|
||||
}
|
||||
|
||||
const panZoom = get(panZoomStore);
|
||||
const panZoom = panZoomStore;
|
||||
|
||||
const viewportStoreSetter = viewportStore.set;
|
||||
const userViewportStoreSetter = userViewportStore.set;
|
||||
|
||||
Reference in New Issue
Block a user