UNPKG

@gravity-ui/graph

Version:

Modern graph editor component

54 lines (53 loc) 2.54 kB
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 })); }))); });