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);
|