jbrowse-plugin-msaview 3.4.1 → 3.6.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 (222) hide show
  1. package/README.md +7 -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/codingFeature.d.ts +14 -0
  7. package/dist/LaunchMsaView/codingFeature.js +38 -0
  8. package/dist/LaunchMsaView/codingFeature.test.js +75 -0
  9. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.d.ts +3 -1
  10. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +29 -26
  11. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +7 -2
  13. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.d.ts +7 -3
  14. package/dist/LaunchMsaView/components/BlastQuery/BlastPanel.js +8 -7
  15. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.js +2 -2
  16. package/dist/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.js +5 -3
  17. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +26 -4
  18. package/dist/LaunchMsaView/components/BlastQuery/consts.js +68 -2
  19. package/dist/LaunchMsaView/components/BlastQuery/searchChoiceStorage.d.ts +1 -1
  20. package/dist/LaunchMsaView/components/HelpButton.d.ts +2 -0
  21. package/dist/LaunchMsaView/components/HelpButton.js +17 -0
  22. package/dist/LaunchMsaView/components/HelpDialog.d.ts +4 -0
  23. package/dist/LaunchMsaView/components/HelpDialog.js +18 -0
  24. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.d.ts +3 -1
  25. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +21 -16
  26. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.d.ts +3 -1
  27. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +10 -4
  28. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +3 -1
  29. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +2 -1
  30. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
  31. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +21 -7
  32. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +4 -1
  33. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.d.ts +3 -1
  34. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +33 -15
  35. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +3 -1
  36. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +2 -1
  37. package/dist/LaunchMsaView/components/QueryRowSelector.js +21 -5
  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 +3 -1
  41. package/dist/LaunchMsaView/components/SubmitCancelActions.js +25 -13
  42. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +33 -3
  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 +4 -1
  49. package/dist/LaunchMsaView/components/calculateProteinSequence.js +20 -10
  50. package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
  51. package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
  52. package/dist/LaunchMsaView/components/launchPlacement.d.ts +8 -0
  53. package/dist/LaunchMsaView/components/launchPlacement.js +25 -0
  54. package/dist/LaunchMsaView/components/useFeatureSequence.d.ts +2 -0
  55. package/dist/LaunchMsaView/components/useFeatureSequence.js +14 -11
  56. package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +10 -1
  57. package/dist/LaunchMsaView/components/useTranscriptSelection.js +21 -3
  58. package/dist/LaunchMsaView/components/useTranscriptSelection.test.d.ts +1 -0
  59. package/dist/LaunchMsaView/components/useTranscriptSelection.test.js +57 -0
  60. package/dist/LaunchMsaView/detectQueryRow.d.ts +11 -0
  61. package/dist/LaunchMsaView/detectQueryRow.js +11 -4
  62. package/dist/LaunchMsaView/detectQueryRow.test.js +30 -0
  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 +13 -0
  68. package/dist/LaunchMsaView/useQueryRowName.js +16 -1
  69. package/dist/LaunchMsaView/useQueryRowName.test.d.ts +1 -0
  70. package/dist/LaunchMsaView/useQueryRowName.test.js +40 -0
  71. package/dist/LaunchMsaView/util.js +5 -2
  72. package/dist/LaunchMsaViewExtensionPoint/index.js +8 -6
  73. package/dist/LaunchMsaViewExtensionPoint/index.test.js +21 -0
  74. package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +9 -6
  75. package/dist/MsaViewPanel/afterCreateAutoruns.js +72 -28
  76. package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
  77. package/dist/MsaViewPanel/components/LaunchProgress.js +22 -10
  78. package/dist/MsaViewPanel/components/MsaViewPanel.js +10 -2
  79. package/dist/MsaViewPanel/components/MsaViewPanel.test.js +47 -2
  80. package/dist/MsaViewPanel/doLaunchBlast.d.ts +8 -0
  81. package/dist/MsaViewPanel/doLaunchBlast.js +78 -93
  82. package/dist/MsaViewPanel/doLaunchBlast.test.d.ts +1 -0
  83. package/dist/MsaViewPanel/doLaunchBlast.test.js +158 -0
  84. package/dist/MsaViewPanel/doLaunchOrthologs.js +40 -5
  85. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +10 -0
  86. package/dist/MsaViewPanel/genomeToMSA.js +25 -7
  87. package/dist/MsaViewPanel/genomeToMSA.test.js +214 -19
  88. package/dist/MsaViewPanel/loadProteinDomains.d.ts +8 -2
  89. package/dist/MsaViewPanel/loadProteinDomains.js +18 -9
  90. package/dist/MsaViewPanel/loadProteinDomains.test.d.ts +1 -0
  91. package/dist/MsaViewPanel/loadProteinDomains.test.js +41 -0
  92. package/dist/MsaViewPanel/model.d.ts +265 -103
  93. package/dist/MsaViewPanel/model.js +174 -48
  94. package/dist/MsaViewPanel/model.test.d.ts +1 -0
  95. package/dist/MsaViewPanel/model.test.js +166 -0
  96. package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +12 -1
  97. package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +8 -8
  98. package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +133 -32
  99. package/dist/MsaViewPanel/msaDataStore.d.ts +2 -0
  100. package/dist/MsaViewPanel/msaDataStore.js +10 -0
  101. package/dist/MsaViewPanel/observeProteinHighlights.test.js +20 -3
  102. package/dist/MsaViewPanel/processInit.test.d.ts +1 -0
  103. package/dist/MsaViewPanel/processInit.test.js +72 -0
  104. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +9 -0
  105. package/dist/MsaViewPanel/resolveConnectedTranscript.js +128 -0
  106. package/dist/MsaViewPanel/runLaunch.d.ts +1 -0
  107. package/dist/MsaViewPanel/runLaunch.js +35 -0
  108. package/dist/MsaViewPanel/runLaunch.test.js +46 -0
  109. package/dist/MsaViewPanel/storedData.test.js +20 -0
  110. package/dist/MsaViewPanel/structureConnection.d.ts +0 -4
  111. package/dist/MsaViewPanel/structureConnection.js +0 -19
  112. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +12 -5
  113. package/dist/MsaViewPanel/util.d.ts +39 -0
  114. package/dist/MsaViewPanel/util.js +44 -0
  115. package/dist/index.d.ts +11 -11
  116. package/dist/jbrowse-plugin-msaview.umd.production.min.js +56 -68
  117. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  118. package/dist/utils/browserAlign.d.ts +42 -0
  119. package/dist/utils/browserAlign.js +297 -0
  120. package/dist/utils/browserAlign.test.d.ts +1 -0
  121. package/dist/utils/browserAlign.test.js +85 -0
  122. package/dist/utils/ebiBlast.d.ts +10 -1
  123. package/dist/utils/ebiBlast.js +27 -1
  124. package/dist/utils/homologSearch.d.ts +31 -0
  125. package/dist/utils/homologSearch.js +6 -0
  126. package/dist/utils/msa.d.ts +3 -11
  127. package/dist/utils/msa.js +14 -29
  128. package/dist/utils/msaRows.d.ts +11 -8
  129. package/dist/utils/msaRows.js +14 -12
  130. package/dist/utils/phmmer.d.ts +11 -1
  131. package/dist/utils/phmmer.js +49 -12
  132. package/dist/utils/unirefHomologs.d.ts +94 -0
  133. package/dist/utils/unirefHomologs.js +193 -0
  134. package/dist/utils/unirefHomologs.test.d.ts +1 -0
  135. package/dist/utils/unirefHomologs.test.js +118 -0
  136. package/dist/version.d.ts +1 -1
  137. package/dist/version.js +1 -1
  138. package/package.json +23 -22
  139. package/src/AddHighlightModel/index.tsx +1 -1
  140. package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
  141. package/src/LaunchMsaView/codingFeature.test.ts +96 -0
  142. package/src/LaunchMsaView/codingFeature.ts +49 -0
  143. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +52 -26
  144. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +9 -1
  145. package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
  146. package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
  147. package/src/LaunchMsaView/components/BlastQuery/MsaAlgorithmSelect.tsx +7 -2
  148. package/src/LaunchMsaView/components/BlastQuery/consts.ts +77 -2
  149. package/src/LaunchMsaView/components/HelpButton.tsx +33 -0
  150. package/src/LaunchMsaView/components/HelpDialog.tsx +78 -0
  151. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +54 -34
  152. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +13 -2
  153. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +4 -0
  154. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +28 -5
  155. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +4 -1
  156. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +63 -13
  157. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +4 -0
  158. package/src/LaunchMsaView/components/QueryRowSelector.tsx +35 -7
  159. package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
  160. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +62 -3
  161. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +37 -15
  162. package/src/LaunchMsaView/components/TabPanel.test.tsx +51 -0
  163. package/src/LaunchMsaView/components/TabPanel.tsx +16 -4
  164. package/src/LaunchMsaView/components/TranscriptSelector.tsx +6 -1
  165. package/src/LaunchMsaView/components/calculateProteinSequence.ts +29 -10
  166. package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
  167. package/src/LaunchMsaView/components/launchPlacement.tsx +41 -0
  168. package/src/LaunchMsaView/components/useFeatureSequence.ts +18 -11
  169. package/src/LaunchMsaView/components/useTranscriptSelection.test.tsx +66 -0
  170. package/src/LaunchMsaView/components/useTranscriptSelection.ts +41 -2
  171. package/src/LaunchMsaView/detectQueryRow.test.ts +34 -0
  172. package/src/LaunchMsaView/detectQueryRow.ts +22 -4
  173. package/src/LaunchMsaView/index.ts +32 -13
  174. package/src/LaunchMsaView/launchTarget.test.ts +88 -15
  175. package/src/LaunchMsaView/launchTarget.ts +49 -14
  176. package/src/LaunchMsaView/useQueryRowName.test.ts +54 -0
  177. package/src/LaunchMsaView/useQueryRowName.ts +22 -1
  178. package/src/LaunchMsaView/util.ts +8 -4
  179. package/src/LaunchMsaViewExtensionPoint/index.test.ts +23 -0
  180. package/src/LaunchMsaViewExtensionPoint/index.ts +32 -6
  181. package/src/MsaViewPanel/afterCreateAutoruns.ts +85 -27
  182. package/src/MsaViewPanel/components/LaunchProgress.tsx +38 -7
  183. package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +50 -2
  184. package/src/MsaViewPanel/components/MsaViewPanel.tsx +10 -2
  185. package/src/MsaViewPanel/doLaunchBlast.test.ts +207 -0
  186. package/src/MsaViewPanel/doLaunchBlast.ts +92 -142
  187. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +13 -0
  188. package/src/MsaViewPanel/doLaunchOrthologs.ts +55 -5
  189. package/src/MsaViewPanel/genomeToMSA.test.ts +251 -31
  190. package/src/MsaViewPanel/genomeToMSA.ts +35 -7
  191. package/src/MsaViewPanel/loadProteinDomains.test.ts +52 -0
  192. package/src/MsaViewPanel/loadProteinDomains.ts +24 -11
  193. package/src/MsaViewPanel/model.test.ts +196 -0
  194. package/src/MsaViewPanel/model.ts +204 -55
  195. package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +152 -32
  196. package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +21 -11
  197. package/src/MsaViewPanel/msaDataStore.ts +10 -0
  198. package/src/MsaViewPanel/observeProteinHighlights.test.ts +27 -3
  199. package/src/MsaViewPanel/processInit.test.ts +84 -0
  200. package/src/MsaViewPanel/resolveConnectedTranscript.ts +160 -0
  201. package/src/MsaViewPanel/runLaunch.test.ts +54 -0
  202. package/src/MsaViewPanel/runLaunch.ts +39 -0
  203. package/src/MsaViewPanel/storedData.test.ts +26 -0
  204. package/src/MsaViewPanel/structureConnection.ts +0 -26
  205. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +14 -5
  206. package/src/MsaViewPanel/util.ts +75 -0
  207. package/src/utils/browserAlign.test.ts +102 -0
  208. package/src/utils/browserAlign.ts +352 -0
  209. package/src/utils/ebiBlast.ts +34 -0
  210. package/src/utils/homologSearch.ts +51 -0
  211. package/src/utils/msa.ts +19 -41
  212. package/src/utils/msaRows.ts +20 -16
  213. package/src/utils/phmmer.ts +57 -11
  214. package/src/utils/unirefHomologs.test.ts +149 -0
  215. package/src/utils/unirefHomologs.ts +323 -0
  216. package/src/version.ts +1 -1
  217. package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
  218. package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
  219. package/dist/MsaViewPanel/structureConnection.test.js +0 -53
  220. package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
  221. package/src/MsaViewPanel/structureConnection.test.ts +0 -62
  222. /package/dist/{MsaViewPanel/structureConnection.test.d.ts → LaunchMsaView/codingFeature.test.d.ts} +0 -0
