react-msaview 5.10.0 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/bundle/index.js +99 -99
  2. package/bundle/index.js.map +4 -4
  3. package/dist/clustalX.js +22 -22
  4. package/dist/clustalX.js.map +1 -1
  5. package/dist/columnCounts.d.ts +7 -0
  6. package/dist/columnCounts.js +18 -0
  7. package/dist/columnCounts.js.map +1 -1
  8. package/dist/components/CopyButton.d.ts +9 -0
  9. package/dist/components/CopyButton.js +23 -0
  10. package/dist/components/CopyButton.js.map +1 -0
  11. package/dist/components/DragHandle.d.ts +25 -0
  12. package/dist/components/DragHandle.js +49 -0
  13. package/dist/components/DragHandle.js.map +1 -0
  14. package/dist/components/Loading.js +2 -2
  15. package/dist/components/Loading.js.map +1 -1
  16. package/dist/components/MSAView.js +2 -2
  17. package/dist/components/MSAView.js.map +1 -1
  18. package/dist/components/ResizeHandles.d.ts +4 -2
  19. package/dist/components/ResizeHandles.js +35 -54
  20. package/dist/components/ResizeHandles.js.map +1 -1
  21. package/dist/components/SequenceTextArea.js +3 -10
  22. package/dist/components/SequenceTextArea.js.map +1 -1
  23. package/dist/components/VerticalScrollbar.js +11 -25
  24. package/dist/components/VerticalScrollbar.js.map +1 -1
  25. package/dist/components/dialogs/FeatureDialog.js +3 -3
  26. package/dist/components/dialogs/FeatureDialog.js.map +1 -1
  27. package/dist/components/dialogs/InterProScanDialog.js +52 -129
  28. package/dist/components/dialogs/InterProScanDialog.js.map +1 -1
  29. package/dist/components/dialogs/{UserProvidedDomainsDialog.d.ts → InterProScanFileDialog.d.ts} +2 -2
  30. package/dist/components/dialogs/{UserProvidedDomainsDialog.js → InterProScanFileDialog.js} +6 -10
  31. package/dist/components/dialogs/InterProScanFileDialog.js.map +1 -0
  32. package/dist/components/dialogs/TrackInfoDialog.d.ts +2 -4
  33. package/dist/components/dialogs/TrackInfoDialog.js +8 -14
  34. package/dist/components/dialogs/TrackInfoDialog.js.map +1 -1
  35. package/dist/components/header/FileMenu.js +3 -3
  36. package/dist/components/header/FileMenu.js.map +1 -1
  37. package/dist/components/header/GappynessSlider.js +1 -1
  38. package/dist/components/header/GappynessSlider.js.map +1 -1
  39. package/dist/components/header/{getDomainsMenu.d.ts → getAnnotationsMenu.d.ts} +1 -1
  40. package/dist/components/header/{getDomainsMenu.js → getAnnotationsMenu.js} +10 -9
  41. package/dist/components/header/getAnnotationsMenu.js.map +1 -0
  42. package/dist/components/header/settingsMenuItems.js +15 -0
  43. package/dist/components/header/settingsMenuItems.js.map +1 -1
  44. package/dist/components/import/ImportForm.js +1 -1
  45. package/dist/components/import/ImportForm.js.map +1 -1
  46. package/dist/components/import/ImportFormExamples.js +66 -57
  47. package/dist/components/import/ImportFormExamples.js.map +1 -1
  48. package/dist/components/minimap/Minimap.js +40 -23
  49. package/dist/components/minimap/Minimap.js.map +1 -1
  50. package/dist/components/minimap/MinimapSVG.d.ts +2 -1
  51. package/dist/components/minimap/MinimapSVG.js +3 -4
  52. package/dist/components/minimap/MinimapSVG.js.map +1 -1
  53. package/dist/components/minimap/minimapLayout.d.ts +10 -1
  54. package/dist/components/minimap/minimapLayout.js +15 -10
  55. package/dist/components/minimap/minimapLayout.js.map +1 -1
  56. package/dist/components/msa/{DomainLegend.d.ts → AnnotationLegend.d.ts} +2 -2
  57. package/dist/components/msa/{DomainLegend.js → AnnotationLegend.js} +4 -4
  58. package/dist/components/msa/AnnotationLegend.js.map +1 -0
  59. package/dist/components/msa/MSACanvas.js +9 -3
  60. package/dist/components/msa/MSACanvas.js.map +1 -1
  61. package/dist/components/msa/MSACanvasBlock.js +12 -3
  62. package/dist/components/msa/MSACanvasBlock.js.map +1 -1
  63. package/dist/components/msa/MSAMouseoverCanvas.js +1 -2
  64. package/dist/components/msa/MSAMouseoverCanvas.js.map +1 -1
  65. package/dist/components/msa/MSAPanel.js +2 -2
  66. package/dist/components/msa/MSAPanel.js.map +1 -1
  67. package/dist/components/msa/domainBandCursor.d.ts +17 -0
  68. package/dist/components/msa/domainBandCursor.js +39 -0
  69. package/dist/components/msa/domainBandCursor.js.map +1 -0
  70. package/dist/components/msa/msaRaster.d.ts +63 -0
  71. package/dist/components/msa/msaRaster.js +252 -0
  72. package/dist/components/msa/msaRaster.js.map +1 -0
  73. package/dist/components/msa/renderMSABlock.d.ts +2 -1
  74. package/dist/components/msa/renderMSABlock.js +27 -41
  75. package/dist/components/msa/renderMSABlock.js.map +1 -1
  76. package/dist/components/msa/renderMSAMouseover.js +1 -2
  77. package/dist/components/msa/renderMSAMouseover.js.map +1 -1
  78. package/dist/components/msa/tileColor.d.ts +6 -0
  79. package/dist/components/msa/tileColor.js +35 -0
  80. package/dist/components/msa/tileColor.js.map +1 -0
  81. package/dist/components/renderCtx.d.ts +1 -1
  82. package/dist/components/tracks/TrackBlocks.d.ts +14 -0
  83. package/dist/components/tracks/TrackBlocks.js +65 -0
  84. package/dist/components/tracks/TrackBlocks.js.map +1 -0
  85. package/dist/components/tracks/{renderTracksSvg.d.ts → drawTracks.d.ts} +22 -10
  86. package/dist/components/tracks/drawTracks.js +193 -0
  87. package/dist/components/tracks/drawTracks.js.map +1 -0
  88. package/dist/components/tree/TreeCanvas.js +8 -2
  89. package/dist/components/tree/TreeCanvas.js.map +1 -1
  90. package/dist/components/tree/TreeCanvasBlock.js +7 -4
  91. package/dist/components/tree/TreeCanvasBlock.js.map +1 -1
  92. package/dist/components/tree/TreeRuler.js +5 -2
  93. package/dist/components/tree/TreeRuler.js.map +1 -1
  94. package/dist/components/tree/renderTreeCanvas.js +5 -2
  95. package/dist/components/tree/renderTreeCanvas.js.map +1 -1
  96. package/dist/constants.d.ts +3 -0
  97. package/dist/constants.js +8 -0
  98. package/dist/constants.js.map +1 -1
  99. package/dist/ggplotPalettes.d.ts +0 -1
  100. package/dist/ggplotPalettes.js +0 -3
  101. package/dist/ggplotPalettes.js.map +1 -1
  102. package/dist/hierarchy.d.ts +2 -6
  103. package/dist/hierarchy.js +4 -6
  104. package/dist/hierarchy.js.map +1 -1
  105. package/dist/index.d.ts +2 -2
  106. package/dist/launchInterProScan.d.ts +0 -23
  107. package/dist/launchInterProScan.js +2 -5
  108. package/dist/launchInterProScan.js.map +1 -1
  109. package/dist/measureTextCanvas.js +21 -6
  110. package/dist/measureTextCanvas.js.map +1 -1
  111. package/dist/model/DataModel.d.ts +1 -4
  112. package/dist/model/DataModel.js +8 -10
  113. package/dist/model/DataModel.js.map +1 -1
  114. package/dist/model.d.ts +86 -107
  115. package/dist/model.js +238 -162
  116. package/dist/model.js.map +1 -1
  117. package/dist/neighborJoining.js +34 -18
  118. package/dist/neighborJoining.js.map +1 -1
  119. package/dist/renderTestEnv.d.ts +43 -0
  120. package/dist/renderTestEnv.js +88 -0
  121. package/dist/renderTestEnv.js.map +1 -0
  122. package/dist/renderToSvg.js +3 -3
  123. package/dist/renderToSvg.js.map +1 -1
  124. package/dist/rowCoordinateCalculations.d.ts +13 -17
  125. package/dist/rowCoordinateCalculations.js +45 -39
  126. package/dist/rowCoordinateCalculations.js.map +1 -1
  127. package/dist/sequenceLogo.d.ts +28 -0
  128. package/dist/sequenceLogo.js +47 -0
  129. package/dist/sequenceLogo.js.map +1 -0
  130. package/dist/types.d.ts +6 -11
  131. package/dist/version.d.ts +1 -1
  132. package/dist/version.js +1 -1
  133. package/dist/version.js.map +1 -1
  134. package/package.json +5 -5
  135. package/src/clustalX.ts +22 -22
  136. package/src/colSpace.test.ts +27 -0
  137. package/src/columnCounts.test.ts +15 -0
  138. package/src/columnCounts.ts +19 -0
  139. package/src/components/CopyButton.tsx +44 -0
  140. package/src/components/DragHandle.tsx +81 -0
  141. package/src/components/Loading.tsx +10 -1
  142. package/src/components/MSAView.tsx +2 -3
  143. package/src/components/ResizeHandles.tsx +70 -87
  144. package/src/components/SequenceTextArea.tsx +3 -16
  145. package/src/components/VerticalScrollbar.tsx +20 -33
  146. package/src/components/dialogs/FeatureDialog.tsx +3 -4
  147. package/src/components/dialogs/InterProScanDialog.tsx +54 -131
  148. package/src/components/dialogs/{UserProvidedDomainsDialog.tsx → InterProScanFileDialog.tsx} +6 -14
  149. package/src/components/dialogs/TrackInfoDialog.tsx +18 -20
  150. package/src/components/header/FileMenu.tsx +3 -3
  151. package/src/components/header/GappynessSlider.tsx +4 -1
  152. package/src/components/header/{getDomainsMenu.ts → getAnnotationsMenu.ts} +10 -9
  153. package/src/components/header/settingsMenuItems.ts +18 -0
  154. package/src/components/import/ImportForm.tsx +1 -1
  155. package/src/components/import/ImportFormExamples.tsx +82 -133
  156. package/src/components/minimap/Minimap.tsx +67 -32
  157. package/src/components/minimap/MinimapSVG.tsx +32 -31
  158. package/src/components/minimap/minimapLayout.test.ts +60 -0
  159. package/src/components/minimap/minimapLayout.ts +15 -10
  160. package/src/components/msa/{DomainLegend.tsx → AnnotationLegend.tsx} +7 -3
  161. package/src/components/msa/MSACanvas.tsx +24 -11
  162. package/src/components/msa/MSACanvasBlock.tsx +13 -3
  163. package/src/components/msa/MSAMouseoverCanvas.tsx +1 -3
  164. package/src/components/msa/MSAPanel.tsx +2 -2
  165. package/src/components/msa/domainBandCursor.ts +41 -0
  166. package/src/components/msa/msaRaster.test.ts +145 -0
  167. package/src/components/msa/msaRaster.ts +350 -0
  168. package/src/components/msa/renderMSABlock.ts +34 -43
  169. package/src/components/msa/renderMSAMouseover.ts +1 -3
  170. package/src/components/msa/tileColor.ts +37 -0
  171. package/src/components/renderCtx.ts +2 -0
  172. package/src/components/tracks/TrackBlocks.tsx +111 -0
  173. package/src/components/tracks/drawTracks.ts +323 -0
  174. package/src/components/tree/TreeCanvas.tsx +16 -4
  175. package/src/components/tree/TreeCanvasBlock.tsx +18 -11
  176. package/src/components/tree/TreeRuler.tsx +7 -2
  177. package/src/components/tree/renderTreeCanvas.test.ts +15 -7
  178. package/src/components/tree/renderTreeCanvas.ts +5 -1
  179. package/src/constants.ts +10 -0
  180. package/src/domainBands.test.ts +24 -0
  181. package/src/domainLetterColor.test.ts +80 -0
  182. package/src/ggplotPalettes.ts +0 -4
  183. package/src/hierarchy.test.ts +7 -7
  184. package/src/hierarchy.ts +6 -9
  185. package/src/impgRender.test.tsx +2 -57
  186. package/src/index.ts +4 -1
  187. package/src/largeTree.test.ts +39 -4
  188. package/src/launchInterProScan.ts +3 -30
  189. package/src/loadErrorDisplay.test.tsx +102 -0
  190. package/src/measureTextCanvas.ts +22 -6
  191. package/src/model/DataModel.ts +11 -11
  192. package/src/model.ts +262 -172
  193. package/src/neighborJoining.ts +36 -18
  194. package/src/renderDomainsSvg.test.tsx +2 -57
  195. package/src/renderMinimapSvg.test.tsx +55 -0
  196. package/src/renderSequenceLogoSvg.test.tsx +176 -0
  197. package/src/renderTestEnv.ts +96 -0
  198. package/src/renderToSvg.tsx +3 -3
  199. package/src/rowCoordinateCalculations.ts +48 -44
  200. package/src/sequenceLogo.test.ts +88 -0
  201. package/src/sequenceLogo.ts +63 -0
  202. package/src/types.ts +17 -16
  203. package/src/version.ts +1 -1
  204. package/src/viewChrome.test.tsx +134 -0
  205. package/dist/components/ConservationTrack.d.ts +0 -8
  206. package/dist/components/ConservationTrack.js +0 -49
  207. package/dist/components/ConservationTrack.js.map +0 -1
  208. package/dist/components/TextTrack.d.ts +0 -8
  209. package/dist/components/TextTrack.js +0 -58
  210. package/dist/components/TextTrack.js.map +0 -1
  211. package/dist/components/dialogs/DomainDialog.d.ts +0 -6
  212. package/dist/components/dialogs/DomainDialog.js +0 -19
  213. package/dist/components/dialogs/DomainDialog.js.map +0 -1
  214. package/dist/components/dialogs/UserProvidedDomainsDialog.js.map +0 -1
  215. package/dist/components/header/SettingsMenu.d.ts +0 -6
  216. package/dist/components/header/SettingsMenu.js +0 -40
  217. package/dist/components/header/SettingsMenu.js.map +0 -1
  218. package/dist/components/header/getDomainsMenu.js.map +0 -1
  219. package/dist/components/msa/DomainLegend.js.map +0 -1
  220. package/dist/components/tracks/renderTracksSvg.js +0 -126
  221. package/dist/components/tracks/renderTracksSvg.js.map +0 -1
  222. package/src/components/ConservationTrack.tsx +0 -99
  223. package/src/components/TextTrack.tsx +0 -103
  224. package/src/components/dialogs/DomainDialog.tsx +0 -45
  225. package/src/components/header/SettingsMenu.tsx +0 -57
  226. package/src/components/tracks/renderTracksSvg.ts +0 -247
