react-msaview 2.1.5 → 3.0.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 (176) hide show
  1. package/bundle/index.js +37 -255
  2. package/dist/DialogQueue.d.ts +25 -0
  3. package/dist/DialogQueue.js +46 -0
  4. package/dist/DialogQueue.js.map +1 -0
  5. package/dist/UniprotTrack.js.map +1 -1
  6. package/dist/colorSchemes.js.map +1 -1
  7. package/dist/components/BoxTrackBlock.js +3 -2
  8. package/dist/components/BoxTrackBlock.js.map +1 -1
  9. package/dist/components/Header.js +10 -13
  10. package/dist/components/Header.js.map +1 -1
  11. package/dist/components/ImportForm/ImportFormExamples.d.ts +6 -0
  12. package/dist/components/ImportForm/ImportFormExamples.js +50 -0
  13. package/dist/components/ImportForm/ImportFormExamples.js.map +1 -0
  14. package/dist/components/ImportForm/data/seq2.js.map +1 -0
  15. package/dist/components/{ImportForm.d.ts → ImportForm/index.d.ts} +1 -1
  16. package/dist/components/ImportForm/index.js +31 -0
  17. package/dist/components/ImportForm/index.js.map +1 -0
  18. package/dist/components/ImportForm/util.d.ts +3 -0
  19. package/dist/components/ImportForm/util.js +15 -0
  20. package/dist/components/ImportForm/util.js.map +1 -0
  21. package/dist/components/{MSABlock.d.ts → MSAPanel/MSABlock.d.ts} +1 -1
  22. package/dist/components/MSAPanel/MSABlock.js +46 -0
  23. package/dist/components/MSAPanel/MSABlock.js.map +1 -0
  24. package/dist/components/{MSACanvas.d.ts → MSAPanel/MSACanvas.d.ts} +1 -1
  25. package/dist/components/{MSACanvas.js → MSAPanel/MSACanvas.js} +10 -3
  26. package/dist/components/MSAPanel/MSACanvas.js.map +1 -0
  27. package/dist/components/{MSAMouseoverCanvas.d.ts → MSAPanel/MSAMouseoverCanvas.d.ts} +1 -1
  28. package/dist/components/MSAPanel/MSAMouseoverCanvas.js +50 -0
  29. package/dist/components/MSAPanel/MSAMouseoverCanvas.js.map +1 -0
  30. package/dist/components/MSAPanel/index.d.ts +5 -0
  31. package/dist/components/MSAPanel/index.js +9 -0
  32. package/dist/components/MSAPanel/index.js.map +1 -0
  33. package/dist/components/MSAPanel/renderMSABlock.d.ts +8 -0
  34. package/dist/components/MSAPanel/renderMSABlock.js +81 -0
  35. package/dist/components/MSAPanel/renderMSABlock.js.map +1 -0
  36. package/dist/components/MSAView.d.ts +2 -2
  37. package/dist/components/MSAView.js +26 -31
  38. package/dist/components/MSAView.js.map +1 -1
  39. package/dist/components/Minimap.d.ts +6 -0
  40. package/dist/components/Minimap.js +72 -0
  41. package/dist/components/Minimap.js.map +1 -0
  42. package/dist/components/OverviewRubberband.d.ts +8 -0
  43. package/dist/components/OverviewRubberband.js +185 -0
  44. package/dist/components/OverviewRubberband.js.map +1 -0
  45. package/dist/components/ResizeHandles.js.map +1 -1
  46. package/dist/components/Rubberband.js +13 -1
  47. package/dist/components/Rubberband.js.map +1 -1
  48. package/dist/components/TextTrack.js +3 -2
  49. package/dist/components/TextTrack.js.map +1 -1
  50. package/dist/components/Track.js +5 -5
  51. package/dist/components/Track.js.map +1 -1
  52. package/dist/components/{TreeBranchMenu.d.ts → TreePanel/TreeBranchMenu.d.ts} +1 -1
  53. package/dist/components/TreePanel/TreeBranchMenu.js.map +1 -0
  54. package/dist/components/{TreeCanvas.d.ts → TreePanel/TreeCanvas.d.ts} +1 -1
  55. package/dist/components/{TreeCanvas.js → TreePanel/TreeCanvas.js} +1 -1
  56. package/dist/components/TreePanel/TreeCanvas.js.map +1 -0
  57. package/dist/components/{TreeCanvasBlock.d.ts → TreePanel/TreeCanvasBlock.d.ts} +1 -1
  58. package/dist/components/TreePanel/TreeCanvasBlock.js +117 -0
  59. package/dist/components/TreePanel/TreeCanvasBlock.js.map +1 -0
  60. package/dist/components/{TreeMenu.d.ts → TreePanel/TreeMenu.d.ts} +1 -1
  61. package/dist/components/{TreeMenu.js → TreePanel/TreeMenu.js} +9 -5
  62. package/dist/components/TreePanel/TreeMenu.js.map +1 -0
  63. package/dist/components/{TreeRuler.d.ts → TreePanel/TreeRuler.d.ts} +1 -1
  64. package/dist/components/TreePanel/TreeRuler.js +8 -0
  65. package/dist/components/TreePanel/TreeRuler.js.map +1 -0
  66. package/dist/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.d.ts +1 -1
  67. package/dist/components/TreePanel/dialogs/TreeNodeInfoDlg.js.map +1 -0
  68. package/dist/components/TreePanel/index.d.ts +6 -0
  69. package/dist/components/TreePanel/index.js +10 -0
  70. package/dist/components/TreePanel/index.js.map +1 -0
  71. package/dist/components/TreePanel/renderTreeCanvas.d.ts +36 -0
  72. package/dist/components/TreePanel/renderTreeCanvas.js +153 -0
  73. package/dist/components/TreePanel/renderTreeCanvas.js.map +1 -0
  74. package/dist/components/dialogs/{AboutDlg.js → AboutDialog.js} +1 -1
  75. package/dist/components/dialogs/AboutDialog.js.map +1 -0
  76. package/dist/components/dialogs/{AddTrackDlg.js → AddTrackDialog.js} +1 -1
  77. package/dist/components/dialogs/AddTrackDialog.js.map +1 -0
  78. package/dist/components/dialogs/{AnnotationDlg.js → AnnotationDialog.js} +1 -1
  79. package/dist/components/dialogs/AnnotationDialog.js.map +1 -0
  80. package/dist/components/dialogs/{MetadataDlg.js → MetadataDialog.js} +1 -1
  81. package/dist/components/dialogs/MetadataDialog.js.map +1 -0
  82. package/dist/components/dialogs/SettingsDialog.js +54 -0
  83. package/dist/components/dialogs/SettingsDialog.js.map +1 -0
  84. package/dist/components/dialogs/{TrackInfoDlg.js → TrackInfoDialog.js} +1 -1
  85. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -0
  86. package/dist/components/dialogs/{TracklistDlg.js → TracklistDialog.js} +1 -1
  87. package/dist/components/dialogs/TracklistDialog.js.map +1 -0
  88. package/dist/components/util.js.map +1 -1
  89. package/dist/layout.js.map +1 -1
  90. package/dist/model.d.ts +80 -47
  91. package/dist/model.js +130 -70
  92. package/dist/model.js.map +1 -1
  93. package/dist/parseNewick.js.map +1 -1
  94. package/dist/parsers/StockholmMSA.js.map +1 -1
  95. package/dist/util.js.map +1 -1
  96. package/dist/version.d.ts +1 -1
  97. package/dist/version.js +1 -1
  98. package/package.json +6 -4
  99. package/src/DialogQueue.ts +47 -0
  100. package/src/components/BoxTrackBlock.tsx +3 -1
  101. package/src/components/Header.tsx +9 -10
  102. package/src/components/ImportForm/ImportFormExamples.tsx +133 -0
  103. package/src/components/ImportForm/index.tsx +63 -0
  104. package/src/components/ImportForm/util.ts +20 -0
  105. package/src/components/MSAPanel/MSABlock.tsx +81 -0
  106. package/src/components/{MSACanvas.tsx → MSAPanel/MSACanvas.tsx} +17 -5
  107. package/src/components/MSAPanel/MSAMouseoverCanvas.tsx +92 -0
  108. package/src/components/MSAPanel/index.tsx +13 -0
  109. package/src/components/MSAPanel/renderMSABlock.ts +160 -0
  110. package/src/components/MSAView.tsx +36 -56
  111. package/src/components/Minimap.tsx +102 -0
  112. package/src/components/OverviewRubberband.tsx +283 -0
  113. package/src/components/Rubberband.tsx +14 -1
  114. package/src/components/TextTrack.tsx +3 -1
  115. package/src/components/Track.tsx +5 -5
  116. package/src/components/{TreeBranchMenu.tsx → TreePanel/TreeBranchMenu.tsx} +1 -1
  117. package/src/components/{TreeCanvas.tsx → TreePanel/TreeCanvas.tsx} +2 -3
  118. package/src/components/TreePanel/TreeCanvasBlock.tsx +190 -0
  119. package/src/components/{TreeMenu.tsx → TreePanel/TreeMenu.tsx} +10 -6
  120. package/src/components/{TreeRuler.tsx → TreePanel/TreeRuler.tsx} +3 -3
  121. package/src/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.tsx +1 -1
  122. package/src/components/TreePanel/index.tsx +16 -0
  123. package/src/components/TreePanel/renderTreeCanvas.ts +245 -0
  124. package/src/components/dialogs/{SettingsDlg.tsx → SettingsDialog.tsx} +72 -47
  125. package/src/model.ts +157 -79
  126. package/src/version.ts +1 -1
  127. package/dist/components/ImportForm.js +0 -84
  128. package/dist/components/ImportForm.js.map +0 -1
  129. package/dist/components/MSABlock.js +0 -103
  130. package/dist/components/MSABlock.js.map +0 -1
  131. package/dist/components/MSACanvas.js.map +0 -1
  132. package/dist/components/MSAMouseoverCanvas.js +0 -61
  133. package/dist/components/MSAMouseoverCanvas.js.map +0 -1
  134. package/dist/components/Ruler.d.ts +0 -6
  135. package/dist/components/Ruler.js +0 -52
  136. package/dist/components/Ruler.js.map +0 -1
  137. package/dist/components/TreeBranchMenu.js.map +0 -1
  138. package/dist/components/TreeCanvas.js.map +0 -1
  139. package/dist/components/TreeCanvasBlock.js +0 -255
  140. package/dist/components/TreeCanvasBlock.js.map +0 -1
  141. package/dist/components/TreeMenu.js.map +0 -1
  142. package/dist/components/TreeRuler.js +0 -8
  143. package/dist/components/TreeRuler.js.map +0 -1
  144. package/dist/components/data/seq2.js.map +0 -1
  145. package/dist/components/dialogs/AboutDlg.js.map +0 -1
  146. package/dist/components/dialogs/AddTrackDlg.js.map +0 -1
  147. package/dist/components/dialogs/AnnotationDlg.js.map +0 -1
  148. package/dist/components/dialogs/MetadataDlg.js.map +0 -1
  149. package/dist/components/dialogs/SettingsDlg.js +0 -48
  150. package/dist/components/dialogs/SettingsDlg.js.map +0 -1
  151. package/dist/components/dialogs/TrackInfoDlg.js.map +0 -1
  152. package/dist/components/dialogs/TracklistDlg.js.map +0 -1
  153. package/dist/components/dialogs/TreeNodeInfoDlg.js.map +0 -1
  154. package/src/components/ImportForm.tsx +0 -192
  155. package/src/components/MSABlock.tsx +0 -164
  156. package/src/components/MSAMouseoverCanvas.tsx +0 -99
  157. package/src/components/Ruler.tsx +0 -123
  158. package/src/components/TreeCanvasBlock.tsx +0 -363
  159. /package/dist/components/{data → ImportForm/data}/seq2.d.ts +0 -0
  160. /package/dist/components/{data → ImportForm/data}/seq2.js +0 -0
  161. /package/dist/components/{TreeBranchMenu.js → TreePanel/TreeBranchMenu.js} +0 -0
  162. /package/dist/components/{dialogs → TreePanel/dialogs}/TreeNodeInfoDlg.js +0 -0
  163. /package/dist/components/dialogs/{AboutDlg.d.ts → AboutDialog.d.ts} +0 -0
  164. /package/dist/components/dialogs/{AddTrackDlg.d.ts → AddTrackDialog.d.ts} +0 -0
  165. /package/dist/components/dialogs/{AnnotationDlg.d.ts → AnnotationDialog.d.ts} +0 -0
  166. /package/dist/components/dialogs/{MetadataDlg.d.ts → MetadataDialog.d.ts} +0 -0
  167. /package/dist/components/dialogs/{SettingsDlg.d.ts → SettingsDialog.d.ts} +0 -0
  168. /package/dist/components/dialogs/{TrackInfoDlg.d.ts → TrackInfoDialog.d.ts} +0 -0
  169. /package/dist/components/dialogs/{TracklistDlg.d.ts → TracklistDialog.d.ts} +0 -0
  170. /package/src/components/{data → ImportForm/data}/seq2.ts +0 -0
  171. /package/src/components/dialogs/{AboutDlg.tsx → AboutDialog.tsx} +0 -0
  172. /package/src/components/dialogs/{AddTrackDlg.tsx → AddTrackDialog.tsx} +0 -0
  173. /package/src/components/dialogs/{AnnotationDlg.tsx → AnnotationDialog.tsx} +0 -0
  174. /package/src/components/dialogs/{MetadataDlg.tsx → MetadataDialog.tsx} +0 -0
  175. /package/src/components/dialogs/{TrackInfoDlg.tsx → TrackInfoDialog.tsx} +0 -0
  176. /package/src/components/dialogs/{TracklistDlg.tsx → TracklistDialog.tsx} +0 -0
