jbrowse-plugin-msaview 3.6.0 → 3.7.1
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.
- package/README.md +46 -24
- package/dist/LaunchMsaView/cleanProteinSequence.test.d.ts +1 -0
- package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
- package/dist/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.js +29 -32
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +10 -15
- package/dist/LaunchMsaView/components/BlastQuery/CachedBlastResults.js +14 -13
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +25 -22
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +14 -19
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.d.ts +2 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.js +18 -2
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.d.ts +1 -0
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.js +13 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +19 -14
- package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +3 -1
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +10 -7
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +20 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.d.ts +8 -1
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +25 -20
- package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
- package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
- package/dist/LaunchMsaView/detectQueryRow.js +4 -7
- package/dist/LaunchMsaView/util.d.ts +6 -0
- package/dist/LaunchMsaView/util.js +7 -1
- package/dist/LaunchMsaViewExtensionPoint/index.js +25 -2
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +60 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.js +12 -3
- package/dist/MsaViewPanel/doLaunchBlast.js +2 -1
- package/dist/MsaViewPanel/doLaunchBlast.test.js +12 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +1 -1
- package/dist/MsaViewPanel/genomeToMSA.js +3 -3
- package/dist/MsaViewPanel/genomeToMSA.test.js +69 -23
- package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
- package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
- package/dist/MsaViewPanel/model.d.ts +16 -13
- package/dist/MsaViewPanel/msaDataStore.d.ts +5 -3
- package/dist/MsaViewPanel/msaDataStore.js +3 -12
- package/dist/MsaViewPanel/msaDataStore.test.js +8 -1
- package/dist/MsaViewPanel/observeProteinHighlights.test.js +4 -4
- package/dist/MsaViewPanel/regionSnapshot.test.d.ts +1 -0
- package/dist/MsaViewPanel/regionSnapshot.test.js +29 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.d.ts +41 -4
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +103 -68
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.d.ts +1 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.test.js +149 -0
- package/dist/MsaViewPanel/runLaunch.d.ts +8 -4
- package/dist/MsaViewPanel/runLaunch.js +15 -4
- package/dist/MsaViewPanel/runLaunch.test.js +16 -0
- package/dist/MsaViewPanel/storedData.test.js +23 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +3 -2
- package/dist/MsaViewPanel/util.d.ts +23 -23
- package/dist/MsaViewPanel/util.js +23 -24
- package/dist/index.js +17 -10
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +38 -36
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +26 -2
- package/dist/utils/blastCache.js +51 -39
- package/dist/utils/blastCache.test.js +46 -19
- package/dist/utils/domainCache.d.ts +1 -1
- package/dist/utils/domainCache.js +18 -14
- package/dist/utils/ebiJobDispatcher.d.ts +11 -0
- package/dist/utils/ebiJobDispatcher.js +12 -13
- package/dist/utils/ebiJobDispatcher.test.js +17 -3
- package/dist/utils/eutils.d.ts +14 -0
- package/dist/utils/eutils.js +48 -14
- package/dist/utils/eutils.test.d.ts +1 -0
- package/dist/utils/eutils.test.js +31 -0
- package/dist/utils/idb.d.ts +16 -3
- package/dist/utils/idb.js +51 -5
- package/dist/utils/idb.test.d.ts +1 -0
- package/dist/utils/idb.test.js +78 -0
- package/dist/utils/ncbiDomains.js +4 -4
- package/dist/utils/ncbiOrthologs.d.ts +1 -1
- package/dist/utils/ncbiOrthologs.js +18 -17
- package/dist/utils/ncbiOrthologs.test.js +17 -1
- package/dist/utils/ncbiTaxonomy.js +14 -15
- package/dist/utils/taxonomyNames.js +23 -19
- package/dist/utils/taxonomyNames.test.d.ts +1 -0
- package/dist/utils/taxonomyNames.test.js +33 -0
- package/dist/utils/unirefHomologs.js +3 -3
- package/dist/utils/unirefHomologs.test.js +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -10
- package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +35 -32
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +10 -14
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +34 -21
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +18 -21
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.test.ts +16 -0
- package/src/LaunchMsaView/components/OrthologQuery/OrthologSourceSelect.tsx +24 -2
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +19 -13
- package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +27 -2
- package/src/LaunchMsaView/components/SubmitCancelActions.tsx +12 -7
- package/src/LaunchMsaView/components/calculateProteinSequence.ts +28 -18
- package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
- package/src/LaunchMsaView/detectQueryRow.ts +5 -7
- package/src/LaunchMsaView/util.ts +7 -1
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +64 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +68 -5
- package/src/MsaViewPanel/afterCreateAutoruns.ts +12 -3
- package/src/MsaViewPanel/doLaunchBlast.test.ts +20 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +2 -1
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +1 -1
- package/src/MsaViewPanel/genomeToMSA.test.ts +75 -23
- package/src/MsaViewPanel/genomeToMSA.ts +3 -2
- package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
- package/src/MsaViewPanel/msaDataStore.test.ts +14 -2
- package/src/MsaViewPanel/msaDataStore.ts +4 -12
- package/src/MsaViewPanel/observeProteinHighlights.test.ts +4 -4
- package/src/MsaViewPanel/regionSnapshot.test.ts +34 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.test.ts +183 -0
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +154 -71
- package/src/MsaViewPanel/runLaunch.test.ts +19 -0
- package/src/MsaViewPanel/runLaunch.ts +21 -4
- package/src/MsaViewPanel/storedData.test.ts +32 -0
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +3 -2
- package/src/MsaViewPanel/util.ts +38 -29
- package/src/index.ts +19 -10
- package/src/utils/blastCache.test.ts +59 -20
- package/src/utils/blastCache.ts +62 -39
- package/src/utils/domainCache.ts +27 -15
- package/src/utils/ebiJobDispatcher.test.ts +18 -3
- package/src/utils/ebiJobDispatcher.ts +12 -16
- package/src/utils/eutils.test.ts +39 -0
- package/src/utils/eutils.ts +64 -14
- package/src/utils/idb.test.ts +106 -0
- package/src/utils/idb.ts +57 -5
- package/src/utils/ncbiDomains.ts +4 -4
- package/src/utils/ncbiOrthologs.test.ts +20 -0
- package/src/utils/ncbiOrthologs.ts +26 -28
- package/src/utils/ncbiTaxonomy.ts +17 -20
- package/src/utils/taxonomyNames.test.ts +41 -0
- package/src/utils/taxonomyNames.ts +33 -19
- package/src/utils/unirefHomologs.test.ts +11 -1
- package/src/utils/unirefHomologs.ts +3 -3
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.d.ts +0 -16
- package/dist/LaunchMsaView/components/BlastQuery/blastLaunchView.js +0 -31
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +0 -16
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +0 -16
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.d.ts +0 -9
- package/dist/LaunchMsaView/components/OrthologQuery/orthologLaunchView.js +0 -15
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.d.ts +0 -13
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -22
- package/src/LaunchMsaView/components/BlastQuery/blastLaunchView.ts +0 -59
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +0 -43
- package/src/LaunchMsaView/components/OrthologQuery/orthologLaunchView.ts +0 -31
- package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +0 -42
package/README.md
CHANGED
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
# jbrowse-plugin-msaview
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
A multiple sequence alignment and phylogenetic tree viewer for JBrowse 2, linked
|
|
4
|
+
column by column to the genome. It packages
|
|
5
|
+
[react-msaview](https://github.com/GMOD/react-msaview), whose
|
|
6
|
+
[user guide](https://github.com/GMOD/react-msaview/blob/main/docs/user_guide.md)
|
|
7
|
+
covers the viewer itself.
|
|
8
8
|
|
|
9
9
|
## Gallery
|
|
10
10
|
|
|
11
11
|

|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
The [demo](#demo) below: TP53 in the genome view beside a vertebrate p53
|
|
14
|
+
alignment carrying per-residue variant tracks. Each release recaptures it with
|
|
15
|
+
`pnpm readme-figure`.
|
|
14
16
|
|
|
15
17
|
## Demo
|
|
16
18
|
|
|
19
|
+
[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]},{%22type%22:%22MsaView%22,%22displayName%22:%22p53%20across%20vertebrates%22,%22msa%22:%22https://gmod.org/JBrowseMSA/demo/data/p53/p53-vertebrates.afa%22,%22tree%22:%22https://gmod.org/JBrowseMSA/demo/data/p53/p53-vertebrates.nh%22,%22query%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:[{%22start%22:102,%22end%22:292,%22label%22:%22DNA-binding%22,%22color%22:%22rgba%28255,140,0,0.15%29%22},175,245,248,249,273,282],%22region%22:%22170-290%22,%22columnTracks%22:[{%22name%22:%22ClinVar%20pathogenic%20missense%22,%22color%22:%22%23c0392b%22,%22max%22:8,%22start%22:105,%22values%22:[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]},{%22name%22:%22AlphaMissense%20mean%20%28x100%29%22,%22color%22:%22%231565c0%22,%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]},{%22name%22:%22MaveDB%20nutlin-3,%20p53WT%22,%22color%22:%22%232e7d32%22,%22max%22:2,%22start%22:27,%22values%22:[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]}]},{%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]}}]}):
|
|
20
|
+
TP53 in hg38, a vertebrate p53 alignment carrying ClinVar, AlphaMissense and
|
|
21
|
+
MaveDB per-residue tracks, and the AlphaFold model of P04637. Hovering a codon,
|
|
22
|
+
a column or a residue lights the other two views.
|
|
23
|
+
[Launching](docs/launching.md#the-readme-demo-taken-apart) takes the link apart.
|
|
24
|
+
|
|
25
|
+
An older shared session:
|
|
17
26
|
https://jbrowse.org/code/jb2/main/index.html?config=https://unpkg.com/jbrowse-plugin-msaview/dist/config.json&session=share-BVmmEYAoAv&password=SuQaN
|
|
18
27
|
|
|
19
28
|
## Features
|
|
@@ -35,7 +44,7 @@ https://jbrowse.org/code/jb2/main/index.html?config=https://unpkg.com/jbrowse-pl
|
|
|
35
44
|
- Aligns and builds trees in the browser, so a UniRef launch needs no job at any
|
|
36
45
|
external service; EBI's aligners remain an option
|
|
37
46
|
- Every launch is also a session-spec URL (`orthologParams`, `searchParams`,
|
|
38
|
-
`connectedTranscript`), see [
|
|
47
|
+
`connectedTranscript`), see [launching](docs/launching.md)
|
|
39
48
|
|
|
40
49
|
## File format supports
|
|
41
50
|
|
|
@@ -46,30 +55,43 @@ https://jbrowse.org/code/jb2/main/index.html?config=https://unpkg.com/jbrowse-pl
|
|
|
46
55
|
- Clustal files (e.g. .aln file, uses clustal-js parser)
|
|
47
56
|
- Newick (tree can be loaded separately as a .nh file)
|
|
48
57
|
|
|
49
|
-
##
|
|
58
|
+
## Availability
|
|
50
59
|
|
|
51
|
-
|
|
60
|
+
This plugin is installed by default on https://genomes.jbrowse.org so you can
|
|
61
|
+
use it on any species there
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
Holmes, I. (2026). Proteins in the Genome Browser: Integration of Phylogenies,
|
|
55
|
-
Alignments, and Structures With Nucleotide-level Evidence in JBrowse 2. Journal
|
|
56
|
-
of Molecular Biology, 169645. https://doi.org/10.1016/j.jmb.2026.169645
|
|
63
|
+
## Documentation
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
Using the plugin:
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
- [Alignments from a gene](docs/alignments-from-a-gene.md): the launch dialog's
|
|
68
|
+
sources — NCBI, PANTHER and UniRef orthologs, EBI searches — and aligning in
|
|
69
|
+
the browser.
|
|
70
|
+
- [Your own alignments](docs/your-own-alignments.md): opening an alignment you
|
|
71
|
+
made, from a file, a link, or a per-gene dataset a site configures.
|
|
72
|
+
- [BLAST](docs/blast.md): why searches run at EBI rather than NCBI, and the
|
|
73
|
+
manual route to NCBI's `nr`.
|
|
61
74
|
|
|
62
|
-
|
|
63
|
-
use it on any species there
|
|
75
|
+
Linking and embedding:
|
|
64
76
|
|
|
65
|
-
|
|
77
|
+
- [Launching from a URL or code](docs/launching.md): worked session-spec links,
|
|
78
|
+
the genome connection, highlights, opening on a residue, and placement.
|
|
79
|
+
- [Launch parameters](docs/launch-parameters.md): every argument and field.
|
|
80
|
+
- [Linked views](docs/linked-views.md): how a column reaches its codon and a
|
|
81
|
+
protein3d structure.
|
|
66
82
|
|
|
67
|
-
|
|
83
|
+
Working on the plugin:
|
|
84
|
+
|
|
85
|
+
- [DEVELOPERS.md](DEVELOPERS.md): running it locally, the checks, the README
|
|
86
|
+
figure and publishing.
|
|
87
|
+
|
|
88
|
+
## Publication
|
|
68
89
|
|
|
69
|
-
|
|
90
|
+
If you find this tool useful please cite our work
|
|
70
91
|
|
|
71
|
-
|
|
92
|
+
Diesh, C., Stevens, G., Bridge, C., Hogue, G., Buels, R., Cain, S., Stein, L., &
|
|
93
|
+
Holmes, I. (2026). Proteins in the Genome Browser: Integration of Phylogenies,
|
|
94
|
+
Alignments, and Structures With Nucleotide-level Evidence in JBrowse 2. Journal
|
|
95
|
+
of Molecular Biology, 169645. https://doi.org/10.1016/j.jmb.2026.169645
|
|
72
96
|
|
|
73
|
-
|
|
74
|
-
pnpm version minor
|
|
75
|
-
```
|
|
97
|
+
See also https://github.com/GMOD/proteinbrowser for overview
|
|
@@ -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
|
|
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 ??
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
//
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
46
|
-
const
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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, {
|
|
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
|
|
33
|
+
const { launchError, submit } = useLaunchSubmit(handleClose);
|
|
35
34
|
const [taxId, setTaxId] = useState(9606);
|
|
36
|
-
const [source, setSource] =
|
|
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 ??
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
} },
|
|
38
|
+
} }, orthologSources.map(val => (React.createElement(MenuItem, { value: val, key: val }, orthologSourceLabels[val])))));
|
|
23
39
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|