@@ -0,0 +1,102 @@
1
+ // @vitest-environment jsdom
2
+ //
3
+ // What a failed load LOOKS like, which is a different question from what the
4
+ // model recorded. modelFilehandleLoaders.test.ts already pins the model half --
5
+ // a rejected fetch sets `error` and clears `loadingMSA`, under the name "a
6
+ // failed fetch surfaces as an error" -- and it passed throughout the whole time
7
+ // a failed url showed the reader a spinner and nothing else.
8
+ //
9
+ // The error display was never missing. ImportForm renders `model.error`, and
10
+ // the reader could not get to it: a non-abort failure KEEPS its filehandle
11
+ // (only an abort clears it), so `hasPendingFilehandle` stayed true and
12
+ // Loading.tsx took its spinner branch ahead of the import form, permanently.
13
+ // Found on AlphaFold's `files/msa/` prefix answering 403 to every key in August
14
+ // 2026 -- the view opened, said "Downloading file" with a Cancel button, and
15
+ // was still saying it at 105s.
16
+ import React, { act } from 'react'
17
+
18
+ import { createRoot } from 'react-dom/client'
19
+ import { afterEach, beforeEach, expect, test } from 'vitest'
20
+
21
+ import MSAView from './components/Loading.tsx'
22
+ import MSAModelF from './model.ts'
23
+
24
+ import type { MsaViewModel } from './model.ts'
25
+ import type { Root } from 'react-dom/client'
26
+
27
+ Reflect.set(globalThis, 'IS_REACT_ACT_ENVIRONMENT', true)
28
+
29
+ let container: HTMLDivElement
30
+ let root: Root
31
+ let model: MsaViewModel
32
+
33
+ function uri(u: string) {
34
+ return { locationType: 'UriLocation' as const, uri: u }
35
+ }
36
+
37
+ function render() {
38
+ act(() => {
39
+ root.render(<MSAView model={model} />)
40
+ })
41
+ }
42
+
43
+ beforeEach(() => {
44
+ container = document.createElement('div')
45
+ document.body.append(container)
46
+ root = createRoot(container)
47
+ model = MSAModelF().create({ type: 'MsaView' })
48
+ model.setWidth(800)
49
+ })
50
+
51
+ afterEach(() => {
52
+ act(() => {
53
+ root.unmount()
54
+ })
55
+ container.remove()
56
+ })
57
+
58
+ test('a load that fails shows the error rather than a spinner forever', () => {
59
+ // the state the loader leaves behind on a 403: the filehandle it could not
60
+ // read is still set, nothing parsed, and the error recorded
61
+ model.setMSAFilehandle(uri('https://example.com/blocked.a3m'))
62
+ model.setLoadingMSA(false)
63
+ model.setError(new Error('HTTP 403 fetching blocked.a3m'))
64
+ render()
65
+
66
+ expect(container.textContent).toContain('403')
67
+ expect(container.textContent).not.toContain('Loading')
68
+ })
69
+
70
+ test('a load still in flight is a spinner, not an error', () => {
71
+ model.setMSAFilehandle(uri('https://example.com/slow.a3m'))
72
+ render()
73
+
74
+ expect(model.loadingMSA).toBe(true)
75
+ expect(container.textContent).not.toContain('Return to import form')
76
+ })
77
+
78
+ // A guard rather than a pin: this one passes without the fix too, because with
79
+ // no filehandle left set the view reaches the ImportForm and that already shows
80
+ // the error. It is here so the new branch cannot quietly swallow the case.
81
+ //
82
+ // It also records the surprise underneath: `dataInitialized` is itself
83
+ // `(msa || tree) && !error`, so ANY error hides a loaded alignment, including a
84
+ // tree or gff that 404s beside a good one -- setError is shared by every
85
+ // filehandle loader. Whether a failed OPTIONAL file should blank a good
86
+ // alignment is a real question and a wider one, since `dataInitialized` is read
87
+ // in several places; narrowing it is a design change rather than this fix.
88
+ test('an error over loaded data says so instead of a bare import form', () => {
89
+ const loaded = MSAModelF().create({
90
+ type: 'MsaView',
91
+ msaFormat: 'fasta',
92
+ height: 200,
93
+ data: { msa: '>seq1\nACDE\n>seq2\nACDF' },
94
+ })
95
+ loaded.setWidth(800)
96
+ loaded.setError(new Error('domains.gff 404'))
97
+ act(() => {
98
+ root.render(<MSAView model={loaded} />)
99
+ })
100
+
101
+ expect(container.textContent).toContain('domains.gff 404')
102
+ })
@@ -1,15 +1,31 @@
1
1
  import { setFontSize } from './setFontSize.ts'
