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
|
@@ -1,31 +1,34 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import { LoadingEllipses, SanitizedHTML } from '@jbrowse/core/ui';
|
|
3
3
|
import { getEnv, getSession } from '@jbrowse/core/util';
|
|
4
|
-
import { MenuItem } from '@mui/material';
|
|
4
|
+
import { MenuItem, Typography } from '@mui/material';
|
|
5
5
|
import { observer } from 'mobx-react';
|
|
6
6
|
import { makeStyles } from 'tss-react/mui';
|
|
7
7
|
import TextField2 from '../../../components/TextField2';
|
|
8
8
|
import { useFetch } from '../../../utils/useFetch';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveQueryRowName, useQueryRowName } from '../../useQueryRowName';
|
|
10
|
+
import { getGeneDisplayName, getLinearGenomeView, getTranscriptDisplayName, } from '../../util';
|
|
10
11
|
import LaunchPanelContent from '../LaunchPanelContent';
|
|
12
|
+
import QueryRowSelector from '../QueryRowSelector';
|
|
13
|
+
import SequenceStatusMessage from '../SequenceStatus';
|
|
11
14
|
import SubmitCancelActions from '../SubmitCancelActions';
|
|
12
15
|
import TranscriptSelector from '../TranscriptSelector';
|
|
16
|
+
import { launchConnectedView, useLaunchSubmit } from '../launchConnectedView';
|
|
13
17
|
import { useTranscriptSelection } from '../useTranscriptSelection';
|
|
14
18
|
import { fetchMSA, fetchMSAList } from './fetchMSAData';
|
|
15
|
-
import { preCalculatedLaunchView } from './preCalculatedLaunchView';
|
|
16
19
|
import { readMsaDatasets } from './types';
|
|
17
20
|
const useStyles = makeStyles()({
|
|
18
21
|
selectedContainer: {
|
|
19
22
|
marginTop: 50,
|
|
20
23
|
},
|
|
21
24
|
});
|
|
22
|
-
const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
|
|
25
|
+
const PreLoadedMSA = observer(function ({ model, feature, handleClose, preferredTranscriptId, }) {
|
|
23
26
|
const session = getSession(model);
|
|
24
27
|
const view = getLinearGenomeView(model);
|
|
25
28
|
const { classes } = useStyles();
|
|
26
29
|
const { pluginManager } = getEnv(model);
|
|
27
30
|
const { assemblyNames } = view;
|
|
28
|
-
const
|
|
31
|
+
const { launchError, submit } = useLaunchSubmit(handleClose);
|
|
29
32
|
const datasets = readMsaDatasets(session.jbrowse);
|
|
30
33
|
const [selectedDatasetId, setSelectedDatasetId] = useState(datasets?.[0]?.datasetId);
|
|
31
34
|
const selectedDataset = datasets?.find(d => d.datasetId === selectedDatasetId);
|
|
@@ -34,8 +37,9 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
|
|
|
34
37
|
feature,
|
|
35
38
|
view,
|
|
36
39
|
validIds: msaList,
|
|
40
|
+
preferredTranscriptId,
|
|
37
41
|
});
|
|
38
|
-
const { selectedId, selectedTranscript } = transcriptSelection;
|
|
42
|
+
const { selectedId, selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
|
|
39
43
|
const { data: msaData, isLoading: msaDataLoading, error: msaDataFetchError, } = useFetch(selectedId && selectedDataset && msaList
|
|
40
44
|
? `${selectedDataset.datasetId}-${selectedId}-msa`
|
|
41
45
|
: null, () => fetchMSA({
|
|
@@ -43,41 +47,60 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
|
|
|
43
47
|
config: selectedDataset.adapter,
|
|
44
48
|
pluginManager,
|
|
45
49
|
}));
|
|
50
|
+
const msaText = useMemo(() => msaData?.map(r => `>${r.get('refName')}\n${r.get('seq')}`).join('\n') ??
|
|
51
|
+
'', [msaData]);
|
|
52
|
+
// The dataset's row for this transcript used to be assumed -- the launch
|
|
53
|
+
// named `<transcriptId>_<assembly>` and hoped the file agreed. Nothing
|
|
54
|
+
// checked, and a name the alignment does not carry fails silently: the view
|
|
55
|
+
// opens, renders, and never navigates. The row is found by sequence instead,
|
|
56
|
+
// the same way the Manual tab finds it.
|
|
57
|
+
const queryRow = useQueryRowName(msaText, proteinSequence);
|
|
58
|
+
// The name this panel used to launch with unconditionally. A dataset built to
|
|
59
|
+
// that convention does carry the row, so it is worth falling back to when the
|
|
60
|
+
// residues do not match closely enough to find it -- but only when the
|
|
61
|
+
// alignment really has it, which is the check the old code never made.
|
|
62
|
+
const querySeqName = resolveQueryRowName(queryRow, `${selectedId}_${assemblyNames[0] ?? ''}`);
|
|
46
63
|
const e = msaListFetchError ??
|
|
47
64
|
msaDataFetchError ??
|
|
48
65
|
transcriptSelection.error ??
|
|
49
|
-
|
|
66
|
+
launchError;
|
|
50
67
|
return (React.createElement(React.Fragment, null,
|
|
51
68
|
React.createElement(LaunchPanelContent, { error: e },
|
|
52
69
|
React.createElement(TextField2, { select: true, label: "Select MSA dataset", value: selectedDatasetId, onChange: event => {
|
|
53
70
|
setSelectedDatasetId(event.target.value);
|
|
54
71
|
} }, datasets?.map(d => (React.createElement(MenuItem, { key: d.datasetId, value: d.datasetId }, d.name)))),
|
|
55
72
|
selectedDataset ? (React.createElement("div", { className: classes.selectedContainer },
|
|
56
|
-
!msaListLoading && msaDataLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading MSA for
|
|
73
|
+
!msaListLoading && msaDataLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading MSA for ${getTranscriptDisplayName(selectedTranscript) || selectedId}` })) : null,
|
|
57
74
|
msaListLoading ? (React.createElement(LoadingEllipses, { variant: "h6", message: `Loading available MSAs for (${selectedDataset.name})` })) : null,
|
|
58
75
|
msaList ? (React.createElement("div", null,
|
|
59
76
|
React.createElement(SanitizedHTML, { html: selectedDataset.description }),
|
|
60
|
-
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
|
|
78
|
+
msaText ? (React.createElement(QueryRowSelector, { ...queryRow, querySeqName: querySeqName })) : null)) : null)) : null),
|
|
79
|
+
React.createElement(SubmitCancelActions, { model: model, hint: msaDataLoading ? (React.createElement(LoadingEllipses, { message: "Loading alignment" })) : !!msaData?.length && !querySeqName ? (React.createElement(Typography, { color: "textSecondary", variant: "body2" }, "no row matches this transcript")) : (React.createElement(SequenceStatusMessage, { status: sequenceStatus })),
|
|
80
|
+
// launching without a query row opens a view that renders and then
|
|
81
|
+
// never navigates, which reads as a broken feature rather than a
|
|
82
|
+
// dataset that does not cover this gene
|
|
83
|
+
submitDisabled: !selectedTranscript || !msaData?.length || !querySeqName, onSubmit: placement => {
|
|
84
|
+
if (selectedTranscript && msaText) {
|
|
85
|
+
submit(() => {
|
|
86
|
+
launchConnectedView({
|
|
67
87
|
view,
|
|
68
|
-
querySeqName,
|
|
69
88
|
feature: selectedTranscript,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
89
|
+
placement,
|
|
90
|
+
displayName: getGeneDisplayName(selectedTranscript),
|
|
91
|
+
treeAreaWidth: 200,
|
|
92
|
+
treeWidth: 100,
|
|
93
|
+
drawNodeBubbles: false,
|
|
94
|
+
labelsAlignRight: true,
|
|
95
|
+
showBranchLen: false,
|
|
96
|
+
colWidth: 10,
|
|
97
|
+
rowHeight: 12,
|
|
98
|
+
colorSchemeName: 'percent_identity_dynamic',
|
|
99
|
+
querySeqName,
|
|
100
|
+
querySeqOffset: queryRow.querySeqOffset,
|
|
101
|
+
data: { msa: msaText },
|
|
75
102
|
});
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (e) {
|
|
80
|
-
setViewError(e);
|
|
103
|
+
});
|
|
81
104
|
}
|
|
82
105
|
}, onCancel: handleClose })));
|
|
83
106
|
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type SequenceStatus = 'loading' | 'ready' | 'missing' | 'error';
|
|
3
|
+
/**
|
|
4
|
+
* Why Submit is grey. The query row is the selected transcript's translation,
|
|
5
|
+
* and fetching and translating it takes a round trip to the sequence adapter,
|
|
6
|
+
* so the button starts disabled on every panel — with nothing to distinguish
|
|
7
|
+
* "wait a moment" from "this gene has no protein and never will".
|
|
8
|
+
*
|
|
9
|
+
* An error says so through the panel's own ErrorMessage, so this stays quiet
|
|
10
|
+
* for that one rather than saying it twice.
|
|
11
|
+
*/
|
|
12
|
+
export default function SequenceStatusMessage({ status, }: {
|
|
13
|
+
status: SequenceStatus;
|
|
14
|
+
}): React.JSX.Element | null;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { LoadingEllipses } from '@jbrowse/core/ui';
|
|
3
|
+
import { Typography } from '@mui/material';
|
|
4
|
+
/**
|
|
5
|
+
* Why Submit is grey. The query row is the selected transcript's translation,
|
|
6
|
+
* and fetching and translating it takes a round trip to the sequence adapter,
|
|
7
|
+
* so the button starts disabled on every panel — with nothing to distinguish
|
|
8
|
+
* "wait a moment" from "this gene has no protein and never will".
|
|
9
|
+
*
|
|
10
|
+
* An error says so through the panel's own ErrorMessage, so this stays quiet
|
|
11
|
+
* for that one rather than saying it twice.
|
|
12
|
+
*/
|
|
13
|
+
export default function SequenceStatusMessage({ status, }) {
|
|
14
|
+
return status === 'loading' ? (React.createElement(LoadingEllipses, { message: "Translating transcript" })) : status === 'missing' ? (React.createElement(Typography, { color: "textSecondary", variant: "body2" }, "no coding sequence to align")) : null;
|
|
15
|
+
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { MsaViewPlacement } from '../../utils/workspaces';
|
|
2
3
|
import type { AbstractTrackModel } from '@jbrowse/core/util';
|
|
3
|
-
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel, cancelLabel, model, }: {
|
|
4
|
-
|
|
4
|
+
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel, cancelLabel, model, }: {
|
|
5
|
+
/** handed the placement the box states, so no launch re-reads storage */
|
|
6
|
+
onSubmit: (placement: MsaViewPlacement) => void;
|
|
5
7
|
onCancel: () => void;
|
|
6
8
|
submitDisabled?: boolean;
|
|
9
|
+
/** why Submit is grey, shown beside it */
|
|
10
|
+
hint?: React.ReactNode;
|
|
7
11
|
submitLabel?: string;
|
|
8
12
|
cancelLabel?: string;
|
|
9
13
|
/** omitted by a panel that submits something other than a view launch */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { getSession } from '@jbrowse/core/util';
|
|
3
3
|
import { Button, Checkbox, DialogActions, FormControlLabel, } from '@mui/material';
|
|
4
4
|
import { readLaunchPlacement, sessionSupportsPlacement, writeLaunchPlacement, } from '../../utils/workspaces';
|
|
5
|
+
import { useLaunchPlacement } from './launchPlacement';
|
|
5
6
|
/**
|
|
6
7
|
* Where the launch puts the view, offered wherever a launch is submitted.
|
|
7
8
|
*
|
|
@@ -13,21 +14,35 @@ import { readLaunchPlacement, sessionSupportsPlacement, writeLaunchPlacement, }
|
|
|
13
14
|
* release that places views its own way — because the box would do nothing
|
|
14
15
|
* there and every launch would quietly ignore it.
|
|
15
16
|
*/
|
|
16
|
-
function PlacementToggle({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { checked } = event.target;
|
|
21
|
-
setSideBySide(checked);
|
|
22
|
-
writeLaunchPlacement(checked ? 'splitRight' : 'stack');
|
|
23
|
-
} }) })) : null;
|
|
17
|
+
function PlacementToggle({ checked, onChange, }) {
|
|
18
|
+
return (React.createElement(FormControlLabel, { label: "Open beside the genome view", control: React.createElement(Checkbox, { checked: checked, onChange: event => {
|
|
19
|
+
onChange(event.target.checked);
|
|
20
|
+
} }) }));
|
|
24
21
|
}
|
|
25
|
-
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel = 'Submit', cancelLabel = 'Cancel', model, }) {
|
|
22
|
+
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel = 'Submit', cancelLabel = 'Cancel', model, }) {
|
|
23
|
+
const [sideBySide, setSideBySide] = useLaunchPlacement();
|
|
24
|
+
// The stored value is what the next dialog opens on, so it is written on
|
|
25
|
+
// submit rather than on the click: a dialog the user backed out of must not
|
|
26
|
+
// move where every future launch lands.
|
|
27
|
+
const offerPlacement = !!model && sessionSupportsPlacement(getSession(model));
|
|
26
28
|
return (React.createElement(DialogActions, { sx: { flexWrap: 'wrap', rowGap: 1 } },
|
|
27
|
-
|
|
28
|
-
React.createElement("div", { style: {
|
|
29
|
+
offerPlacement ? (React.createElement(PlacementToggle, { checked: sideBySide, onChange: setSideBySide })) : null,
|
|
30
|
+
React.createElement("div", { style: {
|
|
31
|
+
display: 'flex',
|
|
32
|
+
alignItems: 'center',
|
|
33
|
+
gap: 8,
|
|
34
|
+
marginLeft: 'auto',
|
|
35
|
+
} },
|
|
36
|
+
hint,
|
|
29
37
|
React.createElement(Button, { sx: { flexShrink: 0 }, color: "primary", variant: "contained", disabled: submitDisabled, onClick: () => {
|
|
30
|
-
|
|
38
|
+
if (offerPlacement) {
|
|
39
|
+
const placement = sideBySide ? 'splitRight' : 'stack';
|
|
40
|
+
writeLaunchPlacement(placement);
|
|
41
|
+
onSubmit(placement);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
onSubmit(readLaunchPlacement());
|
|
45
|
+
}
|
|
31
46
|
} }, submitLabel),
|
|
32
47
|
React.createElement(Button, { sx: { flexShrink: 0 }, color: "secondary", variant: "contained", onClick: () => {
|
|
33
48
|
onCancel();
|
|
@@ -4,6 +4,7 @@ import { cleanup, render, screen } from '@testing-library/react';
|
|
|
4
4
|
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
|
|
5
5
|
import { LAUNCH_PLACEMENT_KEY } from '../../utils/workspaces';
|
|
6
6
|
import SubmitCancelActions from './SubmitCancelActions';
|
|
7
|
+
import { LaunchPlacementProvider } from './launchPlacement';
|
|
7
8
|
// getSession walks the MST tree, and this component only wants the two actions
|
|
8
9
|
// off the far end of that walk
|
|
9
10
|
vi.mock('@jbrowse/core/util', () => ({
|
|
@@ -39,14 +40,61 @@ test('a panel that launches nothing passes no model, and gets no box', () => {
|
|
|
39
40
|
render(React.createElement(SubmitCancelActions, { onSubmit: () => { }, onCancel: () => { } }));
|
|
40
41
|
expect(toggle()).toBeNull();
|
|
41
42
|
});
|
|
42
|
-
test('
|
|
43
|
-
|
|
43
|
+
test('submitting hands the launch the placement and remembers it', () => {
|
|
44
|
+
const onSubmit = vi.fn();
|
|
45
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: onSubmit, onCancel: () => { } }));
|
|
44
46
|
toggle().click();
|
|
45
|
-
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
46
47
|
expect(toggle().checked).toBe(false);
|
|
48
|
+
screen.getByText('Submit').click();
|
|
49
|
+
expect(onSubmit).toHaveBeenCalledWith('stack');
|
|
50
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
51
|
+
});
|
|
52
|
+
// an unticked box used to reach the launch only through storage, so a browser
|
|
53
|
+
// refusing localStorage launched side by side anyway
|
|
54
|
+
test('the box decides the launch even where storage throws', () => {
|
|
55
|
+
const onSubmit = vi.fn();
|
|
56
|
+
const setItem = vi
|
|
57
|
+
.spyOn(Storage.prototype, 'setItem')
|
|
58
|
+
.mockImplementation(() => {
|
|
59
|
+
throw new Error('SecurityError');
|
|
60
|
+
});
|
|
61
|
+
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
62
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: onSubmit, onCancel: () => { } }));
|
|
63
|
+
toggle().click();
|
|
64
|
+
screen.getByText('Submit').click();
|
|
65
|
+
expect(onSubmit).toHaveBeenCalledWith('stack');
|
|
66
|
+
setItem.mockRestore();
|
|
67
|
+
});
|
|
68
|
+
// the box is a property of this launch until it is launched; a dialog the user
|
|
69
|
+
// backed out of should not have moved where every future one lands
|
|
70
|
+
test('cancelling leaves the stored placement alone', () => {
|
|
71
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
|
|
47
72
|
toggle().click();
|
|
73
|
+
screen.getByText('Cancel').click();
|
|
74
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
// a host with no tiling never shows the box, so submitting there must not
|
|
77
|
+
// overwrite the choice the user made on a host that does
|
|
78
|
+
test('a host that cannot tile writes nothing on submit', () => {
|
|
79
|
+
localStorage.setItem(LAUNCH_PLACEMENT_KEY, 'splitRight');
|
|
80
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel({}), onSubmit: () => { }, onCancel: () => { } }));
|
|
81
|
+
screen.getByText('Submit').click();
|
|
48
82
|
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('splitRight');
|
|
49
|
-
|
|
83
|
+
});
|
|
84
|
+
// every visited tab stays mounted, so each has an actions row of its own; with
|
|
85
|
+
// the answer held per row, ticking the box on one tab and submitting from
|
|
86
|
+
// another wrote the other tab's stale one
|
|
87
|
+
test('every tab in one dialog shares the answer', () => {
|
|
88
|
+
render(React.createElement(LaunchPlacementProvider, null,
|
|
89
|
+
React.createElement("div", { "data-testid": "tab-a" },
|
|
90
|
+
React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } })),
|
|
91
|
+
React.createElement("div", { "data-testid": "tab-b" },
|
|
92
|
+
React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { }, submitLabel: "Launch" }))));
|
|
93
|
+
const boxes = screen.getAllByRole('checkbox');
|
|
94
|
+
boxes[0].click();
|
|
95
|
+
expect(boxes[1].checked).toBe(false);
|
|
96
|
+
screen.getByText('Launch').click();
|
|
97
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
50
98
|
});
|
|
51
99
|
test('a stored choice is what the box opens on', () => {
|
|
52
100
|
localStorage.setItem(LAUNCH_PLACEMENT_KEY, 'stack');
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
//
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
// Once visited, a panel stays mounted and is hidden with the `hidden` attribute.
|
|
3
|
+
// Unmounting it discarded whatever the user had typed and re-ran every fetch the
|
|
4
|
+
// panel makes on the way back, so switching tabs to compare two of them cost an
|
|
5
|
+
// EBI round trip and the pasted alignment.
|
|
6
|
+
//
|
|
7
|
+
// Lazy on first visit rather than mounted up front, because a panel mounts
|
|
8
|
+
// fetches of its own: rendering all four on open would query the MSA dataset
|
|
9
|
+
// adapter and the BLAST cache for tabs nobody looked at.
|
|
3
10
|
export default function TabPanel({ children, value, index, ...other }) {
|
|
4
|
-
|
|
11
|
+
const active = value === index;
|
|
12
|
+
const [visited, setVisited] = useState(active);
|
|
13
|
+
if (active && !visited) {
|
|
14
|
+
setVisited(true);
|
|
15
|
+
}
|
|
16
|
+
return (React.createElement("div", { role: "tabpanel", hidden: !active, ...other }, visited || active ? children : null));
|
|
5
17
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { cleanup, render, screen } from '@testing-library/react';
|
|
4
|
+
import { afterEach, expect, test, vi } from 'vitest';
|
|
5
|
+
import TabPanel from './TabPanel';
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
cleanup();
|
|
8
|
+
});
|
|
9
|
+
function Panels({ value }) {
|
|
10
|
+
return (React.createElement(React.Fragment, null,
|
|
11
|
+
React.createElement(TabPanel, { value: value, index: "a" },
|
|
12
|
+
React.createElement(Probe, { name: "a" })),
|
|
13
|
+
React.createElement(TabPanel, { value: value, index: "b" },
|
|
14
|
+
React.createElement(Probe, { name: "b" }))));
|
|
15
|
+
}
|
|
16
|
+
const mounted = vi.fn();
|
|
17
|
+
function Probe({ name }) {
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
mounted(name);
|
|
20
|
+
}, [name]);
|
|
21
|
+
return React.createElement("input", { defaultValue: name });
|
|
22
|
+
}
|
|
23
|
+
test('a visited panel keeps its state while another tab is shown', () => {
|
|
24
|
+
const { rerender } = render(React.createElement(Panels, { value: "a" }));
|
|
25
|
+
const typed = screen.getByDisplayValue('a');
|
|
26
|
+
typed.value = 'edited';
|
|
27
|
+
rerender(React.createElement(Panels, { value: "b" }));
|
|
28
|
+
rerender(React.createElement(Panels, { value: "a" }));
|
|
29
|
+
expect(screen.getByDisplayValue('edited')).toBeTruthy();
|
|
30
|
+
expect(mounted.mock.calls.filter(([n]) => n === 'a')).toHaveLength(1);
|
|
31
|
+
});
|
|
32
|
+
test('an unvisited panel does not mount, so it fetches nothing', () => {
|
|
33
|
+
mounted.mockClear();
|
|
34
|
+
render(React.createElement(Panels, { value: "a" }));
|
|
35
|
+
expect(mounted.mock.calls.map(([n]) => n)).toEqual(['a']);
|
|
36
|
+
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import type { SequenceStatus } from './SequenceStatus';
|
|
2
3
|
import type { Feature } from '@jbrowse/core/util';
|
|
3
|
-
export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, validIds, }: {
|
|
4
|
+
export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, sequenceStatus, validIds, }: {
|
|
4
5
|
feature: Feature;
|
|
5
6
|
options: Feature[];
|
|
6
7
|
selectedId: string;
|
|
7
8
|
selectedTranscript: Feature | undefined;
|
|
8
9
|
setSelectedId: (transcriptId: string) => void;
|
|
9
10
|
proteinSequence: string | undefined;
|
|
11
|
+
sequenceStatus?: SequenceStatus;
|
|
10
12
|
validIds?: string[];
|
|
11
13
|
}): React.JSX.Element;
|
|
@@ -15,7 +15,7 @@ const useStyles = makeStyles()({
|
|
|
15
15
|
marginLeft: 20,
|
|
16
16
|
},
|
|
17
17
|
});
|
|
18
|
-
export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, validIds, }) {
|
|
18
|
+
export default function TranscriptSelector({ feature, options, selectedId, selectedTranscript, setSelectedId, proteinSequence, sequenceStatus = 'ready', validIds, }) {
|
|
19
19
|
const { classes } = useStyles();
|
|
20
20
|
const [showSequence, setShowSequence] = useState(false);
|
|
21
21
|
return (React.createElement(React.Fragment, null,
|
|
@@ -48,5 +48,7 @@ export default function TranscriptSelector({ feature, options, selectedId, selec
|
|
|
48
48
|
} }, showSequence ? 'Hide sequence' : 'Show sequence'))),
|
|
49
49
|
showSequence ? (React.createElement(ReadOnlyTextField2, { value: proteinSequence
|
|
50
50
|
? `>${getTranscriptDisplayName(selectedTranscript)}\n${proteinSequence}`
|
|
51
|
-
: '
|
|
51
|
+
: sequenceStatus === 'missing'
|
|
52
|
+
? 'This transcript has no coding sequence, so there is nothing to translate.'
|
|
53
|
+
: 'Loading...' })) : null));
|
|
52
54
|
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { Feat } from './types';
|
|
2
2
|
import type { Feature } from '@jbrowse/core/util';
|
|
3
|
-
|
|
3
|
+
import type { TranslExcept } from '@jbrowse/core/util/geneticCodes';
|
|
4
|
+
export declare function calculateProteinSequence({ cds, sequence, geneticCodeId, translExcept, }: {
|
|
4
5
|
cds: Feat[];
|
|
5
6
|
sequence: string;
|
|
6
7
|
geneticCodeId?: number;
|
|
8
|
+
translExcept?: TranslExcept[];
|
|
7
9
|
}): string;
|
|
8
10
|
export declare function revlist(list: Feat[], seqlen: number): {
|
|
9
11
|
start: number;
|
|
@@ -11,7 +13,15 @@ export declare function revlist(list: Feat[], seqlen: number): {
|
|
|
11
13
|
type?: string;
|
|
12
14
|
phase?: number;
|
|
13
15
|
}[];
|
|
14
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The translation core's own feature panel shows: the contig's or the
|
|
18
|
+
* feature's genetic code, its alternative initiators, and any `transl_except`
|
|
19
|
+
* (RefSeq's selenocysteines), read off the transcript or its CDS as core does.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getProteinSequenceFromFeature({ feature, seq, assemblyGeneticCodeId, }: {
|
|
15
22
|
seq: string;
|
|
16
23
|
feature: Feature;
|
|
24
|
+
/** the assembly's code for the feature's contig, `{ chrM: 2 }` in hub
|
|
25
|
+
* configs; a transl_table on the feature wins */
|
|
26
|
+
assemblyGeneticCodeId?: number;
|
|
17
27
|
}): string;
|
|
@@ -1,26 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { revcom } from '@jbrowse/core/util';
|
|
2
2
|
import { convertCodingSequenceToPeptides } from '@jbrowse/core/util/convertCodingSequenceToPeptides';
|
|
3
|
-
import { getGeneticCode, parseTranslTable } from '
|
|
4
|
-
// `@jbrowse/core/util/convertCodingSequenceToPeptides`
|
|
5
|
-
//
|
|
6
|
-
// rather than resolved out of the host's JBrowseExports
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export function calculateProteinSequence({ cds, sequence, geneticCodeId, }) {
|
|
13
|
-
// `starts` is deliberately not passed: @jbrowse/core 4.3.0's signature has no
|
|
14
|
-
// such parameter, so alternative initiators (GTG under table 11, ATA under
|
|
15
|
-
// table 2) render as their internal residue rather than M. Core main added it;
|
|
16
|
-
// pass it here when msaview's @jbrowse/core floor reaches that release.
|
|
17
|
-
const { codonTable } = getGeneticCode(geneticCodeId);
|
|
3
|
+
import { getGeneticCode, parseTranslTable, relativizeTranslExcept, } from '@jbrowse/core/util/geneticCodes';
|
|
4
|
+
// `@jbrowse/core/util/convertCodingSequenceToPeptides` and
|
|
5
|
+
// `@jbrowse/core/util/geneticCodes` are deep paths absent from ReExports, so
|
|
6
|
+
// they are bundled rather than resolved out of the host's JBrowseExports: every
|
|
7
|
+
// host runs the version this build installs. Importing translation from the
|
|
8
|
+
// `@jbrowse/core/util` barrel instead is what error-paged the whole app when a
|
|
9
|
+
// core build dropped `defaultCodonTable`.
|
|
10
|
+
export function calculateProteinSequence({ cds, sequence, geneticCodeId, translExcept, }) {
|
|
11
|
+
const { codonTable, starts } = getGeneticCode(geneticCodeId);
|
|
18
12
|
return convertCodingSequenceToPeptides({
|
|
19
13
|
cds,
|
|
20
14
|
sequence,
|
|
21
15
|
codonTable,
|
|
16
|
+
starts,
|
|
17
|
+
translExcept,
|
|
22
18
|
});
|
|
23
19
|
}
|
|
20
|
+
// The CDS list is sorted by start, so adjacent comparison is the whole job.
|
|
21
|
+
function cdsId(feat) {
|
|
22
|
+
return `${feat.start}-${feat.end}`;
|
|
23
|
+
}
|
|
24
|
+
function dedupe(list) {
|
|
25
|
+
return list.filter((item, pos, ary) => !pos || cdsId(item) !== cdsId(ary[pos - 1]));
|
|
26
|
+
}
|
|
24
27
|
export function revlist(list, seqlen) {
|
|
25
28
|
return list
|
|
26
29
|
.map(sub => ({
|
|
@@ -30,8 +33,13 @@ export function revlist(list, seqlen) {
|
|
|
30
33
|
}))
|
|
31
34
|
.toSorted((a, b) => a.start - b.start);
|
|
32
35
|
}
|
|
33
|
-
|
|
34
|
-
|
|
36
|
+
/**
|
|
37
|
+
* The translation core's own feature panel shows: the contig's or the
|
|
38
|
+
* feature's genetic code, its alternative initiators, and any `transl_except`
|
|
39
|
+
* (RefSeq's selenocysteines), read off the transcript or its CDS as core does.
|
|
40
|
+
*/
|
|
41
|
+
export function getProteinSequenceFromFeature({ feature, seq, assemblyGeneticCodeId, }) {
|
|
42
|
+
const { subfeatures, start, end, strand } = feature.toJSON();
|
|
35
43
|
const cds = dedupe(subfeatures
|
|
36
44
|
?.toSorted((a, b) => a.start - b.start)
|
|
37
45
|
.map(sub => ({
|
|
@@ -39,18 +47,25 @@ export function getProteinSequenceFromFeature({ feature, seq, }) {
|
|
|
39
47
|
start: sub.start - start,
|
|
40
48
|
end: sub.end - start,
|
|
41
49
|
}))
|
|
42
|
-
.filter(subfeature => subfeature.type === 'CDS') ?? []
|
|
43
|
-
// a mitochondrial gene declares e.g. transl_table=2, so it translates with
|
|
44
|
-
// NCBI table 2 rather than the standard code. GFF3 usually carries the
|
|
45
|
-
// attribute on the CDS rather than the transcript, so check both.
|
|
50
|
+
.filter(subfeature => subfeature.type === 'CDS') ?? []);
|
|
46
51
|
const cdsSubfeature = feature
|
|
47
52
|
.get('subfeatures')
|
|
48
53
|
?.find((f) => f.get('type')?.toLowerCase() === 'cds');
|
|
49
54
|
const geneticCodeId = parseTranslTable(feature.get('transl_table')) ??
|
|
50
|
-
parseTranslTable(cdsSubfeature?.get('transl_table'))
|
|
55
|
+
parseTranslTable(cdsSubfeature?.get('transl_table')) ??
|
|
56
|
+
assemblyGeneticCodeId;
|
|
57
|
+
const rawTranslExcept = feature.get('transl_except') ?? cdsSubfeature?.get('transl_except');
|
|
51
58
|
return calculateProteinSequence({
|
|
52
59
|
cds: strand === -1 ? revlist(cds, seq.length) : cds,
|
|
53
60
|
sequence: strand === -1 ? revcom(seq) : seq,
|
|
54
61
|
geneticCodeId,
|
|
62
|
+
translExcept: rawTranslExcept
|
|
63
|
+
? relativizeTranslExcept({
|
|
64
|
+
raw: rawTranslExcept,
|
|
65
|
+
featureStart: start,
|
|
66
|
+
featureLength: end - start,
|
|
67
|
+
strand,
|
|
68
|
+
})
|
|
69
|
+
: undefined,
|
|
55
70
|
});
|
|
56
71
|
}
|
|
@@ -6,17 +6,27 @@ export async function fetchSeq({ start, end, refName, session, assemblyName, })
|
|
|
6
6
|
throw new Error('assembly not found');
|
|
7
7
|
}
|
|
8
8
|
const sessionId = 'getSequence';
|
|
9
|
-
|
|
9
|
+
// a named object keeps sessionId, which v4 hosts read from the args
|
|
10
|
+
const args = {
|
|
10
11
|
adapterConfig: getConf(assembly, ['sequence', 'adapter']),
|
|
11
12
|
sessionId,
|
|
12
13
|
regions: [
|
|
13
14
|
{
|
|
14
15
|
start,
|
|
15
16
|
end,
|
|
16
|
-
refName: assembly.getCanonicalRefName(refName),
|
|
17
|
+
refName: assembly.getCanonicalRefName(refName) ?? refName,
|
|
17
18
|
assemblyName,
|
|
18
19
|
},
|
|
19
20
|
],
|
|
20
|
-
}
|
|
21
|
-
|
|
21
|
+
};
|
|
22
|
+
const feats = await rpcManager.call(sessionId, 'CoreGetFeatures', args);
|
|
23
|
+
return {
|
|
24
|
+
seq: feats[0]?.get('seq') ?? '',
|
|
25
|
+
// travels with the sequence because the caller needs both to translate, and
|
|
26
|
+
// resolving the assembly twice to get them is the shape that lost it
|
|
27
|
+
assemblyGeneticCodeId: geneticCodeId(assembly, refName),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function geneticCodeId(assembly, refName) {
|
|
31
|
+
return assembly.getGeneticCodeId?.(refName);
|
|
22
32
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { MsaViewPlacement } from '../../utils/workspaces';
|
|
2
|
+
import type { Feature } from '@jbrowse/core/util';
|
|
3
|
+
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
|
|
4
|
+
/** how a view whose alignment the plugin builds opens */
|
|
5
|
+
export declare const builtAlignmentLook: {
|
|
6
|
+
drawNodeBubbles: boolean;
|
|
7
|
+
colWidth: number;
|
|
8
|
+
rowHeight: number;
|
|
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 declare function launchConnectedView({ view, feature, placement, ...snapshot }: {
|
|
15
|
+
view: LinearGenomeViewModel;
|
|
16
|
+
feature?: Feature;
|
|
17
|
+
placement: MsaViewPlacement;
|
|
18
|
+
} & Record<string, unknown>): void;
|
|
19
|
+
/** runs a panel's launch, closing the dialog on success and keeping the error */
|
|
20
|
+
export declare function useLaunchSubmit(handleClose: () => void): {
|
|
21
|
+
launchError: unknown;
|
|
22
|
+
submit: (launch: () => void) => void;
|
|
23
|
+
};
|