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.
Files changed (108) hide show
  1. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastRIDPanel.d.ts → BlastQuery/BlastAutomaticPanel.d.ts} +2 -3
  2. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.js → BlastQuery/BlastAutomaticPanel.js} +6 -25
  3. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.d.ts → BlastQuery/BlastManualPanel.d.ts} +2 -3
  4. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.js → BlastQuery/BlastManualPanel.js} +7 -4
  5. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.d.ts → BlastQuery/BlastMethodSelector.d.ts} +2 -2
  6. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.js → BlastQuery/BlastMethodSelector.js} +1 -2
  7. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.d.ts → BlastQuery/BlastPanel.d.ts} +3 -12
  8. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.js → BlastQuery/BlastPanel.js} +14 -16
  9. package/dist/LaunchMsaView/components/BlastQuery/BlastSettingsDialog.d.ts +8 -0
  10. package/dist/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.js → BlastQuery/BlastSettingsDialog.js} +13 -9
  11. package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.js +1 -1
  12. package/dist/LaunchMsaView/components/BlastQuery/consts.d.ts +12 -0
  13. package/dist/LaunchMsaView/components/BlastQuery/consts.js +17 -0
  14. package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +3 -3
  15. package/dist/LaunchMsaView/components/OrthologQuery/OrthologPanel.js +15 -35
  16. package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.d.ts +15 -0
  17. package/dist/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.js +67 -0
  18. package/dist/LaunchMsaView/index.js +19 -1
  19. package/dist/MsaViewPanel/components/JobLink.d.ts +5 -0
  20. package/dist/MsaViewPanel/components/JobLink.js +13 -0
  21. package/dist/MsaViewPanel/components/LoadingBLAST.d.ts +1 -2
  22. package/dist/MsaViewPanel/components/LoadingBLAST.js +8 -15
  23. package/dist/MsaViewPanel/components/MsaViewPanel.js +1 -1
  24. package/dist/MsaViewPanel/doLaunchBlast.js +11 -19
  25. package/dist/MsaViewPanel/doLaunchOrthologs.js +43 -12
  26. package/dist/MsaViewPanel/doLaunchOrthologs.test.d.ts +1 -0
  27. package/dist/MsaViewPanel/doLaunchOrthologs.test.js +201 -0
  28. package/dist/MsaViewPanel/model.d.ts +20 -12
  29. package/dist/jbrowse-plugin-msaview.umd.production.min.js +31 -33
  30. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  31. package/dist/utils/blastCache.d.ts +8 -4
  32. package/dist/utils/blastCache.js +4 -5
  33. package/dist/utils/ebiBlast.d.ts +52 -0
  34. package/dist/utils/ebiBlast.js +63 -0
  35. package/dist/utils/ebiJobDispatcher.d.ts +33 -0
  36. package/dist/utils/ebiJobDispatcher.js +80 -0
  37. package/dist/utils/ebiJobDispatcher.test.d.ts +1 -0
  38. package/dist/utils/ebiJobDispatcher.test.js +46 -0
  39. package/dist/utils/eutils.d.ts +9 -0
  40. package/dist/utils/eutils.js +18 -0
  41. package/dist/utils/fetch.js +24 -1
  42. package/dist/utils/msa.d.ts +1 -1
  43. package/dist/utils/msa.js +25 -32
  44. package/dist/utils/ncbiOrthologs.d.ts +17 -75
  45. package/dist/utils/ncbiOrthologs.js +67 -63
  46. package/dist/utils/ncbiOrthologs.test.js +82 -2
  47. package/dist/utils/ncbiTaxonomy.d.ts +11 -0
  48. package/dist/utils/ncbiTaxonomy.js +33 -0
  49. package/dist/utils/types.d.ts +9 -14
  50. package/dist/utils/useLocalStorage.d.ts +1 -0
  51. package/dist/utils/useLocalStorage.js +1 -1
  52. package/dist/version.d.ts +1 -1
  53. package/dist/version.js +1 -1
  54. package/package.json +3 -3
  55. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx → BlastQuery/BlastAutomaticPanel.tsx} +13 -59
  56. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastManualPanel.tsx → BlastQuery/BlastManualPanel.tsx} +7 -6
  57. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastMethodSelector.tsx → BlastQuery/BlastMethodSelector.tsx} +2 -7
  58. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBIBlastPanel.tsx → BlastQuery/BlastPanel.tsx} +21 -25
  59. package/src/LaunchMsaView/components/{NCBIBlastQuery/NCBISettingsDialog.tsx → BlastQuery/BlastSettingsDialog.tsx} +26 -13
  60. package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.tsx +1 -1
  61. package/src/LaunchMsaView/components/BlastQuery/consts.ts +22 -0
  62. package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +5 -7
  63. package/src/LaunchMsaView/components/OrthologQuery/OrthologPanel.tsx +35 -73
  64. package/src/LaunchMsaView/components/OrthologQuery/QuerySpeciesSelect.tsx +92 -0
  65. package/src/LaunchMsaView/index.ts +20 -2
  66. package/src/MsaViewPanel/components/JobLink.tsx +17 -0
  67. package/src/MsaViewPanel/components/LoadingBLAST.tsx +10 -41
  68. package/src/MsaViewPanel/components/MsaViewPanel.tsx +1 -1
  69. package/src/MsaViewPanel/doLaunchBlast.ts +12 -28
  70. package/src/MsaViewPanel/doLaunchOrthologs.test.ts +247 -0
  71. package/src/MsaViewPanel/doLaunchOrthologs.ts +56 -13
  72. package/src/MsaViewPanel/model.ts +10 -7
  73. package/src/utils/blastCache.ts +8 -9
  74. package/src/utils/ebiBlast.ts +114 -0
  75. package/src/utils/ebiJobDispatcher.test.ts +54 -0
  76. package/src/utils/ebiJobDispatcher.ts +120 -0
  77. package/src/utils/eutils.ts +19 -0
  78. package/src/utils/fetch.ts +26 -1
  79. package/src/utils/msa.ts +26 -47
  80. package/src/utils/ncbiOrthologs.test.ts +96 -2
  81. package/src/utils/ncbiOrthologs.ts +83 -71
  82. package/src/utils/ncbiTaxonomy.ts +37 -0
  83. package/src/utils/types.ts +8 -13
  84. package/src/utils/useLocalStorage.ts +1 -1
  85. package/src/version.ts +1 -1
  86. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.d.ts +0 -10
  87. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +0 -74
  88. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.d.ts +0 -5
  89. package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.d.ts +0 -7
  90. package/dist/LaunchMsaView/components/NCBIBlastQuery/consts.js +0 -4
  91. package/dist/MsaViewPanel/components/RIDLink.d.ts +0 -6
  92. package/dist/MsaViewPanel/components/RIDLink.js +0 -12
  93. package/dist/utils/ncbiBlast.d.ts +0 -30
  94. package/dist/utils/ncbiBlast.js +0 -84
  95. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.tsx +0 -137
  96. package/src/LaunchMsaView/components/NCBIBlastQuery/consts.ts +0 -10
  97. package/src/MsaViewPanel/components/RIDLink.tsx +0 -19
  98. package/src/utils/ncbiBlast.ts +0 -143
  99. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/CachedBlastResults.d.ts +0 -0
  100. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.d.ts +0 -0
  101. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.js +0 -0
  102. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.d.ts +0 -0
  103. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.js +0 -0
  104. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.d.ts +0 -0
  105. /package/dist/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/useCachedBlastResults.js +0 -0
  106. /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/MsaAlgorithmSelect.tsx +0 -0
  107. /package/src/LaunchMsaView/components/{NCBIBlastQuery → BlastQuery}/blastLaunchView.ts +0 -0
  108. /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 NCBIBlastRIDPanel: ({ handleClose, feature, model, children, baseUrl, }: {
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 NCBIBlastRIDPanel;
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, blastPrograms } from './consts';
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 NCBIBlastAutomaticPanel = observer(function ({ handleClose, feature, model, children, baseUrl, }) {
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('nr');
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
- const newDb = event.target.value;
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 NCBI. Using blastp can take 10+ minutes to run, quick-blastp is generally a lot faster but is not available for the clustered database. After completion, all the hits will be run through a multiple sequence alignment. Note: we are not able to currently run NCBI COBALT automatically on the BLAST results, even though that is the method NCBI uses on their website. If you need a COBALT alignment, please use the manual approach of submitting BLAST yourself and downloading the resulting files"),
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 NCBIBlastAutomaticPanel;
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 NCBIBlastManualPanel: ({ handleClose, feature, model, children, baseUrl, }: {
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 NCBIBlastManualPanel;
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 NCBIBlastManualPanel = observer(function ({ handleClose, feature, model, children, baseUrl, }) {
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
- const link = `${baseUrl}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${s2}`;
28
- const link2 = `${baseUrl}?PAGE_TYPE=BlastSearch&PAGE=Proteins&PROGRAM=blastp&QUERY=${shorten2(s2, 10)}`;
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 NCBIBlastManualPanel;
45
+ export default BlastManualPanel;
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
- import type { BlastLookupMethod } from './NCBIBlastPanel';
3
- export default function NCBIBlastMethodSelector({ lookupMethod, setLookupMethod, }: {
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 NCBIBlastMethodSelector({ lookupMethod, setLookupMethod, }) {
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, baseUrl, }: {
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 rid: ({ handleClose, feature, model, children, baseUrl, }: {
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 NCBIBlastPanel({ handleClose, model, feature, }: {
18
+ export default function BlastPanel({ handleClose, model, feature, }: {
28
19
  handleClose: () => void;
29
20
  model: AbstractTrackModel;
30
21
  feature: Feature;
@@ -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 NCBIBlastAutomaticPanel from './NCBIBlastAutomaticPanel';
6
- import NCBIBlastManualPanel from './NCBIBlastManualPanel';
7
- import NCBIBlastMethodSelector from './NCBIBlastMethodSelector';
8
- import NCBIBlastRIDPanel from './NCBIBlastRIDPanel';
9
- import NCBISettingsDialog from './NCBISettingsDialog';
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: NCBIBlastAutomaticPanel,
19
- rid: NCBIBlastRIDPanel,
20
- manual: NCBIBlastManualPanel,
17
+ automatic: BlastAutomaticPanel,
18
+ manual: BlastManualPanel,
21
19
  };
22
- export default function NCBIBlastPanel({ handleClose, model, feature, }) {
20
+ export default function BlastPanel({ handleClose, model, feature, }) {
23
21
  const [lookupMethod, setLookupMethod] = useState('automatic');
24
- const [baseUrl, setBaseUrl] = useLocalStorage('msa-blastRootUrl', BASE_BLAST_URL);
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, baseUrl: baseUrl },
34
- React.createElement(NCBIBlastMethodSelector, { lookupMethod: lookupMethod, setLookupMethod: setLookupMethod })),
35
- settingsOpen ? (React.createElement(NCBISettingsDialog, { baseUrl: baseUrl, handleClose: newUrl => {
36
- if (newUrl) {
37
- setBaseUrl(newUrl);
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));
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface BlastSettings {
3
+ ebiEmail: string;
4
+ }
5
+ export default function BlastSettingsDialog({ handleClose, ebiEmail, }: {
6
+ handleClose: (arg?: BlastSettings) => void;
7
+ ebiEmail: string;
8
+ }): React.JSX.Element;
@@ -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
- urlField: {
7
+ field: {
8
8
  minWidth: 300,
9
9
  },
10
+ help: {
11
+ marginBottom: 8,
12
+ },
10
13
  });
11
- export default function NCBISettingsDialog({ handleClose, baseUrl, }) {
14
+ export default function BlastSettingsDialog({ handleClose, ebiEmail, }) {
12
15
  const { classes } = useStyles();
13
- const [tempBaseUrl, setTempBaseUrl] = useState(baseUrl);
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(TextField2, { autoFocus: true, margin: "dense", label: "BLAST Base URL", fullWidth: true, variant: "outlined", value: tempBaseUrl, className: classes.urlField, onChange: e => {
20
- setTempBaseUrl(e.target.value);
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
- setTempBaseUrl(BASE_BLAST_URL);
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(tempBaseUrl);
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}/${result.blastProgram} (${result.msaAlgorithm})`, secondary: `${new Date(result.timestamp).toLocaleString()} - Seq: ${result.proteinSequence.slice(0, 30)}...` }))))))));
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: "NCBI BLAST query", value: "ncbi_blast" }),
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(NCBIBlastPanel, { handleClose: handleClose, feature: feature, model: model })),
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 { Checkbox, FormControlLabel, MenuItem, Typography } from '@mui/material';
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 { COMMON_SPECIES } from '../../../utils/ncbiOrthologs';
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 [excluded, setExcluded] = useState([]);
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 taxa = COMMON_SPECIES.map(s => s.taxId).filter(t => !excluded.includes(t));
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, aligned at EBI in seconds rather than the 10+ minutes BLAST takes."),
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(TextField2, { variant: "outlined", label: "Query species", className: classes.selectField, select: true, value: taxId, onChange: event => {
53
- setTaxId(Number(event.target.value));
54
- }, helperText: "the species this gene is from" }, COMMON_SPECIES.map(s => (React.createElement(MenuItem, { value: s.taxId, key: s.taxId }, s.label)))),
55
- React.createElement(MsaAlgorithmSelect, { className: classes.selectField, value: msaAlgorithm, onChange: setMsaAlgorithm })),
56
- React.createElement(Typography, { variant: "subtitle2", style: { marginTop: 8 } }, "Species to include (those without an ortholog are skipped)"),
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 || taxa.length < 2, onSubmit: () => {
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
- taxa,
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 && self.isGeneLike
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,5 @@
1
+ import React from 'react';
2
+ declare function JobLink({ jobId }: {
3
+ jobId: string;
4
+ }): React.JSX.Element;
5
+ export default JobLink;
@@ -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, baseUrl, }: {
3
+ declare const LoadingBLAST: ({ model, }: {
4
4
  model: JBrowsePluginMsaViewModel;
5
- baseUrl: string;
6
5
  }) => React.JSX.Element;
7
6
  export default LoadingBLAST;