react-msaview 9.1.0 → 9.1.1

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 (272) hide show
  1. package/bundle/index.js +111 -115
  2. package/bundle/index.js.map +4 -4
  3. package/dist/blosum62.d.ts +3 -0
  4. package/dist/blosum62.js +43 -0
  5. package/dist/blosum62.js.map +1 -0
  6. package/dist/colorSchemes.js +1 -0
  7. package/dist/colorSchemes.js.map +1 -1
  8. package/dist/components/Loading.d.ts +3 -2
  9. package/dist/components/Loading.js +2 -2
  10. package/dist/components/Loading.js.map +1 -1
  11. package/dist/components/MSAView.d.ts +3 -2
  12. package/dist/components/MSAView.js +7 -4
  13. package/dist/components/MSAView.js.map +1 -1
  14. package/dist/components/MSAViewer.d.ts +62 -12
  15. package/dist/components/MetadataTable.d.ts +4 -1
  16. package/dist/components/MetadataTable.js +55 -16
  17. package/dist/components/MetadataTable.js.map +1 -1
  18. package/dist/components/Track.js +6 -0
  19. package/dist/components/Track.js.map +1 -1
  20. package/dist/components/controls.js +2 -1
  21. package/dist/components/controls.js.map +1 -1
  22. package/dist/components/dialogs/AnnotationFileDialog.js +7 -2
  23. package/dist/components/dialogs/AnnotationFileDialog.js.map +1 -1
  24. package/dist/components/dialogs/FeatureDialog.js +2 -2
  25. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  26. package/dist/components/{header/MSASettingsMenu.d.ts → dialogs/GapThresholdDialog.d.ts} +3 -2
  27. package/dist/components/dialogs/GapThresholdDialog.js +15 -0
  28. package/dist/components/dialogs/GapThresholdDialog.js.map +1 -0
  29. package/dist/components/{header → dialogs}/GappynessSlider.d.ts +1 -1
  30. package/dist/components/{header → dialogs}/GappynessSlider.js +3 -10
  31. package/dist/components/dialogs/GappynessSlider.js.map +1 -0
  32. package/dist/components/{header → dialogs}/GoToBox.d.ts +4 -2
  33. package/dist/components/dialogs/GoToBox.js +48 -0
  34. package/dist/components/dialogs/GoToBox.js.map +1 -0
  35. package/dist/components/dialogs/GoToDialog.d.ts +6 -0
  36. package/dist/components/dialogs/GoToDialog.js +10 -0
  37. package/dist/components/dialogs/GoToDialog.js.map +1 -0
  38. package/dist/components/dialogs/MetadataDialog.js +11 -3
  39. package/dist/components/dialogs/MetadataDialog.js.map +1 -1
  40. package/dist/components/dialogs/goTo.d.ts +5 -0
  41. package/dist/components/{header → dialogs}/goTo.js +1 -1
  42. package/dist/components/dialogs/goTo.js.map +1 -0
  43. package/dist/components/dialogs/metadataLabels.d.ts +4 -0
  44. package/dist/components/dialogs/metadataLabels.js +52 -0
  45. package/dist/components/dialogs/metadataLabels.js.map +1 -0
  46. package/dist/components/dialogs/metadataRecord.d.ts +1 -0
  47. package/dist/components/dialogs/metadataRecord.js +38 -0
  48. package/dist/components/dialogs/metadataRecord.js.map +1 -0
  49. package/dist/components/header/Header.d.ts +6 -2
  50. package/dist/components/header/Header.js +30 -32
  51. package/dist/components/header/Header.js.map +1 -1
  52. package/dist/components/header/{TreeSettingsMenu.d.ts → HiddenColumnsChip.d.ts} +2 -2
  53. package/dist/components/header/HiddenColumnsChip.js +15 -0
  54. package/dist/components/header/HiddenColumnsChip.js.map +1 -0
  55. package/dist/components/header/MenuBar.d.ts +6 -0
  56. package/dist/components/header/MenuBar.js +33 -0
  57. package/dist/components/header/MenuBar.js.map +1 -0
  58. package/dist/components/header/ScrollZoomCheckbox.d.ts +7 -0
  59. package/dist/components/header/ScrollZoomCheckbox.js +19 -0
  60. package/dist/components/header/ScrollZoomCheckbox.js.map +1 -0
  61. package/dist/components/header/ZoomControls.js +10 -30
  62. package/dist/components/header/ZoomControls.js.map +1 -1
  63. package/dist/components/header/colorSchemeMenuItems.d.ts +4 -0
  64. package/dist/components/header/colorSchemeMenuItems.js +106 -0
  65. package/dist/components/header/colorSchemeMenuItems.js.map +1 -0
  66. package/dist/components/header/getAnnotationsMenu.d.ts +3 -30
  67. package/dist/components/header/getAnnotationsMenu.js +48 -54
  68. package/dist/components/header/getAnnotationsMenu.js.map +1 -1
  69. package/dist/components/header/menuBarItems.d.ts +11 -0
  70. package/dist/components/header/menuBarItems.js +277 -0
  71. package/dist/components/header/menuBarItems.js.map +1 -0
  72. package/dist/components/import/ImportForm.js +12 -6
  73. package/dist/components/import/ImportForm.js.map +1 -1
  74. package/dist/components/minimap/Minimap.js +7 -3
  75. package/dist/components/minimap/Minimap.js.map +1 -1
  76. package/dist/components/minimap/MinimapSVG.js +2 -1
  77. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  78. package/dist/components/minimap/minimapLayout.d.ts +12 -0
  79. package/dist/components/minimap/minimapLayout.js +20 -0
  80. package/dist/components/minimap/minimapLayout.js.map +1 -1
  81. package/dist/components/msa/MSACanvas.js +26 -9
  82. package/dist/components/msa/MSACanvas.js.map +1 -1
  83. package/dist/components/msa/MSACanvasBlock.js +15 -11
  84. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  85. package/dist/components/msa/MSAMouseoverCanvas.js +2 -2
  86. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  87. package/dist/components/msa/WheelZoomHint.d.ts +7 -0
  88. package/dist/components/msa/WheelZoomHint.js +38 -0
  89. package/dist/components/msa/WheelZoomHint.js.map +1 -0
  90. package/dist/components/msa/msaKeys.js +3 -0
  91. package/dist/components/msa/msaKeys.js.map +1 -1
  92. package/dist/components/msa/msaRaster.d.ts +4 -5
  93. package/dist/components/msa/msaRaster.js +5 -5
  94. package/dist/components/msa/msaRaster.js.map +1 -1
  95. package/dist/components/msa/renderBoxFeatureCanvasBlock.js +22 -2
  96. package/dist/components/msa/renderBoxFeatureCanvasBlock.js.map +1 -1
  97. package/dist/components/msa/renderHighlights.d.ts +0 -3
  98. package/dist/components/msa/renderHighlights.js +3 -3
  99. package/dist/components/msa/renderHighlights.js.map +1 -1
  100. package/dist/components/msa/renderMSABlock.d.ts +6 -1
  101. package/dist/components/msa/renderMSABlock.js +44 -17
  102. package/dist/components/msa/renderMSABlock.js.map +1 -1
  103. package/dist/components/msa/tileColor.js +7 -0
  104. package/dist/components/msa/tileColor.js.map +1 -1
  105. package/dist/components/rowpanels/RowPanelBlock.js +2 -2
  106. package/dist/components/rowpanels/RowPanelBlock.js.map +1 -1
  107. package/dist/components/settings/MenuItemsForm.d.ts +10 -0
  108. package/dist/components/settings/MenuItemsForm.js +80 -0
  109. package/dist/components/settings/MenuItemsForm.js.map +1 -0
  110. package/dist/components/settings/SettingsPanel.d.ts +12 -0
  111. package/dist/components/settings/SettingsPanel.js +92 -0
  112. package/dist/components/settings/SettingsPanel.js.map +1 -0
  113. package/dist/components/{header/FileMenu.d.ts → settings/TrackList.d.ts} +2 -2
  114. package/dist/components/settings/TrackList.js +55 -0
  115. package/dist/components/settings/TrackList.js.map +1 -0
  116. package/dist/components/tracks/TrackBlocks.js +2 -2
  117. package/dist/components/tracks/TrackBlocks.js.map +1 -1
  118. package/dist/components/tree/TreeCanvasBlock.js +2 -2
  119. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  120. package/dist/components/tree/TreeMenu.js +4 -6
  121. package/dist/components/tree/TreeMenu.js.map +1 -1
  122. package/dist/components/tree/TreeOverview.js +2 -2
  123. package/dist/components/tree/TreeOverview.js.map +1 -1
  124. package/dist/constants.d.ts +4 -1
  125. package/dist/constants.js +7 -1
  126. package/dist/constants.js.map +1 -1
  127. package/dist/darkReader.d.ts +7 -0
  128. package/dist/darkReader.js +42 -0
  129. package/dist/darkReader.js.map +1 -0
  130. package/dist/element.js +22 -47
  131. package/dist/element.js.map +1 -1
  132. package/dist/featurePanels.d.ts +37 -0
  133. package/dist/featurePanels.js +187 -0
  134. package/dist/featurePanels.js.map +1 -0
  135. package/dist/filehandleLoader.d.ts +41 -0
  136. package/dist/filehandleLoader.js +99 -0
  137. package/dist/filehandleLoader.js.map +1 -0
  138. package/dist/index.d.ts +3 -0
  139. package/dist/index.js +2 -0
  140. package/dist/index.js.map +1 -1
  141. package/dist/model/treeModel.d.ts +9 -0
  142. package/dist/model/treeModel.js +12 -1
  143. package/dist/model/treeModel.js.map +1 -1
  144. package/dist/model.d.ts +142 -32
  145. package/dist/model.js +268 -569
  146. package/dist/model.js.map +1 -1
  147. package/dist/neighborJoining.js +1 -40
  148. package/dist/neighborJoining.js.map +1 -1
  149. package/dist/renderToSvg.js +1 -13
  150. package/dist/renderToSvg.js.map +1 -1
  151. package/dist/rerootTree.d.ts +1 -0
  152. package/dist/rerootTree.js +7 -6
  153. package/dist/rerootTree.js.map +1 -1
  154. package/dist/schemeResidueColor.d.ts +12 -0
  155. package/dist/schemeResidueColor.js +33 -0
  156. package/dist/schemeResidueColor.js.map +1 -0
  157. package/dist/similarityColors.d.ts +11 -0
  158. package/dist/similarityColors.js +90 -0
  159. package/dist/similarityColors.js.map +1 -0
  160. package/dist/svgTestUtil.d.ts +62 -12
  161. package/dist/treeDisplay.d.ts +37 -0
  162. package/dist/treeDisplay.js +209 -0
  163. package/dist/treeDisplay.js.map +1 -0
  164. package/dist/types.d.ts +6 -2
  165. package/dist/useWheelScroll.d.ts +3 -1
  166. package/dist/useWheelScroll.js +36 -17
  167. package/dist/useWheelScroll.js.map +1 -1
  168. package/dist/util.d.ts +2 -0
  169. package/dist/util.js +2 -0
  170. package/dist/util.js.map +1 -1
  171. package/dist/version.d.ts +1 -1
  172. package/dist/version.js +1 -1
  173. package/dist/wheelZoom.d.ts +8 -3
  174. package/dist/wheelZoom.js +44 -32
  175. package/dist/wheelZoom.js.map +1 -1
  176. package/dist/zoomSnapshot.d.ts +29 -0
  177. package/dist/zoomSnapshot.js +47 -0
  178. package/dist/zoomSnapshot.js.map +1 -0
  179. package/package.json +11 -11
  180. package/src/blosum62.ts +45 -0
  181. package/src/colorSchemes.ts +1 -0
  182. package/src/components/Loading.tsx +6 -2
  183. package/src/components/MSAView.tsx +22 -5
  184. package/src/components/MetadataTable.tsx +83 -26
  185. package/src/components/Track.tsx +13 -0
  186. package/src/components/controls.ts +2 -1
  187. package/src/components/dialogs/AnnotationFileDialog.tsx +12 -1
  188. package/src/components/dialogs/FeatureDialog.tsx +2 -5
  189. package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
  190. package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
  191. package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
  192. package/src/components/dialogs/GoToDialog.tsx +30 -0
  193. package/src/components/dialogs/MetadataDialog.tsx +32 -4
  194. package/src/components/{header → dialogs}/goTo.ts +1 -1
  195. package/src/components/dialogs/metadataLabels.ts +54 -0
  196. package/src/components/dialogs/metadataRecord.tsx +61 -0
  197. package/src/components/header/Header.tsx +75 -42
  198. package/src/components/header/HiddenColumnsChip.tsx +39 -0
  199. package/src/components/header/MenuBar.tsx +64 -0
  200. package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
  201. package/src/components/header/ZoomControls.tsx +20 -35
  202. package/src/components/header/colorSchemeMenuItems.ts +121 -0
  203. package/src/components/header/getAnnotationsMenu.ts +52 -56
  204. package/src/components/header/menuBarItems.ts +357 -0
  205. package/src/components/import/ImportForm.tsx +19 -7
  206. package/src/components/minimap/Minimap.tsx +11 -3
  207. package/src/components/minimap/MinimapSVG.tsx +17 -1
  208. package/src/components/minimap/minimapLayout.ts +27 -0
  209. package/src/components/msa/MSACanvas.tsx +22 -3
  210. package/src/components/msa/MSACanvasBlock.tsx +18 -11
  211. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
  212. package/src/components/msa/WheelZoomHint.tsx +56 -0
  213. package/src/components/msa/msaKeys.ts +3 -0
  214. package/src/components/msa/msaRaster.ts +6 -8
  215. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +28 -5
  216. package/src/components/msa/renderHighlights.ts +3 -3
  217. package/src/components/msa/renderMSABlock.ts +51 -15
  218. package/src/components/msa/tileColor.ts +7 -0
  219. package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
  220. package/src/components/settings/MenuItemsForm.tsx +225 -0
  221. package/src/components/settings/SettingsPanel.tsx +156 -0
  222. package/src/components/settings/TrackList.tsx +119 -0
  223. package/src/components/tracks/TrackBlocks.tsx +2 -2
  224. package/src/components/tree/TreeCanvasBlock.tsx +2 -2
  225. package/src/components/tree/TreeMenu.tsx +4 -6
  226. package/src/components/tree/TreeOverview.tsx +2 -2
  227. package/src/constants.ts +9 -1
  228. package/src/darkReader.ts +57 -0
  229. package/src/element.ts +34 -57
  230. package/src/featurePanels.ts +269 -0
  231. package/src/filehandleLoader.ts +136 -0
  232. package/src/index.ts +4 -0
  233. package/src/model/treeModel.ts +13 -0
  234. package/src/model.ts +345 -748
  235. package/src/neighborJoining.ts +1 -42
  236. package/src/renderToSvg.tsx +1 -13
  237. package/src/rerootTree.ts +7 -6
  238. package/src/schemeResidueColor.ts +37 -0
  239. package/src/similarityColors.ts +112 -0
  240. package/src/treeDisplay.ts +278 -0
  241. package/src/types.ts +6 -3
  242. package/src/useWheelScroll.ts +43 -22
  243. package/src/util.ts +3 -0
  244. package/src/version.ts +1 -1
  245. package/src/wheelZoom.ts +55 -33
  246. package/src/zoomSnapshot.ts +80 -0
  247. package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
  248. package/dist/components/header/ColorSchemeMenu.js +0 -37
  249. package/dist/components/header/ColorSchemeMenu.js.map +0 -1
  250. package/dist/components/header/FileMenu.js +0 -92
  251. package/dist/components/header/FileMenu.js.map +0 -1
  252. package/dist/components/header/GappynessSlider.js.map +0 -1
  253. package/dist/components/header/GoToBox.js +0 -31
  254. package/dist/components/header/GoToBox.js.map +0 -1
  255. package/dist/components/header/MSASettingsMenu.js +0 -11
  256. package/dist/components/header/MSASettingsMenu.js.map +0 -1
  257. package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
  258. package/dist/components/header/ScrollZoomIcons.js +0 -35
  259. package/dist/components/header/ScrollZoomIcons.js.map +0 -1
  260. package/dist/components/header/TreeSettingsMenu.js +0 -11
  261. package/dist/components/header/TreeSettingsMenu.js.map +0 -1
  262. package/dist/components/header/goTo.d.ts +0 -20
  263. package/dist/components/header/goTo.js.map +0 -1
  264. package/dist/components/header/settingsMenuItems.d.ts +0 -5
  265. package/dist/components/header/settingsMenuItems.js +0 -171
  266. package/dist/components/header/settingsMenuItems.js.map +0 -1
  267. package/src/components/header/ColorSchemeMenu.tsx +0 -56
  268. package/src/components/header/FileMenu.tsx +0 -107
  269. package/src/components/header/MSASettingsMenu.tsx +0 -26
  270. package/src/components/header/ScrollZoomIcons.tsx +0 -52
  271. package/src/components/header/TreeSettingsMenu.tsx +0 -30
  272. package/src/components/header/settingsMenuItems.ts +0 -216
