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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isGeneLikeType } from './codingFeature';
|
|
1
2
|
import type { MenuItem } from '@jbrowse/core/ui';
|
|
2
3
|
import type { Feature } from '@jbrowse/core/util';
|
|
3
4
|
export interface ContextMenuInfo {
|
|
@@ -13,13 +14,40 @@ export interface DisplayModel {
|
|
|
13
14
|
fetchFullFeature?: (featureId: string, displayedRegionIndex: number) => Promise<Feature | undefined>;
|
|
14
15
|
contextMenuFeature?: Feature;
|
|
15
16
|
}
|
|
16
|
-
export declare function isGeneLikeType(type: unknown): boolean;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host and not
|
|
23
|
-
* on a 3.7 one.
|
|
18
|
+
* What the menu item launches on. A legacy host hands over the whole feature,
|
|
19
|
+
* so whether it codes for anything is known while the menu is built; a canvas
|
|
20
|
+
* host's hit test carries a type and an id, so that question can only be
|
|
21
|
+
* answered after the fetch, and the caller answers it there.
|
|
24
22
|
*/
|
|
25
|
-
|
|
23
|
+
interface ClickedTranscript {
|
|
24
|
+
/**
|
|
25
|
+
* The isoform the click actually landed on, when the dialog opens on its
|
|
26
|
+
* gene. Climbing to the gene is what puts every isoform in the picker, and it
|
|
27
|
+
* threw away which one the user pointed at: the picker then opened on the
|
|
28
|
+
* longest transcript, and on a v4 host that is the only place the choice was
|
|
29
|
+
* ever stated.
|
|
30
|
+
*/
|
|
31
|
+
preferredTranscriptId?: string;
|
|
32
|
+
}
|
|
33
|
+
export type MenuTarget = (ClickedTranscript & {
|
|
34
|
+
feature: Feature;
|
|
35
|
+
}) | (ClickedTranscript & {
|
|
36
|
+
fetchFeature: () => Promise<Feature | undefined>;
|
|
37
|
+
});
|
|
38
|
+
export { isGeneLikeType };
|
|
39
|
+
/**
|
|
40
|
+
* How to get the right-clicked feature, or nothing when there is nothing to
|
|
41
|
+
* launch on. Both host shapes reduce to one target, so the menu item is built
|
|
42
|
+
* and the dialog is opened by one code path — and the same gene test decides
|
|
43
|
+
* both. The strict three-name set the legacy branch used to carry disagreed
|
|
44
|
+
* with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
|
|
45
|
+
* and not on a 3.7 one.
|
|
46
|
+
*
|
|
47
|
+
* Gene-like is not enough on its own: an lncRNA has no protein to align, and
|
|
48
|
+
* accepting it opened a dialog whose Submit never left grey, with nothing
|
|
49
|
+
* saying why. Where the whole feature is in hand the CDS decides here -- but
|
|
50
|
+
* only where there are subfeatures to read it off, since a feature that came
|
|
51
|
+
* with none has not answered the question.
|
|
52
|
+
*/
|
|
53
|
+
export declare function launchTarget(self: DisplayModel): MenuTarget | undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { geneLikeRoot, isGeneLikeType, isKnownNonCoding } from './codingFeature';
|
|
1
2
|
// Read off the clicked item rather than off the display.
|
|
2
3
|
//
|
|
3
4
|
// LinearBasicDisplay used to publish an `isGeneLike` getter and this gated on
|
|
@@ -12,17 +13,20 @@
|
|
|
12
13
|
// costs one comparison. Deliberately the same loose case-insensitive test the
|
|
13
14
|
// host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
|
|
14
15
|
// 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
|
|
15
|
-
export
|
|
16
|
-
const t = String(type ?? '').toLowerCase();
|
|
17
|
-
return t.includes('gene') || t.includes('rna') || t.includes('transcript');
|
|
18
|
-
}
|
|
16
|
+
export { isGeneLikeType };
|
|
19
17
|
/**
|
|
20
|
-
* How to get the right-clicked feature, or nothing when there is
|
|
21
|
-
* launch on. Both host shapes reduce to
|
|
22
|
-
* the dialog is opened by one code path — and the same gene test decides
|
|
23
|
-
* The strict three-name set the legacy branch used to carry disagreed
|
|
24
|
-
* loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
|
|
25
|
-
* on a 3.7 one.
|
|
18
|
+
* How to get the right-clicked feature, or nothing when there is nothing to
|
|
19
|
+
* launch on. Both host shapes reduce to one target, so the menu item is built
|
|
20
|
+
* and the dialog is opened by one code path — and the same gene test decides
|
|
21
|
+
* both. The strict three-name set the legacy branch used to carry disagreed
|
|
22
|
+
* with the loose one above, so a `lnc_RNA` offered the menu item on a 4.3 host
|
|
23
|
+
* and not on a 3.7 one.
|
|
24
|
+
*
|
|
25
|
+
* Gene-like is not enough on its own: an lncRNA has no protein to align, and
|
|
26
|
+
* accepting it opened a dialog whose Submit never left grey, with nothing
|
|
27
|
+
* saying why. Where the whole feature is in hand the CDS decides here -- but
|
|
28
|
+
* only where there are subfeatures to read it off, since a feature that came
|
|
29
|
+
* with none has not answered the question.
|
|
26
30
|
*/
|
|
27
31
|
export function launchTarget(self) {
|
|
28
32
|
const info = self.contextMenuInfo;
|
|
@@ -32,11 +36,17 @@ export function launchTarget(self) {
|
|
|
32
36
|
// rejects the click can only answer with some other feature
|
|
33
37
|
if (info && fetchFullFeature) {
|
|
34
38
|
return isGeneLikeType(info.item.type)
|
|
35
|
-
?
|
|
39
|
+
? {
|
|
40
|
+
fetchFeature: () => fetchFullFeature(info.item.featureId, info.displayedRegionIndex),
|
|
41
|
+
}
|
|
36
42
|
: undefined;
|
|
37
43
|
}
|
|
38
44
|
const legacy = self.contextMenuFeature;
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
if (!legacy) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const root = geneLikeRoot(legacy);
|
|
49
|
+
return isGeneLikeType(root.get('type')) && !isKnownNonCoding(root)
|
|
50
|
+
? { feature: root, preferredTranscriptId: legacy.id() }
|
|
41
51
|
: undefined;
|
|
42
52
|
}
|
|
@@ -1,19 +1,36 @@
|
|
|
1
|
+
import { SimpleFeature } from '@jbrowse/core/util';
|
|
1
2
|
import { describe, expect, test } from 'vitest';
|
|
2
3
|
import { isGeneLikeType, launchTarget } from './launchTarget';
|
|
3
|
-
function feature(type) {
|
|
4
|
-
return {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
function feature(type, subfeatures = ['CDS']) {
|
|
5
|
+
return new SimpleFeature({
|
|
6
|
+
uniqueId: type,
|
|
7
|
+
refName: 'chr1',
|
|
8
|
+
start: 0,
|
|
9
|
+
end: 100,
|
|
10
|
+
type,
|
|
11
|
+
subfeatures: subfeatures.map((t, i) => ({
|
|
12
|
+
uniqueId: `${type}-${i}`,
|
|
13
|
+
refName: 'chr1',
|
|
14
|
+
start: 0,
|
|
15
|
+
end: 100,
|
|
16
|
+
type: t,
|
|
17
|
+
})),
|
|
18
|
+
});
|
|
7
19
|
}
|
|
8
20
|
const modernHost = (type) => ({
|
|
9
21
|
contextMenuItems: () => [],
|
|
10
22
|
contextMenuInfo: { item: { featureId: 'f1', type }, displayedRegionIndex: 0 },
|
|
11
23
|
fetchFullFeature: (featureId) => Promise.resolve(feature(`fetched:${featureId}`)),
|
|
12
24
|
});
|
|
13
|
-
const legacyHost = (
|
|
25
|
+
const legacyHost = (feat) => ({
|
|
14
26
|
contextMenuItems: () => [],
|
|
15
|
-
contextMenuFeature:
|
|
27
|
+
contextMenuFeature: feat,
|
|
16
28
|
});
|
|
29
|
+
async function fetched(target) {
|
|
30
|
+
return target && 'fetchFeature' in target
|
|
31
|
+
? await target.fetchFeature()
|
|
32
|
+
: undefined;
|
|
33
|
+
}
|
|
17
34
|
describe('isGeneLikeType', () => {
|
|
18
35
|
test.each(['gene', 'mRNA', 'transcript', 'lnc_RNA', 'protein_coding_gene'])('accepts %s', type => {
|
|
19
36
|
expect(isGeneLikeType(type)).toBe(true);
|
|
@@ -25,8 +42,8 @@ describe('isGeneLikeType', () => {
|
|
|
25
42
|
describe('launchTarget', () => {
|
|
26
43
|
test('resolves the clicked feature through fetchFullFeature', async () => {
|
|
27
44
|
const target = launchTarget(modernHost('mRNA'));
|
|
28
|
-
expect(target).toBeDefined();
|
|
29
|
-
expect((await target
|
|
45
|
+
expect(await fetched(target)).toBeDefined();
|
|
46
|
+
expect((await fetched(target))?.get('type')).toBe('fetched:f1');
|
|
30
47
|
});
|
|
31
48
|
test('offers nothing for a non-gene click', () => {
|
|
32
49
|
expect(launchTarget(modernHost('CDS'))).toBeUndefined();
|
|
@@ -37,18 +54,66 @@ describe('launchTarget', () => {
|
|
|
37
54
|
});
|
|
38
55
|
// v3.7.0 hosts have contextMenuFeature and nothing else; dropping this
|
|
39
56
|
// fallback once took "Launch MSA view" off every host in the wild.
|
|
40
|
-
test('falls back to a synchronous contextMenuFeature',
|
|
41
|
-
const target = launchTarget(legacyHost('mRNA'));
|
|
42
|
-
expect(target).
|
|
43
|
-
expect((await target())?.get('type')).toBe('mRNA');
|
|
57
|
+
test('falls back to a synchronous contextMenuFeature', () => {
|
|
58
|
+
const target = launchTarget(legacyHost(feature('mRNA')));
|
|
59
|
+
expect(target && 'feature' in target && target.feature.get('type')).toBe('mRNA');
|
|
44
60
|
});
|
|
45
61
|
test('applies the same gene test on both host shapes', () => {
|
|
46
|
-
expect(launchTarget(legacyHost('lnc_RNA'))).toBeDefined();
|
|
47
|
-
expect(launchTarget(legacyHost('exon'))).toBeUndefined();
|
|
62
|
+
expect(launchTarget(legacyHost(feature('lnc_RNA')))).toBeDefined();
|
|
63
|
+
expect(launchTarget(legacyHost(feature('exon')))).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
// gene-like is not enough: an lncRNA translates to nothing, and offering the
|
|
66
|
+
// item opened a dialog whose Submit never left grey
|
|
67
|
+
test('declines a gene-like feature with no CDS below it', () => {
|
|
68
|
+
expect(launchTarget(legacyHost(feature('lnc_RNA', ['exon'])))).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
// a legacy host can hand over a bare record whose children are a separate
|
|
71
|
+
// query, and declining it takes the item off an ordinary gene with no word
|
|
72
|
+
test('offers the item for a gene that arrived with no subfeatures', () => {
|
|
73
|
+
expect(launchTarget(legacyHost(feature('gene', [])))).toBeDefined();
|
|
74
|
+
});
|
|
75
|
+
// a click on an isoform should open the dialog on the gene, so every
|
|
76
|
+
// transcript is there to pick from
|
|
77
|
+
test('climbs to the gene from a clicked isoform', () => {
|
|
78
|
+
const gene = new SimpleFeature({
|
|
79
|
+
uniqueId: 'gene',
|
|
80
|
+
refName: 'chr1',
|
|
81
|
+
start: 0,
|
|
82
|
+
end: 100,
|
|
83
|
+
type: 'gene',
|
|
84
|
+
subfeatures: [
|
|
85
|
+
{
|
|
86
|
+
uniqueId: 'mrna',
|
|
87
|
+
refName: 'chr1',
|
|
88
|
+
start: 0,
|
|
89
|
+
end: 100,
|
|
90
|
+
type: 'mRNA',
|
|
91
|
+
subfeatures: [
|
|
92
|
+
{
|
|
93
|
+
uniqueId: 'cds',
|
|
94
|
+
refName: 'chr1',
|
|
95
|
+
start: 0,
|
|
96
|
+
end: 100,
|
|
97
|
+
type: 'CDS',
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
});
|
|
103
|
+
const mrna = gene.get('subfeatures')[0];
|
|
104
|
+
const target = launchTarget(legacyHost(mrna));
|
|
105
|
+
expect(target && 'feature' in target && target.feature).toBe(gene);
|
|
106
|
+
// the gene is what the dialog opens on, but the picker still opens on the
|
|
107
|
+
// isoform the click landed on -- on a v4 host that click is the only place
|
|
108
|
+
// the choice is ever stated
|
|
109
|
+
expect(target?.preferredTranscriptId).toBe(mrna.id());
|
|
48
110
|
});
|
|
49
111
|
// a host that has both shapes must not fall through to the legacy branch and
|
|
50
112
|
// launch on a stale feature when the click was not on a gene
|
|
51
113
|
test('a non-gene click on a host carrying both shapes offers nothing', () => {
|
|
52
|
-
expect(launchTarget({
|
|
114
|
+
expect(launchTarget({
|
|
115
|
+
...modernHost('CDS'),
|
|
116
|
+
...legacyHost(feature('mRNA')),
|
|
117
|
+
})).toBeUndefined();
|
|
53
118
|
});
|
|
54
119
|
});
|
|
@@ -6,10 +6,23 @@
|
|
|
6
6
|
* effect writing back into state, and an override survives later edits to the
|
|
7
7
|
* alignment because it is the one thing actually stored.
|
|
8
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* The row to launch with. Detection by residues is the answer wherever it has
|
|
11
|
+
* one -- aligners rename the query on the way through, so a name proves
|
|
12
|
+
* nothing -- but a caller with a naming convention of its own can offer it as a
|
|
13
|
+
* fallback, and it is taken only when the alignment really carries that row.
|
|
14
|
+
* An empty result means there is no query row, which a launch has to refuse:
|
|
15
|
+
* the view opens, renders, and then never navigates.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveQueryRowName(queryRow: {
|
|
18
|
+
querySeqName: string;
|
|
19
|
+
names: string[];
|
|
20
|
+
}, fallback: string): string;
|
|
9
21
|
export declare function useQueryRowName(msaText: string, proteinSequence: string): {
|
|
10
22
|
detected: import("./detectQueryRow").QueryRowMatch | undefined;
|
|
11
23
|
names: string[];
|
|
12
24
|
querySeqName: string;
|
|
25
|
+
querySeqOffset: number;
|
|
13
26
|
setQuerySeqName: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
|
|
14
27
|
isAutoDetected: boolean;
|
|
15
28
|
};
|
|
@@ -8,15 +8,30 @@ import { findQueryRow } from './detectQueryRow';
|
|
|
8
8
|
* effect writing back into state, and an override survives later edits to the
|
|
9
9
|
* alignment because it is the one thing actually stored.
|
|
10
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* The row to launch with. Detection by residues is the answer wherever it has
|
|
13
|
+
* one -- aligners rename the query on the way through, so a name proves
|
|
14
|
+
* nothing -- but a caller with a naming convention of its own can offer it as a
|
|
15
|
+
* fallback, and it is taken only when the alignment really carries that row.
|
|
16
|
+
* An empty result means there is no query row, which a launch has to refuse:
|
|
17
|
+
* the view opens, renders, and then never navigates.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveQueryRowName(queryRow, fallback) {
|
|
20
|
+
return (queryRow.querySeqName || (queryRow.names.includes(fallback) ? fallback : ''));
|
|
21
|
+
}
|
|
11
22
|
export function useQueryRowName(msaText, proteinSequence) {
|
|
12
23
|
const [override, setOverride] = useState();
|
|
13
24
|
// parsing runs on every keystroke in the paste box otherwise, and an
|
|
14
25
|
// alignment of a few hundred rows is not free
|
|
15
26
|
const { names, match } = useMemo(() => findQueryRow(msaText, proteinSequence), [msaText, proteinSequence]);
|
|
27
|
+
const querySeqName = override ?? match?.name ?? '';
|
|
16
28
|
return {
|
|
17
29
|
detected: match,
|
|
18
30
|
names,
|
|
19
|
-
querySeqName
|
|
31
|
+
querySeqName,
|
|
32
|
+
// only the detected row's offset is known; a row the user picked instead is
|
|
33
|
+
// taken at face value, residue 1 to residue 1
|
|
34
|
+
querySeqOffset: querySeqName === match?.name ? match.offset : 0,
|
|
20
35
|
setQuerySeqName: setOverride,
|
|
21
36
|
isAutoDetected: override === undefined && !!match,
|
|
22
37
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { act, renderHook } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, test } from 'vitest';
|
|
4
|
+
import { resolveQueryRowName, useQueryRowName } from './useQueryRowName';
|
|
5
|
+
const protein = 'MKWVTFISLLLLFSSAYSRGVFRRDTHKSEIAHRFKDLGEEHFKGLVLIAFSQYLQQCPFD';
|
|
6
|
+
const trimmed = `>aligned_query\n${protein.slice(5, 45)}\n>other\nWRONGWRONGWRONGWRONGWRONGWRONGWRONGWRONG\n`;
|
|
7
|
+
describe('useQueryRowName', () => {
|
|
8
|
+
test('carries the detected row and where it starts', () => {
|
|
9
|
+
const { result } = renderHook(() => useQueryRowName(trimmed, protein));
|
|
10
|
+
expect(result.current.querySeqName).toBe('aligned_query');
|
|
11
|
+
expect(result.current.querySeqOffset).toBe(5);
|
|
12
|
+
});
|
|
13
|
+
// the offset was measured against the detected row; another row is a row the
|
|
14
|
+
// user vouched for, and nothing here knows where in the protein it sits
|
|
15
|
+
test('a row the user picks instead is taken at face value', () => {
|
|
16
|
+
const { result } = renderHook(() => useQueryRowName(trimmed, protein));
|
|
17
|
+
act(() => {
|
|
18
|
+
result.current.setQuerySeqName('other');
|
|
19
|
+
});
|
|
20
|
+
expect(result.current.querySeqName).toBe('other');
|
|
21
|
+
expect(result.current.querySeqOffset).toBe(0);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
// a pre-loaded dataset names its rows by a convention of its own, and the
|
|
25
|
+
// residues can be too diverged for detection to find the row that is right
|
|
26
|
+
// there
|
|
27
|
+
describe('resolveQueryRowName', () => {
|
|
28
|
+
const names = ['ENST1_hg38', 'mm39', 'rn7'];
|
|
29
|
+
test('prefers the detected or picked row over any fallback', () => {
|
|
30
|
+
expect(resolveQueryRowName({ querySeqName: 'mm39', names }, 'ENST1_hg38')).toBe('mm39');
|
|
31
|
+
});
|
|
32
|
+
test('falls back to a name the alignment actually carries', () => {
|
|
33
|
+
expect(resolveQueryRowName({ querySeqName: '', names }, 'ENST1_hg38')).toBe('ENST1_hg38');
|
|
34
|
+
});
|
|
35
|
+
// the whole bug: the old panel launched with this name whether or not the
|
|
36
|
+
// file had the row, and a name no row carries navigates nowhere
|
|
37
|
+
test('answers nothing for a fallback the alignment does not carry', () => {
|
|
38
|
+
expect(resolveQueryRowName({ querySeqName: '', names }, 'ENST9_hg38')).toBe('');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -2,11 +2,14 @@ import { getContainingView, sum } from '@jbrowse/core/util';
|
|
|
2
2
|
export function getLinearGenomeView(model) {
|
|
3
3
|
return getContainingView(model);
|
|
4
4
|
}
|
|
5
|
+
function isNonEmptyString(v) {
|
|
6
|
+
return typeof v === 'string' && v !== '';
|
|
7
|
+
}
|
|
5
8
|
function uniqueDefined(vals) {
|
|
6
|
-
return [...new Set(vals.filter(
|
|
9
|
+
return [...new Set(vals.filter(isNonEmptyString))];
|
|
7
10
|
}
|
|
8
11
|
function joinDefined(sep, parts) {
|
|
9
|
-
return parts.filter(
|
|
12
|
+
return parts.filter(isNonEmptyString).join(sep);
|
|
10
13
|
}
|
|
11
14
|
export function getTranscriptFeatures(feature) {
|
|
12
15
|
// check if we are looking at a 'two-level' or 'three-level' feature by
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { launchMsaView } from '../utils/launchMsaView';
|
|
2
2
|
export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
3
3
|
pluginManager.addToExtensionPoint('LaunchView-MsaView', (args) => {
|
|
4
|
-
const { session, data, msaFileLocation, msaIndexedLocation, msaName, treeFileLocation, querySeqName, ...rest } = args;
|
|
5
|
-
// `orthologParams`
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
const { session, data, msaFileLocation, msaIndexedLocation, msaName, treeFileLocation, querySeqName, searchParams, ...rest } = args;
|
|
5
|
+
// `orthologParams` and `searchParams` name no alignment at all — the
|
|
6
|
+
// view builds one at launch, which is the dialog's Orthologs and BLAST
|
|
7
|
+
// tabs reached declaratively.
|
|
8
8
|
if (!data &&
|
|
9
9
|
!msaFileLocation &&
|
|
10
10
|
!msaIndexedLocation &&
|
|
11
|
-
!rest.orthologParams
|
|
12
|
-
|
|
11
|
+
!rest.orthologParams &&
|
|
12
|
+
!searchParams) {
|
|
13
|
+
throw new Error('No MSA data, file location, orthologParams or searchParams provided when launching MSA view');
|
|
13
14
|
}
|
|
14
15
|
// inline data and the tree URL are native react-msaview snapshot props, set
|
|
15
16
|
// directly, and so is orthologParams (the model's own autorun picks it up).
|
|
@@ -27,6 +28,7 @@ export default function LaunchMsaViewExtensionPointF(pluginManager) {
|
|
|
27
28
|
};
|
|
28
29
|
launchMsaView(session, {
|
|
29
30
|
...rest,
|
|
31
|
+
...(searchParams ? { blastParams: searchParams } : {}),
|
|
30
32
|
data,
|
|
31
33
|
...(treeFileLocation
|
|
32
34
|
? {
|
|
@@ -32,6 +32,27 @@ test('a file location travels through init', () => {
|
|
|
32
32
|
querySeqName: undefined,
|
|
33
33
|
});
|
|
34
34
|
});
|
|
35
|
+
test('searchParams is a source, stored under the name the dialog uses', () => {
|
|
36
|
+
const snapshot = launch({
|
|
37
|
+
searchParams: {
|
|
38
|
+
searchProgram: 'phmmer',
|
|
39
|
+
blastDatabase: 'rp15',
|
|
40
|
+
accession: 'P04637',
|
|
41
|
+
},
|
|
42
|
+
connectedViewId: 'lgv1',
|
|
43
|
+
connectedTranscript: 'NM_000546.6',
|
|
44
|
+
});
|
|
45
|
+
expect(snapshot.blastParams).toEqual({
|
|
46
|
+
searchProgram: 'phmmer',
|
|
47
|
+
blastDatabase: 'rp15',
|
|
48
|
+
accession: 'P04637',
|
|
49
|
+
});
|
|
50
|
+
expect('searchParams' in snapshot).toBe(false);
|
|
51
|
+
expect(snapshot.connectedTranscript).toBe('NM_000546.6');
|
|
52
|
+
});
|
|
53
|
+
test('a launch naming no source at all is refused', () => {
|
|
54
|
+
expect(() => launch({ connectedViewId: 'lgv1' })).toThrow(/searchParams/);
|
|
55
|
+
});
|
|
35
56
|
test('one field set is enough to need init', () => {
|
|
36
57
|
const snapshot = launch({ data: { msa: '>a\nMEEP' }, querySeqName: 'QUERY' });
|
|
37
58
|
expect(snapshot.init).toEqual({
|
|
@@ -12,16 +12,19 @@ export declare function loadStoredData(self: JBrowsePluginMsaViewModel): void;
|
|
|
12
12
|
* it is recorded whether or not the write succeeded, so a browser refusing
|
|
13
13
|
* IndexedDB (private mode) fails once rather than in a loop.
|
|
14
14
|
*
|
|
15
|
-
* A view whose data comes from a filehandle
|
|
16
|
-
*
|
|
15
|
+
* A view whose data comes from a filehandle -- or from the indexed block its
|
|
16
|
+
* kept `init` names -- stores nothing at all: the file is the source of truth
|
|
17
|
+
* and it is refetched at startup.
|
|
17
18
|
*/
|
|
18
19
|
export declare function storeDataToIndexedDB(self: JBrowsePluginMsaViewModel): void;
|
|
19
20
|
/**
|
|
20
21
|
* Same shape as launchBlastIfNeeded, for the ortholog path: the params ARE the
|
|
21
|
-
* request, and
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* request, and `launchCompleted` is what marks it done. They are left in place
|
|
23
|
+
* either way -- on failure so the error stays attributable to a specific
|
|
24
|
+
* request, and on success because they are the only durable statement of what
|
|
25
|
+
* the view is, which is what a stored alignment that expired is rebuilt from.
|
|
26
|
+
* The autorun tracks those two reads alone, so nothing refires until a new
|
|
27
|
+
* request replaces them or a retry clears the mark.
|
|
25
28
|
*/
|
|
26
29
|
export declare function launchOrthologsIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
27
30
|
export declare function launchBlastIfNeeded(self: JBrowsePluginMsaViewModel): void;
|
|
@@ -8,8 +8,10 @@ import { loadProteinDomains } from './loadProteinDomains';
|
|
|
8
8
|
import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } from './msaDataStore';
|
|
9
9
|
import { runLaunch } from './runLaunch';
|
|
10
10
|
import { getProteinViews } from './structureConnection';
|
|
11
|
-
import { getUniprotIdFromAlphaFoldUrl, hasQueryRow } from './util';
|
|
12
|
-
const EXPIRED_MESSAGE = "This view's alignment is no longer in browser storage. Stored alignments are kept for 7 days after they were last used, and are lost when site data is cleared.
|
|
11
|
+
import { getUniprotIdFromAlphaFoldUrl, hasQueryRow, transcriptPosToVisibleCol, } from './util';
|
|
12
|
+
const EXPIRED_MESSAGE = "This view's alignment is no longer in browser storage. Stored alignments are kept for 7 days after they were last used, and are lost when site data is cleared.";
|
|
13
|
+
const RELAUNCHABLE = ' Retry runs the original search again and rebuilds it.';
|
|
14
|
+
const START_OVER = ' Relaunch it from the gene to rebuild it.';
|
|
13
15
|
export function loadStoredData(self) {
|
|
14
16
|
const { dataStoreId, rows } = self;
|
|
15
17
|
if (dataStoreId && rows.length === 0) {
|
|
@@ -35,10 +37,16 @@ export function loadStoredData(self) {
|
|
|
35
37
|
}
|
|
36
38
|
else {
|
|
37
39
|
// the id names nothing, so clearing it is what lets react-msaview's
|
|
38
|
-
// "Return to import form" actually return instead of landing back
|
|
40
|
+
// "Return to import form" actually return instead of landing back
|
|
41
|
+
// here. The request that built the alignment is kept, though: it is
|
|
42
|
+
// the whole of a retry, and dropping it left the user reading an
|
|
43
|
+
// apology with nothing to press.
|
|
39
44
|
transaction(() => {
|
|
40
45
|
self.setDataStoreId(undefined);
|
|
41
|
-
self.setError(new Error(EXPIRED_MESSAGE
|
|
46
|
+
self.setError(new Error(EXPIRED_MESSAGE +
|
|
47
|
+
((self.blastParams ?? self.orthologParams)
|
|
48
|
+
? RELAUNCHABLE
|
|
49
|
+
: START_OVER)));
|
|
42
50
|
});
|
|
43
51
|
}
|
|
44
52
|
}
|
|
@@ -73,16 +81,20 @@ function sameData(a, b) {
|
|
|
73
81
|
* it is recorded whether or not the write succeeded, so a browser refusing
|
|
74
82
|
* IndexedDB (private mode) fails once rather than in a loop.
|
|
75
83
|
*
|
|
76
|
-
* A view whose data comes from a filehandle
|
|
77
|
-
*
|
|
84
|
+
* A view whose data comes from a filehandle -- or from the indexed block its
|
|
85
|
+
* kept `init` names -- stores nothing at all: the file is the source of truth
|
|
86
|
+
* and it is refetched at startup.
|
|
78
87
|
*/
|
|
79
88
|
export function storeDataToIndexedDB(self) {
|
|
80
|
-
const { rows, dataStoreId, isStoringData, lastStoredData } = self;
|
|
89
|
+
const { rows, dataStoreId, isStoringData, lastStoredData, init } = self;
|
|
81
90
|
const data = currentData(self);
|
|
82
91
|
if (rows.length === 0 ||
|
|
83
92
|
isStoringData ||
|
|
84
93
|
self.msaFilehandle ||
|
|
85
94
|
self.treeFilehandle ||
|
|
95
|
+
// an indexed view keeps its init and refetches the block, so a row here
|
|
96
|
+
// would be one nothing ever reads
|
|
97
|
+
!!init?.msaIndexedLocation ||
|
|
86
98
|
!(data.msa || data.tree) ||
|
|
87
99
|
sameData(lastStoredData, data)) {
|
|
88
100
|
return;
|
|
@@ -111,31 +123,47 @@ export function storeDataToIndexedDB(self) {
|
|
|
111
123
|
}
|
|
112
124
|
/**
|
|
113
125
|
* Same shape as launchBlastIfNeeded, for the ortholog path: the params ARE the
|
|
114
|
-
* request, and
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
126
|
+
* request, and `launchCompleted` is what marks it done. They are left in place
|
|
127
|
+
* either way -- on failure so the error stays attributable to a specific
|
|
128
|
+
* request, and on success because they are the only durable statement of what
|
|
129
|
+
* the view is, which is what a stored alignment that expired is rebuilt from.
|
|
130
|
+
* The autorun tracks those two reads alone, so nothing refires until a new
|
|
131
|
+
* request replaces them or a retry clears the mark.
|
|
118
132
|
*/
|
|
119
133
|
export function launchOrthologsIfNeeded(self) {
|
|
120
|
-
if (self.orthologParams
|
|
134
|
+
if (self.orthologParams &&
|
|
135
|
+
!self.launchCompleted &&
|
|
136
|
+
!awaitingTranscript(self)) {
|
|
121
137
|
runLaunch({
|
|
122
138
|
self,
|
|
123
139
|
message: 'Resolving orthologs',
|
|
124
140
|
launch: scope => doLaunchOrthologs({ self, scope }),
|
|
141
|
+
// marked rather than dropped: the request is the only durable statement
|
|
142
|
+
// of what this view is, and a view whose stored alignment expired runs it
|
|
143
|
+
// again
|
|
125
144
|
onLaunched: () => {
|
|
126
|
-
self.
|
|
145
|
+
self.setLaunchCompleted(true);
|
|
127
146
|
},
|
|
128
147
|
});
|
|
129
148
|
}
|
|
130
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* A launch that names its transcript rather than its feature has to wait for
|
|
152
|
+
* the lookup: the query row is that transcript's translation, and the launch
|
|
153
|
+
* cannot start without a query. Both launchers read the same two fields, so
|
|
154
|
+
* the resolver setting `connectedFeature` is what refires them.
|
|
155
|
+
*/
|
|
156
|
+
function awaitingTranscript(self) {
|
|
157
|
+
return !!self.connectedTranscript && !self.connectedFeature;
|
|
158
|
+
}
|
|
131
159
|
export function launchBlastIfNeeded(self) {
|
|
132
|
-
if (self.blastParams) {
|
|
160
|
+
if (self.blastParams && !self.launchCompleted && !awaitingTranscript(self)) {
|
|
133
161
|
runLaunch({
|
|
134
162
|
self,
|
|
135
163
|
message: 'Submitting query',
|
|
136
164
|
launch: scope => doLaunchBlast({ self, scope }),
|
|
137
165
|
onLaunched: () => {
|
|
138
|
-
self.
|
|
166
|
+
self.setLaunchCompleted(true);
|
|
139
167
|
},
|
|
140
168
|
});
|
|
141
169
|
}
|
|
@@ -167,15 +195,28 @@ export function autoLoadProteinDomains(self) {
|
|
|
167
195
|
})();
|
|
168
196
|
}
|
|
169
197
|
}
|
|
170
|
-
// Resolve the declarative `init` launch contract
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
198
|
+
// Resolve the declarative `init` launch contract. msaUrl is handed to
|
|
199
|
+
// react-msaview's native filehandle loader (openLocation + progress + abort +
|
|
200
|
+
// CORS-proxy) and sniffed for an AlphaFold uniprotId; the bgzip name-indexed
|
|
201
|
+
// block is the one source with no native loader, so it's fetched here. Inline
|
|
202
|
+
// data and tree URLs arrive as native snapshot props, not via init.
|
|
203
|
+
//
|
|
204
|
+
// An init that named the indexed block is KEPT rather than cleared, as
|
|
205
|
+
// jbrowse-plugin-tview keeps its own: what it resolves to is one alignment
|
|
206
|
+
// string, react-msaview drops a document over 50kb from the snapshot, and there
|
|
207
|
+
// is no filehandle to reload it from -- so a shared session came back saying the
|
|
208
|
+
// alignment had expired. The init is both smaller than what it fetches and the
|
|
209
|
+
// only durable statement of what the view is.
|
|
175
210
|
export function processInit(self) {
|
|
176
211
|
const { init } = self;
|
|
177
212
|
if (init) {
|
|
178
213
|
const { msaUrl, msaIndexedLocation, msaName, querySeqName } = init;
|
|
214
|
+
const indexed = !!(msaIndexedLocation && msaName);
|
|
215
|
+
// a kept init re-runs this on every session restore; the alignment already
|
|
216
|
+
// in hand is the one it would fetch
|
|
217
|
+
if (indexed && self.data.msa) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
179
220
|
void (async () => {
|
|
180
221
|
try {
|
|
181
222
|
self.setError(undefined);
|
|
@@ -204,7 +245,9 @@ export function processInit(self) {
|
|
|
204
245
|
throw new Error(`No alignment named ${msaName} in ${msaIndexedLocation.uri}`);
|
|
205
246
|
}
|
|
206
247
|
}
|
|
207
|
-
|
|
248
|
+
if (!indexed) {
|
|
249
|
+
self.setInit(undefined);
|
|
250
|
+
}
|
|
208
251
|
}
|
|
209
252
|
catch (e) {
|
|
210
253
|
self.setError(e);
|
|
@@ -238,10 +281,10 @@ export function syncGenomeHoverToMsaColumn(self) {
|
|
|
238
281
|
* Translate genome regions published by a 3D protein view into this MSA's
|
|
239
282
|
* visible columns. The genome is the only coordinate space the two plugins
|
|
240
283
|
* share, so the hops are genome coord -> protein position (the transcript's g2p
|
|
241
|
-
* map) ->
|
|
284
|
+
* map) -> visible column.
|
|
242
285
|
*/
|
|
243
286
|
function genomeHighlightsToVisibleColumns(self, field) {
|
|
244
|
-
const { connectedViewId, transcriptToMsaMap
|
|
287
|
+
const { connectedViewId, transcriptToMsaMap } = self;
|
|
245
288
|
if (!transcriptToMsaMap || !hasQueryRow(self)) {
|
|
246
289
|
return [];
|
|
247
290
|
}
|
|
@@ -255,16 +298,17 @@ function genomeHighlightsToVisibleColumns(self, field) {
|
|
|
255
298
|
for (const highlight of structure[field] ?? []) {
|
|
256
299
|
for (let coord = highlight.start; coord < highlight.end; coord++) {
|
|
257
300
|
const proteinPos = g2p[coord];
|
|
258
|
-
|
|
259
|
-
|
|
301
|
+
const col = proteinPos === undefined
|
|
302
|
+
? undefined
|
|
303
|
+
: transcriptPosToVisibleCol(self, proteinPos);
|
|
304
|
+
if (col !== undefined) {
|
|
305
|
+
columns.add(col);
|
|
260
306
|
}
|
|
261
307
|
}
|
|
262
308
|
}
|
|
263
309
|
}
|
|
264
310
|
}
|
|
265
|
-
return [...columns]
|
|
266
|
-
.map(col => self.globalColToVisibleCol(col))
|
|
267
|
-
.filter((col) => col !== undefined);
|
|
311
|
+
return [...columns];
|
|
268
312
|
}
|
|
269
313
|
function sameColumns(a, b) {
|
|
270
314
|
if (!a || !b) {
|