summaryrefslogtreecommitdiff
path: root/src/zoom/SpacyClause.tsx
blob: 6b6f178b1503693824d2d915cdc367cef6949683 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import React, { memo, useState } from "react";
import { motion } from "motion/react";
import "./spacy.css";
import { NLP } from "sortug-ai";
// import { clauseVariants, createHoverEffect } from "./animations";
// import { useZoom } from "./hooks/useZoom";

function Grammar({ sentence }: { sentence: NLP.Spacy.Sentence }) {
  const [hoveredClause, setHoveredClause] = useState<number | null>(null);

  // Ref to manage the timeout for debouncing mouse leave
  return (
    <div className="clause-container">
      {sentence.words.map((w, idx) => {
        const isRoot =
          w.ancestors.length === 0 || w.dep.toLowerCase() === "root";
        const isSubj = NLP.Spacy.isChild(w, sentence.subj.id);
        const isPred = !isSubj && !isRoot;
        const predClass = isPred ? "pred" : "";
        const relClass = isRoot ? "root" : `rel-${w.dep}`;
        const ownClass = isRoot
          ? ""
          : isSubj
            ? "subj"
            : w.children.length === 0
              ? ""
              : `clause-${w.id}`;
        const clase = w.ancestors.reduce((acc, item) => {
          if (item === sentence.subj.id || item === sentence.root.id)
            return acc;
          else return `${acc} clause-${item}`;
        }, ``);
        const className = `suword ${relClass} ${ownClass} ${clase} ${predClass}`;
        const isHovering =
          !isRoot &&
          !!hoveredClause &&
          (w.id === hoveredClause || w.ancestors.includes(hoveredClause));
        function handleClick(w: NLP.Spacy.Word) {
          console.log("show the whole clause and all that", w);
        }
        return (
          <ClauseSpan
            word={w}
            key={w.id}
            className={className}
            hovering={isHovering}
            setHovering={setHoveredClause}
            onClick={handleClick}
          />
        );
      })}
    </div>
  );
}

const spanVariants: any = {
  initial: {
    // Base style
    backgroundColor: "rgba(0, 0, 0, 0)", // Transparent background initially
    fontWeight: "normal",
    scale: 1,
    zIndex: 0, // Default stacking
    position: "relative", // Needed for zIndex to work reliably
    // Add other base styles if needed
  },
  hovered: {
    // Style when this span's group is hovered
    backgroundColor: "rgba(255, 255, 0, 0.5)", // Yellow highlight
    scale: 1.05,
    zIndex: 1, // Bring hovered spans slightly forward
    boxShadow: "0px 2px 5px rgba(0,0,0,0.2)",
    // Add other hover effects
  },
};

// Define the transition
const spanTransition = {
  type: "spring",
  stiffness: 500,
  damping: 30,
  // duration: 0.1 // Or use duration for non-spring types
};

function ClauseSpan({
  word,
  className,
  hovering,
  setHovering,
  onClick,
}: {
  word: NLP.Spacy.Word;
  className: string;
  hovering: boolean;
  setHovering: (n: number | null) => void;
  onClick: (w: NLP.Spacy.Word) => void;
}) {
  function handleMouseOver() {
    setHovering(word.id);
    // if (word.children.length > 0) setHovering(word.id);
    // else setHovering(word.head);
  }
  function handleMouseLeave() {
    setHovering(null);
  }
  function handleClick(e: React.MouseEvent) {
    e.stopPropagation();
    onClick(word);
  }
  return (
    <motion.span
      className={className}
      variants={spanVariants}
      initial="initial"
      animate={hovering ? "hovered" : "initial"}
      transition={spanTransition}
      onMouseOver={handleMouseOver}
      onMouseLeave={handleMouseLeave}
      onClick={handleClick}
    >
      {word.text}
    </motion.span>
  );
}

export default memo(Grammar);