From 56ef230428fc8836bee5b6e1e6a646a7b1f3b3ef Mon Sep 17 00:00:00 2001 From: peterkogo Date: Fri, 20 Dec 2024 15:16:39 +0100 Subject: [PATCH] cleaned up code for onlyRenderVisibleElements --- packages/svelte/src/lib/store/index.ts | 4 +- .../src/lib/store/initial-store.svelte.ts | 104 +++++++++--------- .../svelte/src/lib/store/visibleElements.ts | 78 ++++++------- 3 files changed, 95 insertions(+), 91 deletions(-) diff --git a/packages/svelte/src/lib/store/index.ts b/packages/svelte/src/lib/store/index.ts index ac65bd75..5326a455 100644 --- a/packages/svelte/src/lib/store/index.ts +++ b/packages/svelte/src/lib/store/index.ts @@ -344,11 +344,11 @@ export function createStore(signals: StoreSignals): SvelteFlowStore { } const updateConnection: UpdateConnection = (newConnection: ConnectionState) => { - store.rawConnection = { ...newConnection }; + store._connection = { ...newConnection }; }; function cancelConnection() { - store.rawConnection = initialConnection; + store._connection = initialConnection; } function reset() { diff --git a/packages/svelte/src/lib/store/initial-store.svelte.ts b/packages/svelte/src/lib/store/initial-store.svelte.ts index c54bb677..96f848be 100644 --- a/packages/svelte/src/lib/store/initial-store.svelte.ts +++ b/packages/svelte/src/lib/store/initial-store.svelte.ts @@ -53,12 +53,13 @@ import type { IsValidConnection, Edge, Node, - EdgeLayouted + EdgeLayouted, + InternalNode } from '$lib/types'; import type { StoreSignals } from './types'; import { MediaQuery } from 'svelte/reactivity'; -import { gatherLayoutedEdges, getVisibleNodes } from './visibleElements'; +import { getLayoutedEdges, getVisibleNodes, type EdgeLayoutAllOptions } from './visibleElements'; export const initialNodeTypes = { input: InputNode, @@ -75,7 +76,6 @@ export const initialEdgeTypes = { }; export const getInitialStore = (signals: StoreSignals) => { - const previousLayoutedEdges = new Map(); // We use a class here, because Svelte adds getters & setter for us. // Inline classes have some performance implications but we just call it once (max twice). class SvelteFlowStore { @@ -112,47 +112,47 @@ export const getInitialStore = (signals: StoreSignals) => { connectionLookup: ConnectionLookup = new Map(); edgeLookup: EdgeLookup = new Map(); + _prevVisibleEdges = new Map(); visible = $derived.by(() => { - // We need to access this._nodes to trigger on changes - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - this._nodes; + const { + // We need to access this._nodes to trigger on changes + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _nodes, + _edges: edges, + _prevVisibleEdges: previousEdges, + nodeLookup, + connectionMode, + onerror, + onlyRenderVisibleElements + } = this; - // We either add all or only visible nodes to visibleNodes here. - let visibleNodes = new Map(); - let layoutedEdges = new Map(); - // TODO: is there a more elegant solution?? - if (this.onlyRenderVisibleElements) { - const transform: Transform = [this.viewport.x, this.viewport.y, this.viewport.zoom]; - getVisibleNodes(this.nodeLookup, transform, this.width, this.height).forEach((node) => { - visibleNodes.set(node.id, node); - }); - layoutedEdges = gatherLayoutedEdges( - this._edges, - this.nodeLookup, - previousLayoutedEdges, - this.connectionMode, - this.onerror, - true, + let visibleNodes: Map; + let visibleEdges: Map; + + const options = { edges, previousEdges, nodeLookup, connectionMode, onerror }; + + if (onlyRenderVisibleElements) { + // We only subscribe to viewport, width, height if onlyRenderVisibleElements is true + const { viewport, width, height } = this; + const transform: Transform = [viewport.x, viewport.y, viewport.zoom]; + + visibleNodes = getVisibleNodes(nodeLookup, transform, width, height); + visibleEdges = getLayoutedEdges({ + ...options, + onlyRenderVisible: true, + visibleNodes, transform, - this.width, - this.height, - visibleNodes - ); + width, + height + }); } else { visibleNodes = this.nodeLookup; - layoutedEdges = gatherLayoutedEdges( - this._edges, - this.nodeLookup, - previousLayoutedEdges, - this.connectionMode, - this.onerror, - false - ); + visibleEdges = getLayoutedEdges(options as EdgeLayoutAllOptions); } return { nodes: visibleNodes, - edges: layoutedEdges + edges: visibleEdges }; }); @@ -196,7 +196,8 @@ export const getInitialStore = (signals: StoreSignals) => { nodeTypes: NodeTypes = $derived({ ...initialNodeTypes, ...signals.props.nodeTypes }); edgeTypes: EdgeTypes = $derived({ ...initialEdgeTypes, ...signals.props.edgeTypes }); - // _viewport is the internal viewport. We either return signals.viewport or _viewport + // _viewport is the internal viewport. + // when binding to viewport, we operate on signals.viewport instead _viewport: Viewport = $state(signals.props.initialViewport ?? { x: 0, y: 0, zoom: 1 }); get viewport() { return signals.viewport ?? this._viewport; @@ -208,27 +209,29 @@ export const getInitialStore = (signals: StoreSignals) => { this._viewport = viewport; } - connectionMode: ConnectionMode = $derived( - signals.props.connectionMode ?? ConnectionMode.Strict - ); - rawConnection: ConnectionState = $state(initialConnection); + // _connection is viewport independent and originating from XYHandle + _connection: ConnectionState = $state(initialConnection); + // We derive a viewport dependent connection here connection: ConnectionState = $derived.by(() => { - if (this.rawConnection.inProgress) { + if (this._connection.inProgress) { return { - ...this.rawConnection, - to: pointToRendererPoint(this.rawConnection.to, [ + ...this._connection, + to: pointToRendererPoint(this._connection.to, [ this.viewport.x, this.viewport.y, this.viewport.zoom ]) }; } else { - return { ...this.rawConnection }; + return this._connection; } }); connectionLineType: ConnectionLineType = $derived( signals.props.connectionLineType ?? ConnectionLineType.Bezier ); + connectionMode: ConnectionMode = $derived( + signals.props.connectionMode ?? ConnectionMode.Strict + ); connectionRadius: number = $derived(signals.props.connectionRadius ?? 20); isValidConnection: IsValidConnection = $derived( signals.props.isValidConnection ?? (() => true) @@ -258,14 +261,14 @@ export const getInitialStore = (signals: StoreSignals) => { edgesInitialized: boolean = $state(false); viewportInitialized: boolean = $state(false); - initialNodesLength: number = signals.nodes?.length ?? 0; - initialEdgesLength: number = signals.edges?.length ?? 0; + _initialNodesLength: number = signals.nodes?.length ?? 0; + _initialEdgesLength: number = signals.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) { + if (this._initialNodesLength === 0) { initialized = this.viewportInitialized; - } else if (this.initialEdgesLength === 0) { + } else if (this._initialEdgesLength === 0) { initialized = this.viewportInitialized && this.nodesInitialized; } else { initialized = this.viewportInitialized && this.nodesInitialized && this.edgesInitialized; @@ -273,13 +276,14 @@ export const getInitialStore = (signals: StoreSignals) => { return initialized; }); - prefersDark = new MediaQuery( + + _prefersDark = new MediaQuery( '(prefers-color-scheme: dark)', signals.props.colorModeSSR === 'dark' ); colorMode: ColorModeClass = $derived( signals.props.colorMode === 'system' - ? this.prefersDark.current + ? this._prefersDark.current ? 'dark' : 'light' : (signals.props.colorMode ?? 'light') diff --git a/packages/svelte/src/lib/store/visibleElements.ts b/packages/svelte/src/lib/store/visibleElements.ts index a0b97432..7b64478b 100644 --- a/packages/svelte/src/lib/store/visibleElements.ts +++ b/packages/svelte/src/lib/store/visibleElements.ts @@ -16,42 +16,43 @@ export function getVisibleNodes( width: number, height: number ) { - return getNodesInside(nodeLookup, { x: 0, y: 0, width: width, height: height }, transform, true); + const visibleNodes = new Map(); + getNodesInside(nodeLookup, { x: 0, y: 0, width: width, height: height }, transform, true).forEach( + (node) => { + visibleNodes.set(node.id, node); + } + ); + return visibleNodes; } -// TODO: is this what we want? -export function gatherLayoutedEdges( - edges: Edge[], - nodeLookup: NodeLookup, - previousLayoutedEdges: Map, - connectionMode: ConnectionMode, - onerror: OnError, - onlyRenderVisibleElements: true, - transform: Transform, - width: number, - height: number, - visibleNodes: Map -): Map; -export function gatherLayoutedEdges( - edges: Edge[], - nodeLookup: NodeLookup, - previousLayoutedEdges: Map, - connectionMode: ConnectionMode, - onerror: OnError, - onlyRenderVisibleElements: false -): Map; -export function gatherLayoutedEdges( - edges: Edge[], - nodeLookup: NodeLookup, - previousLayoutedEdges: Map, - connectionMode: ConnectionMode, - onerror: OnError, - onlyRenderVisibleElements?: boolean, - transform?: Transform, - width?: number, - height?: number, - visibleNodes?: Map -): Map { +export interface EdgeLayoutBaseOptions { + edges: Edge[]; + previousEdges: Map; + nodeLookup: NodeLookup; + connectionMode: ConnectionMode; + onerror: OnError; +} + +export interface EdgeLayoutAllOptions extends EdgeLayoutBaseOptions { + onlyRenderVisible: never; + visibleNodes: never; + transform: never; + width: never; + height: never; +} + +export interface EdgeLayoutOnlyVisibleOptions extends EdgeLayoutBaseOptions { + visibleNodes: Map; + transform: Transform; + width: number; + height: number; + onlyRenderVisible: true; +} + +export type EdgeLayoutOptions = EdgeLayoutAllOptions | EdgeLayoutOnlyVisibleOptions; + +export function getLayoutedEdges(options: EdgeLayoutOptions): Map { + const { edges, nodeLookup, previousEdges, connectionMode, onerror, onlyRenderVisible } = options; const layoutedEdges = new Map(); for (const edge of edges) { const sourceNode = nodeLookup.get(edge.source); @@ -61,7 +62,8 @@ export function gatherLayoutedEdges( continue; } - if (onlyRenderVisibleElements) { + if (onlyRenderVisible) { + const { visibleNodes, transform, width, height } = options; if ( isEdgeVisible({ sourceNode, @@ -82,7 +84,7 @@ export function gatherLayoutedEdges( // the current and previous edge are the same // and the source and target node are the same // and references to internalNodes are the same - const previous = previousLayoutedEdges.get(edge.id); + const previous = previousEdges.get(edge.id); if ( previous && edge === previous.edge && @@ -99,7 +101,7 @@ export function gatherLayoutedEdges( targetNode, sourceHandle: edge.sourceHandle || null, targetHandle: edge.targetHandle || null, - connectionMode: connectionMode, + connectionMode, onError: onerror }); @@ -121,7 +123,5 @@ export function gatherLayoutedEdges( } } - previousLayoutedEdges = layoutedEdges; - return layoutedEdges; }