react-msaview 9.1.0 → 9.2.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 (278) 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 +64 -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 +58 -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/tracks/TrackTooltipContent.js +2 -1
  119. package/dist/components/tracks/TrackTooltipContent.js.map +1 -1
  120. package/dist/components/tracks/drawTracks.js +13 -6
  121. package/dist/components/tracks/drawTracks.js.map +1 -1
  122. package/dist/components/tree/TreeCanvasBlock.js +2 -2
  123. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  124. package/dist/components/tree/TreeMenu.js +4 -6
  125. package/dist/components/tree/TreeMenu.js.map +1 -1
  126. package/dist/components/tree/TreeOverview.js +2 -2
  127. package/dist/components/tree/TreeOverview.js.map +1 -1
  128. package/dist/constants.d.ts +4 -1
  129. package/dist/constants.js +7 -1
  130. package/dist/constants.js.map +1 -1
  131. package/dist/darkReader.d.ts +7 -0
  132. package/dist/darkReader.js +42 -0
  133. package/dist/darkReader.js.map +1 -0
  134. package/dist/element.js +22 -47
  135. package/dist/element.js.map +1 -1
  136. package/dist/featurePanels.d.ts +37 -0
  137. package/dist/featurePanels.js +187 -0
  138. package/dist/featurePanels.js.map +1 -0
  139. package/dist/filehandleLoader.d.ts +41 -0
  140. package/dist/filehandleLoader.js +99 -0
  141. package/dist/filehandleLoader.js.map +1 -0
  142. package/dist/index.d.ts +3 -0
  143. package/dist/index.js +2 -0
  144. package/dist/index.js.map +1 -1
  145. package/dist/model/treeModel.d.ts +9 -0
  146. package/dist/model/treeModel.js +12 -1
  147. package/dist/model/treeModel.js.map +1 -1
  148. package/dist/model.d.ts +148 -32
  149. package/dist/model.js +291 -569
  150. package/dist/model.js.map +1 -1
  151. package/dist/neighborJoining.js +1 -40
  152. package/dist/neighborJoining.js.map +1 -1
  153. package/dist/renderToSvg.js +1 -13
  154. package/dist/renderToSvg.js.map +1 -1
  155. package/dist/rerootTree.d.ts +1 -0
  156. package/dist/rerootTree.js +7 -6
  157. package/dist/rerootTree.js.map +1 -1
  158. package/dist/schemeResidueColor.d.ts +12 -0
  159. package/dist/schemeResidueColor.js +33 -0
  160. package/dist/schemeResidueColor.js.map +1 -0
  161. package/dist/similarityColors.d.ts +11 -0
  162. package/dist/similarityColors.js +90 -0
  163. package/dist/similarityColors.js.map +1 -0
  164. package/dist/svgTestUtil.d.ts +64 -12
  165. package/dist/treeDisplay.d.ts +37 -0
  166. package/dist/treeDisplay.js +209 -0
  167. package/dist/treeDisplay.js.map +1 -0
  168. package/dist/types.d.ts +7 -3
  169. package/dist/useWheelScroll.d.ts +3 -1
  170. package/dist/useWheelScroll.js +36 -17
  171. package/dist/useWheelScroll.js.map +1 -1
  172. package/dist/util.d.ts +2 -0
  173. package/dist/util.js +2 -0
  174. package/dist/util.js.map +1 -1
  175. package/dist/version.d.ts +1 -1
  176. package/dist/version.js +1 -1
  177. package/dist/wheelZoom.d.ts +8 -3
  178. package/dist/wheelZoom.js +44 -32
  179. package/dist/wheelZoom.js.map +1 -1
  180. package/dist/zoomSnapshot.d.ts +29 -0
  181. package/dist/zoomSnapshot.js +47 -0
  182. package/dist/zoomSnapshot.js.map +1 -0
  183. package/package.json +11 -11
  184. package/src/blosum62.ts +45 -0
  185. package/src/colorSchemes.ts +1 -0
  186. package/src/components/Loading.tsx +6 -2
  187. package/src/components/MSAView.tsx +22 -5
  188. package/src/components/MetadataTable.tsx +83 -26
  189. package/src/components/Track.tsx +13 -0
  190. package/src/components/controls.ts +2 -1
  191. package/src/components/dialogs/AnnotationFileDialog.tsx +12 -1
  192. package/src/components/dialogs/FeatureDialog.tsx +2 -5
  193. package/src/components/dialogs/GapThresholdDialog.tsx +36 -0
  194. package/src/components/{header → dialogs}/GappynessSlider.tsx +3 -10
  195. package/src/components/{header → dialogs}/GoToBox.tsx +36 -10
  196. package/src/components/dialogs/GoToDialog.tsx +30 -0
  197. package/src/components/dialogs/MetadataDialog.tsx +32 -4
  198. package/src/components/{header → dialogs}/goTo.ts +1 -1
  199. package/src/components/dialogs/metadataLabels.ts +54 -0
  200. package/src/components/dialogs/metadataRecord.tsx +61 -0
  201. package/src/components/header/Header.tsx +75 -42
  202. package/src/components/header/HiddenColumnsChip.tsx +39 -0
  203. package/src/components/header/MenuBar.tsx +64 -0
  204. package/src/components/header/ScrollZoomCheckbox.tsx +46 -0
  205. package/src/components/header/ZoomControls.tsx +20 -35
  206. package/src/components/header/colorSchemeMenuItems.ts +121 -0
  207. package/src/components/header/getAnnotationsMenu.ts +52 -56
  208. package/src/components/header/menuBarItems.ts +357 -0
  209. package/src/components/import/ImportForm.tsx +19 -7
  210. package/src/components/minimap/Minimap.tsx +11 -3
  211. package/src/components/minimap/MinimapSVG.tsx +17 -1
  212. package/src/components/minimap/minimapLayout.ts +27 -0
  213. package/src/components/msa/MSACanvas.tsx +22 -3
  214. package/src/components/msa/MSACanvasBlock.tsx +18 -11
  215. package/src/components/msa/MSAMouseoverCanvas.tsx +2 -2
  216. package/src/components/msa/WheelZoomHint.tsx +56 -0
  217. package/src/components/msa/msaKeys.ts +3 -0
  218. package/src/components/msa/msaRaster.ts +6 -8
  219. package/src/components/msa/renderBoxFeatureCanvasBlock.ts +28 -5
  220. package/src/components/msa/renderHighlights.ts +3 -3
  221. package/src/components/msa/renderMSABlock.ts +51 -15
  222. package/src/components/msa/tileColor.ts +7 -0
  223. package/src/components/rowpanels/RowPanelBlock.tsx +2 -2
  224. package/src/components/settings/MenuItemsForm.tsx +225 -0
  225. package/src/components/settings/SettingsPanel.tsx +156 -0
  226. package/src/components/settings/TrackList.tsx +124 -0
  227. package/src/components/tracks/TrackBlocks.tsx +2 -2
  228. package/src/components/tracks/TrackTooltipContent.tsx +2 -1
  229. package/src/components/tracks/drawTracks.ts +15 -7
  230. package/src/components/tree/TreeCanvasBlock.tsx +2 -2
  231. package/src/components/tree/TreeMenu.tsx +4 -6
  232. package/src/components/tree/TreeOverview.tsx +2 -2
  233. package/src/constants.ts +9 -1
  234. package/src/darkReader.ts +57 -0
  235. package/src/element.ts +34 -57
  236. package/src/featurePanels.ts +269 -0
  237. package/src/filehandleLoader.ts +136 -0
  238. package/src/index.ts +4 -0
  239. package/src/model/treeModel.ts +13 -0
  240. package/src/model.ts +370 -748
  241. package/src/neighborJoining.ts +1 -42
  242. package/src/renderToSvg.tsx +1 -13
  243. package/src/rerootTree.ts +7 -6
  244. package/src/schemeResidueColor.ts +37 -0
  245. package/src/similarityColors.ts +112 -0
  246. package/src/treeDisplay.ts +278 -0
  247. package/src/types.ts +7 -4
  248. package/src/useWheelScroll.ts +43 -22
  249. package/src/util.ts +3 -0
  250. package/src/version.ts +1 -1
  251. package/src/wheelZoom.ts +55 -33
  252. package/src/zoomSnapshot.ts +80 -0
  253. package/dist/components/header/ColorSchemeMenu.d.ts +0 -12
  254. package/dist/components/header/ColorSchemeMenu.js +0 -37
  255. package/dist/components/header/ColorSchemeMenu.js.map +0 -1
  256. package/dist/components/header/FileMenu.js +0 -92
  257. package/dist/components/header/FileMenu.js.map +0 -1
  258. package/dist/components/header/GappynessSlider.js.map +0 -1
  259. package/dist/components/header/GoToBox.js +0 -31
  260. package/dist/components/header/GoToBox.js.map +0 -1
  261. package/dist/components/header/MSASettingsMenu.js +0 -11
  262. package/dist/components/header/MSASettingsMenu.js.map +0 -1
  263. package/dist/components/header/ScrollZoomIcons.d.ts +0 -6
  264. package/dist/components/header/ScrollZoomIcons.js +0 -35
  265. package/dist/components/header/ScrollZoomIcons.js.map +0 -1
  266. package/dist/components/header/TreeSettingsMenu.js +0 -11
  267. package/dist/components/header/TreeSettingsMenu.js.map +0 -1
  268. package/dist/components/header/goTo.d.ts +0 -20
  269. package/dist/components/header/goTo.js.map +0 -1
  270. package/dist/components/header/settingsMenuItems.d.ts +0 -5
  271. package/dist/components/header/settingsMenuItems.js +0 -171
  272. package/dist/components/header/settingsMenuItems.js.map +0 -1
  273. package/src/components/header/ColorSchemeMenu.tsx +0 -56
  274. package/src/components/header/FileMenu.tsx +0 -107
  275. package/src/components/header/MSASettingsMenu.tsx +0 -26
  276. package/src/components/header/ScrollZoomIcons.tsx +0 -52
  277. package/src/components/header/TreeSettingsMenu.tsx +0 -30
  278. package/src/components/header/settingsMenuItems.ts +0 -216
