useFocusNode.tsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. import React from "react";
  2. import useGraph from "src/store/useGraph";
  3. import { searchQuery, cleanupHighlight, highlightMatchedNodes } from "src/utils/search";
  4. export const useFocusNode = () => {
  5. const zoomPanPinch = useGraph(state => state.zoomPanPinch);
  6. const [selectedNode, setSelectedNode] = React.useState(0);
  7. const [nodeCount, setNodeCount] = React.useState(0);
  8. const [content, setContent] = React.useState({
  9. value: "",
  10. debounced: "",
  11. });
  12. const skip = () => setSelectedNode(current => current + 1);
  13. React.useEffect(() => {
  14. const debouncer = setTimeout(() => {
  15. setContent(val => ({ ...val, debounced: content.value }));
  16. }, 800);
  17. return () => clearTimeout(debouncer);
  18. }, [content.value]);
  19. React.useEffect(() => {
  20. if (!zoomPanPinch) return;
  21. const ref = zoomPanPinch.instance.wrapperComponent;
  22. const matchedNodes: NodeListOf<Element> = searchQuery(
  23. `span[data-key*='${content.debounced}' i]`
  24. );
  25. const matchedNode: Element | null = matchedNodes[selectedNode] || null;
  26. cleanupHighlight();
  27. if (ref && matchedNode && matchedNode.parentElement) {
  28. const newScale = 0.8;
  29. const x = Number(matchedNode.getAttribute("data-x"));
  30. const y = Number(matchedNode.getAttribute("data-y"));
  31. const newPositionX =
  32. (ref.offsetLeft - x) * newScale +
  33. ref.clientWidth / 4 -
  34. matchedNode.getBoundingClientRect().width / 4;
  35. const newPositionY =
  36. (ref.offsetLeft - y) * newScale +
  37. ref.clientHeight / 4 -
  38. matchedNode.getBoundingClientRect().height / 4;
  39. highlightMatchedNodes(matchedNodes, selectedNode);
  40. setNodeCount(matchedNodes.length);
  41. zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
  42. } else {
  43. setSelectedNode(0);
  44. setNodeCount(0);
  45. }
  46. return () => {
  47. if (!content.value) {
  48. setSelectedNode(0);
  49. setNodeCount(0);
  50. }
  51. };
  52. }, [content.debounced, content, selectedNode, zoomPanPinch]);
  53. return [content, setContent, skip, nodeCount, selectedNode] as const;
  54. };