react-msaview 1.3.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/bundle/index.js +285 -97099
  2. package/dist/StructureModel.d.ts +9 -0
  3. package/dist/StructureModel.js +11 -0
  4. package/dist/StructureModel.js.map +1 -0
  5. package/dist/UniprotTrack.d.ts +27 -0
  6. package/dist/UniprotTrack.js +53 -0
  7. package/dist/UniprotTrack.js.map +1 -0
  8. package/dist/colorSchemes.d.ts +5 -11
  9. package/dist/colorSchemes.js +27 -32
  10. package/dist/colorSchemes.js.map +1 -0
  11. package/dist/components/BoxTrack.d.ts +6 -6
  12. package/dist/components/BoxTrack.js +9 -137
  13. package/dist/components/BoxTrack.js.map +1 -0
  14. package/dist/components/BoxTrackBlock.d.ts +8 -0
  15. package/dist/components/BoxTrackBlock.js +136 -0
  16. package/dist/components/BoxTrackBlock.js.map +1 -0
  17. package/dist/components/Header.d.ts +2 -2
  18. package/dist/components/Header.js +55 -48
  19. package/dist/components/Header.js.map +1 -0
  20. package/dist/components/ImportForm.d.ts +2 -2
  21. package/dist/components/ImportForm.js +59 -71
  22. package/dist/components/ImportForm.js.map +1 -0
  23. package/dist/components/MSABlock.d.ts +8 -0
  24. package/dist/components/MSABlock.js +103 -0
  25. package/dist/components/MSABlock.js.map +1 -0
  26. package/dist/components/MSACanvas.d.ts +2 -2
  27. package/dist/components/MSACanvas.js +32 -133
  28. package/dist/components/MSACanvas.js.map +1 -0
  29. package/dist/components/MSAMouseoverCanvas.d.ts +6 -0
  30. package/dist/components/MSAMouseoverCanvas.js +52 -0
  31. package/dist/components/MSAMouseoverCanvas.js.map +1 -0
  32. package/dist/components/MSAView.d.ts +2 -2
  33. package/dist/components/MSAView.js +17 -62
  34. package/dist/components/MSAView.js.map +1 -0
  35. package/dist/components/MultiAlignmentSelector.d.ts +6 -0
  36. package/dist/components/MultiAlignmentSelector.js +13 -0
  37. package/dist/components/MultiAlignmentSelector.js.map +1 -0
  38. package/dist/components/ResizeHandles.d.ts +5 -5
  39. package/dist/components/ResizeHandles.js +31 -32
  40. package/dist/components/ResizeHandles.js.map +1 -0
  41. package/dist/components/Rubberband.d.ts +3 -2
  42. package/dist/components/Rubberband.js +41 -64
  43. package/dist/components/Rubberband.js.map +1 -0
  44. package/dist/components/Ruler.d.ts +2 -16
  45. package/dist/components/Ruler.js +19 -88
  46. package/dist/components/Ruler.js.map +1 -0
  47. package/dist/components/TextTrack.d.ts +5 -5
  48. package/dist/components/TextTrack.js +23 -24
  49. package/dist/components/TextTrack.js.map +1 -0
  50. package/dist/components/Track.d.ts +5 -5
  51. package/dist/components/Track.js +41 -40
  52. package/dist/components/Track.js.map +1 -0
  53. package/dist/components/TreeBranchMenu.d.ts +14 -0
  54. package/dist/components/TreeBranchMenu.js +26 -0
  55. package/dist/components/TreeBranchMenu.js.map +1 -0
  56. package/dist/components/TreeCanvas.d.ts +2 -2
  57. package/dist/components/TreeCanvas.js +24 -356
  58. package/dist/components/TreeCanvas.js.map +1 -0
  59. package/dist/components/TreeCanvasBlock.d.ts +7 -0
  60. package/dist/components/TreeCanvasBlock.js +252 -0
  61. package/dist/components/TreeCanvasBlock.js.map +1 -0
  62. package/dist/components/TreeMenu.d.ts +12 -0
  63. package/dist/components/TreeMenu.js +56 -0
  64. package/dist/components/TreeMenu.js.map +1 -0
  65. package/dist/components/TreeRuler.d.ts +2 -2
  66. package/dist/components/TreeRuler.js +3 -3
  67. package/dist/components/TreeRuler.js.map +1 -0
  68. package/dist/components/VerticalGuide.d.ts +7 -0
  69. package/dist/components/VerticalGuide.js +30 -0
  70. package/dist/components/VerticalGuide.js.map +1 -0
  71. package/dist/components/ZoomControls.d.ts +6 -0
  72. package/dist/components/ZoomControls.js +58 -0
  73. package/dist/components/ZoomControls.js.map +1 -0
  74. package/dist/components/data/seq2.d.ts +3 -3
  75. package/dist/components/data/seq2.js +33 -3
  76. package/dist/components/data/seq2.js.map +1 -0
  77. package/dist/components/dialogs/AboutDlg.d.ts +4 -0
  78. package/dist/components/dialogs/AboutDlg.js +40 -0
  79. package/dist/components/dialogs/AboutDlg.js.map +1 -0
  80. package/{bundle/components → dist/components/dialogs}/AddTrackDlg.d.ts +3 -3
  81. package/dist/components/dialogs/AddTrackDlg.js +26 -0
  82. package/dist/components/dialogs/AddTrackDlg.js.map +1 -0
  83. package/dist/components/{AnnotationDlg.d.ts → dialogs/AnnotationDlg.d.ts} +3 -3
  84. package/dist/components/dialogs/AnnotationDlg.js +65 -0
  85. package/dist/components/dialogs/AnnotationDlg.js.map +1 -0
  86. package/dist/components/dialogs/DetailsDlg.d.ts +7 -0
  87. package/dist/components/dialogs/DetailsDlg.js +13 -0
  88. package/dist/components/dialogs/DetailsDlg.js.map +1 -0
  89. package/dist/components/dialogs/MoreInfoDlg.d.ts +6 -0
  90. package/dist/components/dialogs/MoreInfoDlg.js +11 -0
  91. package/dist/components/dialogs/MoreInfoDlg.js.map +1 -0
  92. package/dist/components/dialogs/SettingsDlg.d.ts +7 -0
  93. package/dist/components/dialogs/SettingsDlg.js +48 -0
  94. package/dist/components/dialogs/SettingsDlg.js.map +1 -0
  95. package/dist/components/dialogs/TrackInfoDlg.d.ts +9 -0
  96. package/{bundle/components → dist/components/dialogs}/TrackInfoDlg.js +12 -13
  97. package/dist/components/dialogs/TrackInfoDlg.js.map +1 -0
  98. package/dist/components/dialogs/TracklistDlg.d.ts +7 -0
  99. package/dist/components/dialogs/TracklistDlg.js +18 -0
  100. package/dist/components/dialogs/TracklistDlg.js.map +1 -0
  101. package/{bundle/components/Ruler.d.ts → dist/components/util.d.ts} +1 -6
  102. package/dist/components/util.js +68 -0
  103. package/dist/components/util.js.map +1 -0
  104. package/dist/index.d.ts +2 -4
  105. package/dist/index.js +3 -3
  106. package/dist/index.js.map +1 -0
  107. package/dist/layout.js +14 -20
  108. package/dist/layout.js.map +1 -0
  109. package/dist/model.d.ts +114 -97
  110. package/dist/model.js +248 -486
  111. package/dist/model.js.map +1 -0
  112. package/dist/parseNewick.d.ts +1 -5
  113. package/dist/parseNewick.js +11 -8
  114. package/dist/parseNewick.js.map +1 -0
  115. package/dist/parsers/ClustalMSA.d.ts +6 -18
  116. package/dist/parsers/ClustalMSA.js +55 -64
  117. package/dist/parsers/ClustalMSA.js.map +1 -0
  118. package/dist/parsers/FastaMSA.d.ts +5 -12
  119. package/dist/parsers/FastaMSA.js +55 -64
  120. package/dist/parsers/FastaMSA.js.map +1 -0
  121. package/dist/parsers/StockholmMSA.d.ts +10 -17
  122. package/dist/parsers/StockholmMSA.js +81 -110
  123. package/dist/parsers/StockholmMSA.js.map +1 -0
  124. package/dist/util.d.ts +34 -7
  125. package/dist/util.js +76 -24
  126. package/dist/util.js.map +1 -0
  127. package/dist/version.d.ts +1 -0
  128. package/dist/version.js +2 -0
  129. package/dist/version.js.map +1 -0
  130. package/package.json +34 -34
  131. package/src/StructureModel.ts +11 -0
  132. package/src/UniprotTrack.ts +59 -0
  133. package/src/colorSchemes.ts +520 -0
  134. package/src/components/BoxTrack.tsx +33 -0
  135. package/src/components/BoxTrackBlock.tsx +198 -0
  136. package/src/components/Header.tsx +106 -0
  137. package/src/components/ImportForm.tsx +192 -0
  138. package/src/components/MSABlock.tsx +164 -0
  139. package/src/components/MSACanvas.tsx +142 -0
  140. package/src/components/MSAMouseoverCanvas.tsx +87 -0
  141. package/src/components/MSAView.tsx +88 -0
  142. package/src/components/MultiAlignmentSelector.tsx +33 -0
  143. package/src/components/ResizeHandles.tsx +137 -0
  144. package/src/components/Rubberband.tsx +270 -0
  145. package/src/components/Ruler.tsx +123 -0
  146. package/src/components/TextTrack.tsx +120 -0
  147. package/src/components/Track.tsx +153 -0
  148. package/src/components/TreeBranchMenu.tsx +67 -0
  149. package/src/components/TreeCanvas.tsx +128 -0
  150. package/src/components/TreeCanvasBlock.tsx +359 -0
  151. package/src/components/TreeMenu.tsx +105 -0
  152. package/src/components/TreeRuler.tsx +12 -0
  153. package/src/components/VerticalGuide.tsx +50 -0
  154. package/src/components/ZoomControls.tsx +78 -0
  155. package/src/components/data/seq2.ts +35 -0
  156. package/src/components/dialogs/AboutDlg.tsx +58 -0
  157. package/src/components/dialogs/AddTrackDlg.tsx +74 -0
  158. package/src/components/dialogs/AnnotationDlg.tsx +144 -0
  159. package/src/components/dialogs/DetailsDlg.tsx +28 -0
  160. package/src/components/dialogs/MoreInfoDlg.tsx +21 -0
  161. package/src/components/dialogs/SettingsDlg.tsx +154 -0
  162. package/src/components/dialogs/TrackInfoDlg.tsx +59 -0
  163. package/src/components/dialogs/TracklistDlg.tsx +59 -0
  164. package/src/components/util.ts +93 -0
  165. package/src/declare.d.ts +1 -0
  166. package/src/index.ts +2 -0
  167. package/src/layout.ts +83 -0
  168. package/src/model.ts +793 -0
  169. package/{bundle/parseNewick.d.ts → src/parseNewick.ts} +35 -5
  170. package/src/parsers/ClustalMSA.ts +79 -0
  171. package/src/parsers/FastaMSA.ts +82 -0
  172. package/src/parsers/StockholmMSA.ts +137 -0
  173. package/src/util.ts +142 -0
  174. package/src/version.ts +1 -0
  175. package/bundle/colorSchemes.d.ts +0 -16
  176. package/bundle/colorSchemes.js +0 -455
  177. package/bundle/components/AboutDlg.d.ts +0 -5
  178. package/bundle/components/AboutDlg.js +0 -47
  179. package/bundle/components/AddTrackDlg.js +0 -26
  180. package/bundle/components/AnnotationDlg.d.ts +0 -11
  181. package/bundle/components/AnnotationDlg.js +0 -77
  182. package/bundle/components/BoxTrack.d.ts +0 -7
  183. package/bundle/components/BoxTrack.js +0 -143
  184. package/bundle/components/DetailsDlg.d.ts +0 -8
  185. package/bundle/components/DetailsDlg.js +0 -12
  186. package/bundle/components/Header.d.ts +0 -6
  187. package/bundle/components/Header.js +0 -63
  188. package/bundle/components/ImportForm.d.ts +0 -6
  189. package/bundle/components/ImportForm.js +0 -89
  190. package/bundle/components/MSACanvas.d.ts +0 -6
  191. package/bundle/components/MSACanvas.js +0 -210
  192. package/bundle/components/MSAView.d.ts +0 -6
  193. package/bundle/components/MSAView.js +0 -88
  194. package/bundle/components/MoreInfoDlg.d.ts +0 -6
  195. package/bundle/components/MoreInfoDlg.js +0 -11
  196. package/bundle/components/ResizeHandles.d.ts +0 -8
  197. package/bundle/components/ResizeHandles.js +0 -110
  198. package/bundle/components/Rubberband.d.ts +0 -7
  199. package/bundle/components/Rubberband.js +0 -196
  200. package/bundle/components/Ruler.js +0 -121
  201. package/bundle/components/SettingsDlg.d.ts +0 -8
  202. package/bundle/components/SettingsDlg.js +0 -40
  203. package/bundle/components/TextTrack.d.ts +0 -7
  204. package/bundle/components/TextTrack.js +0 -72
  205. package/bundle/components/Track.d.ts +0 -11
  206. package/bundle/components/Track.js +0 -81
  207. package/bundle/components/TrackInfoDlg.d.ts +0 -6
  208. package/bundle/components/TracklistDlg.d.ts +0 -8
  209. package/bundle/components/TracklistDlg.js +0 -18
  210. package/bundle/components/TreeCanvas.d.ts +0 -6
  211. package/bundle/components/TreeCanvas.js +0 -431
  212. package/bundle/components/TreeRuler.d.ts +0 -6
  213. package/bundle/components/TreeRuler.js +0 -8
  214. package/bundle/components/data/seq2.d.ts +0 -3
  215. package/bundle/components/data/seq2.js +0 -3
  216. package/bundle/index.d.ts +0 -4
  217. package/bundle/layout.d.ts +0 -23
  218. package/bundle/layout.js +0 -53
  219. package/bundle/model.d.ts +0 -364
  220. package/bundle/model.js +0 -894
  221. package/bundle/parseNewick.js +0 -94
  222. package/bundle/parsers/ClustalMSA.d.ts +0 -39
  223. package/bundle/parsers/ClustalMSA.js +0 -77
  224. package/bundle/parsers/FastaMSA.d.ts +0 -26
  225. package/bundle/parsers/FastaMSA.js +0 -78
  226. package/bundle/parsers/StockholmMSA.d.ts +0 -75
  227. package/bundle/parsers/StockholmMSA.js +0 -142
  228. package/bundle/util.d.ts +0 -17
  229. package/bundle/util.js +0 -33
  230. package/dist/components/AboutDlg.d.ts +0 -5
  231. package/dist/components/AboutDlg.js +0 -50
  232. package/dist/components/AddTrackDlg.d.ts +0 -8
  233. package/dist/components/AddTrackDlg.js +0 -26
  234. package/dist/components/AnnotationDlg.js +0 -77
  235. package/dist/components/DetailsDlg.d.ts +0 -8
  236. package/dist/components/DetailsDlg.js +0 -12
  237. package/dist/components/MoreInfoDlg.d.ts +0 -6
  238. package/dist/components/MoreInfoDlg.js +0 -11
  239. package/dist/components/SettingsDlg.d.ts +0 -8
  240. package/dist/components/SettingsDlg.js +0 -40
  241. package/dist/components/TrackInfoDlg.d.ts +0 -6
  242. package/dist/components/TrackInfoDlg.js +0 -33
  243. package/dist/components/TracklistDlg.d.ts +0 -8
  244. package/dist/components/TracklistDlg.js +0 -18
  245. package/dist/components/package.json +0 -62