@@ -0,0 +1,156 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import ArrowDropDown from '@mui/icons-material/ArrowDropDown'
4
+ import ArrowRight from '@mui/icons-material/ArrowRight'
5
+ import Close from '@mui/icons-material/Close'
6
+ import { Box, ButtonBase, IconButton, Typography } from '@mui/material'
7
+ import { observer } from 'mobx-react'
8
+
9
+ import { settingsPanelWidth } from '../../constants.ts'
10
+ import { annotationsSubMenu } from '../header/getAnnotationsMenu.ts'
11
+ import {
12
+ alignmentSettingsItems,
13
+ colorSettingsItems,
14
+ gapsSubMenu,
15
+ trackSettingsItems,
16
+ treeSubMenu,
17
+ } from '../header/menuBarItems.ts'
18
+ import MenuItemsForm from './MenuItemsForm.tsx'
19
+ import TrackList from './TrackList.tsx'
20
+
21
+ import type { MsaViewModel } from '../../model.ts'
22
+
23
+ const collapsedKey = 'msaview-settings-collapsed'
24
+
25
+ function readCollapsed() {
26
+ try {
27
+ const parsed: unknown = JSON.parse(
28
+ localStorage.getItem(collapsedKey) ?? '[]',
29
+ )
30
+ return new Set(Array.isArray(parsed) ? parsed.map(String) : [])
31
+ } catch {
32
+ return new Set<string>()
33
+ }
34
+ }
35
+
36
+ function writeCollapsed(collapsed: Set<string>) {
37
+ try {
38
+ localStorage.setItem(collapsedKey, JSON.stringify([...collapsed]))
39
+ } catch {}
40
+ }
41
+
42
+ function Section({
43
+ title,
44
+ collapsed,
45
+ onToggle,
46
+ children,
47
+ }: {
48
+ title: string
49
+ collapsed: boolean
50
+ onToggle: () => void
51
+ children: React.ReactNode
52
+ }) {
53
+ const Arrow = collapsed ? ArrowRight : ArrowDropDown
54
+ return (
55
+ <Box sx={{ px: 1.5, py: 1, borderBottom: 1, borderColor: 'divider' }}>
56
+ <ButtonBase
57
+ aria-expanded={!collapsed}
58
+ onClick={onToggle}
59
+ sx={{ width: '100%', justifyContent: 'flex-start', ml: -0.75 }}
60
+ >
61
+ <Arrow fontSize="small" color="action" />
62
+ <Typography variant="overline" color="text.secondary">
63
+ {title}
64
+ </Typography>
65
+ </ButtonBase>
66
+ {collapsed ? null : children}
67
+ </Box>
68
+ )
69
+ }
70
+
71
+ /**
72
+ * The settings whose effect the user watches as they change them, docked to
73
+ * the right of the alignment so nothing it opens covers the tree or the rows.
74
+ * Each section folds, and the browser remembers which ones the user folded.
75
+ */
76
+ const SettingsPanel = observer(function ({
77
+ model,
78
+ height,
79
+ }: {
80
+ model: MsaViewModel
81
+ height: number
82
+ }) {
83
+ const { noTree, noDomains } = model
84
+ const [collapsed, setCollapsed] = useState(readCollapsed)
85
+ const section = (title: string) => ({
86
+ title,
87
+ collapsed: collapsed.has(title),
88
+ onToggle: () => {
89
+ const next = new Set(collapsed)
90
+ if (!next.delete(title)) {
91
+ next.add(title)
92
+ }
93
+ writeCollapsed(next)
94
+ setCollapsed(next)
95
+ },
96
+ })
97
+ return (
98
+ <Box
99
+ data-testid="settings_panel"
100
+ sx={{
101
+ width: settingsPanelWidth,
102
+ height,
103
+ flexShrink: 0,
104
+ overflowY: 'auto',
105
+ borderLeft: 1,
106
+ borderColor: 'divider',
107
+ bgcolor: 'background.paper',
108
+ }}
109
+ >
110
+ <Box
111
+ sx={{
112
+ display: 'flex',
113
+ alignItems: 'center',
114
+ justifyContent: 'space-between',
115
+ px: 1.5,
116
+ pt: 0.5,
117
+ }}
118
+ >
119
+ <Typography variant="subtitle1">Settings</Typography>
120
+ <IconButton
121
+ aria-label="Close settings"
122
+ onClick={() => {
123
+ model.setSettingsOpen(false)
124
+ }}
125
+ >
126
+ <Close fontSize="small" />
127
+ </IconButton>
128
+ </Box>
129
+ <Section {...section('Color')}>
130
+ <MenuItemsForm items={colorSettingsItems(model)} />
131
+ </Section>
132
+ {noTree ? null : (
133
+ <Section {...section('Tree')}>
134
+ <MenuItemsForm items={treeSubMenu(model)} />
135
+ </Section>
136
+ )}
137
+ <Section {...section('Alignment')}>
138
+ <MenuItemsForm items={alignmentSettingsItems(model)} />
139
+ <MenuItemsForm items={gapsSubMenu(model)} />
140
+ </Section>
141
+ {noDomains ? null : (
142
+ <Section {...section('Annotations')}>
143
+ <MenuItemsForm items={annotationsSubMenu(model)} />
144
+ </Section>
145
+ )}
146
+ <Section {...section('Tracks')}>
147
+ <TrackList model={model} />
148
+ <Box sx={{ mt: 1, pt: 0.5, borderTop: 1, borderColor: 'divider' }}>
149
+ <MenuItemsForm items={trackSettingsItems(model)} />
150
+ </Box>
151
+ </Section>
152
+ </Box>
153
+ )
154
+ })
155
+
156
+ export default SettingsPanel
@@ -0,0 +1,124 @@
1
+ import React, { useState } from 'react'
2
+
3
+ import {
4
+ Box,
5
+ Button,
6
+ Checkbox,
7
+ FormControlLabel,
8
+ TextField,
9
+ Typography,
10
+ } from '@mui/material'
11
+ import { observer } from 'mobx-react'
12
+
13
+ import type { MsaViewModel } from '../../model.ts'
14
+
15
+ interface Entry {
16
+ id: string
17
+ label: string
18
+ }
19
+
20
+ function trackGroups(model: MsaViewModel) {
21
+ const computed = new Set(model.computedTrackModels.map(t => t.model.id))
22
+ const computedEntries: Entry[] = []
23
+ const alignmentEntries: Entry[] = []
24
+ const referenceEntries: Entry[] = []
25
+ for (const { model: t } of model.tracks) {
26
+ const entries =
27
+ t.kind === 'reference'
28
+ ? referenceEntries
29
+ : computed.has(t.id)
30
+ ? computedEntries
31
+ : alignmentEntries
32
+ entries.push({ id: t.id, label: t.name })
33
+ }
34
+ return [
35
+ { title: 'Computed from the alignment', entries: computedEntries },
36
+ { title: 'Alignment annotations', entries: alignmentEntries },
37
+ { title: 'Reference row', entries: referenceEntries },
38
+ ].filter(g => g.entries.length > 0)
39
+ }
40
+
41
+ // a filter box shows once the list is long enough to need one
42
+ const filterFrom = 10
43
+
44
+ const TrackList = observer(function ({ model }: { model: MsaViewModel }) {
45
+ const [filter, setFilter] = useState('')
46
+ const shown = new Set(model.turnedOnTracks.map(t => t.model.id))
47
+ const all = trackGroups(model)
48
+ const needle = filter.trim().toLowerCase()
49
+ const groups = all
50
+ .map(g => ({
51
+ ...g,
52
+ entries: g.entries.filter(e => e.label.toLowerCase().includes(needle)),
53
+ }))
54
+ .filter(g => g.entries.length > 0)
55
+ const matching = groups.flatMap(g => g.entries.map(e => e.id))
56
+ const long = all.reduce((n, g) => n + g.entries.length, 0) >= filterFrom
57
+ return (
58
+ <>
59
+ {long ? (
60
+ <>
61
+ <TextField
62
+ size="small"
63
+ placeholder="Filter tracks"
64
+ value={filter}
65
+ onChange={event => {
66
+ setFilter(event.target.value)
67
+ }}
68
+ fullWidth
69
+ />
70
+ <Box sx={{ display: 'flex' }}>
71
+ <Button
72
+ size="small"
73
+ onClick={() => {
74
+ model.setTracksShown(matching, true)
75
+ }}
76
+ >
77
+ Show all
78
+ </Button>
79
+ <Button
80
+ size="small"
81
+ onClick={() => {
82
+ model.setTracksShown(matching, false)
83
+ }}
84
+ >
85
+ Hide all
86
+ </Button>
87
+ </Box>
88
+ </>
89
+ ) : null}
90
+ {groups.map(({ title, entries }) => (
91
+ <Box key={title}>
92
+ {groups.length > 1 ? (
93
+ <Typography variant="caption" color="text.secondary">
94
+ {title}
95
+ </Typography>
96
+ ) : null}
97
+ {entries.map(({ id, label }) => (
98
+ <FormControlLabel
99
+ key={id}
100
+ sx={{ display: 'flex' }}
101
+ control={
102
+ <Checkbox
103
+ size="small"
104
+ checked={shown.has(id)}
105
+ onChange={() => {
106
+ model.toggleTrack(id)
107
+ }}
108
+ sx={{ py: 0.25 }}
109
+ />
110
+ }
111
+ label={label}
112
+ slotProps={{ typography: { variant: 'body2' } }}
113
+ />
114
+ ))}
115
+ </Box>
116
+ ))}
117
+ {groups.length === 0 ? (
118
+ <Typography variant="body2">No track matches</Typography>
119
+ ) : null}
120
+ </>
121
+ )
122
+ })
123
+
124
+ export default TrackList
@@ -1,8 +1,8 @@
1
1
  import React from 'react'
