@gravity-ui/graph
Version:
Modern graph editor component
54 lines (53 loc) • 2.54 kB
JavaScript
import React, { memo, useEffect, useState } from "react";
import { Block as CanvasBlock } from "../components/canvas/blocks/Block";
import { GraphState } from "../graph";
import { ECameraScaleLevel } from "../services/camera/CameraService";
import { useSignal } from "./hooks";
import { useSceneChange } from "./hooks/useSceneChange";
import { useCompareState } from "./utils/hooks/useCompareState";
export const Block = memo((props) => {
const block = useSignal(props.blockState.$state);
if (!block)
return null;
return props.renderBlock(props.graphObject, block, props.blockState);
});
/**
* Optimized comparison using Set instead of map.sort + lodash.isEqual
* For proof that Set is faster than map.sort + lodash.isEqual run `node benchmarks/blocklist-comparison.bench.js`
*
* Note: lodash.isEqual is more memory-efficient for large lists, but we expect no more than 100 blocks
* in detailed view, where Set-based comparison significantly outperforms map.sort + lodash.isEqual
*/
const hasBlockListChanged = (newStates, oldStates) => {
if (newStates.length !== oldStates.length)
return true;
const oldIds = new Set(oldStates.map((state) => state.id));
return newStates.some((state) => !oldIds.has(state.id));
};
export const BlocksList = memo(function BlocksList({ renderBlock, graphObject }) {
const [blockStates, setBlockStates] = useState([]);
const [graphState, setGraphState] = useCompareState(graphObject.state);
useSceneChange(graphObject, () => {
const cameraLevel = graphObject.cameraService.getCameraBlockScaleLevel();
if (cameraLevel !== ECameraScaleLevel.Detailed) {
setBlockStates([]);
return;
}
setBlockStates((prevStates) => {
const statesInRect = graphObject
.getElementsInViewport([CanvasBlock])
.map((component) => component.connectedState);
return hasBlockListChanged(statesInRect, prevStates) ? statesInRect : prevStates;
});
});
useEffect(() => {
setGraphState(graphObject.state);
return graphObject.on("state-change", (event) => {
setGraphState(event.detail.state);
});
}, [graphObject, setGraphState]);
return (React.createElement("div", null, graphState === GraphState.READY &&
blockStates.map((blockState) => {
return (React.createElement(Block, { key: blockState.id, renderBlock: renderBlock, graphObject: graphObject, blockState: blockState }));
})));
});