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
package/README.md CHANGED
@@ -14,6 +14,14 @@ MSAView plugin running in JBrowse 2
14
14
 
15
15
  ## Demo
16
16
 
17
+ [p53 across vertebrates, linked to the genome and the structure](https://jbrowse.org/code/jb2/main/?config=https://jbrowse.org/ucsc/hg38/config.json&session=spec-{%22views%22:[{%22type%22:%22LinearGenomeView%22,%22id%22:%22lgv1%22,%22assembly%22:%22hg38%22,%22loc%22:%22chr17:7,661,779-7,687,538%22,%22tracks%22:[%22hg38-ncbiRefSeqSelect%22,%22hg38-clinvarMain%22]},{%22type%22:%22MsaView%22,%22displayName%22:%22p53%20across%20vertebrates%22,%22msaFileLocation%22:{%22uri%22:%22https://gmod.org/JBrowseMSA/demo/data/p53/p53-vertebrates.afa%22},%22treeFileLocation%22:{%22uri%22:%22https://gmod.org/JBrowseMSA/demo/data/p53/p53-vertebrates.nh%22},%22querySeqName%22:%22Human%22,%22relativeTo%22:%22Human%22,%22connectedViewId%22:%22lgv1%22,%22connectedTranscript%22:%22NM_000546.6%22,%22placement%22:%22splitRight%22,%22colorSchemeName%22:%22clustalx_protein_dynamic%22,%22highlights%22:[{%22row%22:%22Human%22,%22start%22:102,%22end%22:292,%22label%22:%22DNA-binding%22,%22color%22:%22rgba%28255,140,0,0.15%29%22},{%22row%22:%22Human%22,%22start%22:175,%22end%22:175,%22label%22:%22R175%22},{%22row%22:%22Human%22,%22start%22:245,%22end%22:245,%22label%22:%22G245%22},{%22row%22:%22Human%22,%22start%22:248,%22end%22:248,%22label%22:%22R248%22},{%22row%22:%22Human%22,%22start%22:249,%22end%22:249,%22label%22:%22R249%22},{%22row%22:%22Human%22,%22start%22:273,%22end%22:273,%22label%22:%22R273%22},{%22row%22:%22Human%22,%22start%22:282,%22end%22:282,%22label%22:%22R282%22}],%22columnTracks%22:[{%22id%22:%22clinvar%22,%22name%22:%22ClinVar%20pathogenic%20missense%22,%22kind%22:%22bar%22,%22row%22:%22Human%22,%22color%22:%22%23c0392b%22,%22height%22:60,%22max%22:8,%22values%22:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,1,0,0,2,4,3,0,6,0,0,0,0,0,0,0,0,0,0,1,2,1,5,0,0,2,2,3,1,1,4,0,0,2,0,0,2,0,3,0,0,0,1,0,0,0,6,2,0,0,1,1,3,6,2,0,1,0,2,1,1,0,0,2,0,0,2,1,3,0,3,3,1,3,5,1,3,0,0,0,0,0,0,0,0,1,0,0,4,3,1,1,1,0,1,0,0,0,0,0,3,0,0,0,0,0,0,0,3,2,1,1,0,1,0,3,0,0,0,0,0,0,0,0,0,0,0,3,0,4,0,2,4,6,2,3,5,2,0,4,5,4,1,6,2,1,2,0,1,3,0,0,0,2,1,0,0,0,0,0,2,3,3,0,1,4,1,3,6,0,2,1,1,3,0,3,8,3,1,0,2,2,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0,0,5,0,0,0,1,1,0,2,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},{%22id%22:%22alphamissense%22,%22name%22:%22AlphaMissense%20mean%20%28x100%29%22,%22kind%22:%22bar%22,%22row%22:%22Human%22,%22color%22:%22%231565c0%22,%22height%22:60,%22max%22:100,%22values%22:[49,27,19,11,12,14,18,10,12,14,28,17,40,57,73,65,65,68,95,29,30,72,95,21,24,60,39,21,18,17,18,16,17,12,11,13,14,13,14,21,22,20,17,20,16,16,15,14,17,17,15,13,37,29,11,19,16,11,11,12,14,20,14,12,11,20,13,19,15,14,12,13,15,17,14,17,15,19,20,17,15,15,22,22,19,22,20,24,17,23,67,41,46,66,63,37,90,98,63,35,36,20,76,15,99,13,58,48,98,26,81,57,97,43,17,64,87,68,90,98,96,92,73,81,98,91,95,23,13,81,56,97,84,97,98,87,86,81,91,84,94,93,89,66,81,57,77,26,20,13,90,69,17,52,70,49,91,98,87,88,94,87,95,89,48,54,52,76,76,68,92,87,98,78,99,100,98,97,100,96,85,50,38,52,32,74,52,32,83,87,71,47,99,95,89,95,91,96,95,81,20,25,69,48,98,24,64,93,17,23,84,56,99,92,91,93,70,89,76,91,77,27,92,87,57,92,58,35,54,68,75,87,85,87,74,93,96,100,95,98,99,100,94,100,100,98,97,100,99,93,93,59,91,91,87,92,93,99,87,22,21,94,24,76,83,99,98,36,61,97,93,93,99,95,100,97,100,100,99,100,100,97,84,60,98,96,47,62,13,18,56,34,17,17,11,9,9,16,11,15,13,18,20,17,81,75,26,26,23,13,12,14,16,18,18,14,15,15,47,54,46,19,13,34,24,50,56,88,36,87,48,93,63,98,73,40,89,90,45,60,88,51,36,81,77,55,79,75,87,31,54,67,23,18,17,16,25,22,13,15,15,16,20,18,15,20,25,13,15,56,21,47,45,21,16,16,15,21,16,20,66,47,13,26,19,66,13,28,18,16,47,54,66]},{%22id%22:%22mavedb%22,%22name%22:%22MaveDB%20nutlin-3,%20p53WT%22,%22kind%22:%22bar%22,%22row%22:%22Human%22,%22color%22:%22%232e7d32%22,%22height%22:60,%22max%22:2,%22values%22:[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.6,0,0.1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0.1,0,0,0.2,0.4,0,0,0,0,0.1,0,0.8,0,0.7,0.3,1.6,0.1,1.4,0.1,1.3,0,0,0,0.1,0,0.1,0.5,0.2,0.4,0.4,0.8,0.8,1.4,1.5,0.1,0.1,1.2,1,1.7,1.1,1.4,1.3,0.8,0.5,0.5,0.8,0.4,1.2,0.4,1.4,0.7,1.2,0.6,1.1,0.5,0.4,0.6,1.5,1.3,0,0.7,1.3,0.3,1.4,1,1.4,0.8,1.4,0.8,1.8,0.8,0.3,0,0.2,0.8,0.4,0,0.6,0.7,1.7,1,1.2,2,1.1,0.6,2,0.8,0.7,0.3,0.1,0.4,0.2,0.3,0.1,0.1,0.5,0.8,0.1,0.1,1.4,1.6,1.4,0.7,1.3,0.1,0.1,0,0,0,0.9,0.3,1.8,0,0,1,0,0.1,0.9,0.7,1.2,1.1,1.5,1.4,0.6,1.3,0.5,1.5,0.4,0.3,0.6,0.3,0.1,0.4,0.3,0.2,0.7,0.5,0.6,1.4,0.5,1.4,0.6,1.7,1.1,1.9,0.8,1.3,1.3,1.7,0.9,1.7,2.2,2,0.9,1.5,2.1,1,1.4,0.3,1.3,1.2,1.4,0.8,1.4,1.4,0.9,0.5,0.5,0.7,0.5,0.5,0.8,1.6,1,0.9,0.8,1.5,1.3,1.5,1.5,1.7,1.8,0.5,0.4,2,0,0.9,0.6,1,0,0,1,1.8,0,0.1,0.2,0.1,0.2,0,0.1,0.4,0.4,0.3,0.4,0.6,0.4,0.6,0.5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}],%22region%22:{%22row%22:%22Human%22,%22start%22:170,%22end%22:290}},{%22type%22:%22ProteinView%22,%22uniprotId%22:%22P04637%22,%22transcriptId%22:%22NM_000546.6%22,%22connectedViewId%22:%22lgv1%22,%22connectedView%22:{%22assembly%22:%22hg38%22,%22loc%22:%22chr17:7,661,779-7,687,538%22,%22tracks%22:[%22hg38-ncbiRefSeqSelect%22]}}]}):
18
+ TP53 in hg38 with ClinVar, a vertebrate p53 alignment carrying ClinVar,
19
+ AlphaMissense and MaveDB per-residue tracks, and the AlphaFold model of P04637.
20
+ Hovering a codon, a column or a residue lights the other two views. See
21
+ [DEVELOPERS.md](DEVELOPERS.md#demo-p53-variant-evidence) for how the link is
22
+ built.
23
+
24
+ An older shared session:
17
25
  https://jbrowse.org/code/jb2/main/index.html?config=https://unpkg.com/jbrowse-plugin-msaview/dist/config.json&session=share-BVmmEYAoAv&password=SuQaN
18
26
 
19
27
  ## Features
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ import { SimpleFeature } from '@jbrowse/core/util';
2
+ import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
3
+ import { describe, expect, test } from 'vitest';
4
+ import { transcriptPosToVisibleCol } from '../MsaViewPanel/util';
5
+ import { getProteinSequenceFromFeature } from './components/calculateProteinSequence';
6
+ import { findQueryRow } from './detectQueryRow';
7
+ import { cleanProteinSequence } from './util';
8
+ describe('cleanProteinSequence', () => {
9
+ test('drops the stop and a trailing partial codon', () => {
10
+ expect(cleanProteinSequence('MAWK*')).toBe('MAWK');
11
+ expect(cleanProteinSequence('MAWK&')).toBe('MAWK');
12
+ });
13
+ test('keeps a residue for every other codon', () => {
14
+ expect(cleanProteinSequence('&MAWK*')).toBe('XMAWK');
15
+ expect(cleanProteinSequence('MA*WK*')).toBe('MAXWK');
16
+ expect(cleanProteinSequence('MA&WK')).toBe('MAXWK');
17
+ });
18
+ });
19
+ // what the genome view hovers is a codon, and what lights is the query row's
20
+ // residue at g2p's number for that codon, so the two have to count alike
21
+ describe('a launched query row read through g2p', () => {
22
+ function rowResidueUnder(genomePos, json) {
23
+ const seq = json.seq;
24
+ const feature = new SimpleFeature(json);
25
+ const row = cleanProteinSequence(getProteinSequenceFromFeature({ seq, feature }));
26
+ const { g2p } = genomeToTranscriptSeqMapping(feature.toJSON());
27
+ const col = transcriptPosToVisibleCol({
28
+ querySeqName: 'QUERY',
29
+ querySeqOffset: 0,
30
+ seqPosToVisibleCol: (_, p) => (p < row.length ? p : row.length),
31
+ visibleColToSeqPos: (_, c) => (c < row.length ? c : undefined),
32
+ }, g2p[genomePos]);
33
+ return row[col];
34
+ }
35
+ function transcript(seq, cds) {
36
+ return {
37
+ uniqueId: 't1',
38
+ refName: 'chr1',
39
+ start: 0,
40
+ end: seq.length,
41
+ strand: 1,
42
+ type: 'mRNA',
43
+ seq,
44
+ subfeatures: [
45
+ { uniqueId: 'c1', refName: 'chr1', start: 0, end: seq.length, ...cds },
46
+ ],
47
+ };
48
+ }
49
+ test('after a partial first codon', () => {
50
+ // G | ATG GCC TGG AAA TAA: base 4 opens GCC, alanine
51
+ const json = transcript('GATGGCCTGGAAATAA', { type: 'CDS', phase: 1 });
52
+ expect(rowResidueUnder(4, json)).toBe('A');
53
+ expect(rowResidueUnder(10, json)).toBe('K');
54
+ });
55
+ test('after an internal stop', () => {
56
+ // ATG TGA GCC TGG TAA: a selenocysteine GENCODE does not annotate
57
+ const json = transcript('ATGTGAGCCTGGTAA', { type: 'CDS' });
58
+ expect(rowResidueUnder(6, json)).toBe('A');
59
+ });
60
+ });
61
+ describe('query row detection', () => {
62
+ test('matches a row a search launch built from a partial transcript', () => {
63
+ const msa = '>QUERY\nXMAWK\n>other\nMAWR-';
64
+ expect(findQueryRow(msa, '&MAWK*').match).toMatchObject({
65
+ name: 'QUERY',
66
+ quality: 'exact',
67
+ offset: 0,
68
+ });
69
+ });
70
+ test('still places a row that starts at the first whole codon', () => {
71
+ const msa = '>Query_1\nMAWK\n>other\nMAWR';
72
+ expect(findQueryRow(msa, '&MAWK*').match).toMatchObject({
73
+ name: 'Query_1',
74
+ offset: 1,
75
+ });
76
+ });
77
+ });
@@ -9,10 +9,10 @@ import LaunchPanelContent from '../LaunchPanelContent';
9
9
  import SequenceStatusMessage from '../SequenceStatus';
10
10
  import SubmitCancelActions from '../SubmitCancelActions';
11
11
  import TranscriptSelector from '../TranscriptSelector';
12
+ import { builtAlignmentLook, launchConnectedView, useLaunchSubmit, } from '../launchConnectedView';
12
13
  import { useTranscriptSelection } from '../useTranscriptSelection';
13
14
  import CachedBlastResults from './CachedBlastResults';
14
15
  import MsaAlgorithmSelect from './MsaAlgorithmSelect';
15
- import { blastLaunchView } from './blastLaunchView';
16
16
  import { databaseLabel, databaseOptionsFor, defaultMaxHits, defaultSearchFor, searchProgramLabels, searchPrograms, } from './consts';
17
17
  import { useStoredMsaAlgorithm, useStoredSearchChoice, } from './searchChoiceStorage';
18
18
  import { useCachedBlastResults } from './useCachedBlastResults';
@@ -35,7 +35,7 @@ const useStyles = makeStyles()({
35
35
  const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, preferredTranscriptId, }) {
36
36
  const { classes } = useStyles();
37
37
  const view = getLinearGenomeView(model);
38
- const [launchViewError, setLaunchViewError] = useState();
38
+ const { launchError, submit } = useLaunchSubmit(handleClose);
39
39
  // one piece of state, not two: a program and a database that program does not
40
40
  // have is a 400 from EBI minutes after Submit, and holding them apart is what
41
41
  // would let them drift into that
@@ -53,7 +53,7 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
53
53
  preferredTranscriptId,
54
54
  });
55
55
  const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
56
- const e = transcriptSelection.error ?? launchViewError ?? cachedResultsError;
56
+ const e = transcriptSelection.error ?? launchError ?? cachedResultsError;
57
57
  return (React.createElement(React.Fragment, null,
58
58
  React.createElement(LaunchPanelContent, { error: e },
59
59
  children,
@@ -85,37 +85,34 @@ const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, ch
85
85
  React.createElement(Typography, null, "Previous BLAST Results")),
86
86
  React.createElement(AccordionDetails, null,
87
87
  React.createElement(CachedBlastResults, { model: model, handleClose: handleClose, feature: feature })))) : null),
