jbrowse-plugin-msaview 3.5.0 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -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/cleanProteinSequence.test.d.ts +1 -0
- package/dist/LaunchMsaView/cleanProteinSequence.test.js +77 -0
- package/dist/LaunchMsaView/codingFeature.d.ts +14 -0
- package/dist/LaunchMsaView/codingFeature.js +38 -0
- package/dist/LaunchMsaView/codingFeature.test.d.ts +1 -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 +47 -55
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +16 -17
- 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/CachedBlastResults.js +14 -13
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +2 -0
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +28 -4
- 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 +34 -26
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +30 -26
- 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.d.ts +3 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +50 -27
- package/dist/LaunchMsaView/components/SequenceStatus.d.ts +14 -0
- package/dist/LaunchMsaView/components/SequenceStatus.js +15 -0
- package/dist/LaunchMsaView/components/SubmitCancelActions.d.ts +6 -2
- package/dist/LaunchMsaView/components/SubmitCancelActions.js +28 -13
- package/dist/LaunchMsaView/components/SubmitCancelActions.test.js +52 -4
- 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 +12 -2
- package/dist/LaunchMsaView/components/calculateProteinSequence.js +38 -23
- package/dist/LaunchMsaView/components/fetchSeq.d.ts +4 -1
- package/dist/LaunchMsaView/components/fetchSeq.js +14 -4
- package/dist/LaunchMsaView/components/launchConnectedView.d.ts +23 -0
- package/dist/LaunchMsaView/components/launchConnectedView.js +39 -0
- 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.js +4 -7
- 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 +12 -0
- package/dist/LaunchMsaView/useQueryRowName.js +11 -0
- package/dist/LaunchMsaView/useQueryRowName.test.js +18 -1
- package/dist/LaunchMsaView/util.d.ts +6 -0
- package/dist/LaunchMsaView/util.js +12 -3
- package/dist/LaunchMsaViewExtensionPoint/index.test.js +15 -0
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
- package/dist/MsaViewPanel/afterCreateAutoruns.js +38 -14
- package/dist/MsaViewPanel/applyRegion.d.ts +23 -0
- package/dist/MsaViewPanel/applyRegion.js +19 -0
- package/dist/MsaViewPanel/applyRegion.test.d.ts +1 -0
- package/dist/MsaViewPanel/applyRegion.test.js +45 -0
- package/dist/MsaViewPanel/components/LaunchProgress.d.ts +4 -3
- package/dist/MsaViewPanel/components/LaunchProgress.js +10 -7
- package/dist/MsaViewPanel/components/MsaViewPanel.js +7 -3
- package/dist/MsaViewPanel/components/MsaViewPanel.test.js +24 -0
- 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 +24 -6
- package/dist/MsaViewPanel/genomeToMSA.test.js +218 -44
- package/dist/MsaViewPanel/launchAutoruns.test.d.ts +1 -0
- package/dist/MsaViewPanel/launchAutoruns.test.js +36 -0
- package/dist/MsaViewPanel/model.d.ts +156 -61
- package/dist/MsaViewPanel/model.js +69 -3
- package/dist/MsaViewPanel/model.test.js +66 -0
- 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 +109 -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 +43 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +15 -7
- package/dist/MsaViewPanel/util.d.ts +23 -23
- package/dist/MsaViewPanel/util.js +23 -24
- package/dist/index.d.ts +11 -11
- package/dist/index.js +17 -10
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +54 -67
- 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 +2 -2
- package/dist/utils/unirefHomologs.test.js +8 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +23 -23
- package/src/AddHighlightModel/index.tsx +1 -1
- package/src/BgzipFastaMsaAdapter/configSchema.ts +1 -1
- package/src/LaunchMsaView/cleanProteinSequence.test.ts +91 -0
- package/src/LaunchMsaView/codingFeature.test.ts +96 -0
- package/src/LaunchMsaView/codingFeature.ts +49 -0
- package/src/LaunchMsaView/components/BlastQuery/BlastAutomaticPanel.tsx +60 -56
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +18 -15
- package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
- package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
- package/src/LaunchMsaView/components/BlastQuery/CachedBlastResults.tsx +16 -15
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +31 -4
- 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 +46 -23
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +41 -28
- 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 +81 -25
- package/src/LaunchMsaView/components/SequenceStatus.tsx +29 -0
- package/src/LaunchMsaView/components/SubmitCancelActions.test.tsx +88 -4
- package/src/LaunchMsaView/components/SubmitCancelActions.tsx +43 -16
- 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 +51 -22
- package/src/LaunchMsaView/components/fetchSeq.ts +25 -5
- package/src/LaunchMsaView/components/launchConnectedView.ts +56 -0
- 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.ts +5 -7
- 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 +28 -1
- package/src/LaunchMsaView/useQueryRowName.ts +17 -0
- package/src/LaunchMsaView/util.ts +15 -5
- package/src/LaunchMsaViewExtensionPoint/index.test.ts +16 -0
- package/src/LaunchMsaViewExtensionPoint/index.ts +28 -1
- package/src/MsaViewPanel/afterCreateAutoruns.ts +46 -14
- package/src/MsaViewPanel/applyRegion.test.ts +55 -0
- package/src/MsaViewPanel/applyRegion.ts +33 -0
- package/src/MsaViewPanel/components/LaunchProgress.tsx +12 -5
- package/src/MsaViewPanel/components/MsaViewPanel.test.tsx +26 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +7 -5
- 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 +255 -59
- package/src/MsaViewPanel/genomeToMSA.ts +30 -5
- package/src/MsaViewPanel/launchAutoruns.test.ts +45 -0
- package/src/MsaViewPanel/model.test.ts +75 -0
- package/src/MsaViewPanel/model.ts +76 -3
- 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 +160 -67
- package/src/MsaViewPanel/runLaunch.test.ts +19 -0
- package/src/MsaViewPanel/runLaunch.ts +21 -4
- package/src/MsaViewPanel/storedData.test.ts +58 -0
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +17 -7
- 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 +2 -2
- 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 -11
- package/dist/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.js +0 -21
- package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
- package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
- 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 -38
- package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { getSession } from '@jbrowse/core/util';
|
|
3
|
+
import { launchMsaView } from '../../utils/launchMsaView';
|
|
4
|
+
/** how a view whose alignment the plugin builds opens */
|
|
5
|
+
export const builtAlignmentLook = {
|
|
6
|
+
drawNodeBubbles: true,
|
|
7
|
+
colWidth: 10,
|
|
8
|
+
rowHeight: 12,
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Every dialog launch: an MSA view tied to the genome view it came from, and
|
|
12
|
+
* through `feature` to the transcript whose codons its query row is read by.
|
|
13
|
+
*/
|
|
14
|
+
export function launchConnectedView({ view, feature, placement, ...snapshot }) {
|
|
15
|
+
launchMsaView(getSession(view), {
|
|
16
|
+
placement,
|
|
17
|
+
connectedViewId: view.id,
|
|
18
|
+
connectedFeature: feature?.toJSON(),
|
|
19
|
+
...snapshot,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** runs a panel's launch, closing the dialog on success and keeping the error */
|
|
23
|
+
export function useLaunchSubmit(handleClose) {
|
|
24
|
+
const [launchError, setLaunchError] = useState();
|
|
25
|
+
return {
|
|
26
|
+
launchError,
|
|
27
|
+
submit: (launch) => {
|
|
28
|
+
try {
|
|
29
|
+
setLaunchError(undefined);
|
|
30
|
+
launch();
|
|
31
|
+
handleClose();
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
console.error(e);
|
|
35
|
+
setLaunchError(e);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
type PlacementState = [boolean, React.Dispatch<React.SetStateAction<boolean>>];
|
|
3
|
+
export declare function LaunchPlacementProvider({ children, }: {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
}): React.JSX.Element;
|
|
6
|
+
/** the dialog's answer, or a private one for a panel rendered outside it */
|
|
7
|
+
export declare function useLaunchPlacement(): PlacementState;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { createContext, useContext, useState } from 'react';
|
|
2
|
+
import { readLaunchPlacement } from '../../utils/workspaces';
|
|
3
|
+
/**
|
|
4
|
+
* Whether the launch opens beside the genome view — one answer for the whole
|
|
5
|
+
* dialog, not one per tab.
|
|
6
|
+
*
|
|
7
|
+
* Every tab the user visits stays mounted, and the checkbox lives in each
|
|
8
|
+
* panel's actions row, so a `useState` there gave each tab its own copy of the
|
|
9
|
+
* answer: ticking the box on one tab and submitting from another wrote the
|
|
10
|
+
* other tab's stale one.
|
|
11
|
+
*
|
|
12
|
+
* Context rather than a prop through all five panels, none of which has any
|
|
13
|
+
* other business with placement.
|
|
14
|
+
*/
|
|
15
|
+
const LaunchPlacementContext = createContext(undefined);
|
|
16
|
+
export function LaunchPlacementProvider({ children, }) {
|
|
17
|
+
const state = useState(() => readLaunchPlacement() === 'splitRight');
|
|
18
|
+
return (React.createElement(LaunchPlacementContext.Provider, { value: state }, children));
|
|
19
|
+
}
|
|
20
|
+
/** the dialog's answer, or a private one for a panel rendered outside it */
|
|
21
|
+
export function useLaunchPlacement() {
|
|
22
|
+
const shared = useContext(LaunchPlacementContext);
|
|
23
|
+
const own = useState(() => readLaunchPlacement() === 'splitRight');
|
|
24
|
+
return shared ?? own;
|
|
25
|
+
}
|
|
@@ -4,25 +4,28 @@ import { getProteinSequenceFromFeature } from './calculateProteinSequence';
|
|
|
4
4
|
import { fetchSeq } from './fetchSeq';
|
|
5
5
|
export function useFeatureSequence({ view, feature, }) {
|
|
6
6
|
const assemblyName = view?.assemblyNames?.[0];
|
|
7
|
-
const { data: sequence, error } = useFetch(feature && assemblyName
|
|
7
|
+
const { data: sequence, error, isLoading, } = useFetch(feature && assemblyName
|
|
8
8
|
? [feature.id(), assemblyName, 'feature-sequence']
|
|
9
9
|
: null, async () => {
|
|
10
10
|
const { start, end, refName } = feature.toJSON();
|
|
11
|
-
return {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}),
|
|
19
|
-
};
|
|
11
|
+
return fetchSeq({
|
|
12
|
+
start,
|
|
13
|
+
end,
|
|
14
|
+
refName,
|
|
15
|
+
assemblyName: assemblyName,
|
|
16
|
+
session: getSession(view),
|
|
17
|
+
});
|
|
20
18
|
});
|
|
21
19
|
return {
|
|
22
20
|
proteinSequence: sequence && feature
|
|
23
|
-
? getProteinSequenceFromFeature({
|
|
21
|
+
? getProteinSequenceFromFeature({
|
|
22
|
+
seq: sequence.seq,
|
|
23
|
+
feature,
|
|
24
|
+
assemblyGeneticCodeId: sequence.assemblyGeneticCodeId,
|
|
25
|
+
})
|
|
24
26
|
: '',
|
|
25
27
|
sequence,
|
|
26
28
|
error,
|
|
29
|
+
isLoading,
|
|
27
30
|
};
|
|
28
31
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import type { SequenceStatus } from './SequenceStatus';
|
|
1
2
|
import type { Feature } from '@jbrowse/core/util';
|
|
2
|
-
export declare function useTranscriptSelection({ feature, view, validIds, }: {
|
|
3
|
+
export declare function useTranscriptSelection({ feature, view, validIds, preferredTranscriptId, }: {
|
|
3
4
|
feature: Feature;
|
|
4
5
|
view: {
|
|
5
6
|
assemblyNames?: string[];
|
|
6
7
|
} | undefined;
|
|
7
8
|
validIds?: string[];
|
|
9
|
+
/**
|
|
10
|
+
* the isoform the user right-clicked, when the dialog opened on its gene.
|
|
11
|
+
* Ignored when this gene does not carry it, so a stale id falls back to the
|
|
12
|
+
* longest transcript rather than selecting nothing.
|
|
13
|
+
*/
|
|
14
|
+
preferredTranscriptId?: string;
|
|
8
15
|
}): {
|
|
9
16
|
options: Feature[];
|
|
10
17
|
selectedId: string;
|
|
@@ -12,5 +19,7 @@ export declare function useTranscriptSelection({ feature, view, validIds, }: {
|
|
|
12
19
|
selectedTranscript: Feature | undefined;
|
|
13
20
|
proteinSequence: string;
|
|
14
21
|
error: unknown;
|
|
22
|
+
isLoading: boolean;
|
|
23
|
+
sequenceStatus: SequenceStatus;
|
|
15
24
|
validIds: string[] | undefined;
|
|
16
25
|
};
|
|
@@ -16,12 +16,28 @@ function pickSelectedId(currentId, options, validIds) {
|
|
|
16
16
|
const firstValid = options.find(isValid);
|
|
17
17
|
return firstValid ? getId(firstValid) : currentId;
|
|
18
18
|
}
|
|
19
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Submit is disabled until the query sequence is in hand, and a grey button
|
|
21
|
+
* reads the same whether the translation is on its way or was never going to
|
|
22
|
+
* arrive. `missing` is the second case: the feature has no CDS to translate,
|
|
23
|
+
* which the dialog never said.
|
|
24
|
+
*/
|
|
25
|
+
function sequenceStatus({ error, isLoading, proteinSequence, }) {
|
|
26
|
+
if (error) {
|
|
27
|
+
return 'error';
|
|
28
|
+
}
|
|
29
|
+
if (isLoading) {
|
|
30
|
+
return 'loading';
|
|
31
|
+
}
|
|
32
|
+
return proteinSequence ? 'ready' : 'missing';
|
|
33
|
+
}
|
|
34
|
+
export function useTranscriptSelection({ feature, view, validIds, preferredTranscriptId, }) {
|
|
20
35
|
const options = useMemo(() => getSortedTranscriptFeatures(feature), [feature]);
|
|
21
|
-
const [selectedId, setSelectedId] = useState(() => getId(options
|
|
36
|
+
const [selectedId, setSelectedId] = useState(() => getId(options.find(opt => featureMatchesId(opt, preferredTranscriptId ?? '')) ??
|
|
37
|
+
options[0]));
|
|
22
38
|
const validatedSelectedId = pickSelectedId(selectedId, options, validIds);
|
|
23
39
|
const selectedTranscript = options.find(val => getId(val) === validatedSelectedId);
|
|
24
|
-
const { proteinSequence, error } = useFeatureSequence({
|
|
40
|
+
const { proteinSequence, error, isLoading } = useFeatureSequence({
|
|
25
41
|
view,
|
|
26
42
|
feature: selectedTranscript,
|
|
27
43
|
});
|
|
@@ -32,6 +48,8 @@ export function useTranscriptSelection({ feature, view, validIds, }) {
|
|
|
32
48
|
selectedTranscript,
|
|
33
49
|
proteinSequence,
|
|
34
50
|
error,
|
|
51
|
+
isLoading,
|
|
52
|
+
sequenceStatus: sequenceStatus({ error, isLoading, proteinSequence }),
|
|
35
53
|
validIds,
|
|
36
54
|
};
|
|
37
55
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { SimpleFeature } from '@jbrowse/core/util';
|
|
3
|
+
import { renderHook } from '@testing-library/react';
|
|
4
|
+
import { describe, expect, test } from 'vitest';
|
|
5
|
+
import { useTranscriptSelection } from './useTranscriptSelection';
|
|
6
|
+
function transcript(id, cdsLength) {
|
|
7
|
+
return {
|
|
8
|
+
uniqueId: id,
|
|
9
|
+
name: id,
|
|
10
|
+
refName: 'chr1',
|
|
11
|
+
start: 0,
|
|
12
|
+
end: cdsLength,
|
|
13
|
+
type: 'mRNA',
|
|
14
|
+
subfeatures: [
|
|
15
|
+
{
|
|
16
|
+
uniqueId: `${id}-cds`,
|
|
17
|
+
refName: 'chr1',
|
|
18
|
+
start: 0,
|
|
19
|
+
end: cdsLength,
|
|
20
|
+
type: 'CDS',
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// the picker sorts by translated length, so the clicked isoform is deliberately
|
|
26
|
+
// not the one that would be chosen by default
|
|
27
|
+
const gene = new SimpleFeature({
|
|
28
|
+
uniqueId: 'gene1',
|
|
29
|
+
name: 'TP53',
|
|
30
|
+
refName: 'chr1',
|
|
31
|
+
start: 0,
|
|
32
|
+
end: 900,
|
|
33
|
+
type: 'gene',
|
|
34
|
+
subfeatures: [transcript('short', 300), transcript('long', 900)],
|
|
35
|
+
});
|
|
36
|
+
// `view` is left out so useFeatureSequence has no assembly to fetch against and
|
|
37
|
+
// never reaches the session; only the selection is under test here
|
|
38
|
+
function selection(preferredTranscriptId) {
|
|
39
|
+
return renderHook(() => useTranscriptSelection({
|
|
40
|
+
feature: gene,
|
|
41
|
+
view: undefined,
|
|
42
|
+
preferredTranscriptId,
|
|
43
|
+
})).result.current;
|
|
44
|
+
}
|
|
45
|
+
describe('useTranscriptSelection', () => {
|
|
46
|
+
test('opens on the longest transcript with nothing preferred', () => {
|
|
47
|
+
expect(selection().selectedId).toBe('long');
|
|
48
|
+
});
|
|
49
|
+
// climbing to the gene is what puts every isoform in the picker, and it threw
|
|
50
|
+
// away which one the right-click landed on
|
|
51
|
+
test('opens on the isoform the user clicked', () => {
|
|
52
|
+
expect(selection('short').selectedId).toBe('short');
|
|
53
|
+
});
|
|
54
|
+
test('ignores a preferred id this gene does not carry', () => {
|
|
55
|
+
expect(selection('some_other_transcript').selectedId).toBe('long');
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import { getUngappedSequence, parseMSA } from 'msa-parsers';
|
|
2
|
+
import { cleanProteinSequence } from './util';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
4
|
+
* The translation as a search launch would have sent it, so a row that launch
|
|
5
|
+
* built matches exactly; case means nothing on either side.
|
|
5
6
|
*/
|
|
6
7
|
function normalize(seq) {
|
|
7
|
-
return seq
|
|
8
|
-
.replaceAll('*', '')
|
|
9
|
-
.replaceAll('-', '')
|
|
10
|
-
.replaceAll('.', '')
|
|
11
|
-
.toUpperCase();
|
|
8
|
+
return cleanProteinSequence(seq).toUpperCase();
|
|
12
9
|
}
|
|
13
10
|
function identityOverOverlap(a, b) {
|
|
14
11
|
const len = Math.min(a.length, b.length);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getContainingTrack, getSession } from '@jbrowse/core/util';
|
|
2
2
|
import AddIcon from '@mui/icons-material/Add';
|
|
3
|
+
import { isCodingFeature } from './codingFeature';
|
|
3
4
|
import LaunchMsaViewDialog from './components/LaunchMsaViewDialog';
|
|
4
5
|
import { launchTarget } from './launchTarget';
|
|
5
6
|
function isDisplay(elt) {
|
|
@@ -9,19 +10,37 @@ function isDisplay(elt) {
|
|
|
9
10
|
// built: contextMenuItems runs on every right-click and, on a host whose base
|
|
10
11
|
// method reads `this`, is the one place a plugin can take the whole menu down.
|
|
11
12
|
// Keeping it to a pure read of the display is also what lets a test call it.
|
|
12
|
-
function
|
|
13
|
+
function featureName(feature) {
|
|
14
|
+
return feature.get('name') ?? feature.get('id') ?? 'This feature';
|
|
15
|
+
}
|
|
16
|
+
function openDialog(self, target) {
|
|
13
17
|
const track = getContainingTrack(self);
|
|
14
18
|
const session = getSession(track);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
const { preferredTranscriptId } = target;
|
|
20
|
+
const open = (feature) => {
|
|
21
|
+
session.queueDialog(handleClose => [
|
|
22
|
+
LaunchMsaViewDialog,
|
|
23
|
+
{ model: track, handleClose, feature, preferredTranscriptId },
|
|
24
|
+
]);
|
|
25
|
+
};
|
|
26
|
+
if ('feature' in target) {
|
|
27
|
+
open(target.feature);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
target
|
|
31
|
+
.fetchFeature()
|
|
32
|
+
.then(feature => {
|
|
33
|
+
if (!feature) {
|
|
34
|
+
session.notify('Could not load feature for MSA view', 'warning');
|
|
35
|
+
}
|
|
36
|
+
else if (!isCodingFeature(feature)) {
|
|
37
|
+
// the canvas hit test carries a type and nothing else, so this is the
|
|
38
|
+
// first point at which the CDS can be looked for. Saying so beats
|
|
39
|
+
// opening a dialog whose Submit never leaves grey.
|
|
40
|
+
session.notify(`${featureName(feature)} has no coding sequence, so there is no protein to align`, 'info');
|
|
22
41
|
}
|
|
23
42
|
else {
|
|
24
|
-
|
|
43
|
+
open(feature);
|
|
25
44
|
}
|
|
26
45
|
})
|
|
27
46
|
.catch((e) => {
|
|
@@ -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,6 +6,18 @@
|
|
|
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[];
|
|
@@ -8,6 +8,17 @@ 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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
import { act, renderHook } from '@testing-library/react';
|
|
3
3
|
import { describe, expect, test } from 'vitest';
|
|
4
|
-
import { useQueryRowName } from './useQueryRowName';
|
|
4
|
+
import { resolveQueryRowName, useQueryRowName } from './useQueryRowName';
|
|
5
5
|
const protein = 'MKWVTFISLLLLFSSAYSRGVFRRDTHKSEIAHRFKDLGEEHFKGLVLIAFSQYLQQCPFD';
|
|
6
6
|
const trimmed = `>aligned_query\n${protein.slice(5, 45)}\n>other\nWRONGWRONGWRONGWRONGWRONGWRONGWRONGWRONG\n`;
|
|
7
7
|
describe('useQueryRowName', () => {
|
|
@@ -21,3 +21,20 @@ describe('useQueryRowName', () => {
|
|
|
21
21
|
expect(result.current.querySeqOffset).toBe(0);
|
|
22
22
|
});
|
|
23
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
|
+
});
|
|
@@ -13,5 +13,11 @@ export declare function getTranscriptDisplayName(val?: Feature): string;
|
|
|
13
13
|
export declare function getGeneDisplayName(val?: Feature): string;
|
|
14
14
|
export declare function getBlastViewTitle(feature: Feature, transcript: Feature): string;
|
|
15
15
|
export declare function getSortedTranscriptFeatures(feature: Feature): Feature[];
|
|
16
|
+
/**
|
|
17
|
+
* A translation as a query, still one residue per codon: g2p numbers codons,
|
|
18
|
+
* and the query row is read through it. So the partial first codon (`&`), an
|
|
19
|
+
* internal stop and an unreadable codon become X, and only what trails the
|
|
20
|
+
* last whole codon is dropped.
|
|
21
|
+
*/
|
|
16
22
|
export declare function cleanProteinSequence(seq: string): string;
|
|
17
23
|
export declare function getGeneIdentifiers(feature: Feature): string[];
|