jbrowse-plugin-msaview 3.6.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 (156) hide show
  1. package/README.md +8 -0
  2. package/dist/LaunchMsaView/cleanProteinSequence.test.d.ts +1 -0
  3. package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
  4. package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +29 -32
  5. package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +10 -15
  6. package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
  7. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +25 -22
  8. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +14 -19
  9. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
  10. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
  11. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
  12. package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
  13. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +19 -14
  14. package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +3 -1
  15. package/dist/LaunchMsaView/components/SubmitCancelActions.js +10 -7
  16. package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +20 -2
  17. package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +8 -1
  18. package/dist/LaunchMsaView/components/calculateProteinSequence.js +25 -20
  19. package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
  20. package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
  21. package/dist/LaunchMsaView/detectQueryRow.js +4 -7
  22. package/dist/LaunchMsaView/util.d.ts +6 -0
  23. package/dist/LaunchMsaView/util.js +7 -1
  24. package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
  25. package/dist/MsaViewPanel/afterCreateAutoruns.js +12 -3
  26. package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
  27. package/dist/MsaViewPanel/applyRegion.js +19 -0
  28. package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
  29. package/dist/MsaViewPanel/applyRegion.test.js +45 -0
  30. package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
  31. package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
  32. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
  33. package/dist/MsaViewPanel/genomeToMSA.js +3 -3
  34. package/dist/MsaViewPanel/genomeToMSA.test.js +69 -23
  35. package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
  36. package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
  37. package/dist/MsaViewPanel/model.d.ts +18 -14
  38. package/dist/MsaViewPanel/model.js +17 -0
  39. package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
  40. package/dist/MsaViewPanel/msaDataStore.js +3 -12
  41. package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
  42. package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
  43. package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
  44. package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
  45. package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
  46. package/dist/MsaViewPanel/resolveConnectedTranscript.js +103 -68
  47. package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
  48. package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
  49. package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
  50. package/dist/MsaViewPanel/runLaunch.js +15 -4
  51. package/dist/MsaViewPanel/runLaunch.test.js +16 -0
  52. package/dist/MsaViewPanel/storedData.test.js +23 -0
  53. package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +3 -2
  54. package/dist/MsaViewPanel/util.d.ts +23 -23
  55. package/dist/MsaViewPanel/util.js +23 -24
  56. package/dist/index.js +17 -10
  57. package/dist/jbrowse-plugin-msaview.umd.production.min.js +36 -36
  58. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  59. package/dist/utils/blastCache.d.ts +26 -2
  60. package/dist/utils/blastCache.js +51 -39
  61. package/dist/utils/blastCache.test.js +46 -19
  62. package/dist/utils/domainCache.d.ts +1 -1
  63. package/dist/utils/domainCache.js +18 -14
  64. package/dist/utils/ebiJobDispatcher.d.ts +11 -0
  65. package/dist/utils/ebiJobDispatcher.js +12 -13
  66. package/dist/utils/ebiJobDispatcher.test.js +17 -3
  67. package/dist/utils/eutils.d.ts +14 -0
  68. package/dist/utils/eutils.js +48 -14
  69. package/dist/utils/eutils.test.d.ts +1 -0
  70. package/dist/utils/eutils.test.js +31 -0
  71. package/dist/utils/idb.d.ts +16 -3
  72. package/dist/utils/idb.js +51 -5
  73. package/dist/utils/idb.test.d.ts +1 -0
  74. package/dist/utils/idb.test.js +78 -0
  75. package/dist/utils/ncbiDomains.js +4 -4
  76. package/dist/utils/ncbiOrthologs.d.ts +1 -1
  77. package/dist/utils/ncbiOrthologs.js +18 -17
  78. package/dist/utils/ncbiOrthologs.test.js +17 -1
  79. package/dist/utils/ncbiTaxonomy.js +14 -15
  80. package/dist/utils/taxonomyNames.js +23 -19
  81. package/dist/utils/taxonomyNames.test.d.ts +1 -0
  82. package/dist/utils/taxonomyNames.test.js +33 -0
  83. package/dist/utils/unirefHomologs.js +2 -2
  84. package/dist/utils/unirefHomologs.test.js +8 -1
  85. package/dist/version.d.ts +1 -1
  86. package/dist/version.js +1 -1
  87. package/package.json +9 -9
  88. package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
  89. package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +35 -32
  90. package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +10 -14
  91. package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
  92. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +34 -21
  93. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +18 -21
  94. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
  95. package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
  96. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +19 -13
  97. package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +27 -2
  98. package/src/LaunchMsaView/components/SubmitCancelActions.tsx +12 -7
  99. package/src/LaunchMsaView/components/calculateProteinSequence.ts +28 -18
  100. package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
  101. package/src/LaunchMsaView/detectQueryRow.ts +5 -7
  102. package/src/LaunchMsaView/util.ts +7 -1
  103. package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
  104. package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
  105. package/src/MsaViewPanel/afterCreateAutoruns.ts +12 -3
  106. package/src/MsaViewPanel/applyRegion.test.ts +55 -0
  107. package/src/MsaViewPanel/applyRegion.ts +33 -0
  108. package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
  109. package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
  110. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
  111. package/src/MsaViewPanel/genomeToMSA.test.ts +75 -23
  112. package/src/MsaViewPanel/genomeToMSA.ts +3 -2
  113. package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
  114. package/src/MsaViewPanel/model.ts +22 -0
  115. package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
  116. package/src/MsaViewPanel/msaDataStore.ts +4 -12
  117. package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
  118. package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
  119. package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
  120. package/src/MsaViewPanel/resolveConnectedTranscript.ts +154 -71
  121. package/src/MsaViewPanel/runLaunch.test.ts +19 -0
  122. package/src/MsaViewPanel/runLaunch.ts +21 -4
  123. package/src/MsaViewPanel/storedData.test.ts +32 -0
  124. package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +3 -2
  125. package/src/MsaViewPanel/util.ts +38 -29
  126. package/src/index.ts +19 -10
  127. package/src/utils/blastCache.test.ts +59 -20
  128. package/src/utils/blastCache.ts +62 -39
  129. package/src/utils/domainCache.ts +27 -15
  130. package/src/utils/ebiJobDispatcher.test.ts +18 -3
  131. package/src/utils/ebiJobDispatcher.ts +12 -16
  132. package/src/utils/eutils.test.ts +39 -0
  133. package/src/utils/eutils.ts +64 -14
  134. package/src/utils/idb.test.ts +106 -0
  135. package/src/utils/idb.ts +57 -5
  136. package/src/utils/ncbiDomains.ts +4 -4
  137. package/src/utils/ncbiOrthologs.test.ts +20 -0
  138. package/src/utils/ncbiOrthologs.ts +26 -28
  139. package/src/utils/ncbiTaxonomy.ts +17 -20
  140. package/src/utils/taxonomyNames.test.ts +41 -0
  141. package/src/utils/taxonomyNames.ts +33 -19
  142. package/src/utils/unirefHomologs.test.ts +11 -1
  143. package/src/utils/unirefHomologs.ts +2 -2
  144. package/src/version.ts +1 -1
  145. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
  146. package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
  147. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
  148. package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
  149. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
  150. package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
  151. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -13
  152. package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -22
  153. package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
  154. package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
  155. package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
  156. package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -42