@@ -0,0 +1,270 @@
1
+ import React, { useRef, useEffect, useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { makeStyles } from 'tss-react/mui'
4
+ import { Popover, Typography, alpha } from '@mui/material'
5
+ import { Menu } from '@jbrowse/core/ui'
6
+
7
+ // icons
8
+ import AssignmentIcon from '@mui/icons-material/Assignment'
9
+
10
+ // locals
11
+ import { MsaViewModel } from '../model'
12
+ import VerticalGuide from './VerticalGuide'
13
+
14
+ const useStyles = makeStyles()(theme => {
15
+ const background =
16
+ 'tertiary' in theme.palette && theme.palette.tertiary
17
+ ? alpha(theme.palette.tertiary.main, 0.7)
18
+ : alpha(theme.palette.primary.main, 0.7)
19
+ return {
20
+ rubberband: {
21
+ height: '100%',
22
+ background,
23
+ position: 'absolute',
24
+ zIndex: 10,
25
+ textAlign: 'center',
26
+ overflow: 'hidden',
27
+ },
28
+ rubberbandControl: {
29
+ cursor: 'crosshair',
30
+ width: '100%',
31
+ minHeight: 8,
32
+ },
33
+ rubberbandText: {
34
+ color: theme.palette.tertiary
35
+ ? theme.palette.tertiary.contrastText
36
+ : theme.palette.primary.contrastText,
37
+ },
38
+ popover: {
39
+ mouseEvents: 'none',
40
+ cursor: 'crosshair',
41
+ },
42
+ paper: {
43
+ paddingLeft: theme.spacing(1),
44
+ paddingRight: theme.spacing(1),
45
+ },
46
+ }
47
+ })
48
+
49
+ function Rubberband({
50
+ model,
51
+ ControlComponent = <div />,
52
+ }: {
53
+ model: MsaViewModel
54
+ ControlComponent?: React.ReactElement
55
+ }) {
56
+ const { treeAreaWidth } = model
57
+ const [startX, setStartX] = useState<number>()
58
+ const [currentX, setCurrentX] = useState<number>()
59
+
60
+ // clientX and clientY used for anchorPosition for menu
61
+ // offsetX used for calculations about width of selection
62
+ const [anchorPosition, setAnchorPosition] = useState<{
63
+ offsetX: number
64
+ clientX: number
65
+ clientY: number
66
+ }>()
67
+ const [guideX, setGuideX] = useState<number | undefined>()
68
+ const controlsRef = useRef<HTMLDivElement>(null)
69
+ const rubberbandRef = useRef(null)
70
+ const { classes } = useStyles()
71
+ const mouseDragging = startX !== undefined && anchorPosition === undefined
72
+
73
+ useEffect(() => {
74
+ function globalMouseMove(event: MouseEvent) {
75
+ if (controlsRef.current && mouseDragging) {
76
+ const relativeX =
77
+ event.clientX - controlsRef.current.getBoundingClientRect().left
78
+ setCurrentX(relativeX)
79
+ }
80
+ }
81
+
82
+ function globalMouseUp(event: MouseEvent) {
83
+ if (startX !== undefined && controlsRef.current) {
84
+ const { clientX, clientY } = event
85
+ const ref = controlsRef.current
86
+ const offsetX = clientX - ref.getBoundingClientRect().left
87
+ // as stated above, store both clientX/Y and offsetX for different
88
+ // purposes
89
+ setAnchorPosition({
90
+ offsetX,
91
+ clientX,
92
+ clientY,
93
+ })
94
+ setGuideX(undefined)
95
+ }
96
+ }
97
+ if (mouseDragging) {
98
+ window.addEventListener('mousemove', globalMouseMove)
99
+ window.addEventListener('mouseup', globalMouseUp)
100
+ return () => {
101
+ window.removeEventListener('mousemove', globalMouseMove)
102
+ window.removeEventListener('mouseup', globalMouseUp)
103
+ }
104
+ }
105
+ return () => {}
106
+ }, [startX, mouseDragging, anchorPosition])
107
+
108
+ useEffect(() => {
109
+ if (
110
+ !mouseDragging &&
111
+ currentX !== undefined &&
112
+ startX !== undefined &&
113
+ Math.abs(currentX - startX) <= 3
114
+ ) {
115
+ handleClose()
116
+ }
117
+ }, [mouseDragging, currentX, startX, model.colWidth])
118
+
119
+ function mouseDown(event: React.MouseEvent<HTMLDivElement>) {
120
+ event.preventDefault()
121
+ event.stopPropagation()
122
+ const relativeX =
123
+ event.clientX -
124
+ (event.target as HTMLDivElement).getBoundingClientRect().left
125
+ setStartX(relativeX)
126
+ setCurrentX(relativeX)
127
+ }
128
+
129
+ function mouseMove(event: React.MouseEvent<HTMLDivElement>) {
130
+ const target = event.target as HTMLDivElement
131
+ setGuideX(event.clientX - target.getBoundingClientRect().left)
132
+ }
133
+
134
+ function mouseOut() {
135
+ setGuideX(undefined)
136
+ model.clearAnnotPos()
137
+ }
138
+
139
+ function handleClose() {
140
+ setAnchorPosition(undefined)
141
+ setStartX(undefined)
142
+ setCurrentX(undefined)
143
+ }
144
+
145
+ function handleMenuItemClick(_: unknown, callback: Function) {
146
+ callback()
147
+ handleClose()
148
+ }
149
+
150
+ if (startX === undefined) {
151
+ return (
152
+ <>
153
+ {guideX !== undefined ? (
154
+ <VerticalGuide model={model} coordX={guideX} />
155
+ ) : null}
156
+ <div
157
+ data-testid="rubberband_controls"
158
+ className={classes.rubberbandControl}
159
+ role="presentation"
160
+ ref={controlsRef}
161
+ onMouseDown={mouseDown}
162
+ onMouseOut={mouseOut}
163
+ onMouseMove={mouseMove}
164
+ >
165
+ {ControlComponent}
166
+ </div>
167
+ </>
168
+ )
169
+ }
170
+
171
+ const right = anchorPosition ? anchorPosition.offsetX : currentX || 0
172
+ const left = right < startX ? right : startX
173
+ const width = Math.abs(right - startX)
174
+ const leftBpOffset = model.pxToBp(left)
175
+ const rightBpOffset = model.pxToBp(left + width)
176
+ const numOfBpSelected = Math.ceil(width / model.colWidth)
177
+
178
+ const menuItems = [
179
+ {
180
+ label: 'Create annotation',
181
+ icon: AssignmentIcon,
182
+ onClick: () => {
183
+ model.setOffsets(leftBpOffset, rightBpOffset)
184
+ handleClose()
185
+ },
186
+ },
187
+ ]
188
+ return (
189
+ <>
190
+ {rubberbandRef.current ? (
191
+ <>
192
+ <Popover
193
+ className={classes.popover}
194
+ classes={{
195
+ paper: classes.paper,
196
+ }}
197
+ open
198
+ anchorEl={rubberbandRef.current}
199
+ anchorOrigin={{
200
+ vertical: 'top',
201
+ horizontal: 'left',
202
+ }}
203
+ transformOrigin={{
204
+ vertical: 'bottom',
205
+ horizontal: 'right',
206
+ }}
207
+ keepMounted
208
+ disableRestoreFocus
209
+ >
210
+ <Typography>{leftBpOffset + 1}</Typography>
211
+ </Popover>
212
+ <Popover
213
+ className={classes.popover}
214
+ classes={{
215
+ paper: classes.paper,
216
+ }}
217
+ open
218
+ anchorEl={rubberbandRef.current}
219
+ anchorOrigin={{
220
+ vertical: 'top',
221
+ horizontal: 'right',
222
+ }}
223
+ transformOrigin={{
224
+ vertical: 'bottom',
225
+ horizontal: 'left',
226
+ }}
227
+ keepMounted
228
+ disableRestoreFocus
229
+ >
230
+ <Typography>{rightBpOffset + 1}</Typography>
231
+ </Popover>
232
+ </>
233
+ ) : null}
234
+ <div
235
+ ref={rubberbandRef}
236
+ className={classes.rubberband}
237
+ style={{ left: left + treeAreaWidth, width }}
238
+ >
239
+ <Typography variant="h6" className={classes.rubberbandText}>
240
+ {numOfBpSelected.toLocaleString('en-US')} bp
241
+ </Typography>
242
+ </div>
243
+ <div
244
+ data-testid="rubberband_controls"
245
+ className={classes.rubberbandControl}
246
+ role="presentation"
247
+ ref={controlsRef}
248
+ onMouseDown={mouseDown}
249
+ onMouseOut={mouseOut}
250
+ onMouseMove={mouseMove}
251
+ >
252
+ {ControlComponent}
253
+ </div>
254
+ {anchorPosition ? (
255
+ <Menu
256
+ anchorReference="anchorPosition"
257
+ anchorPosition={{
258
+ left: anchorPosition.clientX,
259
+ top: anchorPosition.clientY,
260
+ }}
261
+ onMenuItemClick={handleMenuItemClick}
262
+ open={Boolean(anchorPosition)}
263
+ onClose={handleClose}
264
+ menuItems={menuItems}
265
+ />
266
+ ) : null}
267
+ </>
268
+ )
269
+ }
270
+ export default observer(Rubberband)
@@ -0,0 +1,123 @@
1
+ import React, { useRef } from 'react'
2
+ import { makeStyles } from 'tss-react/mui'
3
+ import { observer } from 'mobx-react'
4
+ // locals
5
+ import { MsaViewModel } from '../model'
6
+ import { makeTicks, mathPower } from './util'
7
+
8
+ const useStyles = makeStyles()({
9
+ majorTickLabel: {
10
+ fontSize: '11px',
11
+ },
12
+ majorTick: {
13
+ stroke: '#555',
14
+ },
15
+ minorTick: {
16
+ stroke: '#999',
17
+ },
18
+ })
19
+
20
+ function RulerBlock({
21
+ start,
22
+ end,
23
+ bpPerPx,
24
+ reversed,
25
+ major,
26
+ minor,
27
+ }: {
28
+ start: number
29
+ end: number
30
+ bpPerPx: number
31
+ reversed?: boolean
32
+ major?: boolean
33
+ minor?: boolean
34
+ }) {
35
+ const { classes } = useStyles()
36
+ const ticks = makeTicks(start, end, bpPerPx, major, minor)
37
+ return (
38
+ <>
39
+ {ticks.map(tick => {
40
+ const x = (reversed ? end - tick.base : tick.base - start) / bpPerPx
41
+ return (
42
+ <line
43
+ key={tick.base}
44
+ x1={x}
45
+ x2={x}
46
+ y1={11}
47
+ y2={tick.type === 'major' ? 11 + 6 : 11 + 4}
48
+ strokeWidth={1}
49
+ stroke={tick.type === 'major' ? '#555' : '#999'}
50
+ className={
51
+ tick.type === 'major' ? classes.majorTick : classes.minorTick
52
+ }
53
+ data-bp={tick.base}
54
+ />
55
+ )
56
+ })}
57
+ {ticks
58
+ .filter(tick => tick.type === 'major')
59
+ .map(tick => {
60
+ const x = (reversed ? end - tick.base : tick.base - start) / bpPerPx
61
+ return (
62
+ <text
63
+ x={x}
64
+ y={10}
65
+ key={`label-${tick.base}`}
66
+ textAnchor="middle"
67
+ style={{ fontSize: '11px' }}
68
+ className={classes.majorTickLabel}
69
+ >
70
+ {mathPower(tick.base + 1)}
71
+ </text>
72
+ )
73
+ })}
74
+ </>
75
+ )
76
+ }
77
+
78
+ const Ruler = observer(function ({ model }: { model: MsaViewModel }) {
79
+ const {
80
+ MSA,
81
+ colWidth,
82
+ msaAreaWidth,
83
+ rulerHeight,
84
+ resizeHandleWidth,
85
+ scrollX,
86
+ blocksX,
87
+ blockSize,
88
+ } = model
89
+ const ref = useRef<HTMLDivElement>(null)
90
+ const offsetX = blocksX[0]
91
+
92
+ return !MSA ? null : (
93
+ <div
94
+ ref={ref}
95
+ style={{
96
+ position: 'relative',
97
+ width: msaAreaWidth,
98
+ cursor: 'crosshair',
99
+ overflow: 'hidden',
100
+ height: rulerHeight,
101
+ background: '#ccc',
102
+ }}
103
+ >
104
+ <svg
105
+ style={{
106
+ width: blocksX.length * blockSize,
107
+ position: 'absolute',
108
+ left: scrollX + offsetX + resizeHandleWidth,
109
+ pointerEvents: 'none',
110
+ }}
111
+ >
112
+ <RulerBlock
113
+ key={offsetX}
114
+ start={offsetX / colWidth}
115
+ end={offsetX / colWidth + (blockSize * blocksX.length) / colWidth}
116
+ bpPerPx={1 / colWidth}
117
+ />
118
+ </svg>
119
+ </div>
120
+ )
121
+ })
122
+
123
+ export default Ruler
@@ -0,0 +1,120 @@
1
+ import React, { useRef, useMemo, useEffect } from 'react'
2
+ import { useTheme } from '@mui/material'
3
+ import { observer } from 'mobx-react'
4
+ // locals
5
+ import { MsaViewModel, ITextTrack } from '../model'
6
+ import { colorContrast } from '../util'
7
+
8
+ const AnnotationBlock = observer(function ({
9
+ track,
10
+ model,
11
+ offsetX,
12
+ }: {
13
+ track: ITextTrack
14
+ model: MsaViewModel
15
+ offsetX: number
16
+ }) {
17
+ const {
18
+ blockSize,
19
+ scrollX,
20
+ bgColor,
21
+ colorScheme: modelColorScheme,
22
+ colWidth,
23
+ rowHeight,
24
+ highResScaleFactor,
25
+ } = model
26
+ const {
27
+ model: { customColorScheme, data },
28
+ } = track
29
+
30
+ const colorScheme = customColorScheme || modelColorScheme
31
+ const theme = useTheme()
32
+ const ref = useRef<HTMLCanvasElement>(null)
33
+ const contrastScheme = useMemo(
34
+ () => colorContrast(colorScheme, theme),
35
+ [colorScheme, theme],
36
+ )
37
+ useEffect(() => {
38
+ if (!ref.current) {
39
+ return
40
+ }
41
+
42
+ const ctx = ref.current.getContext('2d')
43
+ if (!ctx) {
44
+ return
45
+ }
46
+
47
+ // this logic is very similar to MSACanvas
48
+ ctx.resetTransform()
49
+ ctx.scale(highResScaleFactor, highResScaleFactor)
50
+ ctx.clearRect(0, 0, blockSize, rowHeight)
51
+ ctx.translate(-offsetX, 0)
52
+ ctx.textAlign = 'center'
53
+ ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
54
+
55
+ const xStart = Math.max(0, Math.floor(offsetX / colWidth))
56
+ const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
57
+ const str = data?.slice(xStart, xEnd)
58
+ for (let i = 0; str && i < str.length; i++) {
59
+ const letter = str[i]
60
+ const color = colorScheme[letter.toUpperCase()]
61
+ if (bgColor) {
62
+ const x = i * colWidth + offsetX - (offsetX % colWidth)
63
+ ctx.fillStyle = color || 'white'
64
+ ctx.fillRect(x, 0, colWidth, rowHeight)
65
+ if (rowHeight >= 10 && colWidth >= rowHeight / 2) {
66
+ ctx.fillStyle = contrastScheme[letter.toUpperCase()] || 'black'
67
+ ctx.fillText(letter, x + colWidth / 2, rowHeight / 2 + 1) // +1 to avoid cutoff at height:10
68
+ }
69
+ }
70
+ }
71
+ }, [
72
+ bgColor,
73
+ blockSize,
74
+ colWidth,
75
+ rowHeight,
76
+ offsetX,
77
+ contrastScheme,
78
+ colorScheme,
79
+ highResScaleFactor,
80
+ data,
81
+ ])
82
+ return (
83
+ <canvas
84
+ ref={ref}
85
+ height={rowHeight * highResScaleFactor}
86
+ width={blockSize * highResScaleFactor}
87
+ style={{
88
+ position: 'absolute',
89
+ left: scrollX + offsetX,
90
+ width: blockSize,
91
+ height: rowHeight,
92
+ }}
93
+ />
94
+ )
95
+ })
96
+ const AnnotationTrack = observer(function ({
97
+ track,
98
+ model,
99
+ }: {
100
+ track: ITextTrack
101
+ model: MsaViewModel
102
+ }) {
103
+ const { blocksX, msaAreaWidth, rowHeight } = model
104
+ return (
105
+ <div
106
+ style={{
107
+ position: 'relative',
108
+ height: rowHeight,
109
+ width: msaAreaWidth,
110
+ overflow: 'hidden',
111
+ }}
112
+ >
113
+ {blocksX.map(bx => (
114
+ <AnnotationBlock key={bx} track={track} model={model} offsetX={bx} />
115
+ ))}
116
+ </div>
117
+ )
118
+ })
119
+
120
+ export default AnnotationTrack
@@ -0,0 +1,153 @@
1
+ import React, { useState, useRef, useEffect, lazy, Suspense } from 'react'
2
+ import normalizeWheel from 'normalize-wheel'
3
+ import { observer } from 'mobx-react'
4
+ import { IconButton, Menu, MenuItem } from '@mui/material'
5
+ import { makeStyles } from 'tss-react/mui'
6
+
7
+ // icons
8
+ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
9
+
10
+ // locals
11
+ import { MsaViewModel } from '../model'
12
+
13
+ const TrackInfoDialog = lazy(() => import('./dialogs/TrackInfoDlg'))
14
+
15
+ const useStyles = makeStyles()({
16
+ button: {
17
+ padding: 0,
18
+ },
19
+ })
20
+
21
+ export const TrackLabel = observer(function ({
22
+ model,
23
+ track,
24
+ }: {
25
+ model: MsaViewModel
26
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
27
+ track: any
28
+ }) {
29
+ const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>()
30
+ const [trackInfoDlgOpen, setTrackInfoDlgOpen] = useState(false)
31
+ const { rowHeight, treeAreaWidth: width } = model
32
+ const {
33
+ height,
34
+ model: { name },
35
+ } = track
36
+ const { classes } = useStyles()
37
+ const trackLabelHeight = Math.max(8, rowHeight - 8)
38
+
39
+ return (
40
+ <div
41
+ style={{
42
+ width,
43
+ height,
44
+ flexShrink: 0,
45
+ textAlign: 'right',
46
+ fontSize: trackLabelHeight,
47
+ }}
48
+ >
49
+ {name}{' '}
50
+ <IconButton
51
+ className={classes.button}
52
+ style={{
53
+ width: trackLabelHeight,
54
+ height: trackLabelHeight,
55
+ }}
56
+ onClick={event => setAnchorEl(event.currentTarget)}
57
+ >
58
+ <ArrowDropDownIcon />
59
+ </IconButton>
60
+ {anchorEl ? (
61
+ <Menu
62
+ anchorEl={anchorEl}
63
+ transitionDuration={0}
64
+ open
65
+ onClose={() => setAnchorEl(undefined)}
66
+ >
67
+ <MenuItem
68
+ dense
69
+ onClick={() => {
70
+ model.toggleTrack(track.model.id)
71
+ setAnchorEl(undefined)
72
+ }}
73
+ >
74
+ Close
75
+ </MenuItem>
76
+ <MenuItem
77
+ dense
78
+ onClick={() => {
79
+ setTrackInfoDlgOpen(true)
80
+ setAnchorEl(undefined)
81
+ }}
82
+ >
83
+ Get info
84
+ </MenuItem>
85
+ </Menu>
86
+ ) : null}
87
+ {trackInfoDlgOpen ? (
88
+ <Suspense fallback={null}>
89
+ <TrackInfoDialog
90
+ model={track.model}
91
+ onClose={() => setTrackInfoDlgOpen(false)}
92
+ />
93
+ </Suspense>
94
+ ) : null}
95
+ </div>
96
+ )
97
+ })
98
+
99
+ const Track = observer(function ({
100
+ model,
101
+ track,
102
+ }: {
103
+ model: MsaViewModel
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ track: any
106
+ }) {
107
+ const { resizeHandleWidth } = model
108
+ const {
109
+ model: { height, error },
110
+ } = track
111
+ const ref = useRef<HTMLDivElement>(null)
112
+ const scheduled = useRef(false)
113
+ const deltaX = useRef(0)
114
+ useEffect(() => {
115
+ const curr = ref.current
116
+ if (!curr) {
117
+ return
118
+ }
119
+ function onWheel(origEvent: WheelEvent) {
120
+ const event = normalizeWheel(origEvent)
121
+ deltaX.current += event.pixelX
122
+
123
+ if (!scheduled.current) {
124
+ scheduled.current = true
125
+ requestAnimationFrame(() => {
126
+ model.doScrollX(-deltaX.current)
127
+ deltaX.current = 0
128
+ scheduled.current = false
129
+ })
130
+ }
131
+ origEvent.preventDefault()
132
+ }
133
+ curr.addEventListener('wheel', onWheel)
134
+ return () => {
135
+ curr.removeEventListener('wheel', onWheel)
136
+ }
137
+ }, [model])
138
+ return (
139
+ <div key={track.id} style={{ display: 'flex', height }}>
140
+ <TrackLabel model={model} track={track} />
141
+ <div style={{ width: resizeHandleWidth, flexShrink: 0 }} />
142
+ <div ref={ref}>
143
+ {error ? (
144
+ <div style={{ color: 'red', fontSize: 10 }}>{`${error}`}</div>
145
+ ) : (
146
+ <track.ReactComponent model={model} track={track} />
147
+ )}
148
+ </div>
149
+ </div>
150
+ )
151
+ })
152
+
153
+ export default Track