@@ -1,21 +1,57 @@
1
1
  import React from 'react'
2
2
 
3
3
  import { makeStyles } from '@jbrowse/core/util/tss-react'
4
- import { Table, TableBody, TableCell, TableRow } from '@mui/material'
5
4
 
6
- const useStyles = makeStyles()({
5
+ const useStyles = makeStyles()(theme => ({
6
+ list: {
7
+ display: 'grid',
8
+ gridTemplateColumns: 'minmax(8em, max-content) minmax(0, 1fr)',
9
+ columnGap: theme.spacing(3),
10
+ margin: 0,
11
+ },
12
+ row: {
13
+ display: 'contents',
14
+ },
7
15
  key: {
8
- fontWeight: 'bold',
9
- verticalAlign: 'top',
16
+ fontWeight: 600,
17
+ padding: theme.spacing(0.75, 0),
18
+ borderTop: `1px solid ${theme.palette.divider}`,
19
+ },
20
+ code: {
21
+ marginLeft: theme.spacing(1),
22
+ fontWeight: 400,
23
+ fontSize: '0.75em',
24
+ fontFamily: 'monospace',
25
+ color: theme.palette.text.secondary,
10
26
  },
11
27
  value: {
28
+ margin: 0,
29
+ padding: theme.spacing(0.75, 0),
30
+ borderTop: `1px solid ${theme.palette.divider}`,
12
31
  whiteSpace: 'pre-wrap',
13
- wordBreak: 'break-word',
32
+ overflowWrap: 'anywhere',
33
+ },
34
+ mono: {
35
+ fontFamily: 'monospace',
36
+ fontSize: '0.85em',
37
+ maxHeight: '8em',
38
+ overflow: 'auto',
14
39
  },
15
- })
40
+ nested: {
41
+ '& > dl': {
42
+ paddingLeft: theme.spacing(2),
43
+ borderLeft: `2px solid ${theme.palette.divider}`,
44
+ },
45
+ },
46
+ }))
16
47
 
