jbrowse-plugin-msaview 3.5.0 → 3.7.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 (242) hide show
  1. package/README.md +8 -0
  2. package/dist/AddHighlightModel/index.js +3 -3
  3. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.d.ts +8 -2
  4. package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +3 -3
  5. package/dist/BgzipFastaMsaAdapter/configSchema.js +1 -1
  6. package/dist/LaunchMsaView/cleanProteinSequence.test.d.ts +1 -0
  7. package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
  8. package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
  9. package/dist/LaunchMsaView/codingFeature.js +38 -0
  10. package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -0
  11. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +47 -55
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +16 -17
  16. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  17. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  18. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  19. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
  20. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
  21. package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
  22. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  23. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  24. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  25. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  26. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  28. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +34 -26
  30. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +30 -26
  32. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
  33. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
  34. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
  35. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  37. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +50 -27
  38. package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
  39. package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
  40. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +6 -2
  41. package/dist/LaunchMsaView/components/SubmitCancelActions.js +28 -13
  42. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +52 -4
  43. package/dist/LaunchMsaView/components/TabPanel.js +15 -3
  44. package/dist/LaunchMsaView/components/TabPanel.test.d.ts +1 -0
  45. package/dist/LaunchMsaView/components/TabPanel.test.js +36 -0
  46. package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +3 -1
  47. package/dist/LaunchMsaView/components/TranscriptSelector.js +4 -2
  48. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +12 -2
  49. package/dist/LaunchMsaView/components/calculateProteinSequence.js +38 -23
  50. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  51. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  52. package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
  53. package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
  54. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  55. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  56. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  57. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  58. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  59. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  60. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  61. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  62. package/dist/LaunchMsaView/detectQueryRow.js +4 -7
  63. package/dist/LaunchMsaView/index.js +28 -9
  64. package/dist/LaunchMsaView/launchTarget.d.ts +36 -8
  65. package/dist/LaunchMsaView/launchTarget.js +23 -13
  66. package/dist/LaunchMsaView/launchTarget.test.js +80 -15
  67. package/dist/LaunchMsaView/useQueryRowName.d.ts +12 -0
  68. package/dist/LaunchMsaView/useQueryRowName.js +11 -0
  69. package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
  70. package/dist/LaunchMsaView/util.d.ts +6 -0
  71. package/dist/LaunchMsaView/util.js +12 -3
  72. package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
  73. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
  74. package/dist/MsaViewPanel/afterCreateAutoruns.js +38 -14
  75. package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
  76. package/dist/MsaViewPanel/applyRegion.js +19 -0
  77. package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
  78. package/dist/MsaViewPanel/applyRegion.test.js +45 -0
  79. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  80. package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
  81. package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
  82. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
  83. package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
  84. package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
  85. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
  86. package/dist/MsaViewPanel/genomeToMSA.js +24 -6
  87. package/dist/MsaViewPanel/genomeToMSA.test.js +218 -44
  88. package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
  89. package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
  90. package/dist/MsaViewPanel/model.d.ts +156 -61
  91. package/dist/MsaViewPanel/model.js +69 -3
  92. package/dist/MsaViewPanel/model.test.js +66 -0
  93. package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
  94. package/dist/MsaViewPanel/msaDataStore.js +3 -12
  95. package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
  96. package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
  97. package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
  98. package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
  99. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
  100. package/dist/MsaViewPanel/resolveConnectedTranscript.js +109 -68
  101. package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
  102. package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
  103. package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
  104. package/dist/MsaViewPanel/runLaunch.js +15 -4
  105. package/dist/MsaViewPanel/runLaunch.test.js +16 -0
  106. package/dist/MsaViewPanel/storedData.test.js +43 -0
  107. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +15 -7
  108. package/dist/MsaViewPanel/util.d.ts +23 -23
  109. package/dist/MsaViewPanel/util.js +23 -24
  110. package/dist/index.d.ts +11 -11
  111. package/dist/index.js +17 -10
  112. package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
  113. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  114. package/dist/utils/blastCache.d.ts +26 -2
  115. package/dist/utils/blastCache.js +51 -39
  116. package/dist/utils/blastCache.test.js +46 -19
  117. package/dist/utils/domainCache.d.ts +1 -1
  118. package/dist/utils/domainCache.js +18 -14
  119. package/dist/utils/ebiJobDispatcher.d.ts +11 -0
  120. package/dist/utils/ebiJobDispatcher.js +12 -13
  121. package/dist/utils/ebiJobDispatcher.test.js +17 -3
  122. package/dist/utils/eutils.d.ts +14 -0
  123. package/dist/utils/eutils.js +48 -14
  124. package/dist/utils/eutils.test.d.ts +1 -0
  125. package/dist/utils/eutils.test.js +31 -0
  126. package/dist/utils/idb.d.ts +16 -3
  127. package/dist/utils/idb.js +51 -5
  128. package/dist/utils/idb.test.d.ts +1 -0
  129. package/dist/utils/idb.test.js +78 -0
  130. package/dist/utils/ncbiDomains.js +4 -4
  131. package/dist/utils/ncbiOrthologs.d.ts +1 -1
  132. package/dist/utils/ncbiOrthologs.js +18 -17
  133. package/dist/utils/ncbiOrthologs.test.js +17 -1
  134. package/dist/utils/ncbiTaxonomy.js +14 -15
  135. package/dist/utils/taxonomyNames.js +23 -19
  136. package/dist/utils/taxonomyNames.test.d.ts +1 -0
  137. package/dist/utils/taxonomyNames.test.js +33 -0
  138. package/dist/utils/unirefHomologs.js +2 -2
  139. package/dist/utils/unirefHomologs.test.js +8 -1
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/package.json +23 -23
  143. package/src/AddHighlightModel/index.tsx +1 -1
  144. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  145. package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
  146. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  147. package/src/LaunchMsaView/codingFeature.ts +49 -0
  148. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +60 -56
  149. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +18 -15
  150. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  151. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  152. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
  153. package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
  154. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  155. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  156. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  157. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +46 -23
  158. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +41 -28
  159. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
  160. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
  161. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +81 -25
  162. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  163. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +88 -4
  164. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +43 -16
  165. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  166. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  167. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  168. package/src/LaunchMsaView/components/calculateProteinSequence.ts +51 -22
  169. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  170. package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
  171. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  172. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  173. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  174. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  175. package/src/LaunchMsaView/detectQueryRow.ts +5 -7
  176. package/src/LaunchMsaView/index.ts +32 -13
  177. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  178. package/src/LaunchMsaView/launchTarget.ts +49 -14
  179. package/src/LaunchMsaView/useQueryRowName.test.ts +28 -1
  180. package/src/LaunchMsaView/useQueryRowName.ts +17 -0
  181. package/src/LaunchMsaView/util.ts +15 -5
  182. package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
  183. package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
  184. package/src/MsaViewPanel/afterCreateAutoruns.ts +46 -14
  185. package/src/MsaViewPanel/applyRegion.test.ts +55 -0
  186. package/src/MsaViewPanel/applyRegion.ts +33 -0
  187. package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
  188. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
  189. package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
  190. package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
  191. package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
  192. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
  193. package/src/MsaViewPanel/genomeToMSA.test.ts +255 -59
  194. package/src/MsaViewPanel/genomeToMSA.ts +30 -5
  195. package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
  196. package/src/MsaViewPanel/model.test.ts +75 -0
  197. package/src/MsaViewPanel/model.ts +76 -3
  198. package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
  199. package/src/MsaViewPanel/msaDataStore.ts +4 -12
  200. package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
  201. package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
  202. package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
  203. package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -67
  204. package/src/MsaViewPanel/runLaunch.test.ts +19 -0
  205. package/src/MsaViewPanel/runLaunch.ts +21 -4
  206. package/src/MsaViewPanel/storedData.test.ts +58 -0
  207. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +17 -7
  208. package/src/MsaViewPanel/util.ts +38 -29
  209. package/src/index.ts +19 -10
  210. package/src/utils/blastCache.test.ts +59 -20
  211. package/src/utils/blastCache.ts +62 -39
  212. package/src/utils/domainCache.ts +27 -15
  213. package/src/utils/ebiJobDispatcher.test.ts +18 -3
  214. package/src/utils/ebiJobDispatcher.ts +12 -16
  215. package/src/utils/eutils.test.ts +39 -0
  216. package/src/utils/eutils.ts +64 -14
  217. package/src/utils/idb.test.ts +106 -0
  218. package/src/utils/idb.ts +57 -5
  219. package/src/utils/ncbiDomains.ts +4 -4
  220. package/src/utils/ncbiOrthologs.test.ts +20 -0
  221. package/src/utils/ncbiOrthologs.ts +26 -28
  222. package/src/utils/ncbiTaxonomy.ts +17 -20
  223. package/src/utils/taxonomyNames.test.ts +41 -0
  224. package/src/utils/taxonomyNames.ts +33 -19
  225. package/src/utils/unirefHomologs.test.ts +11 -1
  226. package/src/utils/unirefHomologs.ts +2 -2
  227. package/src/version.ts +1 -1
  228. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
  229. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
  230. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
  231. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
  232. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
  233. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
  234. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -11
  235. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -21
  236. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  237. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  238. package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
  239. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
  240. package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
  241. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -38
  242. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
