NodesRect.hooks.ts 2.4 KB

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