2
2
 
3
- import { useTheme } from '@mui/material'
4
3
  import { observer } from 'mobx-react'
5
4
 
5
+ import { useCanvasTheme } from '../../darkReader.ts'
6
6
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
7
7
  import { TrackResizeHandle } from '../ResizeHandles.tsx'
8
8
  import { drawTrackBlock } from './drawTracks.ts'
@@ -21,7 +21,7 @@ const TrackBlock = observer(function ({
21
21
  }) {
22
22
  const { blockSize, highResScaleFactor } = model
23
23
  const { height } = track.model
24
- const theme = useTheme()
24
+ const theme = useCanvasTheme()
25
25
  const canvasWidth = blockSize * highResScaleFactor
26
26
  const canvasHeight = height * highResScaleFactor
27
27
 
@@ -137,7 +137,8 @@ const TrackTooltipContent = observer(function ({
137
137
  case 'logo': {
138
138
  return <LogoTooltip model={model} col={col} />
139
139
  }
140
- case 'text': {
140
+ case 'text':
141
+ case 'reference': {
141
142
  return <TextTooltip model={model} track={track} col={col} />
142
143
  }
143
144
  case 'arc': {
@@ -1,6 +1,7 @@
1
1
  import { columnLogoStack } from '../../sequenceLogo.ts'
2
2
  import { setFontSize } from '../../setFontSize.ts'
3
3
  import { contrastTextFn } from '../../util.ts'
4
+ import { tileColorFn } from '../msa/tileColor.ts'
4
5
  import { visibleColRange } from '../msa/visibleColRange.ts'
5
6
 
6
7
  import type { ColumnCounts } from '../../columnCounts.ts'
@@ -106,7 +107,7 @@ function drawArcs({
106
107
  function drawTextTrackContent({
107
108
  ctx,
108
109
  data,
109
- colorScheme,
110
+ colorAt,
110
111
  contrastText,
111
112
  bgColor,
112
113
  drawLetters,
@@ -117,7 +118,7 @@ function drawTextTrackContent({
117
118
  }: {
118
119
  ctx: RenderCtx
119
120
  data: string | undefined
120
- colorScheme: Record<string, string>
121
+ colorAt: (col: number, letter: string) => string | undefined
121
122
  contrastText: (color: string | undefined) => string
122
123
  bgColor: boolean
123
124
  drawLetters: boolean
@@ -135,9 +136,9 @@ function drawTextTrackContent({
135
136
 
136
137
  for (let i = 0; str && i < str.length; i++) {
137
138
  const letter = str[i]!
138
- const upper = letter.toUpperCase()
139
- const fill = bgColor ? colorScheme[upper] : undefined
140
- const x = (xStart + i) * colWidth
139
+ const col = xStart + i
140
+ const fill = bgColor ? colorAt(col, letter) : undefined
141
+ const x = col * colWidth
141
142
  if (fill) {
142
143
  ctx.fillStyle = fill
143
144
  ctx.fillRect(x, 0, colWidth, rowHeight)
@@ -402,15 +403,22 @@ export function drawTrackBlock({
402
403
  })
403
404
  break
404
405
  }
405
- case 'text': {
406
+ case 'text':
407
+ case 'reference': {
406
408
  const { bgColor, fontSize, rowHeight, showMsaLetters } = model
409
+ const scheme = customColorScheme ?? model.colorScheme
407
410
  ctx.textAlign = 'center'
408
411
  ctx.textBaseline = 'middle'
409
412
  setFontSize(ctx, fontSize)
410
413
  drawTextTrackContent({
411
414
  ctx,
412
415
  data,
413
- colorScheme: customColorScheme ?? model.colorScheme,
416
+ // the reference row takes its colors from the alignment's own
417
+ // scheme, per column under a dynamic one
418
+ colorAt:
419
+ kind === 'reference'
420
+ ? tileColorFn(model)
421
+ : (_col, letter) => scheme[letter.toUpperCase()],
414
422
  contrastText: contrastTextFn(theme),
415
423
  bgColor,
416
424
  drawLetters: showMsaLetters,
@@ -2,9 +2,9 @@ import React, { useState } from 'react'
2
2
 
3
3
  import BaseTooltip from '@jbrowse/core/ui/BaseTooltip'
4
4
  import { makeStyles } from '@jbrowse/core/util/tss-react'
5
- import { useTheme } from '@mui/material'
6
5
  import { observer } from 'mobx-react'
7
6
 
7
+ import { useCanvasTheme } from '../../darkReader.ts'
8
8
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
9
9
  import TreeMenu from './TreeMenu.tsx'
10
10
  import { renderTreeCanvas } from './renderTreeCanvas.ts'
@@ -30,7 +30,7 @@ const TreeCanvasBlock = observer(function ({
30
30
  offsetY: number
31
31
  }) {
32
32
  const { classes } = useStyles()
33
- const theme = useTheme()
33
+ const theme = useCanvasTheme()
34
34
  const [menu, setMenu] = useState<TreeMenuTarget>()
35
35
  const { clickMap, anchor, hitTest, onMouseMove, onMouseLeave } = useTreeHover(
36
36
  { model, offsetY },
@@ -30,7 +30,7 @@ const TreeMenu = observer(function ({
30
30
  const { name, id, leaf } = node
31
31
  const isCollapsed = collapsed.includes(id)
32
32
  const isRoot = id === model.tree.id
33
- const noun = leaf ? 'subtree' : 'this node'
33
+ const noun = leaf ? 'subtree' : 'clade'
34
34
  return (
35
35
  <Menu
36
36
  anchorReference="anchorPosition"
@@ -73,9 +73,7 @@ const TreeMenu = observer(function ({
73
73
  onClose()
74
74
  }}
75
75
  >
76
- {showOnly === id
77
- ? 'Disable show only this node'
78
- : 'Show only this node'}
76
+ {showOnly === id ? 'Show all rows' : 'Show only this clade'}
79
77
  </MenuItem>
80
78
  )}
81
79
  {leaf || isCollapsed ? null : (
@@ -86,7 +84,7 @@ const TreeMenu = observer(function ({
86
84
  onClose()
87
85
  }}
88
86
  >
89
- Rotate this node
87
+ Rotate clade
90
88
  </MenuItem>
91
89
  )}
92
90
  {isRoot ? null : (
@@ -108,7 +106,7 @@ const TreeMenu = observer(function ({
108
106
  onClose()
109
107
  }}
110
108
  >
111
- Indicate differences from this row
109
+ Use as reference row
112
110
  </MenuItem>
113
111
  ) : null}
114
112
  {leaf && relativeTo ? (
@@ -1,8 +1,8 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
- import { useTheme } from '@mui/material'
4
3
  import { observer } from 'mobx-react'
5
4
 
5
+ import { useCanvasTheme } from '../../darkReader.ts'
6
6
  import { useCanvasAutorun } from '../../useCanvasAutorun.ts'
7
7
  import {
8
8
  drawTreeOverview,
@@ -22,7 +22,7 @@ import type { MsaViewModel } from '../../model.ts'
22
22
  */
23
23
  const TreeOverview = observer(function ({ model }: { model: MsaViewModel }) {
24
24
  const { effectiveTreeAreaWidth, overviewHeight, highResScaleFactor } = model
25
- const theme = useTheme()
25
+ const theme = useCanvasTheme()
26
26
  const [hover, setHover] = useState<{ id: string; rows: [number, number] }>()
27
27
  const width = Math.round(effectiveTreeAreaWidth * highResScaleFactor)
28
28
  const height = Math.round(overviewHeight * highResScaleFactor)
package/src/constants.ts CHANGED
@@ -50,6 +50,10 @@ export const defaultScrollZoomAxis: ScrollZoomAxis = 'both'
50
50
  export const minLetterRowHeight = 8
51
51
  export const minLetterColWidth = 5
52
52
 
53
+ // A wheel-zoom frame draws a block's letters only when the block holds at most
54
+ // this many cells; the settle redraw after the gesture draws the rest
55
+ export const zoomFrameMaxLetters = 2000
56
+
53
57
  // A feature span draws its label at two pixels under its height, capped at 11,
54
58
  // so a span shorter than this holds no readable text: an underline bar, a
55
59
  // sub-row band or a features panel over small rows
@@ -68,9 +72,12 @@ export const labelFitFontSize = fontSizeFor(defaultRowHeight)
68
72
  // zoom-in buttons (which otherwise grow unbounded). minColWidth/minRowHeight are
69
73
  // the smooth-zoom floors.
70
74
  export const minColWidth = 0.2
71
- export const minRowHeight = 1
75
+ export const minRowHeight = 0.2
72
76
  export const maxCellSize = 80
73
77
 
78
+ // the settings panel's width, which it takes out of the view's
79
+ export const settingsPanelWidth = 280
80
+
74
81
  // MSA model defaults
75
82
  export const defaultBgColor = true
76
83
  export const defaultColorSchemeName = 'maeditor'
@@ -99,6 +106,7 @@ export const defaultDrawTree = true
99
106
  export const defaultDrawNodeBubbles = true
100
107
  export const defaultDrawNodeLabels = false
101
108
  export const defaultShowTreeOverview = false
109
+ export const defaultShowTreeScaleBar = false
102
110
  export const defaultOverviewHeight = 120
103
111
 
104
112
  // Neighbor joining does not run above this many rows. The distance matrix is
@@ -0,0 +1,57 @@
1
+ import { useSyncExternalStore } from 'react'
2
+
3
+ import { createJBrowseTheme } from '@jbrowse/core/ui/theme'
4
+ import { useTheme } from '@mui/material'
5
+
6
+ import type { Theme } from '@mui/material'
7
+
8
+ // Dark Reader's Dynamic mode rewrites the page's CSS and leaves canvas pixels
9
+ // alone, so a light theme's black letters land on the dark page it paints.
10
+ // Inlined from @jbrowse/core/util/systemColorScheme, which an older host core
11
+ // lacks (see CLAUDE.md on externals).
12
+ const DARK_READER_ATTRIBUTE = 'data-darkreader-scheme'
13
+
14
+ function darkReaderIsDark() {
15
+ return (
16
+ typeof document !== 'undefined' &&
17
+ document.documentElement.getAttribute(DARK_READER_ATTRIBUTE) === 'dark'
18
+ )
19
+ }
20
+
21
+ function onDarkReaderChange(listener: () => void) {
22
+ if (
23
+ typeof MutationObserver !== 'function' ||
24
+ typeof document === 'undefined'
25
+ ) {
26
+ return () => {}
27
+ }
28
+ const observer = new MutationObserver(listener)
29
+ observer.observe(document.documentElement, {
30
+ attributes: true,
31
+ attributeFilter: [DARK_READER_ATTRIBUTE],
32
+ })
33
+ return () => {
34
+ observer.disconnect()
35
+ }
36
+ }
37
+
38
+ let darkTheme: Theme | undefined
39
+
40
+ /**
41
+ * The theme a canvas draws in: the surrounding MUI theme, or a dark one while
42
+ * Dark Reader darkens a light page. The DOM keeps the surrounding theme, which
43
+ * Dark Reader restyles itself, and the SVG export keeps it too.
44
+ */
45
+ export function useCanvasTheme() {
46
+ const theme = useTheme()
47
+ const darkReader = useSyncExternalStore(
48
+ onDarkReaderChange,
49
+ darkReaderIsDark,
50
+ () => false,
51
+ )
52
+ if (darkReader && theme.palette.mode !== 'dark') {
53
+ darkTheme ??= createJBrowseTheme({ palette: { mode: 'dark' } })
54
+ return darkTheme
55
+ }
56
+ return theme
57
+ }
package/src/element.ts CHANGED
@@ -5,16 +5,7 @@ import { mount } from './mount.tsx'
5
5
  import type { MSAViewerProps } from './components/MSAViewer.tsx'
6
6
  import type { MsaViewModel } from './model.ts'
7
7
  import type { MountedViewer } from './mount.tsx'
8
- import type {
9
- Cell,
10
- ColorScheme,
11
- ColumnTrackSpec,
12
- Feature,
13
- Highlight,
14
- MsaSelection,
15
- ResidueMapping,
16
- Viewport,
17
- } from './types.ts'
8
+ import type { Cell, Highlight, MsaSelection, Viewport } from './types.ts'
18
9
 
19
10
  type ElementData = Pick<
20
11
  MSAViewerProps,
@@ -29,6 +20,17 @@ type ElementData = Pick<
29
20
  | 'selection'
30
21
  >
31
22
 
23
+ const dataProperties = [
24
+ 'msa',
25
+ 'tree',
26
+ 'gff',
27
+ 'features',
28
+ 'colorScheme',
29
+ 'highlights',
30
+ 'columnTracks',
31
+ 'residueMappings',
32
+ ] as const satisfies (keyof ElementData)[]
33
+
32
34
  interface NightingaleManager extends HTMLElement {
33
35
  register: (element: HTMLElement) => void
34
36
  unregister: (element: HTMLElement) => void
@@ -115,54 +117,29 @@ export function defineMsaElement(tagName = 'jbrowse-msa') {
115
117
  #reported?: { start: number; end: number }
116
118
  #data: ElementData = {}
117
119
 
118
- get msa() {
119
- return this.#data.msa
120
- }
121
- set msa(value: string | undefined) {
122
- this.#setData({ msa: value })
123
- }
124
- get tree() {
125
- return this.#data.tree
126
- }
127
- set tree(value: string | undefined) {
128
- this.#setData({ tree: value })
129
- }
130
- get gff() {
131
- return this.#data.gff
132
- }
133
- set gff(value: string | undefined) {
134
- this.#setData({ gff: value })
135
- }
136
- get features() {
137
- return this.#data.features
138
- }
139
- set features(value: Feature[] | undefined) {
140
- this.#setData({ features: value })
141
- }
142
- get colorScheme() {
143
- return this.#data.colorScheme
144
- }
145
- set colorScheme(value: ColorScheme | undefined) {
146
- this.#setData({ colorScheme: value })
147
- }
148
- get highlights() {
149
- return this.#data.highlights
150
- }
151
- set highlights(value: Highlight[] | undefined) {
152
- this.#setData({ highlights: value })
153
- }
154
- get columnTracks() {
155
- return this.#data.columnTracks
156
- }
157
- set columnTracks(value: ColumnTrackSpec[] | undefined) {
158
- this.#setData({ columnTracks: value })
159
- }
160
- get residueMappings() {
161
- return this.#data.residueMappings
162
- }
163
- set residueMappings(value: ResidueMapping[] | undefined) {
164
- this.#setData({ residueMappings: value })
120
+ declare msa: ElementData['msa']
121
+ declare tree: ElementData['tree']
122
+ declare gff: ElementData['gff']
123
+ declare features: ElementData['features']
124
+ declare colorScheme: ElementData['colorScheme']
125
+ declare highlights: ElementData['highlights']
126
+ declare columnTracks: ElementData['columnTracks']
127
+ declare residueMappings: ElementData['residueMappings']
128
+
129
+ static {
130
+ for (const key of dataProperties) {
131
+ Object.defineProperty(MsaElement.prototype, key, {
132
+ configurable: true,
133
+ get(this: MsaElement) {
134
+ return this.#data[key]
135
+ },
136
+ set(this: MsaElement, value: ElementData[typeof key]) {
137
+ this.#setData({ [key]: value })
138
+ },
139
+ })
140
+ }
165
141
  }
142
+
166
143
  // the viewer's selection, which the reader's drags change
167
144
  get selection() {
168
145
  return this.#model ? this.#model.selection : this.#data.selection