first working version with using $state for nodes & edges

This commit is contained in:
peterkogo
2024-12-09 12:07:10 +01:00
parent 4fcde6f6bf
commit d0fce3984d
24 changed files with 400 additions and 366 deletions
+54 -59
View File
@@ -1,4 +1,3 @@
import { get } from 'svelte/store';
import {
fitView as fitViewSystem,
panBy as panBySystem,
@@ -23,6 +22,7 @@ import type { EdgeTypes, NodeTypes, Node, Edge, FitViewOptions } from '$lib/type
import { initialEdgeTypes, initialNodeTypes, getInitialStore } from './initial-store.svelte';
import { type StoreSignals, type SvelteFlowStore, type SvelteFlowStoreActions } from './types';
import { syncNodeStores, syncEdgeStores } from './utils';
import drag from '$lib/actions/drag';
// import { getVisibleEdges } from './visible-edges';
// import { getVisibleNodes } from './visible-nodes';
@@ -48,23 +48,15 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
}
function addEdge(edgeParams: Edge | Connection) {
const edges = get(store.edges);
store.edges.set(addEdgeUtil(edgeParams, edges));
// TODO: let's see
store.edges = addEdgeUtil(edgeParams, store.edges);
}
const updateNodePositions: UpdateNodePositions = (nodeDragItems, dragging = false) => {
for (const [id, dragItem] of nodeDragItems) {
const node = store.nodeLookup.get(id)?.internals.userNode;
if (!node) {
continue;
}
node.position = dragItem.position;
node.dragging = dragging;
}
store.nodes.update((nds) => nds);
store.nodes = store.nodes.map((node) => {
const dragItem = nodeDragItems.get(node.id);
return dragItem ? { ...node, position: dragItem.position, dragging } : node;
});
};
function updateNodeInternals(updates: Map<string, InternalNodeUpdate>) {
@@ -93,13 +85,16 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
store.fitViewOnInitDone = fitViewOnInitDone;
}
const newNodes = new Map<string, Node>();
for (const change of changes) {
const node = store.nodeLookup.get(change.id)?.internals.userNode;
const userNode = store.nodeLookup.get(change.id)?.internals.userNode;
if (!node) {
if (!userNode) {
continue;
}
const node = { ...userNode };
switch (change.type) {
case 'dimensions': {
const measured = { ...node.measured, ...change.dimensions };
@@ -114,12 +109,15 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
}
case 'position':
node.position = change.position ?? node.position;
break;
}
newNodes.set(change.id, node);
}
store.nodes.update((nds) => nds);
// store.nodes.update((nds) => nds);
store.nodes = store.nodes.map((node) => newNodes.get(node.id) ?? node);
// signals.updateNodes((node) => newNodes.get(node.id) ?? node);
if (!store.nodesInitialized) {
store.nodesInitialized = true;
@@ -235,68 +233,65 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
}
function unselectNodesAndEdges(params?: { nodes?: Node[]; edges?: Edge[] }) {
const resetNodes = resetSelectedElements(params?.nodes || get(store.nodes));
if (resetNodes) store.nodes.set(get(store.nodes));
resetSelectedElements(params?.nodes || store.nodes);
// if (resetNodes) store.nodes.set(get(store.nodes));
const resetEdges = resetSelectedElements(params?.edges || get(store.edges));
if (resetEdges) store.edges.set(get(store.edges));
resetSelectedElements(params?.edges || store.edges);
// if (resetEdges) store.edges.set(get(store.edges));
}
function addSelectedNodes(ids: string[]) {
const isMultiSelection = store.multiselectionKeyPressed;
store.nodes.update((ns) =>
ns.map((node) => {
const nodeWillBeSelected = ids.includes(node.id);
const selected = isMultiSelection
? node.selected || nodeWillBeSelected
: nodeWillBeSelected;
// store.nodes.update((ns) =>
store.nodes.forEach((node) => {
const nodeWillBeSelected = ids.includes(node.id);
const selected = isMultiSelection ? node.selected || nodeWillBeSelected : nodeWillBeSelected;
// we need to mutate the node here in order to have the correct selected state in the drag handler
node.selected = selected;
// we need to mutate the node here in order to have the correct selected state in the drag handler
node.selected = selected;
return node;
})
);
// return node;
});
// );
if (!isMultiSelection) {
store.edges.update((es) =>
es.map((edge) => {
edge.selected = false;
return edge;
})
);
// store.edges.forEach((es) =>
store.edges.forEach((edge) => {
edge.selected = false;
// return edge;
});
// );
}
}
function addSelectedEdges(ids: string[]) {
const isMultiSelection = store.multiselectionKeyPressed;
store.edges.update((edges) =>
edges.map((edge) => {
const edgeWillBeSelected = ids.includes(edge.id);
const selected = isMultiSelection
? edge.selected || edgeWillBeSelected
: edgeWillBeSelected;
// store.edges.update((edges) =>
store.edges.forEach((edge) => {
const edgeWillBeSelected = ids.includes(edge.id);
const selected = isMultiSelection ? edge.selected || edgeWillBeSelected : edgeWillBeSelected;
edge.selected = selected;
edge.selected = selected;
return edge;
})
);
// return edge;
});
// );
if (!isMultiSelection) {
store.nodes.update((ns) =>
ns.map((node) => {
node.selected = false;
return node;
})
);
// store.nodes.update((ns) =>
store.nodes.forEach((node) => {
node.selected = false;
// return node;
});
// );
}
}
function handleNodeSelection(id: string) {
const node = get(store.nodes)?.find((n) => n.id === id);
const node = store.nodes?.find((n) => n.id === id);
// const node = store.nodeLookup.get(id)?.internals.userNode;
if (!node) {
console.warn('012', errorMessages['error012'](id));
@@ -343,8 +338,8 @@ export function createStore(signals: StoreSignals): SvelteFlowStore {
}
const storeWithActions = Object.assign(store, {
syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
// syncNodeStores: (nodes) => syncNodeStores(store.nodes, nodes),
// syncEdgeStores: (edges) => syncEdgeStores(store.edges, edges),
// syncViewport: (viewport) => syncViewportStores(store.panZoom, store.viewport, viewport),
setNodeTypes,
setEdgeTypes,
@@ -1,4 +1,3 @@
import { get, writable, type Writable } from 'svelte/store';
import {
infiniteExtent,
SelectionMode,
@@ -52,7 +51,7 @@ import type {
OnBeforeDelete,
IsValidConnection
} from '$lib/types';
import { createNodesStore, createEdgesStore } from './utils';
import type { StoreSignals } from './types';
export const initialNodeTypes = {
@@ -71,19 +70,39 @@ export const initialEdgeTypes = {
export const getInitialStore = (signals: StoreSignals) => {
class SvelteFlowStore {
nodes: ReturnType<typeof createNodesStore>;
edges: ReturnType<typeof createEdgesStore>;
get nodes() {
return signals.nodes;
}
set nodes(nodes) {
signals.nodes = nodes;
}
get edges() {
return signals.edges;
}
set edges(edges) {
signals.edges = edges;
}
nodeLookup: NodeLookup = new Map();
parentLookup: ParentLookup = new Map();
connectionLookup: ConnectionLookup = new Map();
edgeLookup: EdgeLookup = new Map();
adoptNodes: true = $derived.by(() => {
adoptUserNodes(signals.nodes, this.nodeLookup, this.parentLookup, {
nodeExtent: this.nodeExtent,
nodeOrigin: this.nodeOrigin,
elevateNodesOnSelect: false,
checkEquality: true
});
return true;
});
adoptEdges: true = $derived.by(() => {
updateConnectionLookup(this.connectionLookup, this.edgeLookup, signals.edges);
return true;
});
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);
domNode: HTMLDivElement | null = $derived(signals.domNode ?? signals.props.domNode ?? null);
width: number = $derived(signals.width ?? signals.props.width ?? 0);
height: number = $derived(signals.height ?? signals.props.height ?? 0);
flowId: string = $derived(signals.props.id ?? '1');
minZoom: number = $derived(signals.props.minZoom ?? 0.5);
@@ -149,12 +168,10 @@ export const getInitialStore = (signals: StoreSignals) => {
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), {
return createMarkerIds(signals.edges, {
defaultColor: this.defaultMarkerColor,
id: this.flowId
});
@@ -172,8 +189,8 @@ export const getInitialStore = (signals: StoreSignals) => {
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;
initialNodesLength: number = signals.nodes.length;
initialEdgesLength: number = signals.edges.length;
initialized: boolean = $derived.by(() => {
let initialized = false;
// if it hasn't been initialised check if it's now
@@ -190,27 +207,6 @@ export const getInitialStore = (signals: StoreSignals) => {
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 && !signals.props.initialViewport && this.width && this.height) {
const bounds = getInternalNodesBounds(this.nodeLookup, {
filter: (node) =>
+8 -5
View File
@@ -6,8 +6,7 @@ import type {
Connection,
UpdateNodePositions,
CoordinateExtent,
UpdateConnection,
Viewport
UpdateConnection
} from '@xyflow/system';
import type { getInitialStore } from './initial-store.svelte';
@@ -15,8 +14,8 @@ import type { Node, Edge, NodeTypes, EdgeTypes, FitViewOptions } from '$lib/type
import type { SvelteFlowProps } from '$lib/container/SvelteFlow';
export type SvelteFlowStoreActions = {
syncNodeStores: (nodesStore: Writable<Node[]>) => void;
syncEdgeStores: (edgeStore: Writable<Edge[]>) => void;
// syncNodeStores: (nodesStore: Writable<Node[]>) => void;
// syncEdgeStores: (edgeStore: Writable<Edge[]>) => void;
// syncViewport: (viewportStore?: Writable<Viewport>) => void;
setNodeTypes: (nodeTypes: NodeTypes) => void;
setEdgeTypes: (edgeTypes: EdgeTypes) => void;
@@ -43,7 +42,11 @@ export type SvelteFlowStoreActions = {
export type ContainerSignals = { domNode?: HTMLDivElement | null; width?: number; height?: number };
export type StoreSignals = {
props: Partial<SvelteFlowProps> & ContainerSignals;
container?: ContainerSignals;
domNode?: HTMLDivElement;
width?: number;
height?: number;
nodes: Node[];
edges: Edge[];
};
export type SvelteFlowStoreState = ReturnType<typeof getInitialStore>;