17
- function isRecord(value: unknown): value is Record<string, unknown> {
18
- return typeof value === 'object' && value !== null && !Array.isArray(value)
48
+ export function isRecord(value: unknown): value is Record<string, unknown> {
49
+ return (
50
+ typeof value === 'object' &&
51
+ value !== null &&
52
+ !Array.isArray(value) &&
53
+ !React.isValidElement(value)
54
+ )
19
55
  }
20
56
 
21
57
  export function isEmpty(value: unknown) {
@@ -24,32 +60,53 @@ export function isEmpty(value: unknown) {
24
60
 
25
61
  export default function MetadataTable({
26
62
  record,
63
+ labels = {},
64
+ monospaceKeys,
27
65
  }: {
28
66
  record: Record<string, unknown>
67
+ labels?: Record<string, string>
68
+ monospaceKeys?: Set<string>
29
69
  }) {
30
- const { classes } = useStyles()
70
+ const { classes, cx } = useStyles()
31
71
  return (
32
- <Table size="small">
33
- <TableBody>
34
- {Object.entries(record)
35
- .filter(([, value]) => value != null && !isEmpty(value))
36
- .map(([key, value]) => (
37
- <TableRow key={key}>
38
- <TableCell component="th" className={classes.key}>
39
- {key}
40
- </TableCell>
41
- <TableCell className={classes.value}>
42
- {isRecord(value) ? (
43
- <MetadataTable record={value} />
72
+ <dl className={classes.list}>
73
+ {Object.entries(record)
74
+ .filter(([, value]) => value != null && !isEmpty(value))
75
+ .map(([key, value]) => {
76
+ const label = labels[key]
77
+ const nested = isRecord(value)
78
+ return (
79
+ <div key={key} className={classes.row}>
80
+ <dt className={classes.key}>
81
+ {label ?? key}
82
+ {label && label !== key ? (
83
+ <span className={classes.code}>{key}</span>
84
+ ) : null}
85
+ </dt>
86
+ <dd
87
+ className={cx(
88
+ classes.value,
89
+ nested && classes.nested,
90
+ monospaceKeys?.has(key) && classes.mono,
91
+ )}
92
+ >
93
+ {nested ? (
94
+ <MetadataTable
95
+ record={value}
96
+ labels={labels}
97
+ monospaceKeys={monospaceKeys}
98
+ />
44
99
  ) : Array.isArray(value) ? (
45
100
  value.join('\n')
101
+ ) : React.isValidElement(value) ? (
102
+ value
46
103
  ) : (
47
104
  String(value)
48
105
  )}
49
- </TableCell>
50
- </TableRow>
51
- ))}
52
- </TableBody>
53
- </Table>
106
+ </dd>
107
+ </div>
108
+ )
109
+ })}
110
+ </dl>
54
111
  )
55
112
  }
@@ -3,6 +3,7 @@ import React, { lazy, useCallback, useRef, useState } from 'react'
3
3
  import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
4
4
  import { makeStyles } from '@jbrowse/core/util/tss-react'
5
5
  import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'
6
+ import CloseIcon from '@mui/icons-material/Close'
6
7
  import { IconButton, Menu, MenuItem } from '@mui/material'
7
8
  import { observer } from 'mobx-react'
8
9
 
@@ -51,6 +52,7 @@ const TrackLabel = observer(function TrackLabel({
51
52
  } = track
52
53
  const { classes } = useStyles()
53
54
  const trackLabelHeight = Math.min(height, fontSize)
55
+ const closeSize = Math.min(height, Math.max(fontSize, 16))
54
56
 
55
57
  return (
56
58
  // a flex row, not inline text: zooming in grows fontSize against a tree
@@ -78,6 +80,17 @@ const TrackLabel = observer(function TrackLabel({
78
80
  >
79
81
  {drawLabels ? name : ''}
80
82
  </span>
83
+ <IconButton
84
+ className={classes.button}
85
+ aria-label={`Hide ${name} track`}
86
+ title="Hide track"
87
+ style={{ width: closeSize, height: closeSize, flexShrink: 0 }}
88
+ onClick={() => {
89
+ model.toggleTrack(track.model.id)
90
+ }}
91
+ >
92
+ <CloseIcon style={{ fontSize: closeSize * 0.8 }} />
93
+ </IconButton>
81
94
  <IconButton
82
95
  className={classes.button}
83
96
  aria-label={`${name} track menu`}
@@ -19,6 +19,7 @@ export const keyboardControls: KeyControl[] = [
19
19
  shiftKey: true,
20
20
  },
21
21
  { label: '+ and -', action: 'Zoom in and out', keys: ['+', '=', '-'] },
22
+ { label: '0', action: 'Reset to the default cell size', keys: ['0'] },
22
23
  {
23
24
  label: 'Home and End',
24
25
  action: 'Jump to the first and last column',
@@ -36,7 +37,7 @@ export const mouseControls: Control[] = [
36
37
  { label: 'Drag', action: 'Pan the alignment' },
37
38
  {
38
39
  label: 'Wheel',
39
- action: 'Scroll, or zoom as set in Alignment settings → Mouse wheel',
40
+ action: 'Scroll, or zoom with Zoom on scroll checked',
40
41
  },
41
42
  { label: 'Ctrl + wheel', action: 'Zoom at the cursor' },
42
43
  selectCells,
@@ -7,6 +7,7 @@ import {
7
7
  DialogContent,
8
8
  FormControl,
9
9
  FormControlLabel,
10
+ Link,
10
11
  Radio,
11
12
  RadioGroup,
12
13
  TextField,
@@ -17,6 +18,8 @@ import { annotationTextToGFF } from 'msa-parsers'
17
18
 
18
19
  import type { MsaViewModel } from '../../model.ts'
19
20
 
21
+ const domainsTutorial = 'https://gmod.org/JBrowseMSA/tutorials/protein_family'
22
+
20
23
  async function fetchText(url: string) {
21
24
  const response = await fetch(url)
22
25
  if (!response.ok) {
@@ -57,7 +60,14 @@ const AnnotationFileDialog = observer(function ({
57
60
  {error ? <ErrorMessage error={error} /> : null}
58
61
  <Typography>
59
62
  Open a GFF3 file of annotations, such as the one `react-msaview-cli
60
- interpro` writes, or the JSON an InterProScan run returns.
63
+ interpro` writes, or the JSON an InterProScan run returns.{' '}
64
+ <Link
65
+ href={domainsTutorial}
66
+ target="_blank"
67
+ rel="noopener noreferrer"
68
+ >
69
+ How to make one
70
+ </Link>
61
71
  </Typography>
62
72
 
63
73
  <div style={{ display: 'flex', margin: 30 }}>
@@ -126,6 +136,7 @@ const AnnotationFileDialog = observer(function ({
126
136
  model.setGFFFilehandle(undefined)
127
137
  model.setGFF(annotationTextToGFF(text))
128
138
  model.setShowDomains(true)
139
+ model.setCellFill('domain')
129
140
  handleClose()
130
141
  } catch (e) {
131
142
  console.error(e)
@@ -102,12 +102,9 @@ const FeatureTable = observer(function ({ model }: { model: MsaViewModel }) {
102
102
  <TableRow key={accession}>
103
103
  <TableCell padding="checkbox">
104
104
  <Checkbox
105
- checked={!model.turnedOffFeatures.get(accession)}
105
+ checked={!model.featureOff(accession)}
106
106
  onChange={() => {
107
- model.setFilter(
108
- accession,
109
- !!model.turnedOffFeatures.get(accession),
110
- )
107
+ model.setFilter(accession, model.featureOff(accession))
111
108
  }}
112
109
  />
113
110
  </TableCell>
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+
3
+ import { Dialog } from '@jbrowse/core/ui'
4
+ import { Button, DialogActions, DialogContent, Typography } from '@mui/material'
5
+ import { observer } from 'mobx-react'
6
+
7
+ import GappynessSlider from './GappynessSlider.tsx'
8
+
9
+ import type { MsaViewModel } from '../../model.ts'
10
+
11
+ const GapThresholdDialog = observer(function ({
12
+ model,
13
+ onClose,
14
+ }: {
15
+ model: MsaViewModel
16
+ onClose: () => void
17
+ }) {
18
+ return (
19
+ <Dialog open onClose={onClose} title="Hide gappy columns">
20
+ <DialogContent>
21
+ <Typography variant="body2" sx={{ mb: 2 }}>
22
+ The viewer hides each column where at least this share of the rows has
23
+ a gap.
24
+ </Typography>
25
+ <GappynessSlider model={model} />
26
+ </DialogContent>
27
+ <DialogActions>
28
+ <Button variant="contained" onClick={onClose}>
29
+ Close
30
+ </Button>
31
+ </DialogActions>
32
+ </Dialog>
33
+ )
34
+ })
35
+
36
+ export default GapThresholdDialog
@@ -10,19 +10,15 @@ const GappynessSlider = observer(function GappynessSlider({
10
10
  }: {
11
11
  model: MsaViewModel
12
12
  }) {
13
- const { hideGaps, allowedGappyness } = model
14
13
  const [dragValue, setDragValue] = useState<number>()
15
- const value = dragValue ?? allowedGappyness
16
- if (!hideGaps) {
17
- return null
18
- }
14
+ const value = dragValue ?? model.allowedGappyness
19
15
  return (
20
16
  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
21
17
  {/* the threshold is inclusive: blanks are columns whose gap count is at
22
18
  least ceil(allowedGappyness% of the rows), so at 100 it hides the
23
19
  columns that are entirely gaps rather than nothing */}
24
20
  <Typography style={{ whiteSpace: 'nowrap' }}>
25
- Hide columns w/ &ge;{value}% gaps
21
+ &ge;{value}% gaps
26
22
  </Typography>
27
23
  <Slider
28
24
  // In JBrowse other MUI Sliders share the page, so `.MuiSlider-thumb`
@@ -36,11 +32,8 @@ const GappynessSlider = observer(function GappynessSlider({
36
32
  // The thumb follows local state while dragging and the model takes the
37
33
  // value on release, since each write recomputes the gap columns over
38
34
  // the whole alignment.
39
- //
40
- // The thumb centers on the track's ends, so the margin keeps its
41
- // overhang off the label and the row search beside it.
42
35
  data-testid="gappyness_slider"
43
- style={{ width: 100, marginInline: 10 }}
36
+ style={{ width: 200, marginInline: 10 }}
44
37
  min={1}
45
38
  max={100}
46
39
  value={value}
@@ -26,14 +26,32 @@ export function labelledInput(params: AutocompleteRenderInputParams) {
26
26
  : { inputProps: { ...inputProps, 'aria-label': label } }
27
27
  }
28
28
 
29
- const GoToBox = observer(function ({ model }: { model: MsaViewModel }) {
29
+ // the menu that opens the dialog hands focus back to its button as it closes,
30
+ // after an autoFocus input would have taken it
31
+ function focusNextFrame(el: HTMLInputElement | null) {
32
+ if (el) {
33
+ requestAnimationFrame(() => {
34
+ el.focus()
35
+ })
36
+ }
37
+ }
38
+
39
+ export function canGoTo(model: MsaViewModel) {
40
+ return findableRowNames(model).length > 1 || model.numColumns > 0
41
+ }
42
+
43
+ const GoToBox = observer(function ({
44
+ model,
45
+ onFound,
46
+ }: {
47
+ model: MsaViewModel
48
+ onFound?: () => void
49
+ }) {
30
50
  const [missed, setMissed] = useState(false)
31
- const names = findableRowNames(model)
32
- return names.length > 1 || model.numColumns > 0 ? (
51
+ return (
33
52
  <Autocomplete
34
53
  freeSolo
35
- size="small"
36
- options={names}
54
+ options={findableRowNames(model)}
37
55
  filterOptions={(options, { inputValue }) =>
38
56
  rowNameSuggestions(options, inputValue)
39
57
  }
@@ -44,21 +62,29 @@ const GoToBox = observer(function ({ model }: { model: MsaViewModel }) {
44
62
  }}
45
63
  onChange={(_, text) => {
46
64
  if (text) {
47
- setMissed(!goTo(model, text))
65
+ const found = goTo(model, text)
66
+ setMissed(!found)
67
+ if (found) {
68
+ onFound?.()
69
+ }
48
70
  }
49
71
  }}
50
- style={{ width: 180, margin: 'auto 8px' }}
51
72
  renderInput={params => (
52
73
  <TextField
53
74
  {...params}
75
+ inputRef={focusNextFrame}
54
76
  error={missed}
55
- placeholder={label}
56
- title="A row name, a column number, or name:residue"
77
+ placeholder="Row name or column number"
78
+ helperText={
79
+ missed
80
+ ? 'No row or column matches'
81
+ : 'Or name:N for residue N of that row'
82
+ }
57
83
  {...labelledInput(params)}
58
84
  />
59
85
  )}
60
86
  />
61
- ) : null
87
+ )
62
88
  })
63
89
 
64
90
  export default GoToBox
@@ -0,0 +1,30 @@
1
+ import React from 'react'
2
+
3
+ import { Dialog } from '@jbrowse/core/ui'
4
+ import { DialogContent } from '@mui/material'
5
+
6
+ import GoToBox from './GoToBox.tsx'
7
+
8
+ import type { MsaViewModel } from '../../model.ts'
9
+
10
+ export default function GoToDialog({
11
+ model,
12
+ onClose,
13
+ }: {
14
+ model: MsaViewModel
15
+ onClose: () => void
16
+ }) {
17
+ return (
18
+ <Dialog
19
+ open
20
+ onClose={onClose}
21
+ title="Go to row or column"
22
+ maxWidth="xs"
23
+ fullWidth
24
+ >
25
+ <DialogContent>
26
+ <GoToBox model={model} onFound={onClose} />
27
+ </DialogContent>
28
+ </Dialog>
29
+ )
30
+ }
@@ -4,8 +4,10 @@ import { Dialog } from '@jbrowse/core/ui'
4
4
  import { DialogContent, Typography } from '@mui/material'
5
5
  import { observer } from 'mobx-react'
6
6
 
7
- import MetadataTable, { isEmpty } from '../MetadataTable.tsx'
7
+ import MetadataTable, { isEmpty, isRecord } from '../MetadataTable.tsx'
8
8
  import SequenceTextArea from '../SequenceTextArea.tsx'
9
+ import { gfLabels, monospaceKeys, sectionLabels } from './metadataLabels.ts'
10
+ import { groupGeneral } from './metadataRecord.tsx'
9
11
 
10
12
  import type { MsaViewModel } from '../../model.ts'
11
13
 
@@ -16,7 +18,13 @@ const MetadataDialog = observer(function ({
16
18
  model: MsaViewModel
17
19
  onClose: () => void
18
20
  }) {
19
- const { header } = model
21
+ const entries = Object.entries(model.header).filter(
22
+ ([, value]) => value != null && !isEmpty(value),
23
+ )
24
+ const fields = Object.fromEntries(
25
+ entries.filter(([, value]) => !isRecord(value)),
26
+ )
27
+ const sections = entries.filter(([, value]) => isRecord(value))
20
28
  return (
21
29
  <Dialog
22
30
  onClose={() => {
@@ -27,10 +35,30 @@ const MetadataDialog = observer(function ({
27
35
  maxWidth="xl"
28
36
  >
29
37
  <DialogContent>
30
- {Object.values(header).every(isEmpty) ? (
38
+ {entries.length === 0 ? (
31
39
  <Typography>This alignment has no metadata.</Typography>
32
40
  ) : (
33
- <MetadataTable record={header} />
41
+ <>
42
+ {Object.keys(fields).length > 0 ? (
43
+ <MetadataTable record={fields} />
44
+ ) : null}
45
+ {sections.map(([section, value]) => (
46
+ <div key={section}>
47
+ <Typography variant="h6">
48
+ {sectionLabels[section] ?? section}
49
+ </Typography>
50
+ {section === 'General' ? (
51
+ <MetadataTable
52
+ record={groupGeneral(value)}
53
+ labels={gfLabels}
54
+ monospaceKeys={monospaceKeys}
55
+ />
56
+ ) : (
57
+ <MetadataTable record={value as Record<string, unknown>} />
58
+ )}
59
+ </div>
60
+ ))}
61
+ </>
34
62
  )}
35
63
  <Typography variant="h6">Sequences</Typography>
36
64
  <SequenceTextArea str={model.rows} />
@@ -21,7 +21,7 @@ export function rowNameSuggestions(names: string[], input: string, limit = 50) {
21
21
  * column the ruler numbers N, or residue N of a row written `name:N`. A name
22
22
  * is tried whole first, so a row called `chr1:100` stays reachable.
23
23
  */
24
- export function resolveGoTo(model: MsaViewModel, text: string) {
24
+ function resolveGoTo(model: MsaViewModel, text: string) {
25
25
  const query = text.trim()
26
26
  const row = model.rowNamesSet.get(query)
27
27
  if (row !== undefined) {
@@ -0,0 +1,54 @@
1
+ export const sectionLabels: Record<string, string> = {
2
+ General: 'Alignment',
3
+ Accessions: 'Row accessions',
4
+ Dbxref: 'Row cross-references',
5
+ }
6
+
7
+ export const gfLabels: Record<string, string> = {
8
+ ID: 'Identifier',
9
+ AC: 'Accession',
10
+ DE: 'Description',
11
+ AU: 'Authors',
12
+ SE: 'Source of seed',
13
+ SS: 'Source of structure',
14
+ BM: 'Build method',
15
+ SM: 'Search method',
16
+ GA: 'Gathering threshold',
17
+ TC: 'Trusted cutoff',
18
+ NC: 'Noise cutoff',
19
+ TP: 'Family type',
20
+ CL: 'Clan',
21
+ MB: 'Clan members',
22
+ PI: 'Previous identifiers',
23
+ DR: 'Database references',
24
+ CC: 'Comment',
25
+ WK: 'Wikipedia',
26
+ NE: 'Pfam family nested in this one',
27
+ NL: 'Nesting location',
28
+ NH: 'Tree (Newick)',
29
+ SQ: 'Number of sequences',
30
+ RC: 'Reference comment',
31
+ RN: 'Reference number',
32
+ RM: 'PubMed ID',
33
+ RT: 'Reference title',
34
+ RA: 'Reference authors',
35
+ RL: 'Reference journal',
36
+ CB: 'Curation: build',
37
+ DC: 'Database comment',
38
+ DW: 'Database web link',
39
+ OS: 'Organism',
40
+ OC: 'Organism classification',
41
+ AM: 'Alignment method',
42
+ FT: 'Feature',
43
+ SO: 'Sequence Ontology term',
44
+ }
45
+
46
+ export const referenceFieldLabels: Record<string, string> = {
47
+ RT: 'Title',
48
+ RA: 'Authors',
49
+ RL: 'Journal',
50
+ RM: 'PubMed',
51
+ RC: 'Comment',
52
+ }
53
+
54
+ export const monospaceKeys = new Set(['NH'])
@@ -0,0 +1,61 @@
1
+ import React from 'react'
2
+
3
+ import { referenceFieldLabels } from './metadataLabels.ts'
4
+
5
+ const referenceTags = ['RM', 'RT', 'RA', 'RL', 'RC'] as const
6
+
7
+ function pubmed(id: string) {
8
+ return /^\d+$/.test(id) ? (
9
+ <a
10
+ href={`https://pubmed.ncbi.nlm.nih.gov/${id}/`}
11
+ target="_blank"
12
+ rel="noreferrer"
13
+ >
14
+ {id}
15
+ </a>
16
+ ) : (
17
+ id
18
+ )
19
+ }
20
+
21
+ function groupReferences(general: Record<string, unknown>) {
22
+ const numbers = general.RN
23
+ if (!Array.isArray(numbers) || numbers.length === 0) {
24
+ return general
25
+ }
26
+ const present = referenceTags.filter(tag => general[tag] != null)
27
+ const aligned = present.every(tag => {
28
+ const values = general[tag]
29
+ return Array.isArray(values) && values.length === numbers.length
30
+ })
31
+ if (!aligned) {
32
+ return general
33
+ }
34
+ const rest = Object.fromEntries(
35
+ Object.entries(general).filter(
36
+ ([tag]) =>
37
+ tag !== 'RN' && !(referenceTags as readonly string[]).includes(tag),
38
+ ),
39
+ )
40
+ const references = Object.fromEntries(
41
+ numbers.map((number: string, i) => [
42
+ number,
43
+ Object.fromEntries(
44
+ present.map(tag => {
45
+ const value = (general[tag] as string[])[i]!
46
+ return [
47
+ referenceFieldLabels[tag],
48
+ tag === 'RM' ? pubmed(value) : value,
49
+ ]
50
+ }),
51
+ ),
52
+ ]),
53
+ )
54
+ return { ...rest, References: references }
55
+ }
56
+
57
+ export function groupGeneral(general: unknown) {
58
+ return general && typeof general === 'object'
59
+ ? groupReferences(general as Record<string, unknown>)
60
+ : {}
61
+ }