jbrowse-plugin-msaview 2.8.2 → 2.10.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/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastRIDPanel.d.ts → BlastQuery/BlastAutomaticPanel.d.ts} +2 -3
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.js → BlastQuery/BlastAutomaticPanel.js} +6 -25
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.d.ts → BlastQuery/BlastManualPanel.d.ts} +2 -3
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.js → BlastQuery/BlastManualPanel.js} +7 -4
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.d.ts → BlastQuery/BlastMethodSelector.d.ts} +2 -2
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.js → BlastQuery/BlastMethodSelector.js} +1 -2
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.d.ts → BlastQuery/BlastPanel.d.ts} +3 -12
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.js → BlastQuery/BlastPanel.js} +14 -16
- package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.d.ts +8 -0
- package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.js → BlastQuery/BlastSettingsDialog.js} +13 -9
- package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.js +1 -1
- package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +12 -0
- package/dist/LaunchMsaView/components/BlastQuery/consts.js +17 -0
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +3 -3
- package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +15 -35
- package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.d.ts +15 -0
- package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +67 -0
- package/dist/LaunchMsaView/index.js +19 -1
- package/dist/MsaViewPanel/components/JobLink.d.ts +5 -0
- package/dist/MsaViewPanel/components/JobLink.js +13 -0
- package/dist/MsaViewPanel/components/LoadingBLAST.d.ts +1 -2
- package/dist/MsaViewPanel/components/LoadingBLAST.js +8 -15
- package/dist/MsaViewPanel/components/MsaViewPanel.js +1 -1
- package/dist/MsaViewPanel/doLaunchBlast.js +11 -19
- package/dist/MsaViewPanel/doLaunchOrthologs.js +43 -12
- package/dist/MsaViewPanel/doLaunchOrthologs.test.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchOrthologs.test.js +201 -0
- package/dist/MsaViewPanel/model.d.ts +20 -12
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -33
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +8 -4
- package/dist/utils/blastCache.js +4 -5
- package/dist/utils/ebiBlast.d.ts +52 -0
- package/dist/utils/ebiBlast.js +63 -0
- package/dist/utils/ebiJobDispatcher.d.ts +33 -0
- package/dist/utils/ebiJobDispatcher.js +80 -0
- package/dist/utils/ebiJobDispatcher.test.d.ts +1 -0
- package/dist/utils/ebiJobDispatcher.test.js +46 -0
- package/dist/utils/eutils.d.ts +9 -0
- package/dist/utils/eutils.js +18 -0
- package/dist/utils/fetch.js +24 -1
- package/dist/utils/msa.d.ts +1 -1
- package/dist/utils/msa.js +25 -32
- package/dist/utils/ncbiOrthologs.d.ts +17 -75
- package/dist/utils/ncbiOrthologs.js +67 -63
- package/dist/utils/ncbiOrthologs.test.js +82 -2
- package/dist/utils/ncbiTaxonomy.d.ts +11 -0
- package/dist/utils/ncbiTaxonomy.js +33 -0
- package/dist/utils/types.d.ts +9 -14
- package/dist/utils/useLocalStorage.d.ts +1 -0
- package/dist/utils/useLocalStorage.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx → BlastQuery/BlastAutomaticPanel.tsx} +13 -59
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.tsx → BlastQuery/BlastManualPanel.tsx} +7 -6
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.tsx → BlastQuery/BlastMethodSelector.tsx} +2 -7
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.tsx → BlastQuery/BlastPanel.tsx} +21 -25
- package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.tsx → BlastQuery/BlastSettingsDialog.tsx} +26 -13
- package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.tsx +1 -1
- package/src/LaunchMsaView/components/BlastQuery/consts.ts +22 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +5 -7
- package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +35 -73
- package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +92 -0
- package/src/LaunchMsaView/index.ts +20 -2
- package/src/MsaViewPanel/components/JobLink.tsx +17 -0
- package/src/MsaViewPanel/components/LoadingBLAST.tsx +10 -41
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +1 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +12 -28
- package/src/MsaViewPanel/doLaunchOrthologs.test.ts +247 -0
- package/src/MsaViewPanel/doLaunchOrthologs.ts +56 -13
- package/src/MsaViewPanel/model.ts +10 -7
- package/src/utils/blastCache.ts +8 -9
- package/src/utils/ebiBlast.ts +114 -0
- package/src/utils/ebiJobDispatcher.test.ts +54 -0
- package/src/utils/ebiJobDispatcher.ts +120 -0
- package/src/utils/eutils.ts +19 -0
- package/src/utils/fetch.ts +26 -1
- package/src/utils/msa.ts +26 -47
- package/src/utils/ncbiOrthologs.test.ts +96 -2
- package/src/utils/ncbiOrthologs.ts +83 -71
- package/src/utils/ncbiTaxonomy.ts +37 -0
- package/src/utils/types.ts +8 -13
- package/src/utils/useLocalStorage.ts +1 -1
- package/src/version.ts +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.d.ts +0 -10
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +0 -74
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.d.ts +0 -5
- package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.d.ts +0 -7
- package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.js +0 -4
- package/dist/MsaViewPanel/components/RIDLink.d.ts +0 -6
- package/dist/MsaViewPanel/components/RIDLink.js +0 -12
- package/dist/utils/ncbiBlast.d.ts +0 -30
- package/dist/utils/ncbiBlast.js +0 -84
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.tsx +0 -137
- package/src/LaunchMsaView/components/NCBIBlastQuery/consts.ts +0 -10
- package/src/MsaViewPanel/components/RIDLink.tsx +0 -19
- package/src/utils/ncbiBlast.ts +0 -143
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.js +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.js +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.d.ts +0 -0
- /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.js +0 -0
- /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.tsx +0 -0
- /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.ts +0 -0
- /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.ts +0 -0
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
|
|
3
|
-
declare const
|
|
3
|
+
declare const BlastAutomaticPanel: ({ handleClose, feature, model, children, }: {
|
|
4
4
|
model: AbstractTrackModel;
|
|
5
5
|
feature: Feature;
|
|
6
|
-
baseUrl: string;
|
|
7
6
|
handleClose: () => void;
|
|
8
7
|
children: React.ReactNode;
|
|
9
8
|
}) => React.JSX.Element;
|
|
10
|
-
export default
|
|
9
|
+
export default BlastAutomaticPanel;
|
|
@@ -6,7 +6,7 @@ import { makeStyles } from 'tss-react/mui';
|
|
|
6
6
|
import CachedBlastResults from './CachedBlastResults';
|
|
7
7
|
import MsaAlgorithmSelect from './MsaAlgorithmSelect';
|
|
8
8
|
import { blastLaunchView } from './blastLaunchView';
|
|
9
|
-
import { blastDatabaseOptions,
|
|
9
|
+
import { blastDatabaseOptions, defaultBlastDatabase } from './consts';
|
|
10
10
|
import { useCachedBlastResults } from './useCachedBlastResults';
|
|
11
11
|
import TextField2 from '../../../components/TextField2';
|
|
12
12
|
import { getBlastViewTitle, getGeneIdentifiers, getLinearGenomeView, } from '../../util';
|
|
@@ -18,13 +18,6 @@ const useStyles = makeStyles()({
|
|
|
18
18
|
selectField: {
|
|
19
19
|
width: 150,
|
|
20
20
|
},
|
|
21
|
-
databaseFieldContainer: {
|
|
22
|
-
display: 'flex',
|
|
23
|
-
},
|
|
24
|
-
clusterSeqMessage: {
|
|
25
|
-
marginLeft: 4,
|
|
26
|
-
alignContent: 'center',
|
|
27
|
-
},
|
|
28
21
|
cachedResultsAccordion: {
|
|
29
22
|
marginTop: 20,
|
|
30
23
|
},
|
|
@@ -32,13 +25,12 @@ const useStyles = makeStyles()({
|
|
|
32
25
|
marginTop: 20,
|
|
33
26
|
},
|
|
34
27
|
});
|
|
35
|
-
const
|
|
28
|
+
const BlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, }) {
|
|
36
29
|
const { classes } = useStyles();
|
|
37
30
|
const view = getLinearGenomeView(model);
|
|
38
31
|
const [launchViewError, setLaunchViewError] = useState();
|
|
39
|
-
const [selectedBlastDatabase, setSelectedBlastDatabase] = useState(
|
|
32
|
+
const [selectedBlastDatabase, setSelectedBlastDatabase] = useState(defaultBlastDatabase);
|
|
40
33
|
const [selectedMsaAlgorithm, setSelectedMsaAlgorithm] = useState('clustalo');
|
|
41
|
-
const [selectedBlastProgram, setSelectedBlastProgram] = useState('quick-blastp');
|
|
42
34
|
const geneIds = useMemo(() => getGeneIdentifiers(feature), [feature]);
|
|
43
35
|
const { results: cachedResults, error: cachedResultsError } = useCachedBlastResults(geneIds);
|
|
44
36
|
const transcriptSelection = useTranscriptSelection({ feature, view });
|
|
@@ -48,20 +40,11 @@ const NCBIBlastAutomaticPanel = observer(function ({ handleClose, feature, model
|
|
|
48
40
|
React.createElement(LaunchPanelContent, { error: e },
|
|
49
41
|
children,
|
|
50
42
|
React.createElement(TextField2, { variant: "outlined", label: "BLAST database", className: classes.selectField, select: true, value: selectedBlastDatabase, onChange: event => {
|
|
51
|
-
|
|
52
|
-
setSelectedBlastDatabase(newDb);
|
|
53
|
-
if (newDb === 'nr_cluster_seq') {
|
|
54
|
-
setSelectedBlastProgram('blastp');
|
|
55
|
-
}
|
|
43
|
+
setSelectedBlastDatabase(event.target.value);
|
|
56
44
|
} }, blastDatabaseOptions.map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
|
|
57
45
|
React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: selectedMsaAlgorithm, onChange: setSelectedMsaAlgorithm }),
|
|
58
|
-
React.createElement("div", { className: classes.databaseFieldContainer },
|
|
59
|
-
React.createElement(TextField2, { variant: "outlined", label: "BLAST program", disabled: selectedBlastDatabase === 'nr_cluster_seq', className: classes.selectField, select: true, value: selectedBlastProgram, onChange: event => {
|
|
60
|
-
setSelectedBlastProgram(event.target.value);
|
|
61
|
-
} }, blastPrograms.map(val => (React.createElement(MenuItem, { value: val, key: val }, val)))),
|
|
62
|
-
selectedBlastDatabase === 'nr_cluster_seq' ? (React.createElement(Typography, { variant: "subtitle2", className: classes.clusterSeqMessage }, "Can only use blastp on nr_cluster_seq")) : null),
|
|
63
46
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection }),
|
|
64
|
-
React.createElement(Typography, { className: classes.infoText }, "This panel will automatically submit a query to
|
|
47
|
+
React.createElement(Typography, { className: classes.infoText }, "This panel will automatically submit a blastp query to EBI, which searches UniProtKB. Searches usually finish in under a minute, and swissprot returns curated sequences that align more cleanly than the many near-identical entries a TrEMBL search brings back. After completion, all the hits will be run through a multiple sequence alignment. Searching NCBI's nr needs the manual approach: NCBI no longer lets a browser read responses from Blast.cgi."),
|
|
65
48
|
cachedResults.length > 0 ? (React.createElement(Accordion, { className: classes.cachedResultsAccordion },
|
|
66
49
|
React.createElement(AccordionSummary, { expandIcon: React.createElement(ExpandMoreIcon, null) },
|
|
67
50
|
React.createElement(Typography, null, "Previous BLAST Results")),
|
|
@@ -76,8 +59,6 @@ const NCBIBlastAutomaticPanel = observer(function ({ handleClose, feature, model
|
|
|
76
59
|
view,
|
|
77
60
|
newViewTitle: getBlastViewTitle(feature, selectedTranscript),
|
|
78
61
|
blastParams: {
|
|
79
|
-
baseUrl,
|
|
80
|
-
blastProgram: selectedBlastProgram,
|
|
81
62
|
blastDatabase: selectedBlastDatabase,
|
|
82
63
|
msaAlgorithm: selectedMsaAlgorithm,
|
|
83
64
|
selectedTranscript,
|
|
@@ -93,4 +74,4 @@ const NCBIBlastAutomaticPanel = observer(function ({ handleClose, feature, model
|
|
|
93
74
|
}
|
|
94
75
|
}, onCancel: handleClose })));
|
|
95
76
|
});
|
|
96
|
-
export default
|
|
77
|
+
export default BlastAutomaticPanel;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
|
|
3
|
-
declare const
|
|
3
|
+
declare const BlastManualPanel: ({ handleClose, feature, model, children, }: {
|
|
4
4
|
children: React.ReactNode;
|
|
5
5
|
model: AbstractTrackModel;
|
|
6
6
|
feature: Feature;
|
|
7
|
-
baseUrl: string;
|
|
8
7
|
handleClose: () => void;
|
|
9
8
|
}) => React.JSX.Element;
|
|
10
|
-
export default
|
|
9
|
+
export default BlastManualPanel;
|
|
@@ -3,6 +3,7 @@ import { shorten2 } from '@jbrowse/core/util';
|
|
|
3
3
|
import { Button, DialogActions, Typography } from '@mui/material';
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
import { makeStyles } from 'tss-react/mui';
|
|
6
|
+
import { BASE_BLAST_URL } from './consts';
|
|
6
7
|
import ExternalLink from '../../../components/ExternalLink';
|
|
7
8
|
import { cleanProteinSequence, getLinearGenomeView } from '../../util';
|
|
8
9
|
import LaunchPanelContent from '../LaunchPanelContent';
|
|
@@ -18,14 +19,16 @@ const useStyles = makeStyles()({
|
|
|
18
19
|
marginTop: 20,
|
|
19
20
|
},
|
|
20
21
|
});
|
|
21
|
-
const
|
|
22
|
+
const BlastManualPanel = observer(function ({ handleClose, feature, model, children, }) {
|
|
22
23
|
const { classes } = useStyles();
|
|
23
24
|
const view = getLinearGenomeView(model);
|
|
24
25
|
const transcriptSelection = useTranscriptSelection({ feature, view });
|
|
25
26
|
const { proteinSequence, error } = transcriptSelection;
|
|
26
27
|
const s2 = cleanProteinSequence(proteinSequence);
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
// a link the user follows to NCBI's own site, not something we fetch — which
|
|
29
|
+
// is exactly why this route still works when the automatic one cannot
|
|
30
|
+
const link = `${BASE_BLAST_URL}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${s2}`;
|
|
31
|
+
const link2 = `${BASE_BLAST_URL}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${shorten2(s2, 10)}`;
|
|
29
32
|
return (React.createElement(React.Fragment, null,
|
|
30
33
|
React.createElement(LaunchPanelContent, { error: error },
|
|
31
34
|
children,
|
|
@@ -39,4 +42,4 @@ const NCBIBlastManualPanel = observer(function ({ handleClose, feature, model, c
|
|
|
39
42
|
handleClose();
|
|
40
43
|
} }, "Close"))));
|
|
41
44
|
});
|
|
42
|
-
export default
|
|
45
|
+
export default BlastManualPanel;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { BlastLookupMethod } from './
|
|
3
|
-
export default function
|
|
2
|
+
import type { BlastLookupMethod } from './BlastPanel';
|
|
3
|
+
export default function BlastMethodSelector({ lookupMethod, setLookupMethod, }: {
|
|
4
4
|
lookupMethod: BlastLookupMethod;
|
|
5
5
|
setLookupMethod: (method: BlastLookupMethod) => void;
|
|
6
6
|
}): React.JSX.Element;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { FormControl, FormControlLabel, Radio, RadioGroup } from '@mui/material';
|
|
3
|
-
export default function
|
|
3
|
+
export default function BlastMethodSelector({ lookupMethod, setLookupMethod, }) {
|
|
4
4
|
return (React.createElement(FormControl, { component: "fieldset" },
|
|
5
5
|
React.createElement(RadioGroup, { row: true, value: lookupMethod, onChange: event => {
|
|
6
6
|
setLookupMethod(event.target.value);
|
|
7
7
|
} },
|
|
8
8
|
React.createElement(FormControlLabel, { value: "automatic", control: React.createElement(Radio, null), label: "Automatic" }),
|
|
9
|
-
React.createElement(FormControlLabel, { value: "rid", control: React.createElement(Radio, null), label: "Load from RID" }),
|
|
10
9
|
React.createElement(FormControlLabel, { value: "manual", control: React.createElement(Radio, null), label: "Manual" }))));
|
|
11
10
|
}
|
|
@@ -1,30 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
|
|
3
3
|
declare const panelMap: {
|
|
4
|
-
readonly automatic: ({ handleClose, feature, model, children,
|
|
4
|
+
readonly automatic: ({ handleClose, feature, model, children, }: {
|
|
5
5
|
model: AbstractTrackModel;
|
|
6
6
|
feature: Feature;
|
|
7
|
-
baseUrl: string;
|
|
8
7
|
handleClose: () => void;
|
|
9
8
|
children: React.ReactNode;
|
|
10
9
|
}) => React.JSX.Element;
|
|
11
|
-
readonly
|
|
12
|
-
model: AbstractTrackModel;
|
|
13
|
-
feature: Feature;
|
|
14
|
-
baseUrl: string;
|
|
15
|
-
handleClose: () => void;
|
|
16
|
-
children: React.ReactNode;
|
|
17
|
-
}) => React.JSX.Element;
|
|
18
|
-
readonly manual: ({ handleClose, feature, model, children, baseUrl, }: {
|
|
10
|
+
readonly manual: ({ handleClose, feature, model, children, }: {
|
|
19
11
|
children: React.ReactNode;
|
|
20
12
|
model: AbstractTrackModel;
|
|
21
13
|
feature: Feature;
|
|
22
|
-
baseUrl: string;
|
|
23
14
|
handleClose: () => void;
|
|
24
15
|
}) => React.JSX.Element;
|
|
25
16
|
};
|
|
26
17
|
export type BlastLookupMethod = keyof typeof panelMap;
|
|
27
|
-
export default function
|
|
18
|
+
export default function BlastPanel({ handleClose, model, feature, }: {
|
|
28
19
|
handleClose: () => void;
|
|
29
20
|
model: AbstractTrackModel;
|
|
30
21
|
feature: Feature;
|
package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.js → BlastQuery/BlastPanel.js}
RENAMED
|
@@ -2,12 +2,11 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import SettingsIcon from '@mui/icons-material/Settings';
|
|
3
3
|
import { IconButton } from '@mui/material';
|
|
4
4
|
import { makeStyles } from 'tss-react/mui';
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import { BASE_BLAST_URL } from './consts';
|
|
5
|
+
import BlastAutomaticPanel from './BlastAutomaticPanel';
|
|
6
|
+
import BlastManualPanel from './BlastManualPanel';
|
|
7
|
+
import BlastMethodSelector from './BlastMethodSelector';
|
|
8
|
+
import BlastSettingsDialog from './BlastSettingsDialog';
|
|
9
|
+
import { DEFAULT_EBI_EMAIL, EBI_EMAIL_STORAGE_KEY, } from '../../../utils/ebiJobDispatcher';
|
|
11
10
|
import { useLocalStorage } from '../../../utils/useLocalStorage';
|
|
12
11
|
const useStyles = makeStyles()({
|
|
13
12
|
settingsButton: {
|
|
@@ -15,13 +14,12 @@ const useStyles = makeStyles()({
|
|
|
15
14
|
},
|
|
16
15
|
});
|
|
17
16
|
const panelMap = {
|
|
18
|
-
automatic:
|
|
19
|
-
|
|
20
|
-
manual: NCBIBlastManualPanel,
|
|
17
|
+
automatic: BlastAutomaticPanel,
|
|
18
|
+
manual: BlastManualPanel,
|
|
21
19
|
};
|
|
22
|
-
export default function
|
|
20
|
+
export default function BlastPanel({ handleClose, model, feature, }) {
|
|
23
21
|
const [lookupMethod, setLookupMethod] = useState('automatic');
|
|
24
|
-
const [
|
|
22
|
+
const [ebiEmail, setEbiEmail] = useLocalStorage(EBI_EMAIL_STORAGE_KEY, DEFAULT_EBI_EMAIL);
|
|
25
23
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
|
26
24
|
const { classes } = useStyles();
|
|
27
25
|
const Panel = panelMap[lookupMethod];
|
|
@@ -30,11 +28,11 @@ export default function NCBIBlastPanel({ handleClose, model, feature, }) {
|
|
|
30
28
|
setSettingsOpen(true);
|
|
31
29
|
} },
|
|
32
30
|
React.createElement(SettingsIcon, null)),
|
|
33
|
-
React.createElement(Panel, { model: model, feature: feature, handleClose: handleClose
|
|
34
|
-
React.createElement(
|
|
35
|
-
settingsOpen ? (React.createElement(
|
|
36
|
-
if (
|
|
37
|
-
|
|
31
|
+
React.createElement(Panel, { model: model, feature: feature, handleClose: handleClose },
|
|
32
|
+
React.createElement(BlastMethodSelector, { lookupMethod: lookupMethod, setLookupMethod: setLookupMethod })),
|
|
33
|
+
settingsOpen ? (React.createElement(BlastSettingsDialog, { ebiEmail: ebiEmail, handleClose: settings => {
|
|
34
|
+
if (settings) {
|
|
35
|
+
setEbiEmail(settings.ebiEmail);
|
|
38
36
|
}
|
|
39
37
|
setSettingsOpen(false);
|
|
40
38
|
} })) : null));
|
|
@@ -1,32 +1,36 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, } from '@mui/material';
|
|
2
|
+
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography, } from '@mui/material';
|
|
3
3
|
import { makeStyles } from 'tss-react/mui';
|
|
4
|
-
import { BASE_BLAST_URL } from './consts';
|
|
5
4
|
import TextField2 from '../../../components/TextField2';
|
|
5
|
+
import { DEFAULT_EBI_EMAIL } from '../../../utils/ebiJobDispatcher';
|
|
6
6
|
const useStyles = makeStyles()({
|
|
7
|
-
|
|
7
|
+
field: {
|
|
8
8
|
minWidth: 300,
|
|
9
9
|
},
|
|
10
|
+
help: {
|
|
11
|
+
marginBottom: 8,
|
|
12
|
+
},
|
|
10
13
|
});
|
|
11
|
-
export default function
|
|
14
|
+
export default function BlastSettingsDialog({ handleClose, ebiEmail, }) {
|
|
12
15
|
const { classes } = useStyles();
|
|
13
|
-
const [
|
|
16
|
+
const [tempEbiEmail, setTempEbiEmail] = useState(ebiEmail);
|
|
14
17
|
return (React.createElement(Dialog, { open: true, maxWidth: "lg", onClose: () => {
|
|
15
18
|
handleClose();
|
|
16
19
|
} },
|
|
17
20
|
React.createElement(DialogTitle, null, "BLAST Settings"),
|
|
18
21
|
React.createElement(DialogContent, null,
|
|
19
|
-
React.createElement(
|
|
20
|
-
|
|
22
|
+
React.createElement(Typography, { variant: "subtitle2", className: classes.help }, "Searches run at EBI, which asks for a contact address on every job so they can reach whoever is generating the load. If your site sends real volume, use your own."),
|
|
23
|
+
React.createElement(TextField2, { autoFocus: true, margin: "dense", label: "EBI contact email", fullWidth: true, variant: "outlined", value: tempEbiEmail, className: classes.field, onChange: e => {
|
|
24
|
+
setTempEbiEmail(e.target.value);
|
|
21
25
|
} }),
|
|
22
26
|
React.createElement(Button, { variant: "contained", onClick: () => {
|
|
23
|
-
|
|
27
|
+
setTempEbiEmail(DEFAULT_EBI_EMAIL);
|
|
24
28
|
} }, "Reset")),
|
|
25
29
|
React.createElement(DialogActions, null,
|
|
26
30
|
React.createElement(Button, { variant: "contained", color: "secondary", onClick: () => {
|
|
27
31
|
handleClose();
|
|
28
32
|
} }, "Cancel"),
|
|
29
33
|
React.createElement(Button, { color: "primary", variant: "contained", onClick: () => {
|
|
30
|
-
handleClose(
|
|
34
|
+
handleClose({ ebiEmail: tempEbiEmail });
|
|
31
35
|
} }, "Save"))));
|
|
32
36
|
}
|
|
@@ -83,6 +83,6 @@ const CachedBlastResults = observer(function ({ model, handleClose, feature, })
|
|
|
83
83
|
React.createElement(ListItemButton, { onClick: () => {
|
|
84
84
|
handleUseCached(result);
|
|
85
85
|
} },
|
|
86
|
-
React.createElement(ListItemText, { primary: `${getResultDisplayName(result)} - ${result.blastDatabase}
|
|
86
|
+
React.createElement(ListItemText, { primary: `${getResultDisplayName(result)} - ${result.blastDatabase}${result.blastProgram ? `/${result.blastProgram}` : ''} (${result.msaAlgorithm})`, secondary: `${new Date(result.timestamp).toLocaleString()} - Seq: ${result.proteinSequence.slice(0, 30)}...` }))))))));
|
|
87
87
|
});
|
|
88
88
|
export default CachedBlastResults;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only used to build the link-out on the manual panel, which sends the user to
|
|
3
|
+
* NCBI's own site to run BLAST there. Nothing fetches this url: NCBI stopped
|
|
4
|
+
* sending Access-Control-Allow-Origin to third-party origins, so a browser
|
|
5
|
+
* cannot read a response from it at all. See docs/blast.md.
|
|
6
|
+
*/
|
|
7
|
+
export declare const BASE_BLAST_URL = "https://blast.ncbi.nlm.nih.gov/Blast.cgi";
|
|
8
|
+
export declare const msaAlgorithms: readonly ["clustalo", "muscle", "kalign", "mafft"];
|
|
9
|
+
export type MsaAlgorithm = (typeof msaAlgorithms)[number];
|
|
10
|
+
export declare const blastDatabaseOptions: readonly ["uniprotkb_swissprot", "uniprotkb", "uniprotkb_reference_proteomes", "uniprotkb_trembl"];
|
|
11
|
+
export type BlastDatabase = (typeof blastDatabaseOptions)[number];
|
|
12
|
+
export declare const defaultBlastDatabase: BlastDatabase;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Only used to build the link-out on the manual panel, which sends the user to
|
|
3
|
+
* NCBI's own site to run BLAST there. Nothing fetches this url: NCBI stopped
|
|
4
|
+
* sending Access-Control-Allow-Origin to third-party origins, so a browser
|
|
5
|
+
* cannot read a response from it at all. See docs/blast.md.
|
|
6
|
+
*/
|
|
7
|
+
export const BASE_BLAST_URL = 'https://blast.ncbi.nlm.nih.gov/Blast.cgi';
|
|
8
|
+
export const msaAlgorithms = ['clustalo', 'muscle', 'kalign', 'mafft'];
|
|
9
|
+
export const blastDatabaseOptions = [
|
|
10
|
+
'uniprotkb_swissprot',
|
|
11
|
+
'uniprotkb',
|
|
12
|
+
'uniprotkb_reference_proteomes',
|
|
13
|
+
'uniprotkb_trembl',
|
|
14
|
+
];
|
|
15
|
+
// curated, so it returns roughly one good sequence per species rather than the
|
|
16
|
+
// many near-identical TrEMBL entries an alignment reads poorly
|
|
17
|
+
export const defaultBlastDatabase = 'uniprotkb_swissprot';
|
|
@@ -2,8 +2,8 @@ import React, { useState } from 'react';
|
|
|
2
2
|
import { Dialog } from '@jbrowse/core/ui';
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
4
|
import { Tab, Tabs } from '@mui/material';
|
|
5
|
+
import BlastPanel from './BlastQuery/BlastPanel';
|
|
5
6
|
import ManualMSALoader from './ManualMSALoader/ManualMSALoader';
|
|
6
|
-
import NCBIBlastPanel from './NCBIBlastQuery/NCBIBlastPanel';
|
|
7
7
|
import OrthologPanel from './OrthologQuery/OrthologPanel';
|
|
8
8
|
import PreLoadedMSA from './PreLoadedMSA/PreLoadedMSADataPanel';
|
|
9
9
|
import { readMsaDatasets } from './PreLoadedMSA/types';
|
|
@@ -20,13 +20,13 @@ export default function LaunchMsaViewDialog({ handleClose, feature, model, }) {
|
|
|
20
20
|
setValue(newValue);
|
|
21
21
|
} },
|
|
22
22
|
React.createElement(Tab, { label: "Orthologs (fast)", value: "orthologs" }),
|
|
23
|
-
React.createElement(Tab, { label: "
|
|
23
|
+
React.createElement(Tab, { label: "BLAST query", value: "ncbi_blast" }),
|
|
24
24
|
hasPreloadedDatasets ? (React.createElement(Tab, { label: "Pre-loaded MSA datasets", value: "preloaded_msa" })) : null,
|
|
25
25
|
React.createElement(Tab, { label: "Manual upload", value: "manual_msa" })),
|
|
26
26
|
React.createElement(TabPanel, { value: value, index: "orthologs" },
|
|
27
27
|
React.createElement(OrthologPanel, { handleClose: handleClose, feature: feature, model: model })),
|
|
28
28
|
React.createElement(TabPanel, { value: value, index: "ncbi_blast" },
|
|
29
|
-
React.createElement(
|
|
29
|
+
React.createElement(BlastPanel, { handleClose: handleClose, feature: feature, model: model })),
|
|
30
30
|
hasPreloadedDatasets ? (React.createElement(TabPanel, { value: value, index: "preloaded_msa" },
|
|
31
31
|
React.createElement(PreLoadedMSA, { model: model, feature: feature, handleClose: handleClose }))) : null,
|
|
32
32
|
React.createElement(TabPanel, { value: value, index: "manual_msa" },
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { Typography } from '@mui/material';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
4
|
import { makeStyles } from 'tss-react/mui';
|
|
5
|
+
import QuerySpeciesSelect from './QuerySpeciesSelect';
|
|
5
6
|
import { orthologLaunchView } from './orthologLaunchView';
|
|
6
7
|
import TextField2 from '../../../components/TextField2';
|
|
7
|
-
import {
|
|
8
|
+
import { defaultMaxSpecies } from '../../../utils/ncbiOrthologs';
|
|
8
9
|
import { getGeneDisplayName, getGeneIdentifiers, getLinearGenomeView, getTranscriptDisplayName, } from '../../util';
|
|
10
|
+
import MsaAlgorithmSelect from '../BlastQuery/MsaAlgorithmSelect';
|
|
9
11
|
import LaunchPanelContent from '../LaunchPanelContent';
|
|
10
|
-
import MsaAlgorithmSelect from '../NCBIBlastQuery/MsaAlgorithmSelect';
|
|
11
12
|
import SubmitCancelActions from '../SubmitCancelActions';
|
|
12
13
|
import TranscriptSelector from '../TranscriptSelector';
|
|
13
14
|
import { useTranscriptSelection } from '../useTranscriptSelection';
|
|
@@ -15,23 +16,6 @@ const useStyles = makeStyles()({
|
|
|
15
16
|
selectField: {
|
|
16
17
|
width: 180,
|
|
17
18
|
},
|
|
18
|
-
// A GRID, not a wrapping flex row of fixed-width items. The old form was three
|
|
19
|
-
// 160px columns inside a 560px box, which is five rows for thirteen species and
|
|
20
|
-
// eight for twenty-three -- and the checkbox list is the tallest thing in the
|
|
21
|
-
// dialog, so those rows are the dialog's height. Five auto-fitted columns is
|
|
22
|
-
// five rows for twenty-three, i.e. more species in less space, and it reflows
|
|
23
|
-
// rather than being pinned to a width the dialog may not have.
|
|
24
|
-
speciesBox: {
|
|
25
|
-
display: 'grid',
|
|
26
|
-
gridTemplateColumns: 'repeat(auto-fit, minmax(130px, 1fr))',
|
|
27
|
-
maxWidth: 700,
|
|
28
|
-
marginTop: 4,
|
|
29
|
-
},
|
|
30
|
-
// The label carries the row height; the default control padding is what makes
|
|
31
|
-
// 23 rows of it tall.
|
|
32
|
-
species: {
|
|
33
|
-
marginRight: 0,
|
|
34
|
-
},
|
|
35
19
|
});
|
|
36
20
|
const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
37
21
|
const { classes } = useStyles();
|
|
@@ -39,28 +23,24 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
39
23
|
const [launchViewError, setLaunchViewError] = useState();
|
|
40
24
|
const [taxId, setTaxId] = useState(9606);
|
|
41
25
|
const [msaAlgorithm, setMsaAlgorithm] = useState('clustalo');
|
|
42
|
-
const [
|
|
26
|
+
const [maxSpecies, setMaxSpecies] = useState(String(defaultMaxSpecies));
|
|
43
27
|
const geneCandidates = useMemo(() => getGeneIdentifiers(feature), [feature]);
|
|
44
28
|
const transcriptSelection = useTranscriptSelection({ feature, view });
|
|
45
29
|
const { selectedTranscript, proteinSequence } = transcriptSelection;
|
|
46
30
|
const e = transcriptSelection.error ?? launchViewError;
|
|
47
|
-
const
|
|
31
|
+
const rowCount = Number(maxSpecies);
|
|
32
|
+
const rowCountValid = Number.isInteger(rowCount) && rowCount >= 2;
|
|
48
33
|
return (React.createElement(React.Fragment, null,
|
|
49
34
|
React.createElement(LaunchPanelContent, { error: e },
|
|
50
|
-
React.createElement(Typography, { variant: "body2" }, "NCBI's precomputed orthologs, one gene per species,
|
|
35
|
+
React.createElement(Typography, { variant: "body2" }, "NCBI's precomputed orthologs, one gene per species, looked up rather than searched for. No BLAST job to queue."),
|
|
51
36
|
React.createElement("div", null,
|
|
52
|
-
React.createElement(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
React.createElement("div", { className: classes.speciesBox }, COMMON_SPECIES.map(s => (React.createElement(FormControlLabel, { className: classes.species, key: s.taxId, control: React.createElement(Checkbox, { checked: !excluded.includes(s.taxId), onChange: event => {
|
|
58
|
-
setExcluded(event.target.checked
|
|
59
|
-
? excluded.filter(t => t !== s.taxId)
|
|
60
|
-
: [...excluded, s.taxId]);
|
|
61
|
-
} }), label: s.label })))),
|
|
37
|
+
React.createElement(QuerySpeciesSelect, { className: classes.selectField, value: taxId, onChange: setTaxId }),
|
|
38
|
+
React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: msaAlgorithm, onChange: setMsaAlgorithm }),
|
|
39
|
+
React.createElement(TextField2, { variant: "outlined", label: "Rows to align", className: classes.selectField, type: "number", value: maxSpecies, onChange: event => {
|
|
40
|
+
setMaxSpecies(event.target.value);
|
|
41
|
+
}, error: !rowCountValid, helperText: "the closest N species NCBI has" })),
|
|
62
42
|
React.createElement(TranscriptSelector, { feature: feature, ...transcriptSelection })),
|
|
63
|
-
React.createElement(SubmitCancelActions, { submitDisabled: !proteinSequence ||
|
|
43
|
+
React.createElement(SubmitCancelActions, { submitDisabled: !proteinSequence || !rowCountValid, onSubmit: () => {
|
|
64
44
|
try {
|
|
65
45
|
if (selectedTranscript) {
|
|
66
46
|
setLaunchViewError(undefined);
|
|
@@ -70,7 +50,7 @@ const OrthologPanel = observer(function ({ handleClose, feature, model, }) {
|
|
|
70
50
|
newViewTitle: `Orthologs - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
|
|
71
51
|
orthologParams: {
|
|
72
52
|
taxId,
|
|
73
|
-
|
|
53
|
+
maxSpecies: rowCount,
|
|
74
54
|
geneCandidates,
|
|
75
55
|
msaAlgorithm,
|
|
76
56
|
selectedTranscript,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The species the query gene came from, as free text resolved against NCBI's
|
|
4
|
+
* taxonomy rather than picked from a fixed list.
|
|
5
|
+
*
|
|
6
|
+
* The resolved taxon is shown back as helper text, which is the whole point of
|
|
7
|
+
* resolving on a debounce instead of on submit: a typo resolves to some other
|
|
8
|
+
* organism rather than to nothing, and the only place that surfaces is the gene
|
|
9
|
+
* lookup, as "could not resolve NLRP1 in taxon 9986".
|
|
10
|
+
*/
|
|
11
|
+
export default function QuerySpeciesSelect({ value, onChange, className, }: {
|
|
12
|
+
value: number;
|
|
13
|
+
onChange: (taxId: number) => void;
|
|
14
|
+
className?: string;
|
|
15
|
+
}): React.JSX.Element;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import TextField2 from '../../../components/TextField2';
|
|
3
|
+
import { resolveTaxId } from '../../../utils/ncbiTaxonomy';
|
|
4
|
+
import { fetchTaxonomyInfo } from '../../../utils/taxonomyNames';
|
|
5
|
+
/**
|
|
6
|
+
* The species the query gene came from, as free text resolved against NCBI's
|
|
7
|
+
* taxonomy rather than picked from a fixed list.
|
|
8
|
+
*
|
|
9
|
+
* The resolved taxon is shown back as helper text, which is the whole point of
|
|
10
|
+
* resolving on a debounce instead of on submit: a typo resolves to some other
|
|
11
|
+
* organism rather than to nothing, and the only place that surfaces is the gene
|
|
12
|
+
* lookup, as "could not resolve NLRP1 in taxon 9986".
|
|
13
|
+
*/
|
|
14
|
+
export default function QuerySpeciesSelect({ value, onChange, className, }) {
|
|
15
|
+
const [text, setText] = useState('human');
|
|
16
|
+
const [resolved, setResolved] = useState();
|
|
17
|
+
const [error, setError] = useState();
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
// read through a call rather than as a property: the cleanup writes it from
|
|
20
|
+
// another turn of the loop, and a bare `run.live` narrows to true after the
|
|
21
|
+
// first check, which reads to the compiler as a redundant second one
|
|
22
|
+
const run = { live: true };
|
|
23
|
+
const cancelled = () => !run.live;
|
|
24
|
+
async function lookup() {
|
|
25
|
+
try {
|
|
26
|
+
setError(undefined);
|
|
27
|
+
const taxId = await resolveTaxId(text);
|
|
28
|
+
if (cancelled()) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (!taxId) {
|
|
32
|
+
setResolved(undefined);
|
|
33
|
+
setError(new Error(`No NCBI taxon matches "${text}"`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const info = (await fetchTaxonomyInfo([taxId])).get(taxId);
|
|
37
|
+
if (cancelled()) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setResolved([info?.sciname, info?.commonName && `(${info.commonName})`]
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.join(' ') || `taxon ${taxId}`);
|
|
43
|
+
onChange(taxId);
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
if (!cancelled()) {
|
|
47
|
+
setError(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
void lookup();
|
|
53
|
+
}, 400);
|
|
54
|
+
return () => {
|
|
55
|
+
run.live = false;
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
};
|
|
58
|
+
// onChange is a setState updater from the parent and stable in practice;
|
|
59
|
+
// including it would re-run the lookup on every parent render
|
|
60
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
61
|
+
}, [text]);
|
|
62
|
+
return (React.createElement(TextField2, { variant: "outlined", label: "Query species", className: className, value: text, onChange: event => {
|
|
63
|
+
setText(event.target.value);
|
|
64
|
+
}, error: !!error, helperText: error
|
|
65
|
+
? `${error}`
|
|
66
|
+
: (resolved ?? `the species this gene is from (taxon ${value})`) }));
|
|
67
|
+
}
|
|
@@ -4,6 +4,24 @@ import LaunchMsaViewDialog from './components/LaunchMsaViewDialog';
|
|
|
4
4
|
function isDisplay(elt) {
|
|
5
5
|
return elt.name === 'LinearBasicDisplay';
|
|
6
6
|
}
|
|
7
|
+
// Read off the clicked item rather than off the display.
|
|
8
|
+
//
|
|
9
|
+
// LinearBasicDisplay used to publish an `isGeneLike` getter and this gated on
|
|
10
|
+
// it. jbrowse-components 684142b3 (2026-08-16) inlined that getter into its own
|
|
11
|
+
// `contextMenuItems`, and every host built after it returns `undefined` here --
|
|
12
|
+
// so the gate was never satisfied, `onClick` stayed undefined, and the item
|
|
13
|
+
// silently left the right-click menu on every gene track. Nothing failed loudly:
|
|
14
|
+
// the display still had contextMenuInfo and fetchFullFeature, and the menu still
|
|
15
|
+
// opened with its own items in it.
|
|
16
|
+
//
|
|
17
|
+
// A predicate over the type we were already given cannot go the same way, and it
|
|
18
|
+
// costs one comparison. Deliberately the same loose case-insensitive test the
|
|
19
|
+
// host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
|
|
20
|
+
// 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
|
|
21
|
+
function isGeneLikeType(type) {
|
|
22
|
+
const t = (type ?? '').toLowerCase();
|
|
23
|
+
return t.includes('gene') || t.includes('rna') || t.includes('transcript');
|
|
24
|
+
}
|
|
7
25
|
const GENE_LIKE_TYPES = new Set(['gene', 'mRNA', 'transcript']);
|
|
8
26
|
function extendStateModel(stateModel) {
|
|
9
27
|
return stateModel.views((self) => {
|
|
@@ -21,7 +39,7 @@ function extendStateModel(stateModel) {
|
|
|
21
39
|
const info = self.contextMenuInfo;
|
|
22
40
|
const fetchFullFeature = self.fetchFullFeature;
|
|
23
41
|
const legacyFeature = self.contextMenuFeature;
|
|
24
|
-
const onClick = info && fetchFullFeature &&
|
|
42
|
+
const onClick = info && fetchFullFeature && isGeneLikeType(info.item.type)
|
|
25
43
|
? () => {
|
|
26
44
|
fetchFullFeature(info.item.featureId, info.displayedRegionIndex)
|
|
27
45
|
.then(feature => {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Typography } from '@mui/material';
|
|
3
|
+
import ExternalLink from '../../components/ExternalLink';
|
|
4
|
+
import { ebiBlastResultUrl } from '../../utils/ebiBlast';
|
|
5
|
+
function JobLink({ jobId }) {
|
|
6
|
+
return (React.createElement(Typography, null,
|
|
7
|
+
"Job ",
|
|
8
|
+
jobId,
|
|
9
|
+
" (",
|
|
10
|
+
React.createElement(ExternalLink, { href: ebiBlastResultUrl(jobId) }, "see status"),
|
|
11
|
+
")"));
|
|
12
|
+
}
|
|
13
|
+
export default JobLink;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { JBrowsePluginMsaViewModel } from '../model';
|
|
3
|
-
declare const LoadingBLAST: ({ model,
|
|
3
|
+
declare const LoadingBLAST: ({ model, }: {
|
|
4
4
|
model: JBrowsePluginMsaViewModel;
|
|
5
|
-
baseUrl: string;
|
|
6
5
|
}) => React.JSX.Element;
|
|
7
6
|
export default LoadingBLAST;
|