jbrowse-plugin-msaview 3.5.0 → 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/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.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 +19 -24
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/BlastQuery/BlastManualPanel.js +6 -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/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 +9 -4
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.d.ts +3 -1
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +17 -8
- 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/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/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.js +5 -2
- package/dist/MsaViewPanel/afterCreateAutoruns.d.ts +6 -4
- package/dist/MsaViewPanel/afterCreateAutoruns.js +26 -11
- 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/genomeToMSA.js +21 -3
- package/dist/MsaViewPanel/genomeToMSA.test.js +153 -25
- package/dist/MsaViewPanel/model.d.ts +143 -52
- package/dist/MsaViewPanel/model.js +52 -3
- package/dist/MsaViewPanel/model.test.js +66 -0
- package/dist/MsaViewPanel/resolveConnectedTranscript.js +10 -4
- package/dist/MsaViewPanel/storedData.test.js +20 -0
- package/dist/MsaViewPanel/syncGenomeHoverToMsaColumn.test.js +12 -5
- package/dist/index.d.ts +11 -11
- 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/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +21 -21
- 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 +25 -24
- package/src/LaunchMsaView/components/BlastQuery/BlastManualPanel.tsx +8 -1
- package/src/LaunchMsaView/components/BlastQuery/BlastPanel.tsx +24 -11
- package/src/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.tsx +4 -4
- 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 +12 -2
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +23 -7
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +63 -13
- package/src/LaunchMsaView/components/PreLoadedMSA/preCalculatedLaunchView.ts +4 -0
- 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/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 +8 -4
- package/src/MsaViewPanel/afterCreateAutoruns.ts +34 -11
- 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/genomeToMSA.test.ts +184 -40
- package/src/MsaViewPanel/genomeToMSA.ts +27 -3
- package/src/MsaViewPanel/model.test.ts +75 -0
- package/src/MsaViewPanel/model.ts +54 -3
- package/src/MsaViewPanel/resolveConnectedTranscript.ts +14 -4
- package/src/MsaViewPanel/storedData.test.ts +26 -0
- package/src/MsaViewPanel/syncGenomeHoverToMsaColumn.test.ts +14 -5
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/geneticCodes.d.ts +0 -15
- package/dist/LaunchMsaView/components/geneticCodes.js +0 -227
- package/src/LaunchMsaView/components/geneticCodes.ts +0 -298
|
@@ -8,6 +8,7 @@ import { useQueryRowName } from '../../useQueryRowName';
|
|
|
8
8
|
import { getGeneDisplayName, getLinearGenomeView } from '../../util';
|
|
9
9
|
import LaunchPanelContent from '../LaunchPanelContent';
|
|
10
10
|
import QueryRowSelector from '../QueryRowSelector';
|
|
11
|
+
import SequenceStatusMessage from '../SequenceStatus';
|
|
11
12
|
import SubmitCancelActions from '../SubmitCancelActions';
|
|
12
13
|
import TranscriptSelector from '../TranscriptSelector';
|
|
13
14
|
import { useTranscriptSelection } from '../useTranscriptSelection';
|
|
@@ -26,7 +27,7 @@ const useStyles = makeStyles()({
|
|
|
26
27
|
marginBottom: 20,
|
|
27
28
|
},
|
|
28
29
|
});
|
|
29
|
-
const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handleClose, }) {
|
|
30
|
+
const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handleClose, preferredTranscriptId, }) {
|
|
30
31
|
const view = getLinearGenomeView(model);
|
|
31
32
|
const { classes } = useStyles();
|
|
32
33
|
const [launchViewError, setLaunchViewError] = useState();
|
|
@@ -35,8 +36,12 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
|
|
|
35
36
|
const [treeText, setTreeText] = useState('');
|
|
36
37
|
const [msaFileLocation, setMsaFileLocation] = useState();
|
|
37
38
|
const [treeFileLocation, setTreeFileLocation] = useState();
|
|
38
|
-
const transcriptSelection = useTranscriptSelection({
|
|
39
|
-
|
|
39
|
+
const transcriptSelection = useTranscriptSelection({
|
|
40
|
+
feature,
|
|
41
|
+
view,
|
|
42
|
+
preferredTranscriptId,
|
|
43
|
+
});
|
|
44
|
+
const { selectedTranscript, proteinSequence, error, sequenceStatus } = transcriptSelection;
|
|
40
45
|
const queryRow = useQueryRowName(msaText, proteinSequence);
|
|
41
46
|
const e = launchViewError ?? error;
|
|
42
47
|
return (React.createElement(React.Fragment, null,
|
|
@@ -58,7 +63,7 @@ const ManualMSALoader = observer(function PreLoadedMSA2({ model, feature, handle
|
|
|
58
63
|
} })))),
|
|
59
64
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
|
|
60
65
|
React.createElement(QueryRowSelector, { ...queryRow })),
|
|
61
|
-
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !selectedTranscript ||
|
|
66
|
+
React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !selectedTranscript ||
|
|
62
67
|
(inputMethod === 'file' && !msaFileLocation) ||
|
|
63
68
|
(inputMethod === 'text' && !msaText.trim()), onSubmit: () => {
|
|
64
69
|
try {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
|
|
3
|
-
declare const OrthologPanel: ({ handleClose, feature, model, }: {
|
|
3
|
+
declare const OrthologPanel: ({ handleClose, feature, model, preferredTranscriptId, }: {
|
|
4
4
|
model: AbstractTrackModel;
|
|
5
5
|
feature: Feature;
|
|
6
6
|
handleClose: () => void;
|
|
7
|
+
/** the isoform the user right-clicked, preselected in the picker */
|
|
8
|
+
preferredTranscriptId?: string;
|
|
7
9
|
}) => React.JSX.Element;
|
|
8
10
|
export default OrthologPanel;
|
|
@@ -9,6 +9,7 @@ import { getGeneDisplayName, getGeneIdentifiers, getLinearGenomeView, getTranscr
|
|
|
9
9
|
import MsaAlgorithmSelect from '../BlastQuery/MsaAlgorithmSelect';
|
|
10
10
|
import { useStoredMsaAlgorithm } from '../BlastQuery/searchChoiceStorage';
|
|
11
11
|
import LaunchPanelContent from '../LaunchPanelContent';
|
|
12
|
+
import SequenceStatusMessage from '../SequenceStatus';
|
|
12
13
|
import SubmitCancelActions from '../SubmitCancelActions';
|
|
13
14
|
import TranscriptSelector from '../TranscriptSelector';
|
|
14
15
|
import { useTranscriptSelection } from '../useTranscriptSelection';
|
|
@@ -20,12 +21,14 @@ const useStyles = makeStyles()({
|
|
|
20
21
|
width: 180,
|
|
21
22
|
},
|
|
22
23
|
});
|
|
24
|
+
// the N was literal: the helper text said "the closest N species" whatever the
|
|
25
|
+
// box held
|
|
23
26
|
const rowsHint = {
|
|
24
|
-
ncbi:
|
|
25
|
-
panther:
|
|
26
|
-
uniref:
|
|
27
|
+
ncbi: rows => `the ${rows} closest species NCBI has`,
|
|
28
|
+
panther: rows => `the ${rows} closest species PANTHER has`,
|
|
29
|
+
uniref: rows => `${rows} rows, one per species, reviewed entries first`,
|
|
27
30
|
};
|
|
28
|
-
const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
31
|
+
const OrthologPanel = observer(function ({ handleClose, feature, model, preferredTranscriptId, }) {
|
|
29
32
|
const { classes } = useStyles();
|
|
30
33
|
const view = getLinearGenomeView(model);
|
|
31
34
|
const [launchViewError, setLaunchViewError] = useState();
|
|
@@ -34,8 +37,12 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
34
37
|
const [msaAlgorithm, setMsaAlgorithm] = useStoredMsaAlgorithm();
|
|
35
38
|
const [maxSpecies, setMaxSpecies] = useState(String(defaultMaxSpecies));
|
|
36
39
|
const geneCandidates = useMemo(() => getGeneIdentifiers(feature), [feature]);
|
|
37
|
-
const transcriptSelection = useTranscriptSelection({
|
|
38
|
-
|
|
40
|
+
const transcriptSelection = useTranscriptSelection({
|
|
41
|
+
feature,
|
|
42
|
+
view,
|
|
43
|
+
preferredTranscriptId,
|
|
44
|
+
});
|
|
45
|
+
const { selectedTranscript, proteinSequence, sequenceStatus } = transcriptSelection;
|
|
39
46
|
const e = transcriptSelection.error ?? launchViewError;
|
|
40
47
|
const rowCount = Number(maxSpecies);
|
|
41
48
|
const rowCountValid = Number.isInteger(rowCount) && rowCount >= 2;
|
|
@@ -48,9 +55,11 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
48
55
|
React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: msaAlgorithm, onChange: setMsaAlgorithm }),
|
|
49
56
|
React.createElement(TextField2, { variant: "outlined", label: "Rows to align", className: classes.selectField, type: "number", value: maxSpecies, onChange: event => {
|
|
50
57
|
setMaxSpecies(event.target.value);
|
|
51
|
-
}, error: !rowCountValid, helperText:
|
|
58
|
+
}, error: !rowCountValid, helperText: rowCountValid
|
|
59
|
+
? rowsHint[source](rowCount)
|
|
60
|
+
: 'a whole number, 2 or more' })),
|
|
52
61
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })),
|
|
53
|
-
React.createElement(SubmitCancelActions, { model: model, submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
|
|
62
|
+
React.createElement(SubmitCancelActions, { model: model, hint: React.createElement(SequenceStatusMessage, { status: sequenceStatus }), submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
|
|
54
63
|
try {
|
|
55
64
|
if (selectedTranscript) {
|
|
56
65
|
setLaunchViewError(undefined);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
|
|
3
|
-
declare const PreLoadedMSA: ({ model, feature, handleClose, }: {
|
|
3
|
+
declare const PreLoadedMSA: ({ model, feature, handleClose, preferredTranscriptId, }: {
|
|
4
4
|
model: AbstractTrackModel;
|
|
5
5
|
feature: Feature;
|
|
6
6
|
handleClose: () => void;
|
|
7
|
+
/** the isoform the user right-clicked, preselected in the picker */
|
|
8
|
+
preferredTranscriptId?: string;
|
|
7
9
|
}) => React.JSX.Element;
|
|
8
10
|
export default PreLoadedMSA;
|
|
@@ -1,13 +1,16 @@
|
|
|
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';
|
|
13
16
|
import { useTranscriptSelection } from '../useTranscriptSelection';
|
|
@@ -19,7 +22,7 @@ const useStyles = makeStyles()({
|
|
|
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();
|
|
@@ -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,6 +47,19 @@ 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 ??
|
|
@@ -53,25 +70,26 @@ const PreLoadedMSA = observer(function ({ model, feature, handleClose, }) {
|
|
|
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
|
-
|
|
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: () => {
|
|
62
84
|
try {
|
|
63
|
-
if (selectedTranscript &&
|
|
64
|
-
const querySeqName = `${selectedId}_${assemblyNames[0]}`;
|
|
85
|
+
if (selectedTranscript && msaText) {
|
|
65
86
|
preCalculatedLaunchView({
|
|
66
87
|
newViewTitle: getGeneDisplayName(selectedTranscript),
|
|
67
88
|
view,
|
|
68
89
|
querySeqName,
|
|
90
|
+
querySeqOffset: queryRow.querySeqOffset,
|
|
69
91
|
feature: selectedTranscript,
|
|
70
|
-
data: {
|
|
71
|
-
msa: msaData
|
|
72
|
-
.map(r => `>${r.get('refName')}\n${r.get('seq')}`)
|
|
73
|
-
.join('\n'),
|
|
74
|
-
},
|
|
92
|
+
data: { msa: msaText },
|
|
75
93
|
});
|
|
76
94
|
handleClose();
|
|
77
95
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Feature } from '@jbrowse/core/util';
|
|
2
2
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
|
|
3
|
-
export declare function preCalculatedLaunchView({ newViewTitle, view, feature, data, querySeqName, }: {
|
|
3
|
+
export declare function preCalculatedLaunchView({ newViewTitle, view, feature, data, querySeqName, querySeqOffset, }: {
|
|
4
4
|
data: {
|
|
5
5
|
msa: string;
|
|
6
6
|
};
|
|
@@ -8,4 +8,6 @@ export declare function preCalculatedLaunchView({ newViewTitle, view, feature, d
|
|
|
8
8
|
view: LinearGenomeViewModel;
|
|
9
9
|
feature: Feature;
|
|
10
10
|
querySeqName: string;
|
|
11
|
+
/** transcript residues before the query row's first residue */
|
|
12
|
+
querySeqOffset?: number;
|
|
11
13
|
}): void;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
2
|
import { launchMsaView } from '../../../utils/launchMsaView';
|
|
3
3
|
import { readLaunchPlacement } from '../../../utils/workspaces';
|
|
4
|
-
export function preCalculatedLaunchView({ newViewTitle, view, feature, data, querySeqName, }) {
|
|
4
|
+
export function preCalculatedLaunchView({ newViewTitle, view, feature, data, querySeqName, querySeqOffset, }) {
|
|
5
5
|
launchMsaView(getSession(view), {
|
|
6
6
|
placement: readLaunchPlacement(),
|
|
7
7
|
displayName: newViewTitle,
|
|
8
8
|
treeAreaWidth: 200,
|
|
9
9
|
querySeqName,
|
|
10
|
+
querySeqOffset,
|
|
10
11
|
treeWidth: 100,
|
|
11
12
|
drawNodeBubbles: false,
|
|
12
13
|
labelsAlignRight: true,
|
|
@@ -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,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AbstractTrackModel } from '@jbrowse/core/util';
|
|
3
|
-
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, submitLabel, cancelLabel, model, }: {
|
|
3
|
+
export default function SubmitCancelActions({ onSubmit, onCancel, submitDisabled, hint, submitLabel, cancelLabel, model, }: {
|
|
4
4
|
onSubmit: () => void;
|
|
5
5
|
onCancel: () => void;
|
|
6
6
|
submitDisabled?: boolean;
|
|
7
|
+
/** why Submit is grey, shown beside it */
|
|
8
|
+
hint?: React.ReactNode;
|
|
7
9
|
submitLabel?: string;
|
|
8
10
|
cancelLabel?: string;
|
|
9
11
|
/** 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
|
-
import {
|
|
4
|
+
import { 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,20 +14,31 @@ 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 launch reads, so it is written on submit
|
|
25
|
+
// rather than on the click: ticking the box and then pressing Cancel used to
|
|
26
|
+
// change where every future launch landed, from a dialog the user backed out
|
|
27
|
+
// of.
|
|
28
|
+
const offerPlacement = !!model && sessionSupportsPlacement(getSession(model));
|
|
26
29
|
return (React.createElement(DialogActions, { sx: { flexWrap: 'wrap', rowGap: 1 } },
|
|
27
|
-
|
|
28
|
-
React.createElement("div", { style: {
|
|
30
|
+
offerPlacement ? (React.createElement(PlacementToggle, { checked: sideBySide, onChange: setSideBySide })) : null,
|
|
31
|
+
React.createElement("div", { style: {
|
|
32
|
+
display: 'flex',
|
|
33
|
+
alignItems: 'center',
|
|
34
|
+
gap: 8,
|
|
35
|
+
marginLeft: 'auto',
|
|
36
|
+
} },
|
|
37
|
+
hint,
|
|
29
38
|
React.createElement(Button, { sx: { flexShrink: 0 }, color: "primary", variant: "contained", disabled: submitDisabled, onClick: () => {
|
|
39
|
+
if (offerPlacement) {
|
|
40
|
+
writeLaunchPlacement(sideBySide ? 'splitRight' : 'stack');
|
|
41
|
+
}
|
|
30
42
|
onSubmit();
|
|
31
43
|
} }, submitLabel),
|
|
32
44
|
React.createElement(Button, { sx: { flexShrink: 0 }, color: "secondary", variant: "contained", onClick: () => {
|
|
@@ -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,43 @@ 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
|
+
test('submitting writes the placement the launch will read', () => {
|
|
43
44
|
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
|
|
44
45
|
toggle().click();
|
|
45
|
-
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
46
46
|
expect(toggle().checked).toBe(false);
|
|
47
|
+
screen.getByText('Submit').click();
|
|
48
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
49
|
+
});
|
|
50
|
+
// the box is a property of this launch until it is launched; a dialog the user
|
|
51
|
+
// backed out of should not have moved where every future one lands
|
|
52
|
+
test('cancelling leaves the stored placement alone', () => {
|
|
53
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } }));
|
|
47
54
|
toggle().click();
|
|
55
|
+
screen.getByText('Cancel').click();
|
|
56
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
// a host with no tiling never shows the box, so submitting there must not
|
|
59
|
+
// overwrite the choice the user made on a host that does
|
|
60
|
+
test('a host that cannot tile writes nothing on submit', () => {
|
|
61
|
+
localStorage.setItem(LAUNCH_PLACEMENT_KEY, 'splitRight');
|
|
62
|
+
render(React.createElement(SubmitCancelActions, { model: trackModel({}), onSubmit: () => { }, onCancel: () => { } }));
|
|
63
|
+
screen.getByText('Submit').click();
|
|
48
64
|
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('splitRight');
|
|
49
|
-
|
|
65
|
+
});
|
|
66
|
+
// every visited tab stays mounted, so each has an actions row of its own; with
|
|
67
|
+
// the answer held per row, ticking the box on one tab and submitting from
|
|
68
|
+
// another wrote the other tab's stale one
|
|
69
|
+
test('every tab in one dialog shares the answer', () => {
|
|
70
|
+
render(React.createElement(LaunchPlacementProvider, null,
|
|
71
|
+
React.createElement("div", { "data-testid": "tab-a" },
|
|
72
|
+
React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { } })),
|
|
73
|
+
React.createElement("div", { "data-testid": "tab-b" },
|
|
74
|
+
React.createElement(SubmitCancelActions, { model: trackModel(tiling), onSubmit: () => { }, onCancel: () => { }, submitLabel: "Launch" }))));
|
|
75
|
+
const boxes = screen.getAllByRole('checkbox');
|
|
76
|
+
boxes[0].click();
|
|
77
|
+
expect(boxes[1].checked).toBe(false);
|
|
78
|
+
screen.getByText('Launch').click();
|
|
79
|
+
expect(localStorage.getItem(LAUNCH_PLACEMENT_KEY)).toBe('stack');
|
|
50
80
|
});
|
|
51
81
|
test('a stored choice is what the box opens on', () => {
|
|
52
82
|
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
|
}
|
|
@@ -11,7 +11,10 @@ export declare function revlist(list: Feat[], seqlen: number): {
|
|
|
11
11
|
type?: string;
|
|
12
12
|
phase?: number;
|
|
13
13
|
}[];
|
|
14
|
-
export declare function getProteinSequenceFromFeature({ feature, seq, }: {
|
|
14
|
+
export declare function getProteinSequenceFromFeature({ feature, seq, assemblyGeneticCodeId, }: {
|
|
15
15
|
seq: string;
|
|
16
16
|
feature: Feature;
|
|
17
|
+
/** the assembly's code for the feature's contig, `{ chrM: 2 }` in hub
|
|
18
|
+
* configs; a transl_table on the feature wins */
|
|
19
|
+
assemblyGeneticCodeId?: number;
|
|
17
20
|
}): string;
|
|
@@ -1,8 +1,9 @@
|
|
|
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
|
-
//
|
|
3
|
+
import { getGeneticCode, parseTranslTable, } from '@jbrowse/core/util/geneticCodes';
|
|
4
|
+
// `@jbrowse/core/util/convertCodingSequenceToPeptides` and
|
|
5
|
+
// `@jbrowse/core/util/geneticCodes` are deep paths, so unlike the
|
|
6
|
+
// `@jbrowse/core/util` barrel they are absent from ReExports and get bundled
|
|
6
7
|
// rather than resolved out of the host's JBrowseExports. That is what makes
|
|
7
8
|
// reusing core's translation safe across every host a config names: this module
|
|
8
9
|
// previously built its codon table at module scope from the barrel's
|
|
@@ -21,6 +22,13 @@ export function calculateProteinSequence({ cds, sequence, geneticCodeId, }) {
|
|
|
21
22
|
codonTable,
|
|
22
23
|
});
|
|
23
24
|
}
|
|
25
|
+
// The CDS list is sorted by start, so adjacent comparison is the whole job.
|
|
26
|
+
function cdsId(feat) {
|
|
27
|
+
return `${feat.start}-${feat.end}`;
|
|
28
|
+
}
|
|
29
|
+
function dedupe(list) {
|
|
30
|
+
return list.filter((item, pos, ary) => !pos || cdsId(item) !== cdsId(ary[pos - 1]));
|
|
31
|
+
}
|
|
24
32
|
export function revlist(list, seqlen) {
|
|
25
33
|
return list
|
|
26
34
|
.map(sub => ({
|
|
@@ -30,7 +38,7 @@ export function revlist(list, seqlen) {
|
|
|
30
38
|
}))
|
|
31
39
|
.toSorted((a, b) => a.start - b.start);
|
|
32
40
|
}
|
|
33
|
-
export function getProteinSequenceFromFeature({ feature, seq, }) {
|
|
41
|
+
export function getProteinSequenceFromFeature({ feature, seq, assemblyGeneticCodeId, }) {
|
|
34
42
|
const { subfeatures, start, strand } = feature.toJSON();
|
|
35
43
|
const cds = dedupe(subfeatures
|
|
36
44
|
?.toSorted((a, b) => a.start - b.start)
|
|
@@ -39,15 +47,17 @@ 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
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
50
|
+
.filter(subfeature => subfeature.type === 'CDS') ?? []);
|
|
51
|
+
// RefSeq declares transl_table=2 on a mitochondrial CDS, usually on the CDS
|
|
52
|
+
// rather than the transcript. GENCODE and UCSC declare nothing, so without
|
|
53
|
+
// the assembly's code all 13 human mitochondrial proteins read TGA as a stop
|
|
54
|
+
// and ATA as I.
|
|
46
55
|
const cdsSubfeature = feature
|
|
47
56
|
.get('subfeatures')
|
|
48
57
|
?.find((f) => f.get('type')?.toLowerCase() === 'cds');
|
|
49
58
|
const geneticCodeId = parseTranslTable(feature.get('transl_table')) ??
|
|
50
|
-
parseTranslTable(cdsSubfeature?.get('transl_table'))
|
|
59
|
+
parseTranslTable(cdsSubfeature?.get('transl_table')) ??
|
|
60
|
+
assemblyGeneticCodeId;
|
|
51
61
|
return calculateProteinSequence({
|
|
52
62
|
cds: strand === -1 ? revlist(cds, seq.length) : cds,
|
|
53
63
|
sequence: strand === -1 ? revcom(seq) : seq,
|
|
@@ -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,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 {};
|