@@ -1,4 +1,5 @@
1
1
  import { getSession } from '@jbrowse/core/util';
2
+ import { untracked } from 'mobx';
2
3
  import { isAbortError } from '../utils/fetch';
3
4
  /**
4
5
  * The in-browser aligner and a search hand back rows and no tree;
@@ -33,11 +34,21 @@ function buildTree(self) {
33
34
  * Run one launch attempt, owning the AbortController that ties it to the view.
34
35
  *
35
36
  * The controller lives on the model because two things end a launch and neither
36
- * is here: the Cancel button, and the disposer `afterCreate` registers. Before
37
- * it existed, closing a view mid-BLAST left the poller checking EBI for the
38
- * job's lifetime and then writing to a node that was gone.
37
+ * is here: the Cancel button, and the disposer `afterCreate` registers. A launch
38
+ * already holding it is aborted first, so a re-fired autorun replaces the
39
+ * attempt rather than orphaning one that nothing can cancel.
40
+ *
41
+ * Runs untracked: it is called from the launch autoruns, and whatever the
42
+ * launch body reads before its first await would otherwise become a reason to
43
+ * launch again.
39
44
  */
40
- export function runLaunch({ self, message, launch, onLaunched, }) {
45
+ export function runLaunch(args) {
46
+ untracked(() => {
47
+ startLaunch(args);
48
+ });
49
+ }
50
+ function startLaunch({ self, message, launch, onLaunched, }) {
51
+ self.launchController?.abort();
41
52
  const controller = new AbortController();
42
53
  const { signal } = controller;
43
54
  self.setLaunchController(controller);
@@ -112,6 +112,22 @@ describe('a launch that is abandoned', () => {
112
112
  expect(writes.some(w => w.startsWith('setError:Aborted'))).toBe(false);
113
113
  });
114
114
  });