88
- React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !hitCountValid, onSubmit: () => {
89
- try {
90
- if (selectedTranscript) {
91
- setLaunchViewError(undefined);
92
- blastLaunchView({
93
- feature: selectedTranscript,
88
+ React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !hitCountValid, onSubmit: placement => {
89
+ if (selectedTranscript) {
90
+ const blastParams = search.program === 'phmmer'
91
+ ? {
92
+ searchProgram: 'phmmer',
93
+ blastDatabase: search.database,
94
+ maxHits: hitCount,
95
+ selectedTranscript: selectedTranscript.toJSON(),
96
+ proteinSequence,
97
+ }
98
+ : {
99
+ searchProgram: 'blastp',
100
+ blastDatabase: search.database,
101
+ msaAlgorithm: selectedMsaAlgorithm,
102
+ maxHits: hitCount,
103
+ selectedTranscript: selectedTranscript.toJSON(),
104
+ proteinSequence,
105
+ };
106
+ submit(() => {
107
+ launchConnectedView({
94
108
  view,
95
- newViewTitle: getBlastViewTitle(feature, selectedTranscript),
96
- blastParams: search.program === 'phmmer'
97
- ? {
98
- searchProgram: 'phmmer',
99
- blastDatabase: search.database,
100
- maxHits: hitCount,
101
- selectedTranscript: selectedTranscript.toJSON(),
102
- proteinSequence,
103
- }
104
- : {
105
- searchProgram: 'blastp',
106
- blastDatabase: search.database,
107
- msaAlgorithm: selectedMsaAlgorithm,
108
- maxHits: hitCount,
109
- selectedTranscript: selectedTranscript.toJSON(),
110
- proteinSequence,
111
- },
109
+ feature: selectedTranscript,
110
+ placement,
111
+ displayName: getBlastViewTitle(feature, selectedTranscript),
112
+ ...builtAlignmentLook,
113
+ blastParams,
112
114
  });
113
- handleClose();
114
- }
115
- }
116
- catch (e) {
117
- console.error(e);
118
- setLaunchViewError(e);
115
+ });
119
116
  }
120
117
  }, onCancel: handleClose })));
