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
package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.tsx → BlastQuery/BlastPanel.tsx}
RENAMED
|
@@ -4,12 +4,14 @@ import SettingsIcon from '@mui/icons-material/Settings'
|
|
|
4
4
|
import { IconButton } from '@mui/material'
|
|
5
5
|
import { makeStyles } from 'tss-react/mui'
|
|
6
6
|
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
7
|
+
import BlastAutomaticPanel from './BlastAutomaticPanel'
|
|
8
|
+
import BlastManualPanel from './BlastManualPanel'
|
|
9
|
+
import BlastMethodSelector from './BlastMethodSelector'
|
|
10
|
+
import BlastSettingsDialog from './BlastSettingsDialog'
|
|
11
|
+
import {
|
|
12
|
+
DEFAULT_EBI_EMAIL,
|
|
13
|
+
EBI_EMAIL_STORAGE_KEY,
|
|
14
|
+
} from '../../../utils/ebiJobDispatcher'
|
|
13
15
|
import { useLocalStorage } from '../../../utils/useLocalStorage'
|
|
14
16
|
|
|
15
17
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util'
|
|
@@ -21,14 +23,13 @@ const useStyles = makeStyles()({
|
|
|
21
23
|
})
|
|
22
24
|
|
|
23
25
|
const panelMap = {
|
|
24
|
-
automatic:
|
|
25
|
-
|
|
26
|
-
manual: NCBIBlastManualPanel,
|
|
26
|
+
automatic: BlastAutomaticPanel,
|
|
27
|
+
manual: BlastManualPanel,
|
|
27
28
|
} as const
|
|
28
29
|
|
|
29
30
|
export type BlastLookupMethod = keyof typeof panelMap
|
|
30
31
|
|
|
31
|
-
export default function
|
|
32
|
+
export default function BlastPanel({
|
|
32
33
|
handleClose,
|
|
33
34
|
model,
|
|
34
35
|
feature,
|
|
@@ -39,9 +40,9 @@ export default function NCBIBlastPanel({
|
|
|
39
40
|
}) {
|
|
40
41
|
const [lookupMethod, setLookupMethod] =
|
|
41
42
|
useState<BlastLookupMethod>('automatic')
|
|
42
|
-
const [
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
const [ebiEmail, setEbiEmail] = useLocalStorage(
|
|
44
|
+
EBI_EMAIL_STORAGE_KEY,
|
|
45
|
+
DEFAULT_EBI_EMAIL,
|
|
45
46
|
)
|
|
46
47
|
const [settingsOpen, setSettingsOpen] = useState(false)
|
|
47
48
|
const { classes } = useStyles()
|
|
@@ -60,24 +61,19 @@ export default function NCBIBlastPanel({
|
|
|
60
61
|
<SettingsIcon />
|
|
61
62
|
</IconButton>
|
|
62
63
|
|
|
63
|
-
<Panel
|
|
64
|
-
|
|
65
|
-
feature={feature}
|
|
66
|
-
handleClose={handleClose}
|
|
67
|
-
baseUrl={baseUrl}
|
|
68
|
-
>
|
|
69
|
-
<NCBIBlastMethodSelector
|
|
64
|
+
<Panel model={model} feature={feature} handleClose={handleClose}>
|
|
65
|
+
<BlastMethodSelector
|
|
70
66
|
lookupMethod={lookupMethod}
|
|
71
67
|
setLookupMethod={setLookupMethod}
|
|
72
68
|
/>
|
|
73
69
|
</Panel>
|
|
74
70
|
|
|
75
71
|
{settingsOpen ? (
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
handleClose={
|
|
79
|
-
if (
|
|
80
|
-
|
|
72
|
+
<BlastSettingsDialog
|
|
73
|
+
ebiEmail={ebiEmail}
|
|
74
|
+
handleClose={settings => {
|
|
75
|
+
if (settings) {
|
|
76
|
+
setEbiEmail(settings.ebiEmail)
|
|
81
77
|
}
|
|
82
78
|
setSettingsOpen(false)
|
|
83
79
|
}}
|
|
@@ -6,27 +6,35 @@ import {
|
|
|
6
6
|
DialogActions,
|
|
7
7
|
DialogContent,
|
|
8
8
|
DialogTitle,
|
|
9
|
+
Typography,
|
|
9
10
|
} from '@mui/material'
|
|
10
11
|
import { makeStyles } from 'tss-react/mui'
|
|
11
12
|
|
|
12
|
-
import { BASE_BLAST_URL } from './consts'
|
|
13
13
|
import TextField2 from '../../../components/TextField2'
|
|
14
|
+
import { DEFAULT_EBI_EMAIL } from '../../../utils/ebiJobDispatcher'
|
|
14
15
|
|
|
15
16
|
const useStyles = makeStyles()({
|
|
16
|
-
|
|
17
|
+
field: {
|
|
17
18
|
minWidth: 300,
|
|
18
19
|
},
|
|
20
|
+
help: {
|
|
21
|
+
marginBottom: 8,
|
|
22
|
+
},
|
|
19
23
|
})
|
|
20
24
|
|
|
21
|
-
export
|
|
25
|
+
export interface BlastSettings {
|
|
26
|
+
ebiEmail: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default function BlastSettingsDialog({
|
|
22
30
|
handleClose,
|
|
23
|
-
|
|
31
|
+
ebiEmail,
|
|
24
32
|
}: {
|
|
25
|
-
handleClose: (arg?:
|
|
26
|
-
|
|
33
|
+
handleClose: (arg?: BlastSettings) => void
|
|
34
|
+
ebiEmail: string
|
|
27
35
|
}) {
|
|
28
36
|
const { classes } = useStyles()
|
|
29
|
-
const [
|
|
37
|
+
const [tempEbiEmail, setTempEbiEmail] = useState(ebiEmail)
|
|
30
38
|
return (
|
|
31
39
|
<Dialog
|
|
32
40
|
open
|
|
@@ -37,22 +45,27 @@ export default function NCBISettingsDialog({
|
|
|
37
45
|
>
|
|
38
46
|
<DialogTitle>BLAST Settings</DialogTitle>
|
|
39
47
|
<DialogContent>
|
|
48
|
+
<Typography variant="subtitle2" className={classes.help}>
|
|
49
|
+
Searches run at EBI, which asks for a contact address on every job so
|
|
50
|
+
they can reach whoever is generating the load. If your site sends real
|
|
51
|
+
volume, use your own.
|
|
52
|
+
</Typography>
|
|
40
53
|
<TextField2
|
|
41
54
|
autoFocus
|
|
42
55
|
margin="dense"
|
|
43
|
-
label="
|
|
56
|
+
label="EBI contact email"
|
|
44
57
|
fullWidth
|
|
45
58
|
variant="outlined"
|
|
46
|
-
value={
|
|
47
|
-
className={classes.
|
|
59
|
+
value={tempEbiEmail}
|
|
60
|
+
className={classes.field}
|
|
48
61
|
onChange={e => {
|
|
49
|
-
|
|
62
|
+
setTempEbiEmail(e.target.value)
|
|
50
63
|
}}
|
|
51
64
|
/>
|
|
52
65
|
<Button
|
|
53
66
|
variant="contained"
|
|
54
67
|
onClick={() => {
|
|
55
|
-
|
|
68
|
+
setTempEbiEmail(DEFAULT_EBI_EMAIL)
|
|
56
69
|
}}
|
|
57
70
|
>
|
|
58
71
|
Reset
|
|
@@ -72,7 +85,7 @@ export default function NCBISettingsDialog({
|
|
|
72
85
|
color="primary"
|
|
73
86
|
variant="contained"
|
|
74
87
|
onClick={() => {
|
|
75
|
-
handleClose(
|
|
88
|
+
handleClose({ ebiEmail: tempEbiEmail })
|
|
76
89
|
}}
|
|
77
90
|
>
|
|
78
91
|
Save
|
|
@@ -149,7 +149,7 @@ const CachedBlastResults = observer(function ({
|
|
|
149
149
|
}}
|
|
150
150
|
>
|
|
151
151
|
<ListItemText
|
|
152
|
-
primary={`${getResultDisplayName(result)} - ${result.blastDatabase}
|
|
152
|
+
primary={`${getResultDisplayName(result)} - ${result.blastDatabase}${result.blastProgram ? `/${result.blastProgram}` : ''} (${result.msaAlgorithm})`}
|
|
153
153
|
secondary={`${new Date(result.timestamp).toLocaleString()} - Seq: ${result.proteinSequence.slice(0, 30)}...`}
|
|
154
154
|
/>
|
|
155
155
|
</ListItemButton>
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
|
|
9
|
+
export const msaAlgorithms = ['clustalo', 'muscle', 'kalign', 'mafft'] as const
|
|
10
|
+
export type MsaAlgorithm = (typeof msaAlgorithms)[number]
|
|
11
|
+
|
|
12
|
+
export const blastDatabaseOptions = [
|
|
13
|
+
'uniprotkb_swissprot',
|
|
14
|
+
'uniprotkb',
|
|
15
|
+
'uniprotkb_reference_proteomes',
|
|
16
|
+
'uniprotkb_trembl',
|
|
17
|
+
] as const
|
|
18
|
+
export type BlastDatabase = (typeof blastDatabaseOptions)[number]
|
|
19
|
+
|
|
20
|
+
// curated, so it returns roughly one good sequence per species rather than the
|
|
21
|
+
// many near-identical TrEMBL entries an alignment reads poorly
|
|
22
|
+
export const defaultBlastDatabase: BlastDatabase = 'uniprotkb_swissprot'
|
|
@@ -4,8 +4,8 @@ import { Dialog } from '@jbrowse/core/ui'
|
|
|
4
4
|
import { getSession } from '@jbrowse/core/util'
|
|
5
5
|
import { Tab, Tabs } from '@mui/material'
|
|
6
6
|
|
|
7
|
+
import BlastPanel from './BlastQuery/BlastPanel'
|
|
7
8
|
import ManualMSALoader from './ManualMSALoader/ManualMSALoader'
|
|
8
|
-
import NCBIBlastPanel from './NCBIBlastQuery/NCBIBlastPanel'
|
|
9
9
|
import OrthologPanel from './OrthologQuery/OrthologPanel'
|
|
10
10
|
import PreLoadedMSA from './PreLoadedMSA/PreLoadedMSADataPanel'
|
|
11
11
|
import { readMsaDatasets } from './PreLoadedMSA/types'
|
|
@@ -41,7 +41,9 @@ export default function LaunchMsaViewDialog({
|
|
|
41
41
|
}}
|
|
42
42
|
>
|
|
43
43
|
<Tab label="Orthologs (fast)" value="orthologs" />
|
|
44
|
-
|
|
44
|
+
{/* the tab value stays 'ncbi_blast' — it is only local state, and
|
|
45
|
+
renaming it buys nothing */}
|
|
46
|
+
<Tab label="BLAST query" value="ncbi_blast" />
|
|
45
47
|
{hasPreloadedDatasets ? (
|
|
46
48
|
<Tab label="Pre-loaded MSA datasets" value="preloaded_msa" />
|
|
47
49
|
) : null}
|
|
@@ -55,11 +57,7 @@ export default function LaunchMsaViewDialog({
|
|
|
55
57
|
/>
|
|
56
58
|
</TabPanel>
|
|
57
59
|
<TabPanel value={value} index="ncbi_blast">
|
|
58
|
-
<
|
|
59
|
-
handleClose={handleClose}
|
|
60
|
-
feature={feature}
|
|
61
|
-
model={model}
|
|
62
|
-
/>
|
|
60
|
+
<BlastPanel handleClose={handleClose} feature={feature} model={model} />
|
|
63
61
|
</TabPanel>
|
|
64
62
|
{hasPreloadedDatasets ? (
|
|
65
63
|
<TabPanel value={value} index="preloaded_msa">
|
|
@@ -1,48 +1,32 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Typography } from '@mui/material'
|
|
4
4
|
import { observer } from 'mobx-react'
|
|
5
5
|
import { makeStyles } from 'tss-react/mui'
|
|
6
6
|
|
|
7
|
+
import QuerySpeciesSelect from './QuerySpeciesSelect'
|
|
7
8
|
import { orthologLaunchView } from './orthologLaunchView'
|
|
8
9
|
import TextField2 from '../../../components/TextField2'
|
|
9
|
-
import {
|
|
10
|
+
import { defaultMaxSpecies } from '../../../utils/ncbiOrthologs'
|
|
10
11
|
import {
|
|
11
12
|
getGeneDisplayName,
|
|
12
13
|
getGeneIdentifiers,
|
|
13
14
|
getLinearGenomeView,
|
|
14
15
|
getTranscriptDisplayName,
|
|
15
16
|
} from '../../util'
|
|
17
|
+
import MsaAlgorithmSelect from '../BlastQuery/MsaAlgorithmSelect'
|
|
16
18
|
import LaunchPanelContent from '../LaunchPanelContent'
|
|
17
|
-
import MsaAlgorithmSelect from '../NCBIBlastQuery/MsaAlgorithmSelect'
|
|
18
19
|
import SubmitCancelActions from '../SubmitCancelActions'
|
|
19
20
|
import TranscriptSelector from '../TranscriptSelector'
|
|
20
21
|
import { useTranscriptSelection } from '../useTranscriptSelection'
|
|
21
22
|
|
|
22
|
-
import type { MsaAlgorithm } from '../
|
|
23
|
+
import type { MsaAlgorithm } from '../BlastQuery/consts'
|
|
23
24
|
import type { AbstractTrackModel, Feature } from '@jbrowse/core/util'
|
|
24
25
|
|
|
25
26
|
const useStyles = makeStyles()({
|
|
26
27
|
selectField: {
|
|
27
28
|
width: 180,
|
|
28
29
|
},
|
|
29
|
-
// A GRID, not a wrapping flex row of fixed-width items. The old form was three
|
|
30
|
-
// 160px columns inside a 560px box, which is five rows for thirteen species and
|
|
31
|
-
// eight for twenty-three -- and the checkbox list is the tallest thing in the
|
|
32
|
-
// dialog, so those rows are the dialog's height. Five auto-fitted columns is
|
|
33
|
-
// five rows for twenty-three, i.e. more species in less space, and it reflows
|
|
34
|
-
// rather than being pinned to a width the dialog may not have.
|
|
35
|
-
speciesBox: {
|
|
36
|
-
display: 'grid',
|
|
37
|
-
gridTemplateColumns: 'repeat(auto-fit, minmax(130px, 1fr))',
|
|
38
|
-
maxWidth: 700,
|
|
39
|
-
marginTop: 4,
|
|
40
|
-
},
|
|
41
|
-
// The label carries the row height; the default control padding is what makes
|
|
42
|
-
// 23 rows of it tall.
|
|
43
|
-
species: {
|
|
44
|
-
marginRight: 0,
|
|
45
|
-
},
|
|
46
30
|
})
|
|
47
31
|
|
|
48
32
|
const OrthologPanel = observer(function ({
|
|
@@ -59,86 +43,64 @@ const OrthologPanel = observer(function ({
|
|
|
59
43
|
const [launchViewError, setLaunchViewError] = useState<unknown>()
|
|
60
44
|
const [taxId, setTaxId] = useState(9606)
|
|
61
45
|
const [msaAlgorithm, setMsaAlgorithm] = useState<MsaAlgorithm>('clustalo')
|
|
62
|
-
const [
|
|
46
|
+
const [maxSpecies, setMaxSpecies] = useState(String(defaultMaxSpecies))
|
|
63
47
|
|
|
64
48
|
const geneCandidates = useMemo(() => getGeneIdentifiers(feature), [feature])
|
|
65
49
|
const transcriptSelection = useTranscriptSelection({ feature, view })
|
|
66
50
|
const { selectedTranscript, proteinSequence } = transcriptSelection
|
|
67
51
|
const e = transcriptSelection.error ?? launchViewError
|
|
68
52
|
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
)
|
|
53
|
+
const rowCount = Number(maxSpecies)
|
|
54
|
+
const rowCountValid = Number.isInteger(rowCount) && rowCount >= 2
|
|
72
55
|
|
|
73
56
|
return (
|
|
74
57
|
<>
|
|
75
58
|
<LaunchPanelContent error={e}>
|
|
76
|
-
{/* One line rather than seven. What a reader needs here is which tab
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
59
|
+
{/* One line rather than seven. What a reader needs here is which tab to
|
|
60
|
+
pick; the rest (species labels, CDD overlay, the query row being the
|
|
61
|
+
selected transcript) is visible in the result or documented, and as
|
|
62
|
+
prose it was most of the dialog's height.
|
|
63
|
+
|
|
64
|
+
The comparison stays but the number moved: the lookup is still
|
|
65
|
+
instant, and it is now the aligner that costs the wait, about half a
|
|
66
|
+
second per row. */}
|
|
81
67
|
<Typography variant="body2">
|
|
82
|
-
NCBI's precomputed orthologs, one gene per species,
|
|
83
|
-
|
|
68
|
+
NCBI's precomputed orthologs, one gene per species, looked up
|
|
69
|
+
rather than searched for. No BLAST job to queue.
|
|
84
70
|
</Typography>
|
|
85
71
|
|
|
86
72
|
<div>
|
|
87
|
-
<
|
|
88
|
-
variant="outlined"
|
|
89
|
-
label="Query species"
|
|
73
|
+
<QuerySpeciesSelect
|
|
90
74
|
className={classes.selectField}
|
|
91
|
-
select
|
|
92
75
|
value={taxId}
|
|
93
|
-
onChange={
|
|
94
|
-
|
|
95
|
-
}}
|
|
96
|
-
helperText="the species this gene is from"
|
|
97
|
-
>
|
|
98
|
-
{COMMON_SPECIES.map(s => (
|
|
99
|
-
<MenuItem value={s.taxId} key={s.taxId}>
|
|
100
|
-
{s.label}
|
|
101
|
-
</MenuItem>
|
|
102
|
-
))}
|
|
103
|
-
</TextField2>
|
|
76
|
+
onChange={setTaxId}
|
|
77
|
+
/>
|
|
104
78
|
|
|
105
79
|
<MsaAlgorithmSelect
|
|
106
80
|
className={classes.selectField}
|
|
107
81
|
value={msaAlgorithm}
|
|
108
82
|
onChange={setMsaAlgorithm}
|
|
109
83
|
/>
|
|
110
|
-
</div>
|
|
111
84
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
setExcluded(
|
|
125
|
-
event.target.checked
|
|
126
|
-
? excluded.filter(t => t !== s.taxId)
|
|
127
|
-
: [...excluded, s.taxId],
|
|
128
|
-
)
|
|
129
|
-
}}
|
|
130
|
-
/>
|
|
131
|
-
}
|
|
132
|
-
label={s.label}
|
|
133
|
-
/>
|
|
134
|
-
))}
|
|
85
|
+
<TextField2
|
|
86
|
+
variant="outlined"
|
|
87
|
+
label="Rows to align"
|
|
88
|
+
className={classes.selectField}
|
|
89
|
+
type="number"
|
|
90
|
+
value={maxSpecies}
|
|
91
|
+
onChange={event => {
|
|
92
|
+
setMaxSpecies(event.target.value)
|
|
93
|
+
}}
|
|
94
|
+
error={!rowCountValid}
|
|
95
|
+
helperText="the closest N species NCBI has"
|
|
96
|
+
/>
|
|
135
97
|
</div>
|
|
136
98
|
|
|
137
99
|
<TranscriptSelector feature={feature} {...transcriptSelection} />
|
|
138
100
|
|
|
139
101
|
</LaunchPanelContent>
|
|
140
102
|
<SubmitCancelActions
|
|
141
|
-
submitDisabled={!proteinSequence ||
|
|
103
|
+
submitDisabled={!proteinSequence || !rowCountValid}
|
|
142
104
|
onSubmit={() => {
|
|
143
105
|
try {
|
|
144
106
|
if (selectedTranscript) {
|
|
@@ -149,7 +111,7 @@ const OrthologPanel = observer(function ({
|
|
|
149
111
|
newViewTitle: `Orthologs - ${getGeneDisplayName(feature)} - ${getTranscriptDisplayName(selectedTranscript)}`,
|
|
150
112
|
orthologParams: {
|
|
151
113
|
taxId,
|
|
152
|
-
|
|
114
|
+
maxSpecies: rowCount,
|
|
153
115
|
geneCandidates,
|
|
154
116
|
msaAlgorithm,
|
|
155
117
|
selectedTranscript,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
import TextField2 from '../../../components/TextField2'
|
|
4
|
+
import { resolveTaxId } from '../../../utils/ncbiTaxonomy'
|
|
5
|
+
import { fetchTaxonomyInfo } from '../../../utils/taxonomyNames'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The species the query gene came from, as free text resolved against NCBI's
|
|
9
|
+
* taxonomy rather than picked from a fixed list.
|
|
10
|
+
*
|
|
11
|
+
* The resolved taxon is shown back as helper text, which is the whole point of
|
|
12
|
+
* resolving on a debounce instead of on submit: a typo resolves to some other
|
|
13
|
+
* organism rather than to nothing, and the only place that surfaces is the gene
|
|
14
|
+
* lookup, as "could not resolve NLRP1 in taxon 9986".
|
|
15
|
+
*/
|
|
16
|
+
export default function QuerySpeciesSelect({
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
className,
|
|
20
|
+
}: {
|
|
21
|
+
value: number
|
|
22
|
+
onChange: (taxId: number) => void
|
|
23
|
+
className?: string
|
|
24
|
+
}) {
|
|
25
|
+
const [text, setText] = useState('human')
|
|
26
|
+
const [resolved, setResolved] = useState<string>()
|
|
27
|
+
const [error, setError] = useState<unknown>()
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
// read through a call rather than as a property: the cleanup writes it from
|
|
31
|
+
// another turn of the loop, and a bare `run.live` narrows to true after the
|
|
32
|
+
// first check, which reads to the compiler as a redundant second one
|
|
33
|
+
const run = { live: true }
|
|
34
|
+
const cancelled = () => !run.live
|
|
35
|
+
async function lookup() {
|
|
36
|
+
try {
|
|
37
|
+
setError(undefined)
|
|
38
|
+
const taxId = await resolveTaxId(text)
|
|
39
|
+
if (cancelled()) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (!taxId) {
|
|
43
|
+
setResolved(undefined)
|
|
44
|
+
setError(new Error(`No NCBI taxon matches "${text}"`))
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
const info = (await fetchTaxonomyInfo([taxId])).get(taxId)
|
|
48
|
+
if (cancelled()) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
setResolved(
|
|
52
|
+
[info?.sciname, info?.commonName && `(${info.commonName})`]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(' ') || `taxon ${taxId}`,
|
|
55
|
+
)
|
|
56
|
+
onChange(taxId)
|
|
57
|
+
} catch (e) {
|
|
58
|
+
if (!cancelled()) {
|
|
59
|
+
setError(e)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const timer = setTimeout(() => {
|
|
64
|
+
void lookup()
|
|
65
|
+
}, 400)
|
|
66
|
+
return () => {
|
|
67
|
+
run.live = false
|
|
68
|
+
clearTimeout(timer)
|
|
69
|
+
}
|
|
70
|
+
// onChange is a setState updater from the parent and stable in practice;
|
|
71
|
+
// including it would re-run the lookup on every parent render
|
|
72
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
73
|
+
}, [text])
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<TextField2
|
|
77
|
+
variant="outlined"
|
|
78
|
+
label="Query species"
|
|
79
|
+
className={className}
|
|
80
|
+
value={text}
|
|
81
|
+
onChange={event => {
|
|
82
|
+
setText(event.target.value)
|
|
83
|
+
}}
|
|
84
|
+
error={!!error}
|
|
85
|
+
helperText={
|
|
86
|
+
error
|
|
87
|
+
? `${error}`
|
|
88
|
+
: (resolved ?? `the species this gene is from (taxon ${value})`)
|
|
89
|
+
}
|
|
90
|
+
/>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
@@ -26,7 +26,6 @@ interface ContextMenuInfo {
|
|
|
26
26
|
interface DisplayModel {
|
|
27
27
|
contextMenuItems: () => MenuItem[]
|
|
28
28
|
contextMenuInfo?: ContextMenuInfo
|
|
29
|
-
isGeneLike?: boolean
|
|
30
29
|
fetchFullFeature?: (
|
|
31
30
|
featureId: string,
|
|
32
31
|
displayedRegionIndex: number,
|
|
@@ -34,6 +33,25 @@ interface DisplayModel {
|
|
|
34
33
|
contextMenuFeature?: Feature
|
|
35
34
|
}
|
|
36
35
|
|
|
36
|
+
// Read off the clicked item rather than off the display.
|
|
37
|
+
//
|
|
38
|
+
// LinearBasicDisplay used to publish an `isGeneLike` getter and this gated on
|
|
39
|
+
// it. jbrowse-components 684142b3 (2026-08-16) inlined that getter into its own
|
|
40
|
+
// `contextMenuItems`, and every host built after it returns `undefined` here --
|
|
41
|
+
// so the gate was never satisfied, `onClick` stayed undefined, and the item
|
|
42
|
+
// silently left the right-click menu on every gene track. Nothing failed loudly:
|
|
43
|
+
// the display still had contextMenuInfo and fetchFullFeature, and the menu still
|
|
44
|
+
// opened with its own items in it.
|
|
45
|
+
//
|
|
46
|
+
// A predicate over the type we were already given cannot go the same way, and it
|
|
47
|
+
// costs one comparison. Deliberately the same loose case-insensitive test the
|
|
48
|
+
// host applies (`isGeneLikeType` in collapseIntronsMenu.ts): real GFFs carry
|
|
49
|
+
// 'mRNA', 'lnc_RNA', 'protein_coding_gene', 'transcript'.
|
|
50
|
+
function isGeneLikeType(type: string | undefined) {
|
|
51
|
+
const t = (type ?? '').toLowerCase()
|
|
52
|
+
return t.includes('gene') || t.includes('rna') || t.includes('transcript')
|
|
53
|
+
}
|
|
54
|
+
|
|
37
55
|
const GENE_LIKE_TYPES = new Set(['gene', 'mRNA', 'transcript'])
|
|
38
56
|
|
|
39
57
|
function extendStateModel(stateModel: IAnyModelType) {
|
|
@@ -54,7 +72,7 @@ function extendStateModel(stateModel: IAnyModelType) {
|
|
|
54
72
|
const fetchFullFeature = self.fetchFullFeature
|
|
55
73
|
const legacyFeature = self.contextMenuFeature
|
|
56
74
|
const onClick =
|
|
57
|
-
info && fetchFullFeature &&
|
|
75
|
+
info && fetchFullFeature && isGeneLikeType(info.item.type)
|
|
58
76
|
? () => {
|
|
59
77
|
fetchFullFeature(info.item.featureId, info.displayedRegionIndex)
|
|
60
78
|
.then(feature => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import { Typography } from '@mui/material'
|
|
4
|
+
|
|
5
|
+
import ExternalLink from '../../components/ExternalLink'
|
|
6
|
+
import { ebiBlastResultUrl } from '../../utils/ebiBlast'
|
|
7
|
+
|
|
8
|
+
function JobLink({ jobId }: { jobId: string }) {
|
|
9
|
+
return (
|
|
10
|
+
<Typography>
|
|
11
|
+
Job {jobId} (
|
|
12
|
+
<ExternalLink href={ebiBlastResultUrl(jobId)}>see status</ExternalLink>)
|
|
13
|
+
</Typography>
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default JobLink
|
|
@@ -5,7 +5,7 @@ import { Typography } from '@mui/material'
|
|
|
5
5
|
import { observer } from 'mobx-react'
|
|
6
6
|
import { makeStyles } from 'tss-react/mui'
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import JobLink from './JobLink'
|
|
9
9
|
|
|
10
10
|
import type { JBrowsePluginMsaViewModel } from '../model'
|
|
11
11
|
|
|
@@ -18,57 +18,26 @@ const useStyles = makeStyles()(theme => ({
|
|
|
18
18
|
},
|
|
19
19
|
}))
|
|
20
20
|
|
|
21
|
-
function RIDError({
|
|
22
|
-
baseUrl,
|
|
23
|
-
rid,
|
|
24
|
-
error,
|
|
25
|
-
}: {
|
|
26
|
-
baseUrl: string
|
|
27
|
-
rid?: string
|
|
28
|
-
error: unknown
|
|
29
|
-
}) {
|
|
30
|
-
return (
|
|
31
|
-
<div>
|
|
32
|
-
{rid ? <RIDLink rid={rid} baseUrl={baseUrl} /> : null}
|
|
33
|
-
<ErrorMessage error={error} />
|
|
34
|
-
</div>
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function RIDProgress({
|
|
39
|
-
baseUrl,
|
|
40
|
-
rid,
|
|
41
|
-
progress,
|
|
42
|
-
}: {
|
|
43
|
-
baseUrl: string
|
|
44
|
-
rid: string
|
|
45
|
-
progress: string
|
|
46
|
-
}) {
|
|
47
|
-
const { classes } = useStyles()
|
|
48
|
-
return (
|
|
49
|
-
<div className={classes.loading}>
|
|
50
|
-
{rid ? <RIDLink baseUrl={baseUrl} rid={rid} /> : null}
|
|
51
|
-
<Typography>{progress}</Typography>
|
|
52
|
-
</div>
|
|
53
|
-
)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
21
|
const LoadingBLAST = observer(function LoadingBLAST2({
|
|
57
22
|
model,
|
|
58
|
-
baseUrl,
|
|
59
23
|
}: {
|
|
60
24
|
model: JBrowsePluginMsaViewModel
|
|
61
|
-
baseUrl: string
|
|
62
25
|
}) {
|
|
63
26
|
const { progress, rid, error } = model
|
|
64
27
|
const { classes } = useStyles()
|
|
65
28
|
return (
|
|
66
29
|
<div className={classes.margin}>
|
|
67
|
-
<LoadingEllipses message="Running
|
|
30
|
+
<LoadingEllipses message="Running EBI BLAST" variant="h5" />
|
|
68
31
|
{error ? (
|
|
69
|
-
<
|
|
32
|
+
<div>
|
|
33
|
+
{rid ? <JobLink jobId={rid} /> : null}
|
|
34
|
+
<ErrorMessage error={error} />
|
|
35
|
+
</div>
|
|
70
36
|
) : rid ? (
|
|
71
|
-
<
|
|
37
|
+
<div className={classes.loading}>
|
|
38
|
+
<JobLink jobId={rid} />
|
|
39
|
+
<Typography>{progress}</Typography>
|
|
40
|
+
</div>
|
|
72
41
|
) : (
|
|
73
42
|
<Typography>{progress || 'Initializing BLAST query'}</Typography>
|
|
74
43
|
)}
|
|
@@ -27,7 +27,7 @@ const MsaViewPanel = observer(function MsaViewPanel2({
|
|
|
27
27
|
<ErrorBoundary>
|
|
28
28
|
<div>
|
|
29
29
|
{blastParams ? (
|
|
30
|
-
<LoadingBLAST model={model}
|
|
30
|
+
<LoadingBLAST model={model} />
|
|
31
31
|
) : loadingStoredData ? (
|
|
32
32
|
<div className={classes.loadingContainer}>
|
|
33
33
|
<LoadingEllipses message="Loading MSA data" variant="h6" />
|