NodesRect.hooks.ts 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { useCallback, useContext, useEffect, useMemo, useState } from "react";
  2. import { DocumentControllerContext } from '$app/stores/effects/document/document_controller';
  3. import { useAppSelector } from '$app/stores/store';
  4. import { RegionGrid } from '$app/utils/region_grid';
  5. export function useNodesRect(container: HTMLDivElement) {
  6. const controller = useContext(DocumentControllerContext);
  7. const version = useVersionUpdate();
  8. const regionGrid = useMemo(() => {
  9. if (!controller) return null;
  10. return new RegionGrid(300);
  11. }, [controller]);
  12. const updateNodeRect = useCallback(
  13. (node: Element) => {
  14. const { x, y, width, height } = node.getBoundingClientRect();
  15. const id = node.getAttribute('data-block-id');
  16. if (!id) return;
  17. const rect = {
  18. id,
  19. x: x + container.scrollLeft,
  20. y: y + container.scrollTop,
  21. width,
  22. height,
  23. };
  24. regionGrid?.updateBlock(rect);
  25. },
  26. [container.scrollLeft, container.scrollTop, regionGrid]
  27. );
  28. const updateViewPortNodesRect = useCallback(() => {
  29. const nodes = container.querySelectorAll('[data-block-id]');
  30. nodes.forEach(updateNodeRect);
  31. }, [container, updateNodeRect]);
  32. // update nodes rect when data changed
  33. useEffect(() => {
  34. updateViewPortNodesRect();
  35. }, [version, updateViewPortNodesRect]);
  36. // update nodes rect when scroll
  37. useEffect(() => {
  38. container.addEventListener('scroll', updateViewPortNodesRect);
  39. return () => {
  40. container.removeEventListener('scroll', updateViewPortNodesRect);
  41. };
  42. }, [container, updateViewPortNodesRect]);
  43. const getIntersectedBlockIds = useCallback(
  44. (rect: { startX: number; startY: number; endX: number; endY: number }) => {
  45. if (!regionGrid) return [];
  46. const { startX, startY, endX, endY } = rect;
  47. const x = Math.min(startX, endX);
  48. const y = Math.min(startY, endY);
  49. const width = Math.abs(endX - startX);
  50. const height = Math.abs(endY - startY);
  51. return regionGrid
  52. .getIntersectingBlocks({
  53. x,
  54. y,
  55. width,
  56. height,
  57. })
  58. .map((block) => block.id);
  59. },
  60. [regionGrid]
  61. );
  62. return {
  63. getIntersectedBlockIds,
  64. };
  65. }
  66. function useVersionUpdate() {
  67. const [version, setVersion] = useState(0);
  68. const data = useAppSelector((state) => {
  69. return state.document;
  70. });
  71. useEffect(() => {
  72. setVersion((v) => {
  73. if (v < Number.MAX_VALUE) return v + 1;
  74. return 0;
  75. });
  76. }, [data]);
  77. return version;
  78. }