refactor(react): separate user nodes and indernal nodes

This commit is contained in:
moklick
2024-03-27 11:22:43 +01:00
parent d775012e1b
commit 844d574c4f
33 changed files with 374 additions and 315 deletions
+45 -61
View File
@@ -1,4 +1,3 @@
import { internalsSymbol } from '../constants';
import {
NodeBase,
CoordinateExtent,
@@ -12,54 +11,53 @@ import {
ConnectionLookup,
EdgeBase,
EdgeLookup,
InternalNodeBase,
} from '../types';
import { getDimensions, getHandleBounds } from './dom';
import { isNumeric } from './general';
import { getNodePositionWithOrigin } from './graph';
type ParentNodes = Record<string, boolean>;
type ParentNodes = Set<string>;
export function updateAbsolutePositions<NodeType extends NodeBase>(
nodes: NodeType[],
nodeLookup: Map<string, NodeType>,
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
nodeOrigin: NodeOrigin = [0, 0],
parentNodes?: ParentNodes
) {
return nodes.map((node) => {
for (const [, node] of nodeLookup) {
if (node.parentNode && !nodeLookup.has(node.parentNode)) {
throw new Error(`Parent node ${node.parentNode} not found`);
}
if (node.parentNode || parentNodes?.[node.id]) {
if (node.parentNode || parentNodes?.has(node.id)) {
const parentNode = node.parentNode ? nodeLookup.get(node.parentNode) : null;
const { x, y, z } = calculateXYZPosition(
node,
nodes,
nodeLookup,
{
...node.position,
z: node[internalsSymbol]?.z ?? 0,
z: node.internals.z ?? 0,
},
parentNode?.origin || nodeOrigin
);
const positionChanged = x !== node.computed?.positionAbsolute?.x || y !== node.computed?.positionAbsolute?.y;
node.computed!.positionAbsolute = positionChanged
const positionChanged = x !== node.internals.positionAbsolute?.x || y !== node.internals.positionAbsolute?.y;
node.internals.positionAbsolute = positionChanged
? {
x,
y,
}
: node.computed?.positionAbsolute;
: node.internals.positionAbsolute;
node[internalsSymbol]!.z = z;
node.internals.z = z;
if (parentNodes?.[node.id]) {
node[internalsSymbol]!.isParent = true;
if (parentNodes?.has(node.id)) {
node.internals.isParent = true;
}
}
return node;
});
nodeLookup.set(node.id, node);
}
}
type UpdateNodesOptions<NodeType extends NodeBase> = {
@@ -70,64 +68,55 @@ type UpdateNodesOptions<NodeType extends NodeBase> = {
export function adoptUserProvidedNodes<NodeType extends NodeBase>(
nodes: NodeType[],
nodeLookup: Map<string, NodeType>,
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
options: UpdateNodesOptions<NodeType> = {
nodeOrigin: [0, 0] as NodeOrigin,
elevateNodesOnSelect: true,
defaults: {},
}
): NodeType[] {
) {
const tmpLookup = new Map(nodeLookup);
nodeLookup.clear();
const parentNodes: ParentNodes = {};
const parentNodes: ParentNodes = new Set();
const selectedNodeZ: number = options?.elevateNodesOnSelect ? 1000 : 0;
const nextNodes = nodes.map((n) => {
nodes.forEach((n) => {
const currentStoreNode = tmpLookup.get(n.id);
if (n === currentStoreNode?.[internalsSymbol]?.userProvidedNode) {
if (n.parentNode) {
parentNodes.add(n.parentNode);
}
if (n === currentStoreNode?.internals?.userProvidedNode) {
nodeLookup.set(n.id, currentStoreNode);
return currentStoreNode;
}
const node: NodeType = {
const node: InternalNodeBase<NodeType> = {
...options.defaults,
...n,
computed: {
positionAbsolute: n.position,
width: n.computed?.width,
height: n.computed?.height,
},
};
const z = (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0);
const currInternals = n?.[internalsSymbol] || currentStoreNode?.[internalsSymbol];
if (node.parentNode) {
parentNodes[node.parentNode] = true;
}
Object.defineProperty(node, internalsSymbol, {
enumerable: false,
value: {
handleBounds: currInternals?.handleBounds,
z,
internals: {
positionAbsolute: n.position,
handleBounds: currentStoreNode?.internals?.handleBounds,
z: (isNumeric(n.zIndex) ? n.zIndex : 0) + (n.selected ? selectedNodeZ : 0),
userProvidedNode: n,
isParent: false,
},
});
};
nodeLookup.set(node.id, node);
return node;
});
const nodesWithPositions = updateAbsolutePositions(nextNodes, nodeLookup, options.nodeOrigin, parentNodes);
return nodesWithPositions;
if (parentNodes.size > 0) {
updateAbsolutePositions(nodeLookup, options.nodeOrigin, parentNodes);
}
}
function calculateXYZPosition<NodeType extends NodeBase>(
node: NodeType,
nodes: NodeType[],
nodeLookup: Map<string, NodeType>,
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
result: XYZPosition,
nodeOrigin: NodeOrigin
): XYZPosition {
@@ -140,12 +129,11 @@ function calculateXYZPosition<NodeType extends NodeBase>(
return calculateXYZPosition(
parentNode,
nodes,
nodeLookup,
{
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,
z: (parentNode.internals.z ?? 0) > (result.z ?? 0) ? parentNode.internals.z ?? 0 : result.z ?? 0,
},
parentNode.origin || nodeOrigin
);
@@ -153,25 +141,25 @@ function calculateXYZPosition<NodeType extends NodeBase>(
export function updateNodeDimensions<NodeType extends NodeBase>(
updates: Map<string, NodeDimensionUpdate>,
nodes: NodeType[],
nodeLookup: Map<string, NodeType>,
nodeLookup: Map<string, InternalNodeBase<NodeType>>,
domNode: HTMLElement | null,
nodeOrigin?: NodeOrigin,
onUpdate?: (id: string, dimensions: Dimensions) => void
): NodeType[] | null {
): { hasUpdate: boolean } {
const viewportNode = domNode?.querySelector('.xyflow__viewport');
let hasUpdate = false;
if (!viewportNode) {
return null;
return { hasUpdate };
}
const style = window.getComputedStyle(viewportNode);
const { m22: zoom } = new window.DOMMatrixReadOnly(style.transform);
const nextNodes = nodes.map((node) => {
const update = updates.get(node.id);
updates.forEach((update) => {
const node = nodeLookup.get(update.id);
if (update) {
if (node) {
const dimensions = getDimensions(update.nodeElement);
const doUpdate = !!(
dimensions.width &&
@@ -180,6 +168,7 @@ export function updateNodeDimensions<NodeType extends NodeBase>(
);
if (doUpdate) {
hasUpdate = true;
onUpdate?.(node.id, dimensions);
const newNode = {
@@ -188,25 +177,20 @@ export function updateNodeDimensions<NodeType extends NodeBase>(
...node.computed,
...dimensions,
},
[internalsSymbol]: {
...node[internalsSymbol],
internals: {
...node.internals,
handleBounds: {
source: getHandleBounds('.source', update.nodeElement, zoom, node.origin || nodeOrigin),
target: getHandleBounds('.target', update.nodeElement, zoom, node.origin || nodeOrigin),
},
},
};
nodeLookup.set(node.id, newNode);
return newNode;
}
}
return node;
});
return nextNodes;
return { hasUpdate };
}
export function panBy({