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