@gravity-ui/graph
Version:
Modern graph editor component
84 lines (83 loc) • 3.46 kB
JavaScript
import React, { memo, useEffect, useMemo, useState } from "react";
import isEqual from "lodash/isEqual";
import { Block as CanvasBlock } from "../components/canvas/blocks/Block";
import { GraphState } from "../graph";
import { ESchedulerPriority } from "../lib";
import { ECameraScaleLevel } from "../services/camera/CameraService";
import { debounce } from "../utils/functions";
import { useSignal } from "./hooks";
import { useGraphEvent } from "./hooks/useGraphEvents";
import { useCompareState } from "./utils/hooks/useCompareState";
import { useFn } from "./utils/hooks/useFn";
export const Block = memo((props) => {
const block = useSignal(props.blockState.$state);
if (!block)
return;
return props.renderBlock(props.graphObject, block, props.blockState);
});
export const BlocksList = memo(function BlocksList({ renderBlock, graphObject }) {
const [blockStates, setBlockStates] = useState([]);
const [isRenderAllowed, setRenderAllowed] = useCompareState(false);
const [graphState, setGraphState] = useCompareState(graphObject.state);
const render = useFn((graphObject, block) => {
return renderBlock(graphObject, block);
});
const updateBlockList = useFn(() => {
if (graphObject.cameraService.getCameraBlockScaleLevel() !== ECameraScaleLevel.Detailed) {
setBlockStates([]);
return;
}
const statesInRect = graphObject.getElementsInViewport([CanvasBlock]).map((component) => component.connectedState);
setBlockStates((blocks) => {
if (!isEqual(statesInRect.map((state) => state.id).sort(), blocks.map((state) => state.id).sort())) {
return statesInRect;
}
return blocks;
});
});
const scheduleListUpdate = useMemo(() => {
return debounce(() => updateBlockList(), {
priority: ESchedulerPriority.HIGHEST,
frameInterval: 1,
});
}, []);
useGraphEvent(graphObject, "state-change", () => {
setGraphState(graphObject.state);
});
useEffect(() => {
setGraphState(graphObject.state);
}, [graphObject]);
useGraphEvent(graphObject, "camera-change", ({ scale }) => {
if (graphObject.cameraService.getCameraBlockScaleLevel(scale) !== ECameraScaleLevel.Detailed) {
setRenderAllowed(false);
return;
}
setRenderAllowed(true);
scheduleListUpdate();
if (!isRenderAllowed) {
scheduleListUpdate.flush();
}
});
useEffect(() => {
return () => {
scheduleListUpdate.cancel();
};
}, []);
// init list
useEffect(() => {
graphObject.hitTest.waitUsableRectUpdate(() => {
if (graphObject.cameraService.getCameraBlockScaleLevel() !== ECameraScaleLevel.Detailed) {
setRenderAllowed(false);
return;
}
setRenderAllowed(true);
scheduleListUpdate.flush();
});
return graphObject.hitTest.onUsableRectUpdate(updateBlockList);
}, [graphObject.hitTest, isRenderAllowed, graphState]);
return (React.createElement(React.Fragment, null, graphState === GraphState.READY &&
isRenderAllowed &&
blockStates.map((blockState) => {
return (React.createElement(Block, { key: blockState.id, renderBlock: render, graphObject: graphObject, blockState: blockState }));
})));
});