121
118
  });
@@ -8,10 +8,10 @@ import TextField2 from '../../../components/TextField2';
8
8
  import { useQueryRowName } from '../../useQueryRowName';
9
9
  import { cleanProteinSequence, getGeneDisplayName, getLinearGenomeView, } from '../../util';
10
10
  import LaunchPanelContent from '../LaunchPanelContent';
11
- import { launchView } from '../ManualMSALoader/launchView';
12
11
  import QueryRowSelector from '../QueryRowSelector';
13
12
  import SubmitCancelActions from '../SubmitCancelActions';
14
13
  import TranscriptSelector from '../TranscriptSelector';
14
+ import { launchConnectedView, useLaunchSubmit } from '../launchConnectedView';
15
15
  import { useTranscriptSelection } from '../useTranscriptSelection';
16
16
  import { BASE_BLAST_URL } from './consts';
17
17
  const useStyles = makeStyles()({
@@ -46,7 +46,7 @@ const useStyles = makeStyles()({
46
46
  const BlastManualPanel = observer(function ({ handleClose, feature, model, children, preferredTranscriptId, }) {
47
47
  const { classes } = useStyles();
48
48
  const view = getLinearGenomeView(model);
49
- const [launchViewError, setLaunchViewError] = useState();
49
+ const { launchError, submit } = useLaunchSubmit(handleClose);
50
50
  const [msaText, setMsaText] = useState('');
51
51
  const [treeText, setTreeText] = useState('');
52
52
  const transcriptSelection = useTranscriptSelection({
@@ -62,7 +62,7 @@ const BlastManualPanel = observer(function ({ handleClose, feature, model, child
62
62
  const link = `${BASE_BLAST_URL}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${s2}`;
63
63
  const link2 = `${BASE_BLAST_URL}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${shorten2(s2, 10)}`;
64
64
  return (React.createElement(React.Fragment, null,
65
- React.createElement(LaunchPanelContent, { error: launchViewError ?? error },
65
+ React.createElement(LaunchPanelContent, { error: launchError ?? error },
66
66
  children,
67
67
  React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
68
68
  React.createElement("div", { className: classes.step },
@@ -83,24 +83,19 @@ const BlastManualPanel = observer(function ({ handleClose, feature, model, child
83
83
  setTreeText(event.target.value);
84
84
  } }),
85
85
  React.createElement(QueryRowSelector, { ...queryRow })))),
86
- React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript || !msaText.trim(), onSubmit: () => {
87
- try {
88
- if (selectedTranscript) {
89
- setLaunchViewError(undefined);
90
- launchView({
91
- newViewTitle: getGeneDisplayName(selectedTranscript),
86
+ React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript || !msaText.trim(), onSubmit: placement => {
87
+ if (selectedTranscript) {
88
+ submit(() => {
89
+ launchConnectedView({
92
90
  view,
93
91
  feature: selectedTranscript,
92
+ placement,
93
+ displayName: getGeneDisplayName(selectedTranscript),
94
94
  querySeqName: queryRow.querySeqName,
95
95
  querySeqOffset: queryRow.querySeqOffset,
96
96
  data: { msa: msaText, tree: treeText },
97
97
  });
98
- handleClose();
99
- }
100
- }
101
- catch (e) {
102
- console.error(e);
103
- setLaunchViewError(e);
98
+ });
104
99
  }
105
100
  }, onCancel: handleClose })));
106
101
  });
@@ -5,7 +5,8 @@ import { Button, IconButton, List, ListItem, ListItemButton, ListItemText, Typog
5
5
  import { observer } from 'mobx-react';
6
6
  import { makeStyles } from 'tss-react/mui';
7
7
  import { featureMatchesId, getGeneIdentifiers, getLinearGenomeView, getSortedTranscriptFeatures, } from '../../util';
8
- import { blastLaunchViewFromCache } from './blastLaunchView';
8
+ import { builtAlignmentLook, launchConnectedView } from '../launchConnectedView';
9
+ import { useLaunchPlacement } from '../launchPlacement';
9
10
  import { useCachedBlastResults } from './useCachedBlastResults';
10
11
  const useStyles = makeStyles()({
11
12
  header: {
@@ -53,22 +54,22 @@ const CachedBlastResults = observer(function ({ model, handleClose, feature, })
53
54
  const { classes } = useStyles();
54
55
  const view = getLinearGenomeView(model);
55
56
  const [operationError, setOperationError] = useState();
57
+ const [sideBySide] = useLaunchPlacement();
56
58
  const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
57
59
  const { results, error, isLoading, handleDelete, handleClearAll } = useCachedBlastResults(geneIds);
58
60
  const handleUseCached = (cached) => {
59
- // reconnect the cached MSA to the genome: the cached query row is named
60
- // 'QUERY' (react-msaview's default querySeqName) and corresponds to the
61
- // transcript stored as transcriptId. Resolving it here restores the
62
- // MSA<->genome navigation and hover-sync a fresh BLAST gets.
63
- const { transcriptId } = cached;
64
- const transcript = transcriptId
65
- ? getSortedTranscriptFeatures(feature).find(t => featureMatchesId(t, transcriptId))
66
- : undefined;
67
- blastLaunchViewFromCache({
61
+ // the cached query row is the plugin's default `QUERY`, translated from
62
+ // the transcript stored as transcriptId, so that transcript relinks it
63
+ const { transcriptId, msa, tree, treeMetadata } = cached;
64
+ launchConnectedView({
68
65
  view,
69
- cached,
70
- newViewTitle: `BLAST - ${getResultDisplayName(cached)}`,
71
- connectedFeature: transcript?.toJSON(),
66
+ feature: transcriptId
67
+ ? getSortedTranscriptFeatures(feature).find(t => featureMatchesId(t, transcriptId))
68
+ : undefined,
69
+ placement: sideBySide ? 'splitRight' : 'stack',
70
+ displayName: `BLAST - ${getResultDisplayName(cached)}`,
71
+ ...builtAlignmentLook,
72
+ data: { msa, tree, treeMetadata },
72
73
  });
73
74
  handleClose();
74
75
  };
@@ -1,9 +1,11 @@
1
1
  import React, { useState } from 'react';
2
2
  import { FileSelector } from '@jbrowse/core/ui';
3
+ import { openLocation } from '@jbrowse/core/util/io';
3
4
  import { FormControl, FormControlLabel, Radio, RadioGroup } from '@mui/material';
4
5
  import { observer } from 'mobx-react';
5
6
  import { makeStyles } from 'tss-react/mui';
6
7
  import TextField2 from '../../../components/TextField2';
8
+ import { useDebounced, useFetch } from '../../../utils/useFetch';
7
9
  import { useQueryRowName } from '../../useQueryRowName';
8
10
  import { getGeneDisplayName, getLinearGenomeView } from '../../util';
9
11
  import LaunchPanelContent from '../LaunchPanelContent';
@@ -11,8 +13,18 @@ import QueryRowSelector from '../QueryRowSelector';
11
13
  import SequenceStatusMessage from '../SequenceStatus';
12
14
  import SubmitCancelActions from '../SubmitCancelActions';
13
15
  import TranscriptSelector from '../TranscriptSelector';
16
+ import { launchConnectedView, useLaunchSubmit } from '../launchConnectedView';
14
17
  import { useTranscriptSelection } from '../useTranscriptSelection';
15
- import { launchView } from './launchView';
18
+ /**
19
+ * The chosen file's text, so its query row is found by sequence the way a
20
+ * pasted alignment's is; without it a file launch named no row and never
21
+ * linked to the genome. Debounced because a URL arrives a keystroke at a time.
22
+ */
23
+ function useMsaFileText(location) {
24
+ const debounced = useDebounced(location, 500);
25
+ const { data } = useFetch(debounced ? [JSON.stringify(debounced), 'msa-file-text'] : null, () => openLocation(debounced).readFile('utf8'));
26
+ return data ?? '';
27
+ }
16
28
  const useStyles = makeStyles()({
17
29
  textAreaFont: {
18
30
  fontFamily: 'Courier New',
@@ -30,7 +42,7 @@ const useStyles = makeStyles()({
30
42
  const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handleClose, preferredTranscriptId, }) {
31
43
  const view = getLinearGenomeView(model);
32
44
  const { classes } = useStyles();
33
- const [launchViewError, setLaunchViewError] = useState();
45
+ const { launchError, submit } = useLaunchSubmit(handleClose);
34
46
  const [inputMethod, setInputMethod] = useState('file');
35
47
  const [msaText, setMsaText] = useState('');
36
48
  const [treeText, setTreeText] = useState('');
@@ -42,8 +54,9 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
42
54
  preferredTranscriptId,
43
55
  });
44
56
  const { selectedTranscript, proteinSequence, error, sequenceStatus } = transcriptSelection;
45
- const queryRow = useQueryRowName(msaText, proteinSequence);
46
- const e = launchViewError ?? error;
57
+ const msaFileText = useMsaFileText(inputMethod === 'file' ? msaFileLocation : undefined);
58
+ const queryRow = useQueryRowName(inputMethod === 'file' ? msaFileText : msaText, proteinSequence);
59
+ const e = launchError ?? error;
47
60
  return (React.createElement(React.Fragment, null,
48
61
  React.createElement(LaunchPanelContent, { error: e },
49
62
  React.createElement(FormControl, { component: "fieldset" },
@@ -65,14 +78,14 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
65
78
  React.createElement(QueryRowSelector, { ...queryRow })),
66
79
  React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !selectedTranscript ||
67
80
  (inputMethod === 'file' && !msaFileLocation) ||
68
- (inputMethod === 'text' && !msaText.trim()), onSubmit: () => {
69
- try {
70
- if (selectedTranscript) {
71
- setLaunchViewError(undefined);
72
- launchView({
73
- newViewTitle: getGeneDisplayName(selectedTranscript),
81
+ (inputMethod === 'text' && !msaText.trim()), onSubmit: placement => {
82
+ if (selectedTranscript) {
83
+ submit(() => {
84
+ launchConnectedView({
74
85
  view,
75
86
  feature: selectedTranscript,
87
+ placement,
88
+ displayName: getGeneDisplayName(selectedTranscript),
76
89
  querySeqName: queryRow.querySeqName,
77
90
  querySeqOffset: queryRow.querySeqOffset,
78
91
  ...(inputMethod === 'file'
@@ -80,19 +93,9 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
80
93
  msaFilehandle: msaFileLocation,
81
94
  treeFilehandle: treeFileLocation,
82
95
  }
83
- : {
84
- data: {
85
- msa: msaText,
86
- tree: treeText,
87
- },
88
- }),
96
+ : { data: { msa: msaText, tree: treeText } }),
89
97
  });
90
- handleClose();
91
- }
92
- }
93
- catch (err) {
94
- console.error(err);
95
- setLaunchViewError(err);
98
+ });
96
99
  }
97
100
  }, onCancel: handleClose })));
98
101
  });
@@ -4,7 +4,6 @@ import { observer } from 'mobx-react';
4
4
  import { makeStyles } from 'tss-react/mui';
5
5
  import TextField2 from '../../../components/TextField2';
6
6
  import { defaultMaxSpecies } from '../../../utils/ncbiOrthologs';
7
- import { useLocalStorage } from '../../../utils/useLocalStorage';
8
7
  import { getGeneDisplayName, getGeneIdentifiers, getLinearGenomeView, getTranscriptDisplayName, } from '../../util';
9
8
  import MsaAlgorithmSelect from '../BlastQuery/MsaAlgorithmSelect';
10
9
  import { useStoredMsaAlgorithm } from '../BlastQuery/searchChoiceStorage';
@@ -12,10 +11,10 @@ import LaunchPanelContent from '../LaunchPanelContent';
12
11
  import SequenceStatusMessage from '../SequenceStatus';
13
12
  import SubmitCancelActions from '../SubmitCancelActions';
14
13
  import TranscriptSelector from '../TranscriptSelector';
14
+ import { builtAlignmentLook, launchConnectedView, useLaunchSubmit, } from '../launchConnectedView';
15
15
  import { useTranscriptSelection } from '../useTranscriptSelection';
16
- import OrthologSourceSelect, { ORTHOLOG_SOURCE_STORAGE_KEY, } from './OrthologSourceSelect';
16
+ import OrthologSourceSelect, { useStoredOrthologSource, } from './OrthologSourceSelect';
17
17
  import QuerySpeciesSelect from './QuerySpeciesSelect';
18
- import { orthologLaunchView } from './orthologLaunchView';
19
18
  const useStyles = makeStyles()({
20
19
  selectField: {
21
20
  width: 180,
@@ -31,9 +30,9 @@ const rowsHint = {
31
30
  const OrthologPanel = observer(function ({ handleClose, feature, model, preferredTranscriptId, }) {
32
31
  const { classes } = useStyles();
33
32
  const view = getLinearGenomeView(model);
34
- const [launchViewError, setLaunchViewError] = useState();
33
+ const { launchError, submit } = useLaunchSubmit(handleClose);
35
34
  const [taxId, setTaxId] = useState(9606);
36
- const [source, setSource] = useLocalStorage(ORTHOLOG_SOURCE_STORAGE_KEY, 'ncbi');
35
+ const [source, setSource] = useStoredOrthologSource();
37
36
  const [msaAlgorithm, setMsaAlgorithm] = useStoredMsaAlgorithm();
38
37
  const [maxSpecies, setMaxSpecies] = useState(String(defaultMaxSpecies));
39
38
  const geneCandidates = useMemo(() => getGeneIdentifiers(feature), [feature]);
@@ -43,7 +42,7 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, preferre
43
42
  preferredTranscriptId,
44
43
  });
45
44
  const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
46
- const e = transcriptSelection.error ?? launchViewError;
45
+ const e = transcriptSelection.error ?? launchError;
47
46
  const rowCount = Number(maxSpecies);
48
47
  const rowCountValid = Number.isInteger(rowCount) && rowCount >= 2;
49
48
  return (React.createElement(React.Fragment, null,
@@ -59,14 +58,15 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, preferre
59
58
  ? rowsHint[source](rowCount)
60
59
  : 'a whole number, 2 or more' })),
61
60
  React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })),
62
- React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
63
- try {
64
- if (selectedTranscript) {
65
- setLaunchViewError(undefined);
66
- orthologLaunchView({
67
- feature: selectedTranscript,
61
+ React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !rowCountValid, onSubmit: placement => {
62
+ if (selectedTranscript) {
63
+ submit(() => {
64
+ launchConnectedView({
68
65
  view,
69
- newViewTitle: `Orthologs - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
66
+ feature: selectedTranscript,
67
+ placement,
68
+ displayName: `Orthologs - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
69
+ ...builtAlignmentLook,
70
70
  orthologParams: {
71
71
  taxId,
72
72
  source,
@@ -77,12 +77,7 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, preferre
77
77
  proteinSequence,
78
78
  },
79
79
  });
80
- handleClose();
81
- }
82
- }
83
- catch (e) {
84
- console.error(e);
85
- setLaunchViewError(e);
80
+ });
86
81
  }
87
82
  }, onCancel: handleClose })));
88
83
  });
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { OrthologSource } from '../../../MsaViewPanel/model';
3
- export declare const ORTHOLOG_SOURCE_STORAGE_KEY = "msaview-ortholog-source";
4
3
  export declare const orthologSourceLabels: Record<OrthologSource, string>;
4
+ export declare function validOrthologSource(stored: unknown): OrthologSource;
5
+ export declare function useStoredOrthologSource(): readonly ["ncbi" | "panther" | "uniref", (source: OrthologSource) => void];
5
6
  export default function OrthologSourceSelect({ value, onChange, className, }: {
6
7
  value: OrthologSource;
7
8
  onChange: (val: OrthologSource) => void;
@@ -1,12 +1,28 @@
1
1
  import React from 'react';
2
2
  import { MenuItem } from '@mui/material';
3
3
  import TextField2 from '../../../components/TextField2';
4
- export const ORTHOLOG_SOURCE_STORAGE_KEY = 'msaview-ortholog-source';
4
+ import { useLocalStorage } from '../../../utils/useLocalStorage';
5
+ const ORTHOLOG_SOURCE_STORAGE_KEY = 'msaview-ortholog-source';
5
6
  export const orthologSourceLabels = {
6
7
  ncbi: 'NCBI orthologs',
7
8
  panther: 'PANTHER',
8
9
  uniref: 'UniRef cluster',
9
10
  };
11
+ const orthologSources = Object.keys(orthologSourceLabels);
12
+ // another plugin version on the same origin may have stored a source this one
13
+ // lacks, and the tab indexes its hints by it
14
+ export function validOrthologSource(stored) {
15
+ return orthologSources.find(s => s === stored) ?? 'ncbi';
16
+ }
17
+ export function useStoredOrthologSource() {
18
+ const [stored, setStored] = useLocalStorage(ORTHOLOG_SOURCE_STORAGE_KEY, 'ncbi');
19
+ return [
20
+ validOrthologSource(stored),
21
+ (source) => {
22
+ setStored(source);
23
+ },
24
+ ];
25
+ }
10
26
  // Which species a source can answer for, in the words a reader picking one
11
27
  // needs: NCBI's ortholog sets stop at vertebrates and insects, PANTHER's run
12
28
  // from human to yeast and Arabidopsis, and a UniRef cluster is every UniProtKB
@@ -19,5 +35,5 @@ const hints = {
19
35
  export default function OrthologSourceSelect({ value, onChange, className, }) {
20
36
  return (React.createElement(TextField2, { variant: "outlined", label: "Source", className: className, select: true, value: value, helperText: hints[value], onChange: event => {
21
37
  onChange(event.target.value);
22
- } }, Object.keys(orthologSourceLabels).map(val => (React.createElement(MenuItem, { value: val, key: val }, orthologSourceLabels[val])))));
38
+ } }, orthologSources.map(val => (React.createElement(MenuItem, { value: val, key: val }, orthologSourceLabels[val])))));
23
39
  }
@@ -0,0 +1,13 @@
1
+ import { expect, test } from 'vitest';
2
+ import { validOrthologSource } from './OrthologSourceSelect';
3
+ test('a stored source this version knows comes back as it went in', () => {
4
+ expect(validOrthologSource('panther')).toBe('panther');
5
+ expect(validOrthologSource('uniref')).toBe('uniref');
6
+ });
7
+ // the Orthologs tab is the dialog's default, and it looked its helper text up
8
+ // by whatever was stored, so an unknown value threw on open
9
+ test('anything else falls back to NCBI', () => {
10
+ expect(validOrthologSource('ensembl')).toBe('ncbi');
11
+ expect(validOrthologSource(undefined)).toBe('ncbi');
12
+ expect(validOrthologSource({ source: 'panther' })).toBe('ncbi');
13
+ });
@@ -13,9 +13,9 @@ import QueryRowSelector from '../QueryRowSelector';
13
13
  import SequenceStatusMessage from '../SequenceStatus';
14
14
  import SubmitCancelActions from '../SubmitCancelActions';
15
15
  import TranscriptSelector from '../TranscriptSelector';
16
+ import { launchConnectedView, useLaunchSubmit } from '../launchConnectedView';
16
17
  import { useTranscriptSelection } from '../useTranscriptSelection';
17
18
  import { fetchMSA, fetchMSAList } from './fetchMSAData';
18
- import { preCalculatedLaunchView } from './preCalculatedLaunchView';
19
19
  import { readMsaDatasets } from './types';
20
20
  const useStyles = makeStyles()({
21
21
  selectedContainer: {
@@ -28,7 +28,7 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
28
28
  const { classes } = useStyles();
29
29
  const { pluginManager } = getEnv(model);
30
30
  const { assemblyNames } = view;
31
- const [viewError, setViewError] = useState();
31
+ const { launchError, submit } = useLaunchSubmit(handleClose);
32
32
  const datasets = readMsaDatasets(session.jbrowse);
33
33
  const [selectedDatasetId, setSelectedDatasetId] = useState(datasets?.[0]?.datasetId);
34
34
  const selectedDataset = datasets?.find(d => d.datasetId === selectedDatasetId);
@@ -63,7 +63,7 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
63
63
  const e = msaListFetchError ??
64
64
  msaDataFetchError ??
65
65
  transcriptSelection.error ??
66
- viewError;
66
+ launchError;
67
67
  return (React.createElement(React.Fragment, null,
68
68
  React.createElement(LaunchPanelContent, { error: e },
69
69
  React.createElement(TextField2, { select: true, label: "Select MSA dataset", value: selectedDatasetId, onChange: event => {
@@ -80,22 +80,27 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferred
80
80
  // launching without a query row opens a view that renders and then
81
81
  // never navigates, which reads as a broken feature rather than a
82
82
  // dataset that does not cover this gene
83
- submitDisabled: !selectedTranscript || !msaData?.length || !querySeqName, onSubmit: () => {
84
- try {
85
- if (selectedTranscript && msaText) {
86
- preCalculatedLaunchView({
87
- newViewTitle: getGeneDisplayName(selectedTranscript),
83
+ submitDisabled: !selectedTranscript || !msaData?.length || !querySeqName, onSubmit: placement => {
84
+ if (selectedTranscript && msaText) {
85
+ submit(() => {
86
+ launchConnectedView({
88
87
  view,
88
+ feature: selectedTranscript,
89
+ placement,
90
+ displayName: getGeneDisplayName(selectedTranscript),
91
+ treeAreaWidth: 200,
92
+ treeWidth: 100,
93
+ drawNodeBubbles: false,
94
+ labelsAlignRight: true,
95
+ showBranchLen: false,
96
+ colWidth: 10,
97
+ rowHeight: 12,
98
+ colorSchemeName: 'percent_identity_dynamic',
89
99
  querySeqName,
90
100
  querySeqOffset: queryRow.querySeqOffset,
91
- feature: selectedTranscript,
92
101
  data: { msa: msaText },
93
102
  });
94
- handleClose();
95
- }
96
- }
97
- catch (e) {
98
- setViewError(e);
103
+ });
99
104
  }
100
105
  }, onCancel: handleClose })));
101
106
  });
@@ -1,7 +1,9 @@
1
1
  import React from 'react';
2
+ import type { MsaViewPlacement } from '../../utils/workspaces';
2
3
  import type { AbstractTrackModel } from '@jbrowse/core/util';
3
4
  export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel, cancelLabel, model, }: {
4
- onSubmit: () => void;
5
+ /** handed the placement the box states, so no launch re-reads storage */
6
+ onSubmit: (placement: MsaViewPlacement) => void;
5
7
  onCancel: () => void;
6
8
  submitDisabled?: boolean;
7
9
  /** why Submit is grey, shown beside it */