@@ -47,7 +47,7 @@ describe('genomeToMSA', () => {
47
47
  });
48
48
  const model = {
49
49
  querySeqName: 'hg38.chr1',
50
- rows: [['hg38.chr1', 'ACGTACGTAC']],
50
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
51
51
  transcriptToMsaMap: undefined,
52
52
  mafRegion: {
53
53
  refName: 'chr1',
@@ -67,7 +67,7 @@ describe('genomeToMSA', () => {
67
67
  });
68
68
  const model = {
69
69
  querySeqName: 'hg38.chr1',
70
- rows: [['hg38.chr1', 'ACGTACGTAC']],
70
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
71
71
  transcriptToMsaMap: undefined,
72
72
  mafRegion: {
73
73
  refName: 'chr1',
@@ -92,7 +92,7 @@ describe('genomeToMSA', () => {
92
92
  const mockSeqPosToVisibleCol = vi.fn().mockReturnValue(5);
93
93
  const model = {
94
94
  querySeqName: 'hg38.chr1',
95
- rows: [['hg38.chr1', 'ACGTACGTAC']],
95
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
96
96
  transcriptToMsaMap: undefined,
97
97
  mafRegion: {
98
98
  refName: 'chr1',
@@ -105,6 +105,7 @@ describe('genomeToMSA', () => {
105
105
  assemblyNames: ['hg38'],
106
106
  },
107
107
  seqPosToVisibleCol: mockSeqPosToVisibleCol,
108
+ visibleColToSeqPos: () => 4,
108
109
  };
109
110
  const result = genomeToMSA({ model });
110
111
  // hover coord 1005 is 1-based, so the 0-based genome position is 1004,
@@ -112,6 +113,34 @@ describe('genomeToMSA', () => {
112
113
  expect(mockSeqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 4);
113
114
  expect(result).toBe(5);
114
115
  });
116
+ test('a position past the end of the row lights nothing', () => {
117
+ mockSession({
118
+ hovered: {
119
+ hoverFeature: {},
120
+ hoverPosition: { coord: 1009, refName: 'chr1' },
121
+ },
122
+ });
123
+ // react-msaview answers one column past the end for a residue the row
124
+ // does not have, and that column maps back to no residue
125
+ const model = {
126
+ querySeqName: 'hg38.chr1',
127
+ rowMap: new Map([['hg38.chr1', 'ACGTACG']]),
128
+ transcriptToMsaMap: undefined,
129
+ mafRegion: {
130
+ refName: 'chr1',
131
+ start: 1000,
132
+ end: 1010,
133
+ assemblyName: 'hg38',
134
+ },
135
+ connectedView: {
136
+ initialized: true,
137
+ assemblyNames: ['hg38'],
138
+ },
139
+ seqPosToVisibleCol: () => 7,
140
+ visibleColToSeqPos: () => undefined,
141
+ };
142
+ expect(genomeToMSA({ model })).toBeUndefined();
143
+ });
115
144
  test('returns undefined when hover refName does not match mafRegion', () => {
116
145
  mockSession({
117
146
  hovered: {
@@ -121,7 +150,7 @@ describe('genomeToMSA', () => {
121
150
  });
122
151
  const model = {
123
152
  querySeqName: 'hg38.chr1',
124
- rows: [['hg38.chr1', 'ACGTACGTAC']],
153
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
125
154
  transcriptToMsaMap: undefined,
126
155
  mafRegion: {
127
156
  refName: 'chr1',
@@ -148,7 +177,7 @@ describe('genomeToMSA', () => {
148
177
  });
149
178
  const model = {
150
179
  querySeqName: 'hg38.chr1',
151
- rows: [['hg38.chr1', 'ACGTACGTAC']],
180
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
152
181
  transcriptToMsaMap: undefined,
153
182
  mafRegion: {
154
183
  refName: 'chr1',
@@ -175,7 +204,7 @@ describe('genomeToMSA', () => {
175
204
  });
176
205
  const model = {
177
206
  querySeqName: 'hg38.chr1',
178
- rows: [['hg38.chr1', 'ACGTACGTAC']],
207
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
179
208
  transcriptToMsaMap: undefined,
180
209
  mafRegion: {
181
210
  refName: 'chr1',
@@ -201,7 +230,7 @@ describe('genomeToMSA', () => {
201
230
  });
202
231
  const model = {
203
232
  querySeqName: 'hg38.chr1',
204
- rows: [['hg38.chr1', 'ACGTACGTAC']],
233
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
205
234
  transcriptToMsaMap: undefined,
206
235
  mafRegion: {
207
236
  refName: 'chr1',
@@ -231,7 +260,7 @@ describe('genomeToMSA', () => {
231
260
  const model = {
232
261
  querySeqName: 'QUERY',
233
262
  querySeqOffset: 0,
234
- rows: [['QUERY', 'MKVLTAEEK']],
263
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
235
264
  transcriptToMsaMap: {
236
265
  refName: 'chr1',
237
266
  // g2p is keyed by 0-based genome position, the hover coord is 1-based
@@ -258,7 +287,7 @@ describe('genomeToMSA', () => {
258
287
  const mockSeqPosToVisibleCol = vi.fn();
259
288
  const model = {
260
289
  querySeqName: 'QUERY',
261
- rows: [['QUERY', 'MKVLTAEEK']],
290
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
262
291
  transcriptToMsaMap: {
263
292
  refName: 'chr1',
264
293
  g2p: { 1004: 10 },
@@ -283,7 +312,7 @@ describe('genomeToMSA', () => {
283
312
  const model = {
284
313
  querySeqName: 'QUERY',
285
314
  querySeqOffset: 4,
286
- rows: [['QUERY', 'MKVLTAEEK']],
315
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
287
316
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
288
317
  mafRegion: undefined,
289
318
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -304,7 +333,7 @@ describe('genomeToMSA', () => {
304
333
  const model = {
305
334
  querySeqName: 'QUERY',
306
335
  querySeqOffset: 20,
307
- rows: [['QUERY', 'MKVLTAEEK']],
336
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
308
337
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
309
338
  mafRegion: undefined,
310
339
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -326,7 +355,7 @@ describe('genomeToMSA', () => {
326
355
  const model = {
327
356
  querySeqName: 'QUERY',
328
357
  querySeqOffset: 0,
329
- rows: [['QUERY', 'MKV']],
358
+ rowMap: new Map([['QUERY', 'MKV']]),
330
359
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
331
360
  mafRegion: undefined,
332
361
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -344,7 +373,7 @@ describe('genomeToMSA', () => {
344
373
  });
345
374
  const model = {
346
375
  querySeqName: 'QUERY',
347
- rows: [['QUERY', 'MKVLTAEEK']],
376
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
348
377
  transcriptToMsaMap: {
349
378
  refName: 'chr1',
350
379
  g2p: { 1000: 0 }, // No entry for 1004
@@ -366,7 +395,7 @@ describe('genomeToMSA', () => {
366
395
  });
367
396
  const model = {
368
397
  querySeqName: 'QUERY',
369
- rows: [['QUERY', 'MKVLTAEEK']],
398
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
370
399
  transcriptToMsaMap: undefined,
371
400
  mafRegion: undefined,
372
401
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -375,10 +404,6 @@ describe('genomeToMSA', () => {
375
404
  const result = genomeToMSA({ model });
376
405
  expect(result).toBeUndefined();
377
406
  });
378
- // seqPosToVisibleCol answers 0 for a row name it does not know, so without a
379
- // guard an alignment whose query row is missing -- the default 'QUERY' on an
380
- // uploaded file, or the empty name the manual panel leaves when it matches
381
- // nothing -- lights column 0 on every genome hover
382
407
  test('returns undefined when querySeqName names no row here', () => {
383
408
  mockSession({
384
409
  hovered: {
@@ -389,7 +414,7 @@ describe('genomeToMSA', () => {
389
414
  const seqPosToVisibleCol = vi.fn(() => 0);
390
415
  const model = {
391
416
  querySeqName: 'QUERY',
392
- rows: [['some_other_row', 'MKVLTAEEK']],
417
+ rowMap: new Map([['some_other_row', 'MKVLTAEEK']]),
393
418
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 3 } },
394
419
  mafRegion: undefined,
395
420
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -398,6 +423,26 @@ describe('genomeToMSA', () => {
398
423
  expect(genomeToMSA({ model })).toBeUndefined();
399
424
  expect(seqPosToVisibleCol).not.toHaveBeenCalled();
400
425
  });
426
+ test('a query row folded into a collapsed clade still maps', () => {
427
+ mockSession({
428
+ hovered: {
429
+ hoverFeature: {},
430
+ hoverPosition: { coord: 1005, refName: 'chr1' },
431
+ },
432
+ });
433
+ const model = {
434
+ querySeqName: 'QUERY',
435
+ querySeqOffset: 0,
436
+ rows: [['collapsed_clade', 'MKVLTAEEK']],
437
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
438
+ transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 3 } },
439
+ mafRegion: undefined,
440
+ connectedView: { initialized: true, assemblyNames: ['hg38'] },
441
+ seqPosToVisibleCol: (_name, pos) => pos,
442
+ visibleColToSeqPos: (_name, col) => col,
443
+ };
444
+ expect(genomeToMSA({ model })).toBe(3);
445
+ });
401
446
  // A hover names the chromosome the assembly's way and the transcript names it
402
447
  // the annotation file's way, so on GENCODE-on-hg38 -- jbrowse.org's own
403
448
  // config -- the two are '1' and 'chr1' and pointing at a codon lit nothing.
@@ -416,7 +461,7 @@ describe('genomeToMSA', () => {
416
461
  const model = {
417
462
  querySeqName: 'QUERY',
418
463
  querySeqOffset: 0,
419
- rows: [['QUERY', 'MKVLTAEEK']],
464
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
420
465
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
421
466
  mafRegion: undefined,
422
467
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -438,7 +483,7 @@ describe('genomeToMSA', () => {
438
483
  const seqPosToVisibleCol = vi.fn();
439
484
  const model = {
440
485
  querySeqName: 'QUERY',
441
- rows: [['QUERY', 'MKVLTAEEK']],
486
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
442
487
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
443
488
  mafRegion: undefined,
444
489
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -462,7 +507,7 @@ describe('genomeToMSA', () => {
462
507
  const model = {
463
508
  querySeqName: 'QUERY',
464
509
  querySeqOffset: 0,
465
- rows: [['QUERY', 'MKVLTAEEK']],
510
+ rowMap: new Map([['QUERY', 'MKVLTAEEK']]),
466
511
  transcriptToMsaMap: { refName: 'chr1', g2p: { 1004: 10 } },
467
512
  mafRegion: undefined,
468
513
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
@@ -483,7 +528,7 @@ describe('genomeToMSA', () => {
483
528
  const seqPosToVisibleCol = vi.fn().mockReturnValue(5);
484
529
  const model = {
485
530
  querySeqName: 'hg38.chr1',
486
- rows: [['hg38.chr1', 'ACGTACGTAC']],
531
+ rowMap: new Map([['hg38.chr1', 'ACGTACGTAC']]),
487
532
  transcriptToMsaMap: undefined,
488
533
  mafRegion: {
489
534
  refName: 'chr1',
@@ -493,6 +538,7 @@ describe('genomeToMSA', () => {
493
538
  },
494
539
  connectedView: { initialized: true, assemblyNames: ['hg38'] },
495
540
  seqPosToVisibleCol,
541
+ visibleColToSeqPos: () => 4,
496
542
  };
497
543
  expect(genomeToMSA({ model })).toBe(5);
498
544
  expect(seqPosToVisibleCol).toHaveBeenCalledWith('hg38.chr1', 4);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import { doLaunchBlast } from './doLaunchBlast';
3
+ import stateModelFactory from './model';
4
+ vi.mock('@jbrowse/core/util', async (importOriginal) => ({
5
+ ...(await importOriginal()),
6
+ getSession: () => ({ views: [], hovered: undefined }),
7
+ }));
8
+ vi.mock('./doLaunchBlast', () => ({ doLaunchBlast: vi.fn() }));
9
+ vi.mock('./msaDataStore', () => ({
10
+ cleanupOldData: vi.fn(async () => { }),
11
+ deleteMsaData: vi.fn(async () => { }),
12
+ generateDataStoreId: vi.fn(),
13
+ retrieveMsaData: vi.fn(),
14
+ storeMsaData: vi.fn(),
15
+ }));
16
+ // the real launch reads querySeqName before its first await, as resolveQuery
17
+ // does, which used to make that name a reason to launch again
18
+ test('a write the launch body read does not start a second launch', () => {
19
+ vi.mocked(doLaunchBlast).mockImplementation(({ self }) => {
20
+ void self.querySeqName;
21
+ return new Promise(() => { });
22
+ });
23
+ const model = stateModelFactory().create({
24
+ type: 'MsaView',
25
+ id: 'msa1',
26
+ blastParams: {
27
+ searchProgram: 'blastp',
28
+ blastDatabase: 'uniprotkb_swissprot',
29
+ msaAlgorithm: 'clustalo',
30
+ proteinSequence: 'MKV',
31
+ },
32
+ });
33
+ expect(doLaunchBlast).toHaveBeenCalledTimes(1);
34
+ model.setQuerySeqName('P53_HUMAN_query');
35
+ expect(doLaunchBlast).toHaveBeenCalledTimes(1);
36
+ });
@@ -7,6 +7,7 @@ import type { TranscriptRef } from './util';
7
7
  import type { MenuItem } from '@jbrowse/core/ui';
8
8
  import type { Instance } from '@jbrowse/mobx-state-tree';
9
9
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
10
+ import type { Region } from 'react-msaview';
10
11
  type LGV = LinearGenomeViewModel;
11
12
  type MaybeLGV = LGV | undefined;
12
13
  export interface IRegion {
@@ -547,7 +548,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
547
548
  clades: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Clade, import("react-msaview").Clade, import("react-msaview").Clade>>, [undefined]>;
548
549
  encodings: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").Encoding, import("react-msaview").Encoding, import("react-msaview").Encoding>>, [undefined]>;
549
550
  rowPanels: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").IArrayType<import("@jbrowse/mobx-state-tree").IType<import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec, import("react-msaview").RowPanelSpec>>, [undefined]>;
550
- }, "init" | "querySeqName" | "querySeqOffset" | "zoomToBaseLevel" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "uniprotId" | "dataStoreId" | "mafRegion"> & {
551
+ }, "init" | "querySeqName" | "querySeqOffset" | "zoomToBaseLevel" | "launchCompleted" | "connectedViewId" | "connectedFeature" | "connectedTranscript" | "blastParams" | "orthologParams" | "uniprotId" | "dataStoreId" | "mafRegion" | "region"> & {
551
552
  connectedViewId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
552
553
  connectedFeature: import("@jbrowse/mobx-state-tree").IType<any, any, any>;
553
554
  connectedTranscript: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
@@ -561,6 +562,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
561
562
  dataStoreId: import("@jbrowse/mobx-state-tree").IMaybe<import("@jbrowse/mobx-state-tree").ISimpleType<string>>;
562
563
  launchCompleted: import("@jbrowse/mobx-state-tree").IType<boolean | undefined, boolean, boolean>;
563
564
  mafRegion: import("@jbrowse/mobx-state-tree").IType<MafRegion | undefined, MafRegion | undefined, MafRegion | undefined>;
565
+ region: import("@jbrowse/mobx-state-tree").IType<Region | undefined, Region | undefined, Region | undefined>;
564
566
  }, {
565
567
  width: number;
566
568
  bodyMounted: boolean;
@@ -768,20 +770,22 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
768
770
  readonly showMsaLetters: boolean;
769
771
  readonly showTreeText: boolean;
770
772
  } & {
771
- readonly labelWidthMap: Map<string, number>;
772
- readonly labelWidthScale: number;
773
- readonly labelsWidth: number;
774
- readonly secondaryStructureConsensus: string | undefined;
775
- readonly seqConsensus: string | undefined;
776
- readonly secondaryStructureArcs: import("react-msaview").Arc[] | undefined;
777
- readonly adapterTrackModels: import("react-msaview").BasicTrack[];
778
773
  readonly columnTrackContent: Map<string, {
779
774
  values?: number[];
780
775
  data?: string;
781
776
  arcs?: import("react-msaview").Arc[];
782
777
  }>;
783
778
  trackHeight(kind: import("react-msaview").TrackKind, heightKey?: string, given?: number): number;
779
+ columnTrackModel(track: import("react-msaview").ColumnTrackSpec): import("react-msaview").BasicTrack;
784
780
  readonly columnTrackModels: import("react-msaview").BasicTrack[];
781
+ } & {
782
+ readonly labelWidthMap: Map<string, number>;
783
+ readonly labelWidthScale: number;
784
+ readonly labelsWidth: number;
785
+ readonly secondaryStructureConsensus: string | undefined;
786
+ readonly seqConsensus: string | undefined;
787
+ readonly secondaryStructureArcs: import("react-msaview").Arc[] | undefined;
788
+ readonly adapterTrackModels: import("react-msaview").BasicTrack[];
785
789
  readonly basePairTrackModels: import("react-msaview").BasicTrack[];
786
790
  readonly computedTrackModels: import("react-msaview").BasicTrack[];
787
791
  readonly tracks: import("react-msaview").BasicTrack[];
@@ -794,7 +798,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
794
798
  visibleColToGlobalCol(visibleCol: number): number;
795
799
  seqPosToGlobalCol(rowName: string, seqPos: number): number | undefined;
796
800
  seqPosToVisibleCol(rowName: string, seqPos: number): number | undefined;
797
- visibleSpan({ row, start: rawStart, end: rawEnd }: import("react-msaview").Region): {
801
+ visibleSpan({ row, start: rawStart, end: rawEnd }: Region): {
798
802
  startCol: number;
799
803
  endCol: number;
800
804
  } | undefined;
@@ -874,9 +878,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
874
878
  readonly fillPalette: {
875
879
  [x: string]: string;
876
880
  };
877
- readonly strokePalette: {
878
- [k: string]: string;
879
- };
880
881
  readonly featureFillEncoding: import("react-msaview").ResolvedEncoding | undefined;
881
882
  readonly featureColors: Map<import("msa-parsers").Annotation, {
882
883
  fill: string;
@@ -898,7 +899,6 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
898
899
  }[];
899
900
  readonly resolvedHighlights: import("react-msaview").ResolvedHighlight[];
900
901
  columnStatsAt(col: number): import("react-msaview").ColumnStats | undefined;
901
- readonly mouseOverColumnStats: import("react-msaview").ColumnStats | undefined;
902
902
  getRowData(name: string): {
903
903
  data: {
904
904
  name?: string;
@@ -923,7 +923,7 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
923
923
  setFilter(accession: string, shown: boolean): void;
924
924
  fit(): void;
925
925
  fitVertically(): void;
926
- zoomToRegion(region: import("react-msaview").Region): void;
926
+ zoomToRegion(region: Region): void;
927
927
  fitHorizontally(): void;
928
928
  afterCreate(): void;
929
929
  } & {
@@ -1035,6 +1035,10 @@ export default function stateModelFactory(): import("@jbrowse/mobx-state-tree").
1035
1035
  * #action
1036
1036
  */
1037
1037
  setMafRegion(arg?: MafRegion): void;
1038
+ /**
1039
+ * #action
1040
+ */
1041
+ setRegion(arg?: Region): void;
1038
1042
  /**
1039
1043
  * #action
1040
1044
  */
@@ -5,6 +5,7 @@ import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
5
5
  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
+ import { applyRegion } from './applyRegion';
8
9
  import { msaCoordToGenomeCoord, msaCoordToGenomeRegions, } from './msaCoordToGenomeCoord';
9
10
  import { deleteMsaData } from './msaDataStore';
10
11
  import { resolveConnectedTranscriptIfNeeded } from './resolveConnectedTranscript';
@@ -107,6 +108,13 @@ export default function stateModelFactory() {
107
108
  * #property
108
109
  */
109
110
  mafRegion: types.frozen(),
111
+ /**
112
+ * #property
113
+ * where the view opens: 1-based residues of `row`, or alignment
114
+ * columns without one. Zoomed onto once and then cleared, so a
115
+ * reloaded session keeps the reader's own scroll.
116
+ */
117
+ region: types.frozen(),
110
118
  }))
111
119
  .volatile(() => ({
112
120
  /**
@@ -299,6 +307,12 @@ export default function stateModelFactory() {
299
307
  setMafRegion(arg) {
300
308
  self.mafRegion = arg;
301
309
  },
310
+ /**
311
+ * #action
312
+ */
313
+ setRegion(arg) {
314
+ self.region = arg;
315
+ },
302
316
  /**
303
317
  * #action
304
318
  */
@@ -493,6 +507,9 @@ export default function stateModelFactory() {
493
507
  // so they're factories returning the autorun body rather than plain fns
494
508
  addDisposer(self, autorun(syncGenomeHoverToMsaColumn(self)));
495
509
  addDisposer(self, autorun(observeProteinHighlights(self)));
510
+ addDisposer(self, autorun(() => {
511
+ applyRegion(self);
512
+ }));
496
513
  },
497
514
  }));
498
515
  }
@@ -3,13 +3,15 @@ export interface MsaDataPayload {
3
3
  msa?: string;
4
4
  tree?: string;
5
5
  treeMetadata?: string;
6
+ gff?: string;
6
7
  }
7
8
  export declare function generateDataStoreId(): string;
8
9
  export declare function storeMsaData(id: string, data: MsaDataPayload): Promise<boolean>;
9
10
  export declare function retrieveMsaData(id: string): Promise<{
10
- msa: string | undefined;
11
- tree: string | undefined;
12
- treeMetadata: string | undefined;
11
+ msa?: string;
12
+ tree?: string;
13
+ treeMetadata?: string;
14
+ gff?: string;
13
15
  } | undefined>;
14
16
  /** drop one view's row, for a reset that just orphaned it */
15
17
  export declare function deleteMsaData(id: string): Promise<void>;
@@ -14,13 +14,7 @@ export function generateDataStoreId() {
14
14
  export async function storeMsaData(id, data) {
15
15
  try {
16
16
  const db = await getDB();
17
- const storedData = {
18
- id,
19
- msa: data.msa,
20
- tree: data.tree,
21
- treeMetadata: data.treeMetadata,
22
- timestamp: Date.now(),
23
- };
17
+ const storedData = { ...data, id, timestamp: Date.now() };
24
18
  await db.put(STORE_NAME, storedData);
25
19
  return true;
26
20
  }
@@ -45,11 +39,8 @@ export async function retrieveMsaData(id) {
45
39
  catch (e) {
46
40
  console.warn('Failed to refresh MSA data timestamp:', e);
47
41
  }
48
- return {
49
- msa: result.msa,
50
- tree: result.tree,
51
- treeMetadata: result.treeMetadata,
52
- };
42
+ const { id: _id, timestamp: _timestamp, ...payload } = result;
43
+ return payload;
53
44
  }
54
45
  catch (e) {
55
46
  console.warn('Failed to retrieve MSA data:', e);
@@ -1,5 +1,5 @@
1
1
  import { beforeEach, expect, test, vi } from 'vitest';
2
- import { retrieveMsaData } from './msaDataStore';
2
+ import { retrieveMsaData, storeMsaData } from './msaDataStore';
3
3
  // An in-memory stand-in for the one object store this module opens, with a
4
4
  // switch for the write half so a browser that refuses writes can be played back.
5
5
  const { rows, state } = vi.hoisted(() => ({
@@ -42,3 +42,10 @@ test('a refresh that fails still hands back the data it read', async () => {
42
42
  state.putFails = true;
43
43
  expect((await retrieveMsaData('msa-1'))?.msa).toBe('>a\nMK');
44
44
  });
45
+ test('every document a view keeps comes back, and nothing else', async () => {
46
+ await storeMsaData('msa-1', { msa: '>a\nMK', gff: 'a\t.\tdomain\t1\t2' });
47
+ expect(await retrieveMsaData('msa-1')).toEqual({
48
+ msa: '>a\nMK',
49
+ gff: 'a\t.\tdomain\t1\t2',
50
+ });
51
+ });
@@ -19,7 +19,7 @@ function makeModel({ highlightColumns, querySeqOffset = 0, } = {}) {
19
19
  const model = {
20
20
  querySeqName: 'query',
21
21
  querySeqOffset,
22
- rows: [['query', 'MKVLTAEEK']],
22
+ rowMap: new Map([['query', 'MKVLTAEEK']]),
23
23
  connectedViewId: CONNECTED,
24
24
  // g2p is indexed by genome coord; identity keeps the arithmetic out of the way
25
25
  transcriptToMsaMap: {
@@ -213,11 +213,11 @@ describe('scope and redundant writes', () => {
213
213
  run();
214
214
  expect(calls).toEqual([]);
215
215
  });
216
- // seqPosToGlobalCol answers 0 for a row name it does not know, so without a
217
- // guard a structure hover would light column 0 of whatever row is first
218
216
  test('a query row this alignment does not have contributes no column', () => {
219
217
  const { model, calls } = makeModel();
220
- Object.assign(model, { rows: [['some_other_row', 'MKVLTAEEK']] });
218
+ Object.assign(model, {
219
+ rowMap: new Map([['some_other_row', 'MKVLTAEEK']]),
220
+ });
221
221
  const run = observeProteinHighlights(model);
222
222
  session({ hover: [{ start: 10, end: 12 }] });
223
223
  run();
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import { expect, test, vi } from 'vitest';
2
+ import stateModelFactory from './model';
3
+ vi.mock('@jbrowse/core/util', async (importOriginal) => ({
4
+ ...(await importOriginal()),
5
+ getSession: () => ({ views: [], hovered: undefined }),
6
+ }));
7
+ vi.mock('./msaDataStore', () => ({
8
+ cleanupOldData: vi.fn(async () => { }),
9
+ deleteMsaData: vi.fn(async () => { }),
10
+ generateDataStoreId: vi.fn(),
11
+ retrieveMsaData: vi.fn(),
12
+ storeMsaData: vi.fn(async () => true),
13
+ }));
14
+ const MSA = `>Human\n${'MEEPQSDPSV'.repeat(20)}\n>Mouse\n${'MEE-QSDPSV'.repeat(20)}`;
15
+ test('a spec region waits for the view to have a width, zooms, and is dropped', () => {
16
+ const region = { row: 'Human', start: 150, end: 160 };
17
+ const model = stateModelFactory().create({
18
+ type: 'MsaView',
19
+ id: 'msaview1',
20
+ data: { msa: MSA },
21
+ region,
22
+ gffFilehandle: { uri: 'http://example.com/a.gff' },
23
+ });
24
+ expect(model.region).toEqual(region);
25
+ expect(model.gffFilehandle).toMatchObject({ locationType: 'UriLocation' });
26
+ model.setWidth(800);
27
+ expect(model.region).toBeUndefined();
28
+ expect(model.scrollX).toBeLessThan(0);
29
+ });
@@ -1,9 +1,46 @@
1
1
  import type { JBrowsePluginMsaViewModel } from './model';
2
+ interface Region {
3
+ assemblyName: string;
4
+ refName: string;
5
+ start: number;
6
+ end: number;
7
+ }
8
+ interface GenomeView {
9
+ tracks: {
10
+ type: string;
11
+ }[];
12
+ displayedRegions: Region[];
13
+ dynamicBlocks?: {
14
+ contentBlocks?: Region[];
15
+ };
16
+ }
17
+ /**
18
+ * The part of the genome on screen, then everything the view can scroll to.
19
+ * `displayedRegions` is usually whole chromosomes -- navigating to a locus
20
+ * displays its parent region -- so it is the fallback, not the first ask.
21
+ */
22
+ export declare function searchWindows(view: GenomeView): {
23
+ assemblyName: string;
24
+ refName: string;
25
+ start: number;
26
+ end: number;
27
+ }[][];
28
+ /**
29
+ * Gene models live on feature tracks. Asking an alignments, variant or
30
+ * quantitative track for a chromosome's worth of features to look for a
31
+ * transcript among them is the slowest possible way to find nothing.
32
+ */
33
+ export declare function transcriptTracks<T extends {
34
+ type: string;
35
+ }>(tracks: T[]): T[];
2
36
  /**
3
37
  * Resolve `connectedTranscript` into `connectedFeature` once the connected
4
- * genome view has its assembly, regions and tracks, then hand the translation
5
- * to whichever launch is waiting for a query. Left in place on failure so the
6
- * error stays attributable; the autorun's tracked reads are the view's
7
- * readiness, so nothing refires until that changes.
38
+ * genome view has its assembly and tracks, then hand the translation to
39
+ * whichever launch is waiting for a query.
40
+ *
41
+ * The autorun re-fires on the view's readiness (a track opened after a miss)
42
+ * and on the request itself, which is what `retryLaunch` re-states. The lookup
43
+ * runs untracked, so scrolling the genome view is not a reason to look again.
8
44
  */
9
45
  export declare function resolveConnectedTranscriptIfNeeded(self: JBrowsePluginMsaViewModel): void;
46
+ export {};