@@ -6,12 +6,17 @@ import { autorun } from 'mobx';
6
6
  import { MSAModelF } from 'react-msaview';
7
7
  import { autoLoadProteinDomains, launchBlastIfNeeded, launchOrthologsIfNeeded, loadStoredData, observeProteinHighlights, processInit, runCleanup, storeDataToIndexedDB, syncGenomeHoverToMsaColumn, } from './afterCreateAutoruns';
8
8
  import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
9
+ import { deleteMsaData } from './msaDataStore';
10
+ import { resolveConnectedTranscriptIfNeeded } from './resolveConnectedTranscript';
9
11
  /**
10
- * Where the ortholog set comes from. NCBI's sets cover vertebrates and
12
+ * Where the homolog set comes from. NCBI's ortholog sets cover vertebrates and
11
13
  * insects; PANTHER's span its 144 reference proteomes, human to yeast to
12
- * Arabidopsis, so a gene from outside NCBI's scope aligns only through it.
14
+ * Arabidopsis; a UniRef cluster is every UniProtKB entry within 50% (or 90%)
15
+ * identity of the query, one per species, from any organism at all -- and the
16
+ * one source of the three whose rows need no ortholog call, only UniProt's
17
+ * REST api (see utils/unirefHomologs.ts).
13
18
  */
