Skip to content

Commit 9b6c92f

Browse files
committed
relationship interface feeling good!
1 parent e1ddf61 commit 9b6c92f

File tree

9 files changed

+278
-131
lines changed

9 files changed

+278
-131
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"version": "0.2.0",
44
"homepage": "https://waoai.github.io/react-nlp-annotate/",
55
"dependencies": {
6+
"@material-ui/lab": "^4.0.0-alpha.56",
7+
"color-alpha": "^1.0.4",
68
"react-hotkeys": "^2.0.0",
79
"react-material-workspace-layout": "^0.1.6",
810
"react-use": "^15.3.3",

src/components/ArrowToMouse/index.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// @flow
2+
3+
import React, { useRef } from "react"
4+
import { styled } from "@material-ui/core/styles"
5+
import { useMouse } from "react-use"
6+
7+
const Container = styled("div")({
8+
position: "absolute",
9+
left: 0,
10+
top: 0,
11+
right: 0,
12+
bottom: 0,
13+
pointerEvents: "none"
14+
})
15+
16+
export const ArrowToMouse = ({ startAt } = {}) => {
17+
const ref = useRef(null)
18+
let { elX: mx, elY: my } = useMouse(ref)
19+
let dx, dy
20+
if (mx === 0 && my === 0) {
21+
dx = 0
22+
dy = 0
23+
mx = startAt.left + startAt.width / 2
24+
my = startAt.top + startAt.height / 2
25+
} else {
26+
const a = mx - startAt.left
27+
const b = my - startAt.top
28+
const c = Math.sqrt(a * a + b * b)
29+
const sf = c < 100 ? (c / 100) ** 2 : 1
30+
dx = (a / c) * sf
31+
dy = (b / c) * sf
32+
}
33+
return (
34+
<Container ref={ref}>
35+
<svg
36+
width={Math.max(mx, startAt.left + startAt.width + 8)}
37+
height={Math.max(my, startAt.top + startAt.height + 8)}
38+
>
39+
<defs>
40+
<marker
41+
id={"arrowhead"}
42+
markerWidth="5"
43+
markerHeight="5"
44+
refX="0"
45+
refY="2.5"
46+
orient="auto"
47+
>
48+
<polygon fill={"rgba(255,0,0,0.75)"} points="0 0, 6 2.5, 0 5" />
49+
</marker>
50+
</defs>
51+
<rect
52+
x={startAt.left - 5}
53+
y={startAt.top - 5}
54+
width={startAt.width + 10}
55+
height={startAt.height + 10}
56+
stroke="rgba(255,0,0,0.75)"
57+
stroke-dasharray="10 5"
58+
fill="none"
59+
/>
60+
<line
61+
x1={startAt.left + startAt.width / 2}
62+
y1={startAt.top + startAt.height / 2}
63+
x2={mx - dx * 30}
64+
y2={my - dy * 30}
65+
marker-end={`url(#arrowhead)`}
66+
stroke-width={3}
67+
stroke="rgba(255,0,0,0.75)"
68+
fill="none"
69+
/>
70+
</svg>
71+
</Container>
72+
)
73+
}
74+
75+
export default ArrowToMouse

src/components/Document/index.js

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import stringToSequence from "../../string-to-sequence.js"
1010
import Tooltip from "@material-ui/core/Tooltip"
1111
import RelationshipArrows from "../RelationshipArrows"
1212
import colors from "../../colors"
13+
import ArrowToMouse from "../ArrowToMouse"
1314
import { useTimeout, useWindowSize } from "react-use"
15+
import classNames from "classnames"
1416

1517
const Container = styled("div")(({ relationshipsOn }) => ({
1618
lineHeight: 1.5,
@@ -21,6 +23,7 @@ const Container = styled("div")(({ relationshipsOn }) => ({
2123

2224
const SequenceItem = styled("span")(({ color, relationshipsOn }) => ({
2325
display: "inline-flex",
26+
cursor: "pointer",
2427
backgroundColor: color,
2528
color: "#fff",
2629
padding: 4,
@@ -30,12 +33,20 @@ const SequenceItem = styled("span")(({ color, relationshipsOn }) => ({
3033
paddingRight: 10,
3134
borderRadius: 4,
3235
userSelect: "none",
36+
boxSizing: "border-box",
3337
"&.unlabeled": {
3438
color: "#333",
3539
paddingTop: 4,
3640
paddingBottom: 4,
3741
paddingLeft: 2,
38-
paddingRight: 2
42+
paddingRight: 2,
43+
".notSpace:hover": {
44+
paddingTop: 2,
45+
paddingBottom: 2,
46+
paddingLeft: 0,
47+
paddingRight: 0,
48+
border: `2px dashed #ccc`
49+
}
3950
}
4051
}))
4152

@@ -68,7 +79,7 @@ export default function Document({
6879
sequence,
6980
relationships,
7081
onHighlightedChanged = () => null,
71-
onLastPairClickedChanged = () => null,
82+
onCreateEmptyRelationship = () => null,
7283
onSequenceChange = () => null,
7384
onRelationshipsChange = () => null,
7485
nothingHighlighted = false,
@@ -77,7 +88,7 @@ export default function Document({
7788
}: Props) {
7889
const sequenceItemPositionsRef = useRef({})
7990
const [mouseDown, changeMouseDown] = useState()
80-
const [timeoutCalled, cancelTimeout, resetTimeout] = useTimeout(10) // Force rerender after mounting
91+
const [timeoutCalled, cancelTimeout, resetTimeout] = useTimeout(30) // Force rerender after mounting
8192
const windowSize = useWindowSize()
8293
useEffect(() => {
8394
resetTimeout()
@@ -87,10 +98,18 @@ export default function Document({
8798
changeHighlightedRangeState
8899
] = useState([null, null])
89100

101+
const [firstSequenceItem, setFirstSequenceItem] = useState(null)
102+
const [secondSequenceItem, setSecondSequenceItem] = useState(null)
103+
104+
useEffect(() => {
105+
setFirstSequenceItem(null)
106+
setSecondSequenceItem(null)
107+
changeHighlightedRangeState([null, null])
108+
changeMouseDown(false)
109+
}, [createRelationshipsMode])
110+
90111
const changeHighlightedRange = ([first, last]) => {
91-
if (first !== firstSelected && first !== null && firstSelected !== null) {
92-
onLastPairClickedChanged([firstSelected, first])
93-
}
112+
if (createRelationshipsMode) return
94113
changeHighlightedRangeState([first, last])
95114
const highlightedItems = []
96115
for (let i = Math.min(first, last); i <= Math.max(first, last); i++)
@@ -111,7 +130,15 @@ export default function Document({
111130
<Container
112131
relationshipsOn={Boolean(relationships)}
113132
onMouseDown={() => changeMouseDown(true)}
114-
onMouseUp={() => changeMouseDown(false)}
133+
onMouseUp={() => {
134+
if (createRelationshipsMode && firstSequenceItem) {
135+
setFirstSequenceItem(null)
136+
if (secondSequenceItem) {
137+
setSecondSequenceItem(null)
138+
}
139+
}
140+
changeMouseDown(false)
141+
}}
115142
>
116143
{sequence.map((seq, i) => (
117144
<SequenceItem
@@ -128,10 +155,26 @@ export default function Document({
128155
}
129156
}}
130157
relationshipsOn={Boolean(relationships)}
131-
onClick={e => e.stopPropagation()}
158+
onMouseUp={e => {
159+
if (!createRelationshipsMode) return
160+
if (!secondSequenceItem) {
161+
setFirstSequenceItem(null)
162+
setSecondSequenceItem(null)
163+
onCreateEmptyRelationship([firstSequenceItem, seq.textId])
164+
} else {
165+
setFirstSequenceItem(null)
166+
setSecondSequenceItem(null)
167+
}
168+
}}
132169
onMouseDown={() => {
133-
if (seq.label && !createRelationshipsMode) return
134-
changeHighlightedRange([i, i])
170+
if (createRelationshipsMode) {
171+
if (!firstSequenceItem) {
172+
setFirstSequenceItem(seq.textId)
173+
}
174+
} else {
175+
if (seq.label) return
176+
changeHighlightedRange([i, i])
177+
}
135178
}}
136179
onMouseMove={() => {
137180
if (!mouseDown) return
@@ -145,7 +188,10 @@ export default function Document({
145188
}
146189
}
147190
}}
148-
className={seq.label ? "label" : "unlabeled"}
191+
className={classNames(
192+
seq.label ? "label" : "unlabeled",
193+
seq.text.trim().length > 0 && "notSpace"
194+
)}
149195
color={
150196
seq.label
151197
? seq.color || colorLabelMap[seq.label] || "#333"
@@ -164,7 +210,7 @@ export default function Document({
164210
) : (
165211
<div>{seq.text}</div>
166212
)}
167-
{seq.label && (
213+
{seq.label && !createRelationshipsMode && (
168214
<LabeledText
169215
onClick={e => {
170216
e.stopPropagation()
@@ -180,6 +226,14 @@ export default function Document({
180226
)}
181227
</SequenceItem>
182228
))}
229+
{firstSequenceItem && !secondSequenceItem && (
230+
<ArrowToMouse
231+
startAt={
232+
((sequenceItemPositionsRef.current || {})[firstSequenceItem] || {})
233+
.offset
234+
}
235+
/>
236+
)}
183237
{relationships && (
184238
<RelationshipArrows
185239
onClickArrow={({ label, from, to }) => {

0 commit comments

Comments
 (0)