@@ -1,26 +1,21 @@
1
- import React, { lazy, Suspense } from 'react'
1
+ import React, { Suspense } from 'react'
2
2
 
3
3
  import { observer } from 'mobx-react'
4
4
  import { Typography } from '@mui/material'
5
5
 
6
6
  // locals
7
7
  import ImportForm from './ImportForm'
8
- import Rubberband from './Rubberband'
9
- import TreeCanvas from './TreeCanvas'
10
- import MSACanvas from './MSACanvas'
11
- import Ruler from './Ruler'
12
- import TreeRuler from './TreeRuler'
8
+ import TreeRuler from './TreePanel/TreeRuler'
13
9
  import Header from './Header'
14
10
  import Track from './Track'
15
-
16
11
  import { HorizontalResizeHandle, VerticalResizeHandle } from './ResizeHandles'
17
12
  import { MsaViewModel } from '../model'
18
- import MSAMouseoverCanvas from './MSAMouseoverCanvas'
19
-
20
- const AnnotationDialog = lazy(() => import('./dialogs/AnnotationDlg'))
13
+ import MSAPanel from './MSAPanel'
14
+ import TreePanel from './TreePanel'
15
+ import Minimap from './Minimap'
21
16
 
22
- export default observer(function ({ model }: { model: MsaViewModel }) {
23
- const { done, initialized, treeAreaWidth, height, turnedOnTracks } = model
17
+ const MSAView = observer(function ({ model }: { model: MsaViewModel }) {
18
+ const { done, initialized } = model
24
19
 
25
20
  return (
26
21
  <div>
@@ -29,58 +24,43 @@ export default observer(function ({ model }: { model: MsaViewModel }) {
29
24
  ) : !done ? (
30
25
  <Typography variant="h4">Loading...</Typography>
31
26
  ) : (
32
- <div>
33
- <div style={{ height, overflow: 'hidden' }}>
34
- <Header model={model} />
35
- <div>
36
- <div style={{ position: 'relative' }}>
37
- <div style={{ display: 'flex' }}>
38
- <div style={{ flexShrink: 0, width: treeAreaWidth }}>
39
- <TreeRuler model={model} />
40
- </div>
27
+ <MSAView2 model={model} />
28
+ )}
29
+ </div>
30
+ )
31
+ })
41
32
 
42
- <Rubberband
43
- model={model}
44
- ControlComponent={<Ruler model={model} />}
45
- />
46
- </div>
47
- {turnedOnTracks?.map(track => (
48
- <Track key={track.model.id} model={model} track={track} />
49
- ))}
33
+ const MSAView2 = observer(function ({ model }: { model: MsaViewModel }) {
34
+ const { height, turnedOnTracks, DialogComponent, DialogProps } = model
35
+ return (
36
+ <div>
37
+ <div style={{ height, overflow: 'hidden' }}>
38
+ <Header model={model} />
39
+ <div style={{ position: 'relative' }}>
40
+ <div style={{ display: 'flex' }}>
41
+ <TreeRuler model={model} />
42
+ <Minimap model={model} />
43
+ </div>
44
+ {turnedOnTracks?.map(track => (
45
+ <Track key={track.model.id} model={model} track={track} />
46
+ ))}
50
47
 
51
- <div style={{ display: 'flex' }}>
52
- <div style={{ flexShrink: 0, width: treeAreaWidth }}>
53
- <TreeCanvas model={model} />
54
- </div>
55
- <VerticalResizeHandle model={model} />
56
- <MSACanvas model={model} />
57
- <MSAMouseoverCanvas model={model} />
58
- </div>
59
- </div>
60
- </div>
48
+ <div style={{ display: 'flex' }}>
49
+ <TreePanel model={model} />
50
+ <VerticalResizeHandle model={model} />
51
+ <MSAPanel model={model} />
61
52
  </div>
62
- <HorizontalResizeHandle model={model} />
63
53
  </div>
64
- )}
54
+ </div>
55
+ <HorizontalResizeHandle model={model} />
65
56
 
66
- {model.DialogComponent ? (
57
+ {DialogComponent ? (
67
58
  <Suspense fallback={null}>
68
- <model.DialogComponent
69
- {...(model.DialogProps || {})}
70
- onClose={() => model.setDialogComponent()}
71
- />
72
- </Suspense>
73
- ) : null}
74
-
75
- {model.annotPos ? (
76
- <Suspense fallback={null}>
77
- <AnnotationDialog
78
- data={model.annotPos}
79
- model={model}
80
- onClose={() => model.clearAnnotationClickBoundaries()}
81
- />
59
+ <DialogComponent {...DialogProps} />
82
60
  </Suspense>
83
61
  ) : null}
84
62
  </div>
85
63
  )
86
64
  })
65
+
66
+ export default MSAView
@@ -0,0 +1,102 @@
1
+ import React, { useEffect, useRef, useState } from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { MsaViewModel } from '../model'
4
+
5
+ const Minimap = observer(function ({ model }: { model: MsaViewModel }) {
6
+ const [mouseDown, setMouseDown] = useState<{
7
+ clientX: number
8
+ scrollX: number
9
+ }>()
10
+ const scheduled = useRef(false)
11
+ const [hovered, setHovered] = useState(false)
12
+ const {
13
+ scrollX,
14
+ msaAreaWidth: W,
15
+ minimapHeight: H,
16
+ colWidth,
17
+ numColumns,
18
+ } = model
19
+ const unit = W / numColumns / colWidth
20
+ const left = -scrollX
21
+ const right = left + W
22
+ const s = left * unit
23
+ const e = right * unit
24
+ const fill = 'rgba(66, 119, 127, 0.3)'
25
+
26
+ useEffect(() => {
27
+ if (mouseDown !== undefined) {
28
+ function fn(event: MouseEvent) {
29
+ if (mouseDown !== undefined) {
30
+ if (!scheduled.current) {
31
+ scheduled.current = true
32
+ window.requestAnimationFrame(() => {
33
+ model.setScrollX(
34
+ mouseDown.scrollX - (event.clientX - mouseDown.clientX) / unit,
35
+ )
36
+ scheduled.current = false
37
+ })
38
+ }
39
+ }
40
+ }
41
+ function fn2() {
42
+ setMouseDown(undefined)
43
+ }
44
+ document.addEventListener('mousemove', fn)
45
+ document.addEventListener('mouseup', fn2)
46
+ return () => {
47
+ document.removeEventListener('mousemove', fn)
48
+ document.removeEventListener('mousemove', fn2)
49
+ }
50
+ }
51
+ }, [model, unit, mouseDown])
52
+
53
+ const BAR_HEIGHT = 12
54
+ const H2 = H - 12
55
+ return (
56
+ <div style={{ position: 'relative', height: H, width: '100%' }}>
57
+ <div
58
+ style={{
59
+ boxSizing: 'border-box',
60
+ height: BAR_HEIGHT,
61
+ border: '1px solid #555',
62
+ }}
63
+ />
64
+ <div
65
+ style={{
66
+ position: 'absolute',
67
+ top: 0,
68
+ left: Math.max(0, s),
69
+ background: hovered ? 'rgba(66,119,127,0.6)' : fill,
70
+ cursor: 'pointer',
71
+ border: '1px solid #555',
72
+ boxSizing: 'border-box',
73
+ height: BAR_HEIGHT,
74
+ width: e - s,
75
+ zIndex: 100,
76
+ }}
77
+ onMouseOver={() => setHovered(true)}
78
+ onMouseOut={() => setHovered(false)}
79
+ onMouseDown={event => {
80
+ setMouseDown({
81
+ clientX: event.clientX,
82
+ scrollX: model.scrollX,
83
+ })
84
+ }}
85
+ />
86
+
87
+ <svg height={H2} style={{ width: '100%' }}>
88
+ <polygon
89
+ fill={fill}
90
+ points={[
91
+ [e, 0],
92
+ [s, 0],
93
+ [0, H2],
94
+ [W, H2],
95
+ ].toString()}
96
+ />
97
+ </svg>
98
+ </div>
99
+ )
100
+ })
101
+
102
+ export default Minimap
@@ -0,0 +1,283 @@
1
+ import React, { useRef, useEffect, useState, lazy } 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 AnnotationDialog = lazy(() => import('./dialogs/AnnotationDialog'))
15
+
16
+ const useStyles = makeStyles()(theme => {
17
+ const background =
18
+ 'tertiary' in theme.palette && theme.palette.tertiary
19
+ ? alpha(theme.palette.tertiary.main, 0.7)
20
+ : alpha(theme.palette.primary.main, 0.7)
21
+ return {
22
+ rubberband: {
23
+ height: '100%',
24
+ background,
25
+ position: 'absolute',
26
+ zIndex: 10,
27
+ textAlign: 'center',
28
+ overflow: 'hidden',
29
+ },
30
+ rubberbandControl: {
31
+ cursor: 'crosshair',
32
+ width: '100%',
33
+ minHeight: 8,
34
+ },
35
+ rubberbandText: {
36
+ color: theme.palette.tertiary
37
+ ? theme.palette.tertiary.contrastText
38
+ : theme.palette.primary.contrastText,
39
+ },
40
+ popover: {
41
+ mouseEvents: 'none',
42
+ cursor: 'crosshair',
43
+ },
44
+ paper: {
45
+ paddingLeft: theme.spacing(1),
46
+ paddingRight: theme.spacing(1),
47
+ },
48
+ }
49
+ })
50
+
51
+ function OverviewRubberband({
52
+ model,
53
+ ControlComponent = <div />,
54
+ }: {
55
+ model: MsaViewModel
56
+ ControlComponent?: React.ReactElement
57
+ }) {
58
+ const { treeAreaWidth } = model
59
+ const [startX, setStartX] = useState<number>()
60
+ const [currentX, setCurrentX] = useState<number>()
61
+
62
+ // clientX and clientY used for anchorPosition for menu
63
+ // offsetX used for calculations about width of selection
64
+ const [anchorPosition, setAnchorPosition] = useState<{
65
+ offsetX: number
66
+ clientX: number
67
+ clientY: number
68
+ }>()
69
+ const [guideX, setGuideX] = useState<number | undefined>()
70
+ const controlsRef = useRef<HTMLDivElement>(null)
71
+ const rubberbandRef = useRef(null)
72
+ const { classes } = useStyles()
73
+ const mouseDragging = startX !== undefined && anchorPosition === undefined
74
+
75
+ useEffect(() => {
76
+ function globalMouseMove(event: MouseEvent) {
77
+ if (controlsRef.current && mouseDragging) {
78
+ const relativeX =
79
+ event.clientX - controlsRef.current.getBoundingClientRect().left
80
+ setCurrentX(relativeX)
81
+ }
82
+ }
83
+
84
+ function globalMouseUp(event: MouseEvent) {
85
+ if (startX !== undefined && controlsRef.current) {
86
+ const { clientX, clientY } = event
87
+ const ref = controlsRef.current
88
+ const offsetX = clientX - ref.getBoundingClientRect().left
89
+ // as stated above, store both clientX/Y and offsetX for different
90
+ // purposes
91
+ setAnchorPosition({
92
+ offsetX,
93
+ clientX,
94
+ clientY,
95
+ })
96
+ setGuideX(undefined)
97
+ }
98
+ }
99
+ if (mouseDragging) {
100
+ window.addEventListener('mousemove', globalMouseMove)
101
+ window.addEventListener('mouseup', globalMouseUp)
102
+ return () => {
103
+ window.removeEventListener('mousemove', globalMouseMove)
104
+ window.removeEventListener('mouseup', globalMouseUp)
105
+ }
106
+ }
107
+ return () => {}
108
+ }, [startX, mouseDragging, anchorPosition])
109
+
110
+ useEffect(() => {
111
+ if (
112
+ !mouseDragging &&
113
+ currentX !== undefined &&
114
+ startX !== undefined &&
115
+ Math.abs(currentX - startX) <= 3
116
+ ) {
117
+ handleClose()
118
+ }
119
+ }, [mouseDragging, currentX, startX, model.colWidth])
120
+
121
+ function mouseDown(event: React.MouseEvent<HTMLDivElement>) {
122
+ event.preventDefault()
123
+ event.stopPropagation()
124
+ const relativeX =
125
+ event.clientX -
126
+ (event.target as HTMLDivElement).getBoundingClientRect().left
127
+ setStartX(relativeX)
128
+ setCurrentX(relativeX)
129
+ }
130
+
131
+ function mouseMove(event: React.MouseEvent<HTMLDivElement>) {
132
+ const target = event.target as HTMLDivElement
133
+ setGuideX(event.clientX - target.getBoundingClientRect().left)
134
+ }
135
+
136
+ function mouseOut() {
137
+ setGuideX(undefined)
138
+ model.clearAnnotationClickBoundaries()
139
+ }
140
+
141
+ function handleClose() {
142
+ setAnchorPosition(undefined)
143
+ setStartX(undefined)
144
+ setCurrentX(undefined)
145
+ }
146
+
147
+ function handleMenuItemClick(_: unknown, callback: Function) {
148
+ callback()
149
+ handleClose()
150
+ }
151
+
152
+ if (startX === undefined) {
153
+ return (
154
+ <>
155
+ {guideX !== undefined ? (
156
+ <VerticalGuide model={model} coordX={guideX} />
157
+ ) : null}
158
+ <div
159
+ data-testid="rubberband_controls"
160
+ className={classes.rubberbandControl}
161
+ role="presentation"
162
+ ref={controlsRef}
163
+ onMouseDown={mouseDown}
164
+ onMouseOut={mouseOut}
165
+ onMouseMove={mouseMove}
166
+ >
167
+ {ControlComponent}
168
+ </div>
169
+ </>
170
+ )
171
+ }
172
+
173
+ const right = anchorPosition ? anchorPosition.offsetX : currentX || 0
174
+ const left = right < startX ? right : startX
175
+ const width = Math.abs(right - startX)
176
+ const leftBpOffset = model.pxToBp(left)
177
+ const rightBpOffset = model.pxToBp(left + width)
178
+ const numOfBpSelected = Math.ceil(width / model.colWidth)
179
+
180
+ const menuItems = [
181
+ {
182
+ label: 'Create annotation',
183
+ icon: AssignmentIcon,
184
+ onClick: () => {
185
+ model.setAnnotationClickBoundaries(leftBpOffset, rightBpOffset)
186
+ model.queueDialog(onClose => [
187
+ AnnotationDialog,
188
+ {
189
+ data: model.annotPos,
190
+ model,
191
+ onClose: () => {
192
+ model.clearAnnotationClickBoundaries()
193
+ onClose()
194
+ },
195
+ },
196
+ ])
197
+ handleClose()
198
+ },
199
+ },
200
+ ]
201
+ return (
202
+ <>
203
+ {rubberbandRef.current ? (
204
+ <>
205
+ <Popover
206
+ className={classes.popover}
207
+ classes={{
208
+ paper: classes.paper,
209
+ }}
210
+ open
211
+ anchorEl={rubberbandRef.current}
212
+ anchorOrigin={{
213
+ vertical: 'top',
214
+ horizontal: 'left',
215
+ }}
216
+ transformOrigin={{
217
+ vertical: 'bottom',
218
+ horizontal: 'right',
219
+ }}
220
+ keepMounted
221
+ disableRestoreFocus
222
+ >
223
+ <Typography>{leftBpOffset + 1}</Typography>
224
+ </Popover>
225
+ <Popover
226
+ className={classes.popover}
227
+ classes={{
228
+ paper: classes.paper,
229
+ }}
230
+ open
231
+ anchorEl={rubberbandRef.current}
232
+ anchorOrigin={{
233
+ vertical: 'top',
234
+ horizontal: 'right',
235
+ }}
236
+ transformOrigin={{
237
+ vertical: 'bottom',
238
+ horizontal: 'left',
239
+ }}
240
+ keepMounted
241
+ disableRestoreFocus
242
+ >
243
+ <Typography>{rightBpOffset + 1}</Typography>
244
+ </Popover>
245
+ </>
246
+ ) : null}
247
+ <div
248
+ ref={rubberbandRef}
249
+ className={classes.rubberband}
250
+ style={{ left: left + treeAreaWidth, width }}
251
+ >
252
+ <Typography variant="h6" className={classes.rubberbandText}>
253
+ {numOfBpSelected.toLocaleString('en-US')} bp
254
+ </Typography>
255
+ </div>
256
+ <div
257
+ data-testid="rubberband_controls"
258
+ className={classes.rubberbandControl}
259
+ role="presentation"
260
+ ref={controlsRef}
261
+ onMouseDown={mouseDown}
262
+ onMouseOut={mouseOut}
263
+ onMouseMove={mouseMove}
264
+ >
265
+ {ControlComponent}
266
+ </div>
267
+ {anchorPosition ? (
268
+ <Menu
269
+ anchorReference="anchorPosition"
270
+ anchorPosition={{
271
+ left: anchorPosition.clientX,
272
+ top: anchorPosition.clientY,
273
+ }}
274
+ onMenuItemClick={handleMenuItemClick}
275
+ open={Boolean(anchorPosition)}
276
+ onClose={handleClose}
277
+ menuItems={menuItems}
278
+ />
279
+ ) : null}
280
+ </>
281
+ )
282
+ }
283
+ export default observer(OverviewRubberband)
@@ -1,4 +1,4 @@
1
- import React, { useRef, useEffect, useState } from 'react'
1
+ import React, { useRef, useEffect, useState, lazy } from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
  import { makeStyles } from 'tss-react/mui'
4
4
  import { Popover, Typography, alpha } from '@mui/material'
@@ -11,6 +11,8 @@ import AssignmentIcon from '@mui/icons-material/Assignment'
11
11
  import { MsaViewModel } from '../model'
12
12
  import VerticalGuide from './VerticalGuide'
13
13
 
14
+ const AnnotationDialog = lazy(() => import('./dialogs/AnnotationDialog'))
15
+
14
16
  const useStyles = makeStyles()(theme => {
15
17
  const background =
16
18
  'tertiary' in theme.palette && theme.palette.tertiary
@@ -181,6 +183,17 @@ function Rubberband({
181
183
  icon: AssignmentIcon,
182
184
  onClick: () => {
183
185
  model.setAnnotationClickBoundaries(leftBpOffset, rightBpOffset)
186
+ model.queueDialog(onClose => [
187
+ AnnotationDialog,
188
+ {
189
+ data: model.annotPos,
190
+ model,
191
+ onClose: () => {
192
+ model.clearAnnotationClickBoundaries()
193
+ onClose()
194
+ },
195
+ },
196
+ ])
184
197
  handleClose()
185
198
  },
186
199
  },
@@ -20,6 +20,7 @@ const AnnotationBlock = observer(function ({
20
20
  bgColor,
21
21
  colorScheme: modelColorScheme,
22
22
  colWidth,
23
+ fontSize,
23
24
  rowHeight,
24
25
  highResScaleFactor,
25
26
  } = model
@@ -50,7 +51,7 @@ const AnnotationBlock = observer(function ({
50
51
  ctx.clearRect(0, 0, blockSize, rowHeight)
51
52
  ctx.translate(-offsetX, 0)
52
53
  ctx.textAlign = 'center'
53
- ctx.font = ctx.font.replace(/\d+px/, `${Math.max(8, rowHeight - 8)}px`)
54
+ ctx.font = ctx.font.replace(/\d+px/, `${fontSize}px`)
54
55
 
55
56
  const xStart = Math.max(0, Math.floor(offsetX / colWidth))
56
57
  const xEnd = Math.max(0, Math.ceil((offsetX + blockSize) / colWidth))
@@ -69,6 +70,7 @@ const AnnotationBlock = observer(function ({
69
70
  }
70
71
  }
71
72
  }, [
73
+ fontSize,
72
74
  bgColor,
73
75
  blockSize,
74
76
  colWidth,
@@ -10,7 +10,7 @@ import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
10
10
  // locals
11
11
  import { MsaViewModel } from '../model'
12
12
 
13
- const TrackInfoDialog = lazy(() => import('./dialogs/TrackInfoDlg'))
13
+ const TrackInfoDialog = lazy(() => import('./dialogs/TrackInfoDialog'))
14
14
 
15
15
  const useStyles = makeStyles()({
16
16
  button: {
@@ -27,7 +27,7 @@ export const TrackLabel = observer(function ({
27
27
  track: any
28
28
  }) {
29
29
  const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>()
30
- const [trackInfoDlgOpen, setTrackInfoDlgOpen] = useState(false)
30
+ const [trackInfoDialogOpen, setTrackInfoDialogOpen] = useState(false)
31
31
  const { rowHeight, treeAreaWidth: width } = model
32
32
  const {
33
33
  height,
@@ -76,7 +76,7 @@ export const TrackLabel = observer(function ({
76
76
  <MenuItem
77
77
  dense
78
78
  onClick={() => {
79
- setTrackInfoDlgOpen(true)
79
+ setTrackInfoDialogOpen(true)
80
80
  setAnchorEl(undefined)
81
81
  }}
82
82
  >
@@ -84,11 +84,11 @@ export const TrackLabel = observer(function ({
84
84
  </MenuItem>
85
85
  </Menu>
86
86
  ) : null}
87
- {trackInfoDlgOpen ? (
87
+ {trackInfoDialogOpen ? (
88
88
  <Suspense fallback={null}>
89
89
  <TrackInfoDialog
90
90
  model={track.model}
91
- onClose={() => setTrackInfoDlgOpen(false)}
91
+ onClose={() => setTrackInfoDialogOpen(false)}
92
92
  />
93
93
  </Suspense>
94
94
  ) : null}
@@ -3,7 +3,7 @@ import { Menu, MenuItem } from '@mui/material'
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  // locals
6
- import { MsaViewModel } from '../model'
6
+ import { MsaViewModel } from '../../model'
7
7
 
8
8
  interface Node {
9
9
  x: number
@@ -3,10 +3,9 @@ import normalizeWheel from 'normalize-wheel'
3
3
  import { observer } from 'mobx-react'
4
4
 
5
5
  // locals
6
- import { MsaViewModel } from '../model'
6
+ import { MsaViewModel } from '../../model'
7
7
  import TreeCanvasBlock from './TreeCanvasBlock'
8
-
9
- const padding = 600
8
+ import { padding } from './renderTreeCanvas'
10
9
 
11
10
  const TreeCanvas = observer(function ({ model }: { model: MsaViewModel }) {
12
11
  const ref = useRef<HTMLDivElement>(null)