11// @flow
22
3- import React , { useState } from "react"
4- import type { SequenceItem } from "../../types"
5- import { makeStyles } from "@material-ui/styles"
3+ import React , { useState , useRef , useEffect } from "react"
4+ import type {
5+ SequenceItem as SequenceItemData ,
6+ Relationship
7+ } from "../../types"
8+ import { styled } from "@material-ui/styles"
69import stringToSequence from "../../string-to-sequence.js"
710import Tooltip from "@material-ui/core/Tooltip"
11+ import RelationshipArrows from "../RelationshipArrows"
12+ import colors from "../../colors"
13+ import ArrowToMouse from "../ArrowToMouse"
14+ import { useTimeout , useWindowSize } from "react-use"
15+ import classNames from "classnames"
816
9- const useStyles = makeStyles ( { } )
17+ const Container = styled ( "div" ) ( ( { relationshipsOn } ) => ( {
18+ lineHeight : 1.5 ,
19+ marginTop : relationshipsOn ? 64 : 0 ,
20+ display : "flex" ,
21+ flexWrap : "wrap"
22+ } ) )
23+
24+ const SequenceItem = styled ( "span" ) ( ( { color, relationshipsOn } ) => ( {
25+ display : "inline-flex" ,
26+ cursor : "pointer" ,
27+ backgroundColor : color ,
28+ color : "#fff" ,
29+ padding : 4 ,
30+ margin : 4 ,
31+ marginBottom : relationshipsOn ? 64 : 4 ,
32+ paddingLeft : 10 ,
33+ paddingRight : 10 ,
34+ borderRadius : 4 ,
35+ userSelect : "none" ,
36+ boxSizing : "border-box" ,
37+ "&.unlabeled" : {
38+ color : "#333" ,
39+ paddingTop : 4 ,
40+ paddingBottom : 4 ,
41+ paddingLeft : 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+ }
50+ }
51+ } ) )
52+
53+ const LabeledText = styled ( "div" ) ( {
54+ display : "inline-flex" ,
55+ cursor : "pointer" ,
56+ alignSelf : "center" ,
57+ fontSize : 11 ,
58+ width : 18 ,
59+ height : 18 ,
60+ alignItems : "center" ,
61+ justifyContent : "center" ,
62+ marginLeft : 4 ,
63+ borderRadius : 9 ,
64+ color : "#fff" ,
65+ backgroundColor : "rgba(0,0,0,0.2)"
66+ } )
1067
1168type Props = {
12- sequence : Array < SequenceItem > ,
69+ sequence : Array < SequenceItemData > ,
70+ relationships : Array < Relationship > ,
1371 canModifySequence ? : boolean ,
1472 onSequenceChange ?: ( Array < SequenceItem > ) => any ,
1573 onHighlightedChanged ?: ( Array < number > ) => any ,
@@ -19,17 +77,39 @@ type Props = {
1977
2078export default function Document ( {
2179 sequence,
80+ relationships,
2281 onHighlightedChanged = ( ) => null ,
82+ onCreateEmptyRelationship = ( ) => null ,
2383 onSequenceChange = ( ) => null ,
84+ onRelationshipsChange = ( ) => null ,
2485 nothingHighlighted = false ,
86+ createRelationshipsMode = false ,
2587 colorLabelMap = { }
2688} : Props ) {
89+ const sequenceItemPositionsRef = useRef ( { } )
2790 const [ mouseDown , changeMouseDown ] = useState ( )
91+ const [ timeoutCalled , cancelTimeout , resetTimeout ] = useTimeout ( 30 ) // Force rerender after mounting
92+ const windowSize = useWindowSize ( )
93+ useEffect ( ( ) => {
94+ resetTimeout ( )
95+ } , [ windowSize ] )
2896 const [
2997 [ firstSelected , lastSelected ] ,
3098 changeHighlightedRangeState
3199 ] = useState ( [ null , null ] )
100+
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+
32111 const changeHighlightedRange = ( [ first , last ] ) => {
112+ if ( createRelationshipsMode ) return
33113 changeHighlightedRangeState ( [ first , last ] )
34114 const highlightedItems = [ ]
35115 for ( let i = Math . min ( first , last ) ; i <= Math . max ( first , last ) ; i ++ )
@@ -47,55 +127,79 @@ export default function Document({
47127 }
48128
49129 return (
50- < div
130+ < Container
131+ relationshipsOn = { Boolean ( relationships ) }
51132 onMouseDown = { ( ) => changeMouseDown ( true ) }
52- onMouseUp = { ( ) => changeMouseDown ( false ) }
133+ onMouseUp = { ( ) => {
134+ if ( createRelationshipsMode && firstSequenceItem ) {
135+ setFirstSequenceItem ( null )
136+ if ( secondSequenceItem ) {
137+ setSecondSequenceItem ( null )
138+ }
139+ }
140+ changeMouseDown ( false )
141+ } }
53142 >
54143 { sequence . map ( ( seq , i ) => (
55- < span
56- key = { i }
144+ < SequenceItem
145+ key = { seq . textId || i }
146+ ref = { elm => {
147+ if ( ! elm ) return
148+ sequenceItemPositionsRef . current [ seq . textId ] = {
149+ offset : {
150+ left : elm . offsetLeft ,
151+ top : elm . offsetTop ,
152+ width : elm . offsetWidth ,
153+ height : elm . offsetHeight
154+ }
155+ }
156+ } }
157+ relationshipsOn = { Boolean ( relationships ) }
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+ } }
57169 onMouseDown = { ( ) => {
58- if ( seq . label ) return
59- 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+ }
60178 } }
61179 onMouseMove = { ( ) => {
62- if ( seq . label ) return
63- if ( mouseDown && i !== lastSelected ) {
64- changeHighlightedRange ( [
65- firstSelected === null ? i : firstSelected ,
66- i
67- ] )
180+ if ( ! mouseDown ) return
181+ if ( ! createRelationshipsMode ) {
182+ if ( seq . label ) return
183+ if ( i !== lastSelected ) {
184+ changeHighlightedRange ( [
185+ firstSelected === null ? i : firstSelected ,
186+ i
187+ ] )
188+ }
68189 }
69190 } }
70- style = {
191+ className = { classNames (
192+ seq . label ? "label" : "unlabeled" ,
193+ seq . text . trim ( ) . length > 0 && "notSpace"
194+ ) }
195+ color = {
71196 seq . label
72- ? {
73- display : "inline-flex" ,
74- backgroundColor :
75- seq . color || colorLabelMap [ seq . label ] || "#333" ,
76- color : "#fff" ,
77- padding : 4 ,
78- margin : 4 ,
79- paddingLeft : 10 ,
80- paddingRight : 10 ,
81- borderRadius : 4 ,
82- userSelect : "none"
83- }
84- : {
85- display : "inline-flex" ,
86- backgroundColor :
87- seq . text !== " " && highlightedItems . includes ( i )
88- ? "#ccc"
89- : "inherit" ,
90- color : "#333" ,
91- marginTop : 4 ,
92- marginBottom : 4 ,
93- paddingTop : 4 ,
94- paddingBottom : 4 ,
95- paddingLeft : 2 ,
96- paddingRight : 2 ,
97- userSelect : "none"
98- }
197+ ? seq . color || colorLabelMap [ seq . label ] || "#333"
198+ : ! createRelationshipsMode &&
199+ seq . text !== " " &&
200+ highlightedItems . includes ( i )
201+ ? "#ccc"
202+ : "inherit"
99203 }
100204 key = { i }
101205 >
@@ -106,35 +210,46 @@ export default function Document({
106210 ) : (
107211 < div > { seq . text } </ div >
108212 ) }
109- { seq . label && (
110- < div
111- onClick = { ( ) => {
213+ { seq . label && ! createRelationshipsMode && (
214+ < LabeledText
215+ onClick = { e => {
216+ e . stopPropagation ( )
112217 onSequenceChange (
113218 sequence
114219 . flatMap ( s => ( s !== seq ? s : stringToSequence ( s . text ) ) )
115220 . filter ( s => s . text . length > 0 )
116221 )
117222 } }
118- style = { {
119- display : "inline-flex" ,
120- cursor : "pointer" ,
121- alignSelf : "center" ,
122- fontSize : 11 ,
123- width : 18 ,
124- height : 18 ,
125- alignItems : "center" ,
126- justifyContent : "center" ,
127- marginLeft : 4 ,
128- borderRadius : 9 ,
129- color : "#fff" ,
130- backgroundColor : "rgba(0,0,0,0.2)"
131- } }
132223 >
133224 < span > { "\u2716" } </ span >
134- </ div >
225+ </ LabeledText >
135226 ) }
136- </ span >
227+ </ SequenceItem >
137228 ) ) }
138- </ div >
229+ { firstSequenceItem && ! secondSequenceItem && (
230+ < ArrowToMouse
231+ startAt = {
232+ ( ( sequenceItemPositionsRef . current || { } ) [ firstSequenceItem ] || { } )
233+ . offset
234+ }
235+ />
236+ ) }
237+ { relationships && (
238+ < RelationshipArrows
239+ onClickArrow = { ( { label, from, to } ) => {
240+ onRelationshipsChange (
241+ relationships . filter (
242+ r => ! ( r . from === from && r . to === to && r . label === label )
243+ )
244+ )
245+ } }
246+ positions = { sequenceItemPositionsRef . current }
247+ arrows = { relationships . map ( ( a , i ) => ( {
248+ ...a ,
249+ color : a . color || colors [ i % colors . length ]
250+ } ) ) }
251+ />
252+ ) }
253+ </ Container >
139254 )
140255}
0 commit comments