2
2
 
3
- let canvasHandle: HTMLCanvasElement | undefined
3
+ // Cached across calls: labelWidthMap measures every leaf label on every layout,
4
+ // and both createElement and getContext are far more expensive than measureText.
5
+ // `null` records that this environment has no 2d context, so the lookup is not
6
+ // retried on every label.
7
+ let ctxHandle: CanvasRenderingContext2D | null | undefined
4
8
 
5
- export function measureTextCanvas(text: string, fontSize: number) {
6
- if (!canvasHandle) {
7
- canvasHandle = document.createElement('canvas')
9
+ function getCtx() {
10
+ if (ctxHandle === undefined) {
11
+ ctxHandle =
12
+ typeof document === 'undefined'
13
+ ? null
14
+ : document.createElement('canvas').getContext('2d')
8
15
  }
16
+ return ctxHandle
17
+ }
9
18
 
10
- const ctx = canvasHandle.getContext('2d')
19
+ // Rough average glyph width as a fraction of the font size for the sans-serif
20
+ // stack the tree labels use. Only reached where there is no canvas to measure
21
+ // with (jsdom, SSR); estimating the label gutter beats throwing out of the
22
+ // layout getters that read this on every render.
23
+ const fallbackGlyphWidthRatio = 0.6
24
+
25
+ export function measureTextCanvas(text: string, fontSize: number) {
26
+ const ctx = getCtx()
11
27
  if (!ctx) {
12
- throw new Error('no canvas context')
28
+ return text.length * fontSize * fallbackGlyphWidthRatio
13
29
  }
14
30
  setFontSize(ctx, fontSize)
15
31
  return ctx.measureText(text).width
@@ -53,15 +53,15 @@ export function DataModelF() {
53
53
  self.gff = gff
54
54
  },
55
55
  }))
56
- .postProcessSnapshot(snap => {
57
- const { tree, msa, treeMetadata, gff } = snap
58
- const max = 50_000
59
- return {
60
- tree: tree && tree.length > max ? undefined : tree,
61
- msa: msa && msa.length > max ? undefined : msa,
62
- treeMetadata:
63
- treeMetadata && treeMetadata.length > max ? undefined : treeMetadata,
64
- gff: gff && gff.length > max ? undefined : gff,
65
- }
66
- })
56
+ .postProcessSnapshot(snap =>
57
+ // a large document is dropped from the snapshot rather than inlined
58
+ // into a session or a shared URL, and every field here holds one, so
59
+ // the rule is the same for all of them
60
+ Object.fromEntries(
61
+ Object.entries(snap).map(([key, text]) => [
62
+ key,
63
+ text && text.length > 50_000 ? undefined : text,
64
+ ]),
65
+ ),
66
+ )
67
67
  }