useFocusNode.tsx 2.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  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 togglePerfMode = useGraph(state => state.togglePerfMode);
  6. const zoomPanPinch = useGraph(state => state.zoomPanPinch);
  7. const [selectedNode, setSelectedNode] = 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. togglePerfMode(!content.value.length);
  15. const debouncer = setTimeout(() => {
  16. setContent(val => ({ ...val, debounced: content.value }));
  17. }, 800);
  18. return () => clearTimeout(debouncer);
  19. }, [content.value, togglePerfMode]);
  20. React.useEffect(() => {
  21. if (!zoomPanPinch) return;
  22. const ref = zoomPanPinch.instance.wrapperComponent;
  23. const matchedNodes: NodeListOf<Element> = searchQuery(
  24. `span[data-key*='${content.debounced}' i]`
  25. );
  26. const matchedNode: Element | null = matchedNodes[selectedNode] || null;
  27. cleanupHighlight();
  28. if (ref && matchedNode && matchedNode.parentElement) {
  29. const newScale = 0.4;
  30. const x = Number(matchedNode.getAttribute("data-x"));
  31. const y = Number(matchedNode.getAttribute("data-y"));
  32. const newPositionX =
  33. (ref.offsetLeft - x) * newScale +
  34. ref.clientWidth / 10 -
  35. matchedNode.getBoundingClientRect().width / 10;
  36. const newPositionY =
  37. (ref.offsetLeft - y) * newScale +
  38. ref.clientHeight / 10 -
  39. matchedNode.getBoundingClientRect().height / 10;
  40. highlightMatchedNodes(matchedNodes, selectedNode);
  41. zoomPanPinch?.setTransform(newPositionX, newPositionY, newScale);
  42. } else {
  43. setSelectedNode(0);
  44. }
  45. return () => {
  46. if (!content.value) setSelectedNode(0);
  47. };
  48. }, [content.debounced, content.value, selectedNode, zoomPanPinch]);
  49. return [content, setContent, skip] as const;
  50. };