14
- export const orthologSources = ['ncbi', 'panther'];
19
+ export const orthologSources = ['ncbi', 'panther', 'uniref'];
15
20
  /**
16
21
  * #stateModel MsaViewPlugin
17
22
  * extends
@@ -28,6 +33,14 @@ export default function stateModelFactory() {
28
33
  * #property
29
34
  */
30
35
  connectedFeature: types.frozen(),
36
+ /**
37
+ * #property
38
+ * the short form of `connectedFeature`: a transcript id (NM_000546.6)
39
+ * looked up in the connected genome view's tracks at launch, so a
40
+ * session spec can name the transcript instead of carrying its whole
41
+ * exon model. Resolved once into `connectedFeature` and left in place.
42
+ */
43
+ connectedTranscript: types.maybe(types.string),
31
44
  /**
32
45
  * #property
33
46
  */
@@ -45,6 +58,17 @@ export default function stateModelFactory() {
45
58
  // error-pages the whole app rather than just this view. Restore
46
59
  // stripDefault once a release ships it.
47
60
  querySeqName: 'QUERY',
61
+ /**
62
+ * #property
63
+ * transcript residues before the query row's first residue, so a
64
+ * transcript position is a row position plus this. A pasted alignment
65
+ * often carries only the aligned region of the query -- BLAST reports
66
+ * what it aligned -- and mapping that row's first residue to the
67
+ * transcript's first residue put every click off by the trimmed
68
+ * amount. Negative when the row runs past the transcript's start.
69
+ */
70
+ // see querySeqName above re: types.stripDefault
71
+ querySeqOffset: 0,
48
72
  /**
49
73
  * #property
50
74
  */
@@ -62,6 +86,23 @@ export default function stateModelFactory() {
62
86
  * #property
63
87
  */
64
88
  dataStoreId: types.maybe(types.string),
89
+ /**
90
+ * #property
91
+ * whether the request above has already produced this view's alignment.
92
+ *
93
+ * The params used to be cleared to mark that, which threw away the only
94
+ * statement of what the view is: browser storage expires an alignment
95
+ * after 7 days, and the view could then only tell the user to start
96
+ * over from the gene. A flag says the same thing and duplicates
97
+ * nothing, where a kept copy of the request put a second protein
98
+ * sequence and a second exon model in every saved session.
99
+ *
100
+ * A snapshot property rather than a volatile, because the case it
101
+ * exists for is by construction a later session: expiry is discovered
102
+ * by `loadStoredData` on restore, and a volatile would be false there
103
+ * and refire the whole search unasked.
104
+ */
105
+ launchCompleted: false,
65
106
  /**
66
107
  * #property
67
108
  */
@@ -76,10 +117,6 @@ export default function stateModelFactory() {
76
117
  * #volatile
77
118
  */
78
119
  progress: '',
79
- /**
80
- * #volatile
81
- */
82
- error: undefined,
83
120
  /**
84
121
  * #volatile
85
122
  */
@@ -109,20 +146,6 @@ export default function stateModelFactory() {
109
146
  * when NCBI returns no domains (leaving the annotation list empty)
110
147
  */
111
148
  domainsRequested: false,
112
- }))
113
- .views(self => ({
114
- /**
115
- * #method
116
- */
117
- getRowByName(rowName) {
118
- return self.rows.find(r => r[0] === rowName);
119
- },
120
- /**
121
- * #method
122
- */
123
- getSequenceByRowName(rowName) {
124
- return this.getRowByName(rowName)?.[1];
125
- },
126
149
  }))
127
150
  .views(self => ({
128
151
  /**
@@ -176,6 +199,26 @@ export default function stateModelFactory() {
176
199
  ...this.connectedClickHighlights,
177
200
  ];
178
201
  },
202
+ }))
203
+ .views(self => ({
204
+ /**
205
+ * #getter
206
+ * overrides base
207
+ *
208
+ * react-msaview drops any snapshot document over 50kb and warns that a
209
+ * link to the view opens without it. True for an alignment this browser
210
+ * is holding in IndexedDB; wrong for an indexed view, whose kept `init`
211
+ * names a bgzip block over HTTP that processInit refetches wherever the
212
+ * session is opened -- so that one showed "Not in the link" about an
213
+ * alignment the link does carry.
214
+ */
215
+ get hostRestoresData() {
216
+ const { msaIndexedLocation, msaName } = self.init ?? {};
217
+ // `hostCarriesData` first: it is the base answer, the volatile a simple
218
+ // host flips, and overriding the getter without it threw that switch
219
+ // away for every view this one does not speak for
220
+ return self.hostCarriesData || !!(msaIndexedLocation && msaName);
221
+ },
179
222
  }))
180
223
  .actions(self => ({
181
224
  /**
@@ -184,12 +227,6 @@ export default function stateModelFactory() {
184
227
  setZoomToBaseLevel(arg) {
185
228
  self.zoomToBaseLevel = arg;
186
229
  },
187
- /**
188
- * #action
189
- */
190
- setError(e) {
191
- self.error = e;
192
- },
193
230
  /**
194
231
  * #action
195
232
  */
@@ -208,6 +245,12 @@ export default function stateModelFactory() {
208
245
  setBlastParams(args) {
209
246
  self.blastParams = args;
210
247
  },
248
+ /**
249
+ * #action
250
+ */
251
+ setConnectedFeature(arg) {
252
+ self.connectedFeature = arg;
253
+ },
211
254
  /**
212
255
  * #action
213
256
  */
@@ -226,6 +269,12 @@ export default function stateModelFactory() {
226
269
  setQuerySeqName(arg) {
227
270
  self.querySeqName = arg;
228
271
  },
272
+ /**
273
+ * #action
274
+ */
275
+ setQuerySeqOffset(arg) {
276
+ self.querySeqOffset = arg;
277
+ },
229
278
  /**
230
279
  * #action
231
280
  */
@@ -238,6 +287,12 @@ export default function stateModelFactory() {
238
287
  setDataStoreId(arg) {
239
288
  self.dataStoreId = arg;
240
289
  },
290
+ /**
291
+ * #action
292
+ */
293
+ setLaunchCompleted(arg) {
294
+ self.launchCompleted = arg;
295
+ },
241
296
  /**
242
297
  * #action
243
298
  */
@@ -270,21 +325,51 @@ export default function stateModelFactory() {
270
325
  },
271
326
  /**
272
327
  * #action
273
- * Abandon the launch in flight. The EBI job keeps running on their side —
274
- * nothing here can recall it, and its JobLink stays valid — so this only
275
- * stops the polling and the writes it would make. Dropping the params is
276
- * what keeps the request from refiring; the view falls back to the import
277
- * form, which is where react-msaview's own cancel leaves it too.
328
+ * Abandon the launch, in flight or failed. The EBI job keeps running on
329
+ * their side — nothing here can recall it, and its JobLink stays valid —
330
+ * so this only stops the polling and the writes it would make. Dropping
331
+ * the request (the params, or the init) is what keeps it from refiring on
332
+ * the next reload; the view falls back to the import form, which is where
333
+ * react-msaview's own cancel leaves it too.
278
334
  */
279
335
  cancelLaunch() {
280
336
  self.launchController?.abort();
281
337
  self.launchController = undefined;
282
338
  self.blastParams = undefined;
283
339
  self.orthologParams = undefined;
340
+ self.init = undefined;
341
+ self.launchCompleted = false;
284
342
  self.progress = '';
285
343
  self.rid = undefined;
286
344
  self.error = undefined;
287
345
  },
346
+ /**
347
+ * #action
348
+ * Run the request again. The request IS the params, so re-stating them is
349
+ * the whole retry: each is a frozen property, and a fresh object is a
350
+ * change the launch autoruns wake on.
351
+ *
352
+ * A launch that already succeeded keeps its params and is marked
353
+ * `launchCompleted`, so clearing that mark is the whole retry for a view
354
+ * whose stored alignment expired.
355
+ */
356
+ retryLaunch() {
357
+ self.launchController?.abort();
358
+ self.launchController = undefined;
359
+ self.progress = '';
360
+ self.rid = undefined;
361
+ self.error = undefined;
362
+ self.launchCompleted = false;
363
+ if (self.blastParams) {
364
+ self.blastParams = { ...self.blastParams };
365
+ }
366
+ if (self.orthologParams) {
367
+ self.orthologParams = { ...self.orthologParams };
368
+ }
369
+ if (self.init) {
370
+ self.init = { ...self.init };
371
+ }
372
+ },
288
373
  /**
289
374
  * #action
290
375
  */
@@ -325,23 +410,63 @@ export default function stateModelFactory() {
325
410
  },
326
411
  };
327
412
  })
328
- .views(self => ({
329
- /**
330
- * #method
331
- */
332
- extraViewMenuItems() {
333
- return [
334
- {
335
- label: 'Zoom to base level on click?',
336
- checked: self.zoomToBaseLevel,
337
- type: 'checkbox',
338
- onClick: () => {
339
- self.setZoomToBaseLevel(!self.zoomToBaseLevel);
413
+ .actions(self => {
414
+ const superReset = self.reset.bind(self);
415
+ return {
416
+ /**
417
+ * #action
418
+ * overrides base
419
+ *
420
+ * react-msaview's reset applies a snapshot filtered to its own
421
+ * `preservedOnReset` list, and a downstream property is never on it:
422
+ * returning to the import form dropped the view's name, un-minimized
423
+ * it, and forgot the zoom-on-click preference. The volatiles fail the
424
+ * other way -- applySnapshot cannot reach them, so the last file's
425
+ * state carried into the next one and the domain auto-load, which fires
426
+ * once per view, never fired again.
427
+ */
428
+ reset() {
429
+ const { displayName, minimized, zoomToBaseLevel, dataStoreId } = self;
430
+ superReset();
431
+ if (displayName !== undefined) {
432
+ self.setDisplayName(displayName);
433
+ }
434
+ self.setMinimized(minimized);
435
+ self.setZoomToBaseLevel(zoomToBaseLevel);
436
+ self.setDomainsRequested(false);
437
+ self.setLastStoredData(undefined);
438
+ if (dataStoreId) {
439
+ // nothing points at that row now
440
+ void deleteMsaData(dataStoreId);
441
+ }
442
+ },
443
+ };
444
+ })
445
+ .views(self => {
446
+ // react-msaview's extraViewMenuItems() has had no caller since v5.6.0, in
447
+ // that package or in JBrowse; the view hamburger renders menuItems(), so
448
+ // extend that
449
+ const superMenuItems = self.menuItems.bind(self);
450
+ return {
451
+ /**
452
+ * #method
453
+ * overrides base
454
+ */
455
+ menuItems() {
456
+ return [
457
+ ...superMenuItems(),
458
+ {
459
+ label: 'Zoom to base level on click?',
460
+ type: 'checkbox',
461
+ checked: self.zoomToBaseLevel,
462
+ onClick: () => {
463
+ self.setZoomToBaseLevel(!self.zoomToBaseLevel);
464
+ },
340
465
  },
341
- },
342
- ];
343
- },
344
- }))
466
+ ];
467
+ },
468
+ };
469
+ })
345
470
  .actions(self => ({
346
471
  afterCreate() {
347
472
  runCleanup();
@@ -354,6 +479,7 @@ export default function stateModelFactory() {
354
479
  for (const fn of [
355
480
  loadStoredData,
356
481
  storeDataToIndexedDB,
482
+ resolveConnectedTranscriptIfNeeded,
357
483
  launchBlastIfNeeded,
358
484
  launchOrthologsIfNeeded,
359
485
  processInit,
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,166 @@
1
+ import { beforeEach, describe, expect, test, vi } from 'vitest';
2
+ import { doLaunchBlast } from './doLaunchBlast';
3
+ import stateModelFactory from './model';
4
+ import { deleteMsaData } from './msaDataStore';
5
+ // the launch autoruns are live on a real model, and a blastParams write is what
6
+ // wakes them -- so the search is mocked rather than sent to EBI. The session is
7
+ // mocked for the same reason: the hover and highlight autoruns reach for one on
8
+ // every change, and there is no session around a bare model.
9
+ vi.mock('@jbrowse/core/util', async (importOriginal) => ({
10
+ ...(await importOriginal()),
11
+ getSession: () => ({ views: [], hovered: undefined }),
12
+ }));
13
+ vi.mock('./doLaunchBlast', () => ({ doLaunchBlast: vi.fn() }));
14
+ vi.mock('./msaDataStore', () => ({
15
+ cleanupOldData: vi.fn(async () => { }),
16
+ deleteMsaData: vi.fn(async () => { }),
17
+ generateDataStoreId: vi.fn(),
18
+ retrieveMsaData: vi.fn(),
19
+ storeMsaData: vi.fn(),
20
+ }));
21
+ const BLAST_PARAMS = {
22
+ searchProgram: 'blastp',
23
+ blastDatabase: 'uniprotkb_swissprot',
24
+ msaAlgorithm: 'clustalo',
25
+ proteinSequence: 'MKV',
26
+ };
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ vi.mocked(doLaunchBlast).mockReturnValue(new Promise(() => { }));
30
+ });
31
+ function view() {
32
+ return stateModelFactory().create({ type: 'MsaView', id: 'msaview1' });
33
+ }
34
+ // The item lived in extraViewMenuItems(), which react-msaview stopped calling
35
+ // in v5.6.0 and is deleting: the view hamburger renders menuItems(), so a
36
+ // checkbox nobody can reach is a feature that silently left.
37
+ describe('view menu', () => {
38
+ test('offers the zoom-to-base-level toggle', () => {
39
+ expect(view()
40
+ .menuItems()
41
+ .map(f => ('label' in f ? f.label : ''))).toContain('Zoom to base level on click?');
42
+ });
43
+ test('the toggle drives zoomToBaseLevel', () => {
44
+ const model = view();
45
+ const item = model.menuItems().at(-1);
46
+ expect(item.checked).toBe(false);
47
+ item.onClick();
48
+ expect(model.zoomToBaseLevel).toBe(true);
49
+ });
50
+ });
51
+ // the params ARE the request, and they outlive the failure: a view that only
52
+ // drew the error resubmitted the job on every reload and offered no way out
53
+ describe('a failed launch', () => {
54
+ test('retries by re-stating the request', () => {
55
+ const model = view();
56
+ model.setBlastParams({ ...BLAST_PARAMS });
57
+ expect(doLaunchBlast).toHaveBeenCalledTimes(1);
58
+ model.setError(new Error('EBI said no'));
59
+ model.retryLaunch();
60
+ expect(model.error).toBeUndefined();
61
+ expect(model.blastParams).toEqual(BLAST_PARAMS);
62
+ expect(doLaunchBlast).toHaveBeenCalledTimes(2);
63
+ });
64
+ // the params used to be cleared on success to mark the request done, which
65
+ // threw away the only durable statement of what the view is: a stored
66
+ // alignment expires after 7 days and there was nothing left to run again
67
+ test('a successful launch keeps its request and is not run twice', async () => {
68
+ vi.mocked(doLaunchBlast).mockResolvedValue({
69
+ msa: '>a\nMK\n>b\nMK',
70
+ tree: '(a,b);',
71
+ treeMetadata: '{}',
72
+ });
73
+ const model = view();
74
+ model.setBlastParams({ ...BLAST_PARAMS });
75
+ await new Promise(res => setTimeout(res, 0));
76
+ expect(model.blastParams).toEqual(BLAST_PARAMS);
77
+ expect(model.launchCompleted).toBe(true);
78
+ expect(doLaunchBlast).toHaveBeenCalledTimes(1);
79
+ vi.mocked(doLaunchBlast).mockReturnValue(new Promise(() => { }));
80
+ model.setError(new Error('alignment is no longer in browser storage'));
81
+ model.retryLaunch();
82
+ expect(model.error).toBeUndefined();
83
+ expect(model.launchCompleted).toBe(false);
84
+ expect(doLaunchBlast).toHaveBeenCalledTimes(2);
85
+ });
86
+ // a session reopens with the request still on it, so the mark is what stops
87
+ // the whole search running again unasked
88
+ test('a restored session does not rerun a request it already ran', () => {
89
+ stateModelFactory().create({
90
+ type: 'MsaView',
91
+ id: 'msaview2',
92
+ blastParams: { ...BLAST_PARAMS },
93
+ launchCompleted: true,
94
+ });
95
+ expect(doLaunchBlast).not.toHaveBeenCalled();
96
+ });
97
+ test('dismisses by dropping it', () => {
98
+ const model = view();
99
+ model.setBlastParams({ ...BLAST_PARAMS });
100
+ model.setError(new Error('EBI said no'));
101
+ model.cancelLaunch();
102
+ expect(model.blastParams).toBeUndefined();
103
+ expect(model.error).toBeUndefined();
104
+ });
105
+ });
106
+ // react-msaview drops a snapshot document past 50kb and reports it through
107
+ // `unshareableData`, which the header renders as "Not in the link". An indexed
108
+ // view's alignment IS in the link -- its init names the block and processInit
109
+ // refetches it -- so that view answers the question itself.
110
+ describe('what the snapshot cannot carry', () => {
111
+ const big = `>a\n${'M'.repeat(60_000)}`;
112
+ test('an indexed view says the host brings its alignment back', () => {
113
+ const model = view();
114
+ model.setInit({
115
+ msaIndexedLocation: { uri: 'msa.fa.gz' },
116
+ msaName: 'ENST1',
117
+ });
118
+ model.setMSA(big);
119
+ expect(model.hostRestoresData).toBe(true);
120
+ expect(model.unshareableData).toEqual([]);
121
+ });
122
+ // the base getter reads a volatile a host flips to say it brings the data
123
+ // back by its own means; overriding without it threw that switch away
124
+ test('a host that says it carries the data is still believed', () => {
125
+ const model = view();
126
+ model.setMSA(big);
127
+ expect(model.unshareableData).not.toEqual([]);
128
+ model.setHostCarriesData(true);
129
+ expect(model.hostRestoresData).toBe(true);
130
+ expect(model.unshareableData).toEqual([]);
131
+ });
132
+ test('a pasted alignment is reported, because a link really does lose it', () => {
133
+ const model = view();
134
+ model.setMSA(big);
135
+ expect(model.hostRestoresData).toBe(false);
136
+ expect(model.unshareableData.map(d => d.what)).toEqual(['alignment']);
137
+ });
138
+ });
139
+ // react-msaview's reset keeps only what is on its own preservedOnReset list,
140
+ // and a downstream property is never on it; its volatiles survive instead,
141
+ // which is the direction that carries the last file's state into the next one
142
+ describe('returning to the import form', () => {
143
+ test('keeps what is about the view, not about the file', () => {
144
+ const model = view();
145
+ model.setDisplayName('BLAST - TP53');
146
+ model.setMinimized(true);
147
+ model.setZoomToBaseLevel(true);
148
+ model.setMSA('>a\nMK');
149
+ model.reset();
150
+ expect(model.displayName).toBe('BLAST - TP53');
151
+ expect(model.minimized).toBe(true);
152
+ expect(model.zoomToBaseLevel).toBe(true);
153
+ expect(model.dataInitialized).toBe(false);
154
+ });
155
+ test('clears the volatiles applySnapshot cannot reach, and the stored row', () => {
156
+ const model = view();
157
+ model.setDomainsRequested(true);
158
+ model.setLastStoredData({ msa: '>a\nMK' });
159
+ model.setDataStoreId('msa-1');
160
+ model.reset();
161
+ expect(model.domainsRequested).toBe(false);
162
+ expect(model.lastStoredData).toBeUndefined();
163
+ expect(model.dataStoreId).toBeUndefined();
164
+ expect(deleteMsaData).toHaveBeenCalledWith('msa-1');
165
+ });
166
+ });
@@ -11,12 +11,23 @@ interface CoordModel {
11
11
  p2gCodon: Record<number, number[]>;
12
12
  } | undefined;
13
13
  mafRegion?: MafRegion;
14
- rows: string[][];
14
+ /** transcript residues before the query row's first residue */
15
+ querySeqOffset?: number;
16
+ /**
17
+ * react-msaview's converter, which is the only thing that knows which columns
18
+ * are on screen. `rows` carries the whole gapped alignment, so counting
19
+ * residues along one of them answers for a column index the user cannot
20
+ * produce once `hideGaps` drops columns -- and it drops them as soon as a
21
+ * clade collapses or allowedGappyness falls below 100.
22
+ */
23
+ visibleColToSeqPos: (rowName: string, visibleCol: number) => number | undefined;
15
24
  }
16
25
  /**
17
26
  * The genome regions covered by MSA column `coord` of the query row, in 0-based
18
27
  * half-open coordinates (what bpToPx and navTo take).
19
28
  *
29
+ * `coord` is a VISIBLE column -- what mouseCol and mouseClickCol carry.
30
+ *
20
31
  * Usually one region -- one codon, or one base in a MAF alignment -- but a
21
32
  * codon split across an exon boundary yields one region per contiguous piece,
22
33
  * which is why this returns a list.
@@ -1,20 +1,17 @@
1
1
  import { getCodonRanges } from 'g2p_mapper';
2
- import { gappedToUngappedPosition } from './structureConnection';
3
2
  /**
4
3
  * The genome regions covered by MSA column `coord` of the query row, in 0-based
5
4
  * half-open coordinates (what bpToPx and navTo take).
6
5
  *
6
+ * `coord` is a VISIBLE column -- what mouseCol and mouseClickCol carry.
7
+ *
7
8
  * Usually one region -- one codon, or one base in a MAF alignment -- but a
8
9
  * codon split across an exon boundary yields one region per contiguous piece,
9
10
  * which is why this returns a list.
10
11
  */
11
12
  export function msaCoordToGenomeRegions({ model, coord: mouseCol, }) {
12
- const { querySeqName, transcriptToMsaMap, mafRegion, rows } = model;
13
- const querySeq = rows.find(f => f[0] === querySeqName)?.[1];
14
- if (!querySeq) {
15
- return [];
16
- }
17
- const ungappedPos = gappedToUngappedPosition(querySeq, mouseCol);
13
+ const { querySeqName, transcriptToMsaMap, mafRegion } = model;
14
+ const ungappedPos = model.visibleColToSeqPos(querySeqName, mouseCol);
18
15
  if (ungappedPos === undefined) {
19
16
  return [];
20
17
  }
@@ -26,13 +23,16 @@ export function msaCoordToGenomeRegions({ model, coord: mouseCol, }) {
26
23
  }
27
24
  if (transcriptToMsaMap) {
28
25
  const { refName, p2gCodon } = transcriptToMsaMap;
26
+ // the row is often only the aligned region of the transcript's protein, so
27
+ // its residues are the transcript's shifted by querySeqOffset
28
+ const proteinPos = ungappedPos + (model.querySeqOffset ?? 0);
29
29
  // p2gCodon holds every genomic base of the codon, so the range is exact on
30
30
  // either strand. Deriving it from consecutive p2g entries instead
31
31
  // (p2g[pos]..p2g[pos+1]) was off by one base on the reverse strand -- where
32
32
  // p2g stores the codon's *highest* coordinate -- dropped the final residue,
33
33
  // whose successor has no p2g entry, and spanned the whole intron for a
34
34
  // codon split across an exon boundary.
35
- return (getCodonRanges(p2gCodon, ungappedPos)?.map(([start, end]) => ({
35
+ return (getCodonRanges(p2gCodon, proteinPos)?.map(([start, end]) => ({
36
36
  refName,
37
37
  start,
38
38
  end,