115
+ describe('a launch started while another holds the controller', () => {
116
+ test('aborts the first, so nothing is left polling that Cancel cannot reach', async () => {
117
+ const { model } = makeModel();
118
+ const signals = [];
119
+ const launch = (scope) => {
120
+ signals.push(scope.signal);
121
+ return new Promise(() => { });
122
+ };
123
+ runLaunch({ self: model, message: 'a', onLaunched: () => { }, launch });
124
+ runLaunch({ self: model, message: 'b', onLaunched: () => { }, launch });
125
+ await settle();
126
+ expect(signals.map(s => s.aborted)).toEqual([true, false]);
127
+ model.launchController.abort();
128
+ expect(signals[1].aborted).toBe(true);
129
+ });
130
+ });
115
131
  describe('a launch that fails', () => {
116
132
  test('records the error and stops the spinner', async () => {
117
133
  vi.spyOn(console, 'error').mockImplementation(() => { });
@@ -49,6 +49,9 @@ function makeModel(over = {}) {
49
49
  setTreeMetadata(arg) {
50
50
  model.data.treeMetadata = arg;
51
51
  },
52
+ setGFF(arg) {
53
+ model.data.gff = arg;
54
+ },
52
55
  ...over,
53
56
  };
54
57
  return model;
@@ -82,6 +85,14 @@ describe('restoring a view from IndexedDB', () => {
82
85
  storeDataToIndexedDB(model);
83
86
  expect(mockStore).not.toHaveBeenCalled();
84
87
  });
88
+ // react-msaview drops a GFF over 50kB from the snapshot too
89
+ test('a stored GFF comes back with the alignment', async () => {
90
+ mockRetrieve.mockResolvedValue({ msa: MSA, gff: 'a\t.\tdomain\t1\t2' });
91
+ const model = makeModel({ dataStoreId: 'msa-1' });
92
+ loadStoredData(model);
93
+ await settle();
94
+ expect(model.data.gff).toBe('a\t.\tdomain\t1\t2');
95
+ });
85
96
  // the row expires or the user clears site data, and the view used to reopen as
86
97
  // a blank import form with no hint that anything had been lost
87
98
  test('a row that is gone says so, in words that name the policy', async () => {
@@ -95,6 +106,26 @@ describe('restoring a view from IndexedDB', () => {
95
106
  // to import form" lands straight back on this error
96
107
  expect(model.dataStoreId).toBeUndefined();
97
108
  });
109
+ // the request that built the alignment is kept past the launch, so the view
110
+ // can offer to run it again instead of sending the user back to the gene
111
+ test('a row that is gone points at Retry when the search is still known', async () => {
112
+ mockRetrieve.mockResolvedValue(undefined);
113
+ const model = makeModel({
114
+ dataStoreId: 'msa-1',
115
+ blastParams: { blastDatabase: 'uniprotkb_swissprot' },
116
+ });
117
+ loadStoredData(model);
118
+ await settle();
119
+ expect(model.error.message).toMatch(/Retry/);
120
+ expect(model.blastParams).toBeDefined();
121
+ });
122
+ test('a row that is gone with no search behind it says to relaunch', async () => {
123
+ mockRetrieve.mockResolvedValue(undefined);
124
+ const model = makeModel({ dataStoreId: 'msa-1' });
125
+ loadStoredData(model);
126
+ await settle();
127
+ expect(model.error.message).toMatch(/Relaunch it from the gene/);
128
+ });
98
129
  });
99
130
  describe('keeping IndexedDB up to date', () => {
100
131
  test('the first alignment is written under a fresh id', async () => {
@@ -127,6 +158,18 @@ describe('keeping IndexedDB up to date', () => {
127
158
  });
128
159
  expect(model.dataStoreId).toBe('msa-1');
129
160
  });
161
+ test('a GFF loaded after the first write updates the row', async () => {
162
+ const model = makeModel({
163
+ dataStoreId: 'msa-1',
164
+ rows: [['a', 'MK']],
165
+ data: { msa: MSA },
166
+ lastStoredData: { msa: MSA },
167
+ });
168
+ model.data.gff = 'a\t.\tdomain\t1\t2';
169
+ storeDataToIndexedDB(model);
170
+ await settle();
171
+ expect(mockStore).toHaveBeenCalledWith('msa-1', expect.objectContaining({ gff: 'a\t.\tdomain\t1\t2' }));
172
+ });
130
173
  // a browser that refuses IndexedDB (private mode) answers every write the same
131
174
  // way, and the autorun reruns on its own isStoringData flag -- so a write that
132
175
  // is not recorded as attempted is a write that retries forever
@@ -15,32 +15,40 @@ const mafRegion = {
15
15
  assemblyName: 'hg38',
16
16
  };
17
17
  // A model wired through the real genomeToMSA path: a connected genome view
18
- // over a maf region, with seqPosToVisibleCol as identity so the asserted
18
+ // over a maf region, with the column converters as identity so the asserted
19
19
  // column equals the ungapped offset into the region.
20
20
  function makeModel() {
21
21
  const calls = [];
22
22
  const model = {
23
23
  querySeqName: 'hg38.chr1',
24
- rows: [['hg38.chr1', 'ACGTACGTAC']],
24
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
25
25
  transcriptToMsaMap: undefined,
26
26
  mafRegion,
27
27
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
28
28
  seqPosToVisibleCol: (_name, pos) => pos,
29
+ visibleColToSeqPos: (_name, col) => col,
29
30
  setMousePos: (col) => {
30
31
  calls.push(col);
31
32
  },
32
33
  };
33
34
  return { model, calls };
34
35
  }
35
- function hoverGenome(coord) {
36
+ // this config spells the refName the same way on both sides, so the assembly
37
+ // hands every name straight back
38
+ const assemblyManager = {
39
+ get: () => ({ getCanonicalRefName: (refName) => refName }),
40
+ };
41
+ function mockSession(hovered) {
36
42
  mockGetSession.mockReturnValue({
37
- hovered: { hoverFeature: {}, hoverPosition: { coord, refName: 'chr1' } },
43
+ assemblyManager,
44
+ hovered,
38
45
  });
39
46
  }
47
+ function hoverGenome(coord) {
48
+ mockSession({ hoverFeature: {}, hoverPosition: { coord, refName: 'chr1' } });
49
+ }
40
50
  function clearGenomeHover() {
41
- mockGetSession.mockReturnValue({
42
- hovered: null,
43
- });
51
+ mockSession(null);
44
52
  }
45
53
  describe('syncGenomeHoverToMsaColumn (real genomeToMSA mapping)', () => {
46
54
  beforeEach(() => {
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Whether `querySeqName` names a row this alignment actually has.
2
+ * Whether `querySeqName` names a row this alignment actually has. The name
3
+ * defaults to `QUERY`, which an uploaded alignment has no reason to carry, so
4
+ * the genome and structure hovers ask this before mapping anything.
3
5
  *
4
- * react-msaview's `seqPosToGlobalCol` answers 0 for a name it does not know, so
5
- * without this every genome position maps to the first column and hovering the
6
- * genome — or a connected structure — lights column 0 of an unrelated row. The
7
- * name is wrong more often than it looks: it defaults to `QUERY`, which an
8
- * uploaded alignment has no reason to carry, and the manual panel leaves it
9
- * empty when it cannot match the protein to a row.
10
- *
11
- * The other direction has no such hole: msaCoordToGenomeRegions needs the query
12
- * row's sequence to map a column at all, so a missing row is already nothing
13
- * there.
6
+ * `rowMap`, not `rows`: `rows` is what is on screen, and a query row folded
7
+ * into a collapsed clade is still in the alignment, still has its columns, and
8
+ * still maps.
14
9
  */
15
10
  export declare function hasQueryRow(model: {
16
- rows: string[][];
11
+ rowMap: ReadonlyMap<string, string>;
17
12
  querySeqName: string;
18
13
  }): boolean;
19
14
  /**
@@ -35,23 +30,28 @@ interface LiveFeature {
35
30
  export declare function transcriptFields(transcript?: TranscriptRef): Record<string, unknown>;
36
31
  /** what a transcript is called, preferring its name over its id */
37
32
  export declare function transcriptName(transcript?: TranscriptRef): string | undefined;
38
- export interface QueryRowModel {
33
+ interface QueryRowColumns {
39
34
  querySeqName: string;
40
- querySeqOffset: number;
41
35
  seqPosToVisibleCol: (rowName: string, seqPos: number) => number | undefined;
42
36
  visibleColToSeqPos: (rowName: string, visibleCol: number) => number | undefined;
43
37
  }
38
+ export interface QueryRowModel extends QueryRowColumns {
39
+ querySeqOffset: number;
40
+ }
44
41
  /**
45
- * The visible column showing residue `proteinPos` (0-based) of the transcript,
46
- * or undefined when the query row does not carry that residue or react-msaview
47
- * is hiding its column.
42
+ * The visible column showing residue `seqPos` (0-based) of the query row, or
43
+ * undefined when the row does not carry that residue or react-msaview is
44
+ * hiding its column.
48
45
  *
49
- * Two things separate the transcript from the row. `querySeqOffset` is the
50
- * trimming — a pasted BLAST alignment carries the aligned region, not the whole
51
- * protein. And the row can simply stop short: react-msaview answers one column
52
- * past the end for a position it does not have, which would light the last
53
- * column for every residue beyond the row, so the round trip back through
54
- * visibleColToSeqPos is what rejects those.
46
+ * react-msaview answers one column past the end for a position the row does not
47
+ * have, which would light the last column for every residue beyond the row, so
48
+ * the round trip back through visibleColToSeqPos is what rejects those.
49
+ */
50
+ export declare function querySeqPosToVisibleCol(model: QueryRowColumns, seqPos: number): number | undefined;
51
+ /**
52
+ * The visible column showing residue `proteinPos` (0-based) of the transcript.
53
+ * `querySeqOffset` is what separates the two: a pasted BLAST alignment carries
54
+ * the aligned region, not the whole protein.
55
55
  */
56
56
  export declare function transcriptPosToVisibleCol(model: QueryRowModel, proteinPos: number): number | undefined;
57
57
  export declare function hasHoverPosition(hovered: unknown): hovered is {
@@ -1,19 +1,14 @@
1
1
  /**
2
- * Whether `querySeqName` names a row this alignment actually has.
2
+ * Whether `querySeqName` names a row this alignment actually has. The name
3
+ * defaults to `QUERY`, which an uploaded alignment has no reason to carry, so
4
+ * the genome and structure hovers ask this before mapping anything.
3
5
  *
4
- * react-msaview's `seqPosToGlobalCol` answers 0 for a name it does not know, so
5
- * without this every genome position maps to the first column and hovering the
6
- * genome — or a connected structure — lights column 0 of an unrelated row. The
7
- * name is wrong more often than it looks: it defaults to `QUERY`, which an
8
- * uploaded alignment has no reason to carry, and the manual panel leaves it
9
- * empty when it cannot match the protein to a row.
10
- *
11
- * The other direction has no such hole: msaCoordToGenomeRegions needs the query
12
- * row's sequence to map a column at all, so a missing row is already nothing
13
- * there.
6
+ * `rowMap`, not `rows`: `rows` is what is on screen, and a query row folded
7
+ * into a collapsed clade is still in the alignment, still has its columns, and
8
+ * still maps.
14
9
  */
15
10
  export function hasQueryRow(model) {
16
- return model.rows.some(r => r[0] === model.querySeqName);
11
+ return model.rowMap.has(model.querySeqName);
17
12
  }
18
13
  /**
19
14
  * Read the transcript's fields whichever shape it arrived in: a caller inside
@@ -36,20 +31,16 @@ export function transcriptName(transcript) {
36
31
  return str(fields.name) ?? str(fields.id);
37
32
  }
38
33
  /**
39
- * The visible column showing residue `proteinPos` (0-based) of the transcript,
40
- * or undefined when the query row does not carry that residue or react-msaview
41
- * is hiding its column.
34
+ * The visible column showing residue `seqPos` (0-based) of the query row, or
35
+ * undefined when the row does not carry that residue or react-msaview is
36
+ * hiding its column.
42
37
  *
43
- * Two things separate the transcript from the row. `querySeqOffset` is the
44
- * trimming — a pasted BLAST alignment carries the aligned region, not the whole
45
- * protein. And the row can simply stop short: react-msaview answers one column
46
- * past the end for a position it does not have, which would light the last
47
- * column for every residue beyond the row, so the round trip back through
48
- * visibleColToSeqPos is what rejects those.
38
+ * react-msaview answers one column past the end for a position the row does not
39
+ * have, which would light the last column for every residue beyond the row, so
40
+ * the round trip back through visibleColToSeqPos is what rejects those.
49
41
  */
50
- export function transcriptPosToVisibleCol(model, proteinPos) {
51
- const { querySeqName, querySeqOffset } = model;
52
- const seqPos = proteinPos - querySeqOffset;
42
+ export function querySeqPosToVisibleCol(model, seqPos) {
43
+ const { querySeqName } = model;
53
44
  if (seqPos < 0) {
54
45
  return undefined;
55
46
  }
@@ -59,6 +50,14 @@ export function transcriptPosToVisibleCol(model, proteinPos) {
59
50
  ? col
60
51
  : undefined;
61
52
  }
53
+ /**
54
+ * The visible column showing residue `proteinPos` (0-based) of the transcript.
55
+ * `querySeqOffset` is what separates the two: a pasted BLAST alignment carries
56
+ * the aligned region, not the whole protein.
57
+ */
58
+ export function transcriptPosToVisibleCol(model, proteinPos) {
59
+ return querySeqPosToVisibleCol(model, proteinPos - model.querySeqOffset);
60
+ }
62
61
  export function hasHoverPosition(hovered) {
63
62
  return (!!hovered &&
64
63
  typeof hovered === 'object' &&
package/dist/index.d.ts CHANGED
@@ -7,20 +7,20 @@ export default class MsaViewPlugin extends Plugin {
7
7
  configure(pluginManager: PluginManager): void;
8
8
  rootConfigurationSchema: (pluginManager: PluginManager) => {
9
9
  msa: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
10
- datasets: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
11
- datasetId: {
12
- type: string;
13
- defaultValue: string;
10
+ readonly datasets: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{
11
+ readonly datasetId: {
12
+ readonly type: "string";
13
+ readonly defaultValue: "";
14
14
  };
15
- description: {
16
- type: string;
17
- defaultValue: string;
15
+ readonly description: {
16
+ readonly type: "string";
17
+ readonly defaultValue: "";
18
18
  };
19
- name: {
20
- type: string;
21
- defaultValue: string;
19
+ readonly name: {
20
+ readonly type: "string";
21
+ readonly defaultValue: "";
22
22
  };
23
- adapter: import("@jbrowse/mobx-state-tree").IAnyModelType;
23
+ readonly adapter: import("@jbrowse/mobx-state-tree").IAnyType;
24
24
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>>>;
25
25
  }, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
26
26
  };
package/dist/index.js CHANGED
@@ -45,16 +45,23 @@ export default class MsaViewPlugin extends Plugin {
45
45
  BgzipFastaMsaAdapterF(pluginManager);
46
46
  }
47
47
  configure(pluginManager) {
48
- if (isAbstractMenuManager(pluginManager.rootModel)) {
49
- pluginManager.rootModel.appendToSubMenu(['Add'], {
50
- label: 'Multiple sequence alignment view',
51
- icon: GridOn,
52
- onClick: (session) => {
53
- // stacked, by default: nothing was launched from, so there is no
54
- // connected view for it to sit beside
55
- launchMsaView(session, {});
56
- },
57
- });
48
+ // a throw here error-pages the whole app, and a menu entry is not worth a
49
+ // session: every released host's File menu already throws on append
50
+ try {
51
+ if (isAbstractMenuManager(pluginManager.rootModel)) {
52
+ pluginManager.rootModel.appendToSubMenu(['Add'], {
53
+ label: 'Multiple sequence alignment view',
54
+ icon: GridOn,
55
+ onClick: (session) => {
56
+ // stacked, by default: nothing was launched from, so there is no
57
+ // connected view for it to sit beside
58
+ launchMsaView(session, {});
59
+ },
60
+ });
61
+ }
62
+ }
63
+ catch (e) {
64
+ console.error('jbrowse-plugin-msaview: could not add the Add menu item', e);
58
65
  }
59
66
  }
60
67
  }