jbrowse-plugin-protein3d 0.10.0 → 0.11.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 (75) hide show
  1. package/README.md +12 -2
  2. package/dist/LaunchProteinView/components/LaunchProteinViewDialog.js +7 -3
  3. package/dist/LaunchProteinView/components/PdbResultsTable.d.ts +7 -0
  4. package/dist/LaunchProteinView/components/PdbResultsTable.js +70 -0
  5. package/dist/LaunchProteinView/components/PdbSearch.d.ts +13 -0
  6. package/dist/LaunchProteinView/components/PdbSearch.js +92 -0
  7. package/dist/LaunchProteinView/components/ProteinViewActions.js +1 -0
  8. package/dist/LaunchProteinView/hooks/useAlphaFoldDBSearch.d.ts +23 -20
  9. package/dist/LaunchProteinView/hooks/useAlphaFoldDBSearch.js +9 -88
  10. package/dist/LaunchProteinView/hooks/usePdbBestStructures.d.ts +6 -0
  11. package/dist/LaunchProteinView/hooks/usePdbBestStructures.js +19 -0
  12. package/dist/LaunchProteinView/hooks/useUniProtIdLookup.d.ts +39 -0
  13. package/dist/LaunchProteinView/hooks/useUniProtIdLookup.js +99 -0
  14. package/dist/LaunchProteinView/services/pdbeBestStructures.d.ts +22 -0
  15. package/dist/LaunchProteinView/services/pdbeBestStructures.js +66 -0
  16. package/dist/LaunchProteinView/utils/launchHelpers.d.ts +2 -1
  17. package/dist/LaunchProteinView/utils/launchHelpers.js +2 -1
  18. package/dist/LaunchProteinView/utils/structureUrls.d.ts +9 -0
  19. package/dist/LaunchProteinView/utils/structureUrls.js +19 -0
  20. package/dist/LaunchProteinViewExtensionPoint/index.js +9 -9
  21. package/dist/ProteinView/__fixtures__/structureFixtures.d.ts +7 -0
  22. package/dist/ProteinView/__fixtures__/structureFixtures.js +12 -0
  23. package/dist/ProteinView/chooseMappedEntity.d.ts +19 -10
  24. package/dist/ProteinView/chooseMappedEntity.js +38 -14
  25. package/dist/ProteinView/components/AlignmentRuler.d.ts +5 -4
  26. package/dist/ProteinView/components/AlignmentRuler.js +7 -6
  27. package/dist/ProteinView/components/ChainSelect.js +4 -3
  28. package/dist/ProteinView/components/FeatureBar.js +14 -3
  29. package/dist/ProteinView/components/HeaderStructureInfo.js +10 -2
  30. package/dist/ProteinView/components/ProteinAlignment.js +14 -10
  31. package/dist/ProteinView/components/ProteinAlignmentHelpButton.js +1 -2
  32. package/dist/ProteinView/components/ProteinAlignmentHelpDialog.js +1 -0
  33. package/dist/ProteinView/extractStructureSequences.d.ts +51 -0
  34. package/dist/ProteinView/extractStructureSequences.js +108 -7
  35. package/dist/ProteinView/model.d.ts +36 -0
  36. package/dist/ProteinView/model.js +1 -1
  37. package/dist/ProteinView/proteinViewSpec.d.ts +8 -0
  38. package/dist/ProteinView/structureModel.d.ts +36 -0
  39. package/dist/ProteinView/structureModel.js +56 -8
  40. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +14 -14
  41. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  42. package/dist/version.d.ts +1 -1
  43. package/dist/version.js +1 -1
  44. package/package.json +3 -2
  45. package/src/LaunchProteinView/components/LaunchProteinViewDialog.tsx +15 -3
  46. package/src/LaunchProteinView/components/PdbResultsTable.tsx +117 -0
  47. package/src/LaunchProteinView/components/PdbSearch.tsx +216 -0
  48. package/src/LaunchProteinView/components/ProteinViewActions.tsx +1 -0
  49. package/src/LaunchProteinView/hooks/useAlphaFoldDBSearch.ts +9 -112
  50. package/src/LaunchProteinView/hooks/usePdbBestStructures.ts +32 -0
  51. package/src/LaunchProteinView/hooks/useUniProtIdLookup.ts +128 -0
  52. package/src/LaunchProteinView/services/pdbeBestStructures.test.ts +72 -0
  53. package/src/LaunchProteinView/services/pdbeBestStructures.ts +87 -0
  54. package/src/LaunchProteinView/utils/launchHelpers.ts +3 -0
  55. package/src/LaunchProteinView/utils/structureUrls.test.ts +15 -0
  56. package/src/LaunchProteinView/utils/structureUrls.ts +26 -0
  57. package/src/LaunchProteinViewExtensionPoint/index.ts +15 -9
  58. package/src/ProteinView/__fixtures__/structureFixtures.ts +23 -0
  59. package/src/ProteinView/chooseMappedEntity.test.ts +63 -0
  60. package/src/ProteinView/chooseMappedEntity.ts +63 -21
  61. package/src/ProteinView/components/AlignmentRuler.tsx +11 -5
  62. package/src/ProteinView/components/ChainSelect.tsx +4 -3
  63. package/src/ProteinView/components/FeatureBar.tsx +31 -3
  64. package/src/ProteinView/components/HeaderStructureInfo.tsx +17 -3
  65. package/src/ProteinView/components/ProteinAlignment.tsx +28 -11
  66. package/src/ProteinView/components/ProteinAlignmentHelpButton.tsx +0 -3
  67. package/src/ProteinView/components/ProteinAlignmentHelpDialog.tsx +9 -0
  68. package/src/ProteinView/components/rulerTicks.test.ts +16 -2
  69. package/src/ProteinView/extractStructureSequences.test.ts +144 -2
  70. package/src/ProteinView/extractStructureSequences.ts +144 -7
  71. package/src/ProteinView/model.ts +1 -1
  72. package/src/ProteinView/proteinViewSpec.ts +4 -0
  73. package/src/ProteinView/structureModel.test.ts +104 -0
  74. package/src/ProteinView/structureModel.ts +67 -6
  75. package/src/version.ts +1 -1
package/README.md CHANGED
@@ -8,8 +8,9 @@ The major workflow enabled by this is
8
8
  mouseover between genome and structure
9
9
 
10
10
  It has features to automatically look up a protein structure of interest using
11
- the UniProt ID mapping API to connect to AlphaFoldDB, and can also use Foldseek
12
- to look up related structures also
11
+ the UniProt ID mapping API to connect to AlphaFoldDB, lists the experimental PDB
12
+ entries SIFTS maps to that UniProt entry, and can also use Foldseek to look up
13
+ related structures
13
14
 
14
15
  ## Coordinate-mapping harness
15
16
 
@@ -48,6 +49,15 @@ use it on any species there
48
49
 
49
50
  See [DEVELOPERS.md](DEVELOPERS.md)
50
51
 
52
+ ## How it works
53
+
54
+ Notes on the parts that are easy to get subtly wrong, written for someone
55
+ extending the plugin or checking what a number on screen means:
56
+
57
+ - [Residue numbering](docs/residue-numbering.md): how a paper's R248 becomes
58
+ position 154 in the file, `label_seq_id` 155 for Mol\*, and the codon on
59
+ chr17, and how a session spec names a residue the literature's way.
60
+
51
61
  ## Publishing
52
62
 
53
63
  just push a new tag using e.g.
@@ -5,6 +5,7 @@ import { Tab, Tabs } from '@mui/material';
5
5
  import AlphaFoldDBSearch from './AlphaFoldDBSearch';
6
6
  import FoldseekSearch from './FoldseekSearch';
7
7
  import HelpButton from './HelpButton';
8
+ import PdbSearch from './PdbSearch';
8
9
  import TabPanel from './TabPanel';
9
10
  import UserProvidedStructure from './UserProvidedStructure';
10
11
  import { DEFAULT_ALIGNMENT_ALGORITHM } from '../../ProteinView/types';
@@ -21,12 +22,15 @@ export default function LaunchProteinViewDialog({ handleClose, feature, model, }
21
22
  setChoice(val);
22
23
  } },
23
24
  React.createElement(Tab, { value: 0, label: "AlphaFoldDB search" }),
24
- React.createElement(Tab, { value: 1, label: "Foldseek search" }),
25
- React.createElement(Tab, { value: 2, label: "Open file manually" })),
25
+ React.createElement(Tab, { value: 1, label: "PDB search" }),
26
+ React.createElement(Tab, { value: 2, label: "Foldseek search" }),
27
+ React.createElement(Tab, { value: 3, label: "Open file manually" })),
26
28
  React.createElement(TabPanel, { value: choice, index: 0 },
27
29
  React.createElement(AlphaFoldDBSearch, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm })),
28
30
  React.createElement(TabPanel, { value: choice, index: 1 },
29
- React.createElement(FoldseekSearch, { session: session, view: view, feature: feature, handleClose: handleClose })),
31
+ React.createElement(PdbSearch, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm })),
30
32
  React.createElement(TabPanel, { value: choice, index: 2 },
33
+ React.createElement(FoldseekSearch, { session: session, view: view, feature: feature, handleClose: handleClose })),
34
+ React.createElement(TabPanel, { value: choice, index: 3 },
31
35
  React.createElement(UserProvidedStructure, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm }))));
32
36
  }
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { PdbStructureEntry } from '../services/pdbeBestStructures';
3
+ export default function PdbResultsTable({ entries, selectedPdbId, onSelect, }: {
4
+ entries: PdbStructureEntry[];
5
+ selectedPdbId?: string;
6
+ onSelect: (pdbId: string) => void;
7
+ }): React.JSX.Element;
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { Paper, Radio, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from '@mui/material';
3
+ import { makeStyles } from 'tss-react/mui';
4
+ import ExternalLink from '../../components/ExternalLink';
5
+ import { rcsbEntryUrl } from '../services/pdbeBestStructures';
6
+ const useStyles = makeStyles()(theme => ({
7
+ tableContainer: {
8
+ maxHeight: 300,
9
+ },
10
+ headerCell: {
11
+ fontWeight: 'bold',
12
+ backgroundColor: theme.palette.mode === 'dark'
13
+ ? theme.palette.grey[900]
14
+ : theme.palette.grey[100],
15
+ },
16
+ selectedRow: {
17
+ backgroundColor: theme.palette.action.selected,
18
+ },
19
+ clickableRow: {
20
+ cursor: 'pointer',
21
+ '&:hover': {
22
+ backgroundColor: theme.palette.action.hover,
23
+ },
24
+ },
25
+ }));
26
+ const MAX_ROWS = 100;
27
+ export default function PdbResultsTable({ entries, selectedPdbId, onSelect, }) {
28
+ const { classes } = useStyles();
29
+ const shown = entries.slice(0, MAX_ROWS);
30
+ return (React.createElement(React.Fragment, null,
31
+ React.createElement(Typography, { variant: "body2", color: "textSecondary" },
32
+ entries.length,
33
+ " PDB entries, ranked by PDBe on coverage and resolution",
34
+ entries.length > shown.length
35
+ ? ` (showing the first ${MAX_ROWS})`
36
+ : ''),
37
+ React.createElement(TableContainer, { component: Paper, className: classes.tableContainer },
38
+ React.createElement(Table, { size: "small", stickyHeader: true, "data-testid": "pdb-results-table" },
39
+ React.createElement(TableHead, null,
40
+ React.createElement(TableRow, null,
41
+ React.createElement(TableCell, { className: classes.headerCell, padding: "checkbox" }),
42
+ React.createElement(TableCell, { className: classes.headerCell }, "PDB ID"),
43
+ React.createElement(TableCell, { className: classes.headerCell }, "Method"),
44
+ React.createElement(TableCell, { className: classes.headerCell }, "Resolution"),
45
+ React.createElement(TableCell, { className: classes.headerCell }, "UniProt residues"),
46
+ React.createElement(TableCell, { className: classes.headerCell }, "Coverage"),
47
+ React.createElement(TableCell, { className: classes.headerCell }, "Chains"))),
48
+ React.createElement(TableBody, null, shown.map(entry => {
49
+ const selected = entry.pdbId === selectedPdbId;
50
+ return (React.createElement(TableRow, { key: entry.pdbId, className: `${classes.clickableRow} ${selected ? classes.selectedRow : ''}`, onClick: () => {
51
+ onSelect(entry.pdbId);
52
+ } },
53
+ React.createElement(TableCell, { padding: "checkbox" },
54
+ React.createElement(Radio, { checked: selected, size: "small" })),
55
+ React.createElement(TableCell, null,
56
+ React.createElement(ExternalLink, { href: rcsbEntryUrl(entry.pdbId) }, entry.pdbId.toUpperCase())),
57
+ React.createElement(TableCell, null, entry.experimentalMethod),
58
+ React.createElement(TableCell, null, entry.resolution === undefined
59
+ ? '-'
60
+ : `${entry.resolution.toFixed(2)} Å`),
61
+ React.createElement(TableCell, null,
62
+ entry.unpStart,
63
+ "-",
64
+ entry.unpEnd),
65
+ React.createElement(TableCell, null,
66
+ (entry.coverage * 100).toFixed(0),
67
+ "%"),
68
+ React.createElement(TableCell, null, entry.chains.join(', '))));
69
+ }))))));
70
+ }
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import type { AlignmentAlgorithm } from '../../ProteinView/types';
3
+ import type { AbstractSessionModel, Feature } from '@jbrowse/core/util';
4
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
5
+ declare const PdbSearch: ({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }: {
6
+ feature: Feature;
7
+ session: AbstractSessionModel;
8
+ view: LinearGenomeViewModel;
9
+ handleClose: () => void;
10
+ alignmentAlgorithm: AlignmentAlgorithm;
11
+ onAlignmentAlgorithmChange: (algorithm: AlignmentAlgorithm) => void;
12
+ }) => React.JSX.Element;
13
+ export default PdbSearch;
@@ -0,0 +1,92 @@
1
+ import React, { useState } from 'react';
2
+ import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
+ import { DialogActions, DialogContent, TextField, Typography, } from '@mui/material';
4
+ import { observer } from 'mobx-react';
5
+ import { makeStyles } from 'tss-react/mui';
6
+ import IdentifierSelector from './IdentifierSelector';
7
+ import PdbResultsTable from './PdbResultsTable';
8
+ import ProteinViewActions from './ProteinViewActions';
9
+ import TranscriptSelector from './TranscriptSelector';
10
+ import UniProtIdInput from './UniProtIdInput';
11
+ import UniProtResultsTable from './UniProtResultsTable';
12
+ import ExternalLink from '../../components/ExternalLink';
13
+ import usePdbBestStructures from '../hooks/usePdbBestStructures';
14
+ import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection';
15
+ import useUniProtIdLookup from '../hooks/useUniProtIdLookup';
16
+ import { getPdbStructureUrl, uniprotEntryUrl } from '../utils/structureUrls';
17
+ const useStyles = makeStyles()({
18
+ dialogContent: {
19
+ width: '80em',
20
+ '& > *': {
21
+ marginBottom: 20,
22
+ },
23
+ '& > *:last-child': {
24
+ marginBottom: 0,
25
+ },
26
+ },
27
+ endRow: {
28
+ display: 'flex',
29
+ flexDirection: 'row',
30
+ gap: 12,
31
+ alignItems: 'flex-start',
32
+ },
33
+ });
34
+ // Experimental structures of the gene's protein, found through SIFTS: PDBe
35
+ // lists every entry mapped to the UniProt accession, ranked on coverage and
36
+ // resolution, so a reader who does not know a PDB id can still reach one.
37
+ // A crystal is usually a fragment, often with partners, so the view aligns
38
+ // the transcript to it after launch rather than expecting a sequence match
39
+ // here.
40
+ const PdbSearch = observer(function PdbSearch({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
41
+ const { classes } = useStyles();
42
+ const lookup = useUniProtIdLookup({ feature, view });
43
+ const { uniprotId, isAutoMode, isLookupLoading } = lookup;
44
+ const { entries, error: pdbError, isLoading: isPdbLoading, } = usePdbBestStructures(uniprotId);
45
+ const [userPdbId, setUserPdbId] = useState();
46
+ const { transcripts, isoformSequences, isLoading: isIsoformLoading, error: isoformError, selectedTranscriptId, setSelectedTranscriptId, selectedTranscript, selectedIsoform, } = useTranscriptIsoformSelection({ feature, view, resetKey: uniprotId });
47
+ const selectedPdbId = userPdbId && entries?.some(e => e.pdbId === userPdbId)
48
+ ? userPdbId
49
+ : entries?.[0]?.pdbId;
50
+ const loadingStatuses = [
51
+ isLookupLoading && 'Looking up UniProt ID',
52
+ isIsoformLoading && 'Loading protein sequences from transcript isoforms',
53
+ isPdbLoading && 'Listing PDB entries from PDBe',
54
+ ].filter((s) => !!s);
55
+ const isLoading = loadingStatuses.length > 0;
56
+ const error = isLoading
57
+ ? undefined
58
+ : (isoformError ?? lookup.lookupError ?? pdbError);
59
+ return (React.createElement(React.Fragment, null,
60
+ React.createElement(DialogContent, { className: classes.dialogContent },
61
+ error ? React.createElement(ErrorMessage, { error: error }) : null,
62
+ React.createElement(UniProtIdInput, { lookupMode: lookup.lookupMode, onLookupModeChange: lookup.setLookupMode, manualUniprotId: lookup.manualUniprotId, onManualUniprotIdChange: lookup.setManualUniprotId, featureUniprotId: lookup.featureUniprotId, endContent: lookup.showIdentifierSelector ? (React.createElement("div", { className: classes.endRow },
63
+ React.createElement(IdentifierSelector, { recognizedIds: lookup.recognizedIds, geneName: lookup.geneName, selectedId: lookup.selectedQueryId, onSelectedIdChange: lookup.setSelectedQueryId }),
64
+ React.createElement(TextField, { size: "small", label: "Organism (NCBI taxon)", helperText: "Scopes the gene-name search", value: lookup.taxonId, onChange: event => {
65
+ lookup.setTaxonId(event.target.value);
66
+ }, placeholder: String(lookup.effectiveTaxonId), slotProps: { inputLabel: { shrink: true } }, sx: { width: 180 } }))) : null }),
67
+ loadingStatuses.map(status => (React.createElement(LoadingEllipses, { key: status, variant: "subtitle2", message: status }))),
68
+ isAutoMode && lookup.uniprotEntries.length > 0 ? (React.createElement(React.Fragment, null,
69
+ React.createElement(Typography, { variant: "body2", color: "textSecondary" },
70
+ "Searched UniProt by ",
71
+ lookup.searchDescription),
72
+ React.createElement(UniProtResultsTable, { entries: lookup.uniprotEntries, selectedAccession: lookup.selectedTableAccession, onSelect: lookup.setSelectedUniprotId }))) : null,
73
+ isAutoMode &&
74
+ !isLookupLoading &&
75
+ lookup.uniprotEntries.length === 0 ? (React.createElement(Typography, { variant: "body2", color: "textSecondary" },
76
+ "No UniProt entries found for ",
77
+ lookup.searchDescriptionOr,
78
+ ". Try a different identifier above, or search",
79
+ ' ',
80
+ React.createElement(ExternalLink, { href: "https://www.uniprot.org/" }, "UniProt"),
81
+ ' ',
82
+ "directly and use \"Enter manually\".")) : null,
83
+ uniprotId && entries && !isPdbLoading ? (entries.length > 0 ? (React.createElement(PdbResultsTable, { entries: entries, selectedPdbId: selectedPdbId, onSelect: setUserPdbId })) : (React.createElement(Typography, null,
84
+ "PDBe lists no experimental structure for",
85
+ ' ',
86
+ React.createElement(ExternalLink, { href: uniprotEntryUrl(uniprotId) }, uniprotId),
87
+ ". The AlphaFoldDB tab has a predicted one."))) : null,
88
+ isoformSequences && selectedTranscript ? (React.createElement(TranscriptSelector, { val: selectedTranscriptId, setVal: setSelectedTranscriptId, feature: feature, isoforms: transcripts, isoformSequences: isoformSequences })) : null),
89
+ React.createElement(DialogActions, null,
90
+ React.createElement(ProteinViewActions, { handleClose: handleClose, uniprotId: uniprotId, userSelectedProteinSequence: selectedIsoform, selectedTranscript: selectedTranscript, url: selectedPdbId ? getPdbStructureUrl(selectedPdbId) : undefined, feature: feature, view: view, session: session, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: onAlignmentAlgorithmChange, isLoading: isLoading, error: error }))));
91
+ });
92
+ export default PdbSearch;
@@ -16,6 +16,7 @@ export default function ProteinViewActions({ handleClose, uniprotId, userSelecte
16
16
  uniprotId,
17
17
  userSelectedProteinSequence,
18
18
  selectedTranscript,
19
+ url,
19
20
  });
20
21
  // Disable launch while loading — SWR's keepPreviousData would otherwise let
21
22
  // a user click Launch on stale results (wrong UniProt ID) during a refetch.
@@ -1,34 +1,18 @@
1
1
  import type { SequenceSearchType } from './useAlphaFoldSequenceSearch';
2
- import type { LookupMode } from '../components/UniProtIdInput';
3
2
  import type { Feature } from '@jbrowse/core/util';
4
3
  import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
5
4
  export default function useAlphaFoldDBSearch({ feature, view, }: {
6
5
  feature: Feature;
7
6
  view: LinearGenomeViewModel;
8
7
  }): {
9
- lookupMode: LookupMode;
10
- setLookupMode: import("react").Dispatch<import("react").SetStateAction<LookupMode>>;
11
- manualUniprotId: string;
12
- setManualUniprotId: import("react").Dispatch<import("react").SetStateAction<string>>;
13
- taxonId: string;
14
- setTaxonId: import("react").Dispatch<import("react").SetStateAction<string>>;
15
- effectiveTaxonId: number;
16
- selectedQueryId: string;
17
- setSelectedQueryId: import("react").Dispatch<import("react").SetStateAction<string>>;
18
8
  sequenceSearchType: SequenceSearchType;
19
9
  setSequenceSearchType: import("react").Dispatch<import("react").SetStateAction<SequenceSearchType>>;
20
- selectedUniprotId: string | undefined;
21
- setSelectedUniprotId: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
22
10
  userSelection: string | undefined;
23
11
  setUserSelection: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
24
12
  transcriptOptions: Feature[];
25
13
  selectedTranscript: Feature | undefined;
26
14
  isoformSequences: import("../utils/util").IsoformSequences | undefined;
27
15
  userSelectedProteinSequence: import("../utils/util").IsoformSequence | undefined;
28
- uniprotEntries: import("../services/lookupMethods").UniProtEntry[];
29
- recognizedIds: string[];
30
- geneName: string | undefined;
31
- featureUniprotId: string | undefined;
32
16
  uniprotId: string | undefined;
33
17
  url: string | undefined;
34
18
  confidenceUrl: string | undefined;
@@ -36,15 +20,34 @@ export default function useAlphaFoldDBSearch({ feature, view, }: {
36
20
  error: any;
37
21
  loadingStatuses: string[];
38
22
  isSequenceSearchLoading: boolean;
39
- showIdentifierSelector: boolean;
40
23
  showStructureSelectors: boolean;
41
24
  sequencesMatch: boolean | undefined;
42
- searchDescription: string;
43
- searchDescriptionOr: string;
44
- selectedTableAccession: string | undefined;
45
25
  showUniprotResults: boolean;
46
26
  showNoResults: boolean;
47
27
  showSequenceSearchStatus: boolean;
48
28
  showAlphaFoldDBSearchStatus: boolean;
49
29
  isLoading: boolean;
30
+ lookupMode: import("../components/UniProtIdInput").LookupMode;
31
+ setLookupMode: import("react").Dispatch<import("react").SetStateAction<import("../components/UniProtIdInput").LookupMode>>;
32
+ manualUniprotId: string;
33
+ setManualUniprotId: import("react").Dispatch<import("react").SetStateAction<string>>;
34
+ taxonId: string;
35
+ setTaxonId: import("react").Dispatch<import("react").SetStateAction<string>>;
36
+ effectiveTaxonId: number;
37
+ selectedQueryId: string;
38
+ setSelectedQueryId: import("react").Dispatch<import("react").SetStateAction<string>>;
39
+ selectedUniprotId: string | undefined;
40
+ setSelectedUniprotId: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
41
+ selectedTableAccession: string | undefined;
42
+ uniprotEntries: import("../services/lookupMethods").UniProtEntry[];
43
+ isLookupLoading: boolean;
44
+ lookupError: any;
45
+ featureUniprotId: string | undefined;
46
+ recognizedIds: string[];
47
+ geneName: string | undefined;
48
+ isAutoMode: boolean;
49
+ isSequenceMode: boolean;
50
+ showIdentifierSelector: boolean;
51
+ searchDescription: string;
52
+ searchDescriptionOr: string;
50
53
  };
@@ -1,65 +1,14 @@
1
1
  import { useState } from 'react';
2
- import { getConf } from '@jbrowse/core/configuration';
3
- import { getSession } from '@jbrowse/core/util';
4
2
  import useAlphaFoldData from './useAlphaFoldData';
5
3
  import useAlphaFoldSequenceSearch from './useAlphaFoldSequenceSearch';
6
- import useDebouncedValue from './useDebouncedValue';
7
4
  import useTranscriptIsoformSelection from './useTranscriptIsoformSelection';
8
- import useUniProtSearch from './useUniProtSearch';
9
- import getSearchDescription from '../utils/getSearchDescription';
10
- import { extractFeatureIdentifiers, extractTaxonId, stripStopCodon, } from '../utils/util';
5
+ import useUniProtIdLookup from './useUniProtIdLookup';
6
+ import { stripStopCodon } from '../utils/util';
11
7
  export default function useAlphaFoldDBSearch({ feature, view, }) {
12
- const [lookupMode, setLookupMode] = useState('auto');
13
- const [manualUniprotId, setManualUniprotId] = useState('');
14
- const [taxonIdInput, setTaxonIdInput] = useState('');
15
- const geneIds = extractFeatureIdentifiers(feature);
16
- // The gene-name UniProt search is ambiguous across species, so scope it to
17
- // the assembly's organism. jb2hubs assemblies carry the NCBI taxon in the
18
- // reference-sequence track metadata (UCSC: metadata.taxId, GenArk:
19
- // metadata.ucsc.taxId). Falls back to human via searchUniProtEntries when
20
- // absent; a user override (taxonIdInput) always wins.
21
- const assemblyName = view.assemblyNames[0];
22
- const assembly = assemblyName
23
- ? getSession(view).assemblyManager.get(assemblyName)
24
- : undefined;
25
- const assemblyTaxonId = assembly
26
- ? extractTaxonId(getConf(assembly, ['sequence', 'metadata']))
27
- : undefined;
28
- const overrideTaxon = Number(taxonIdInput.trim());
29
- const effectiveTaxonId = taxonIdInput.trim() !== '' &&
30
- Number.isFinite(overrideTaxon) &&
31
- overrideTaxon > 0
32
- ? overrideTaxon
33
- : assemblyTaxonId;
34
- const [selectedQueryId, setSelectedQueryId] = useState('auto');
8
+ const lookup = useUniProtIdLookup({ feature, view });
9
+ const { uniprotId, isSequenceMode, isAutoMode, isLookupLoading } = lookup;
35
10
  const [sequenceSearchType, setSequenceSearchType] = useState('md5');
36
- const [selectedUniprotId, setSelectedUniprotId] = useState();
37
- const featureUniprotId = geneIds.uniprotId;
38
- const effectiveLookupMode = lookupMode === 'auto' && featureUniprotId ? 'feature' : lookupMode;
39
- const isSequenceMode = effectiveLookupMode === 'sequence';
40
- const isAutoMode = effectiveLookupMode === 'auto';
41
- const { entries: uniprotEntries, isLoading: isLookupLoading, error: lookupError, } = useUniProtSearch({
42
- recognizedIds: geneIds.recognizedIds,
43
- geneId: geneIds.geneId,
44
- geneName: geneIds.geneName,
45
- organismId: effectiveTaxonId,
46
- selectedQueryId,
47
- enabled: isAutoMode,
48
- });
49
- // Debounce manual entry so fetches don't fire on every keystroke and
50
- // pollute the SWR cache with partial-ID 404s.
51
- const debouncedManualUniprotId = useDebouncedValue(manualUniprotId, 400);
52
- const autoUniprotId = uniprotEntries[0]?.accession;
53
- const uniprotId = effectiveLookupMode === 'feature'
54
- ? featureUniprotId
55
- : isAutoMode
56
- ? (selectedUniprotId ?? autoUniprotId)
57
- : effectiveLookupMode === 'manual'
58
- ? debouncedManualUniprotId
59
- : undefined;
60
- const { isLoading: isAlphaFoldLoading, isValidating: isAlphaFoldValidating, error: alphaFoldError, url: alphaFoldUrl, confidenceUrl: alphaFoldConfidenceUrl, structureSequences: alphaFoldStructureSequences, } = useAlphaFoldData({
61
- uniprotId: isSequenceMode ? undefined : uniprotId,
62
- });
11
+ const { isLoading: isAlphaFoldLoading, isValidating: isAlphaFoldValidating, error: alphaFoldError, url: alphaFoldUrl, confidenceUrl: alphaFoldConfidenceUrl, structureSequences: alphaFoldStructureSequences, } = useAlphaFoldData({ uniprotId });
63
12
  const { transcripts: transcriptOptions, isoformSequences, structureSequence: alphaFoldStructureSequence, isLoading: isIsoformLoading, error: isoformError, selectedTranscriptId: effectiveTranscriptId, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: userSelectedProteinSequence, } = useTranscriptIsoformSelection({
64
13
  feature,
65
14
  view,
@@ -99,33 +48,18 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
99
48
  // Only show errors once all loading is done — the synchronous
100
49
  // effectiveLookupMode and autoTranscriptId computations prevent the
101
50
  // one-frame gaps that previously caused brief error flashes
102
- const rawError = isoformError ?? lookupError ?? alphaFoldError ?? sequenceSearchError;
51
+ const rawError = isoformError ?? lookup.lookupError ?? alphaFoldError ?? sequenceSearchError;
103
52
  const error = isLoading ? undefined : rawError;
104
53
  return {
105
- lookupMode: effectiveLookupMode,
106
- setLookupMode,
107
- manualUniprotId,
108
- setManualUniprotId,
109
- taxonId: taxonIdInput,
110
- setTaxonId: setTaxonIdInput,
111
- // shown as the field placeholder so the user sees the organism in effect
112
- effectiveTaxonId: effectiveTaxonId ?? 9606,
113
- selectedQueryId,
114
- setSelectedQueryId,
54
+ ...lookup,
115
55
  sequenceSearchType,
116
56
  setSequenceSearchType,
117
- selectedUniprotId,
118
- setSelectedUniprotId,
119
57
  userSelection: effectiveTranscriptId,
120
58
  setUserSelection,
121
59
  transcriptOptions,
122
60
  selectedTranscript,
123
61
  isoformSequences,
124
62
  userSelectedProteinSequence,
125
- uniprotEntries,
126
- recognizedIds: geneIds.recognizedIds,
127
- geneName: geneIds.geneName,
128
- featureUniprotId,
129
63
  uniprotId: finalUniprotId,
130
64
  url: finalUrl,
131
65
  confidenceUrl: finalConfidenceUrl,
@@ -133,7 +67,6 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
133
67
  error,
134
68
  loadingStatuses,
135
69
  isSequenceSearchLoading,
136
- showIdentifierSelector: isAutoMode && (geneIds.recognizedIds.length > 0 || !!geneIds.geneName),
137
70
  showStructureSelectors: !!isoformSequences &&
138
71
  !!selectedTranscript &&
139
72
  (isSequenceMode || !!(finalStructureSequence && finalUniprotId)),
@@ -143,25 +76,13 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
143
76
  ? stripStopCodon(userSelectedProteinSequence.seq) ===
144
77
  finalStructureSequence
145
78
  : undefined,
146
- searchDescription: getSearchDescription({
147
- selectedQueryId,
148
- recognizedIds: geneIds.recognizedIds,
149
- geneName: geneIds.geneName,
150
- }),
151
- searchDescriptionOr: getSearchDescription({
152
- selectedQueryId,
153
- recognizedIds: geneIds.recognizedIds,
154
- geneName: geneIds.geneName,
155
- joinWord: 'or',
156
- }),
157
- selectedTableAccession: selectedUniprotId ?? autoUniprotId,
158
79
  showUniprotResults: !!isoformSequences &&
159
80
  isAutoMode &&
160
- (uniprotEntries.length > 0 || isLookupLoading),
81
+ (lookup.uniprotEntries.length > 0 || isLookupLoading),
161
82
  showNoResults: !!isoformSequences &&
162
83
  isAutoMode &&
163
84
  !isLookupLoading &&
164
- uniprotEntries.length === 0,
85
+ lookup.uniprotEntries.length === 0,
165
86
  showSequenceSearchStatus: isSequenceMode,
166
87
  showAlphaFoldDBSearchStatus: !!finalStructureSequence && !!finalUniprotId && !isSequenceMode,
167
88
  isLoading,
@@ -0,0 +1,6 @@
1
+ import { type PdbStructureEntry } from '../services/pdbeBestStructures';
2
+ export default function usePdbBestStructures(uniprotId: string | undefined): {
3
+ entries: PdbStructureEntry[] | undefined;
4
+ error: any;
5
+ isLoading: boolean;
6
+ };
@@ -0,0 +1,19 @@
1
+ import useSWR from 'swr';
2
+ import { STATIC_SWR_OPTIONS } from './swrOptions';
3
+ import { parseBestStructures, pdbeBestStructuresUrl, } from '../services/pdbeBestStructures';
4
+ // PDBe answers 404 for an accession with no experimental structure, which is
5
+ // the ordinary case for most genes rather than a failure.
6
+ async function fetchBestStructures(url) {
7
+ const response = await fetch(url);
8
+ if (response.status === 404) {
9
+ return [];
10
+ }
11
+ if (!response.ok) {
12
+ throw new Error(`HTTP ${response.status} fetching ${url} ${await response.text()}`);
13
+ }
14
+ return parseBestStructures(await response.json());
15
+ }
16
+ export default function usePdbBestStructures(uniprotId) {
17
+ const { data, error, isLoading } = useSWR(uniprotId ? pdbeBestStructuresUrl(uniprotId) : null, fetchBestStructures, STATIC_SWR_OPTIONS);
18
+ return { entries: data, error, isLoading };
19
+ }
@@ -0,0 +1,39 @@
1
+ import type { LookupMode } from '../components/UniProtIdInput';
2
+ import type { Feature } from '@jbrowse/core/util';
3
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
4
+ /**
5
+ * Which UniProt entry a feature is, by the dialog's lookup modes: the
6
+ * feature's own attribute, the ID-mapping search over its recognised ids and
7
+ * gene name, a typed accession, or none (sequence mode, where the structure
8
+ * search itself names the entry). Shared by every tab that starts from an
9
+ * accession so they agree on what the gene is.
10
+ */
11
+ export default function useUniProtIdLookup({ feature, view, }: {
12
+ feature: Feature;
13
+ view: LinearGenomeViewModel;
14
+ }): {
15
+ lookupMode: LookupMode;
16
+ setLookupMode: import("react").Dispatch<import("react").SetStateAction<LookupMode>>;
17
+ manualUniprotId: string;
18
+ setManualUniprotId: import("react").Dispatch<import("react").SetStateAction<string>>;
19
+ taxonId: string;
20
+ setTaxonId: import("react").Dispatch<import("react").SetStateAction<string>>;
21
+ effectiveTaxonId: number;
22
+ selectedQueryId: string;
23
+ setSelectedQueryId: import("react").Dispatch<import("react").SetStateAction<string>>;
24
+ selectedUniprotId: string | undefined;
25
+ setSelectedUniprotId: import("react").Dispatch<import("react").SetStateAction<string | undefined>>;
26
+ selectedTableAccession: string | undefined;
27
+ uniprotEntries: import("../services/lookupMethods").UniProtEntry[];
28
+ isLookupLoading: boolean;
29
+ lookupError: any;
30
+ uniprotId: string | undefined;
31
+ featureUniprotId: string | undefined;
32
+ recognizedIds: string[];
33
+ geneName: string | undefined;
34
+ isAutoMode: boolean;
35
+ isSequenceMode: boolean;
36
+ showIdentifierSelector: boolean;
37
+ searchDescription: string;
38
+ searchDescriptionOr: string;
39
+ };
@@ -0,0 +1,99 @@
1
+ import { useState } from 'react';
2
+ import { getConf } from '@jbrowse/core/configuration';
3
+ import { getSession } from '@jbrowse/core/util';
4
+ import useDebouncedValue from './useDebouncedValue';
5
+ import useUniProtSearch from './useUniProtSearch';
6
+ import getSearchDescription from '../utils/getSearchDescription';
7
+ import { extractFeatureIdentifiers, extractTaxonId } from '../utils/util';
8
+ /**
9
+ * Which UniProt entry a feature is, by the dialog's lookup modes: the
10
+ * feature's own attribute, the ID-mapping search over its recognised ids and
11
+ * gene name, a typed accession, or none (sequence mode, where the structure
12
+ * search itself names the entry). Shared by every tab that starts from an
13
+ * accession so they agree on what the gene is.
14
+ */
15
+ export default function useUniProtIdLookup({ feature, view, }) {
16
+ const [lookupMode, setLookupMode] = useState('auto');
17
+ const [manualUniprotId, setManualUniprotId] = useState('');
18
+ const [taxonIdInput, setTaxonIdInput] = useState('');
19
+ const geneIds = extractFeatureIdentifiers(feature);
20
+ // The gene-name UniProt search is ambiguous across species, so scope it to
21
+ // the assembly's organism. jb2hubs assemblies carry the NCBI taxon in the
22
+ // reference-sequence track metadata (UCSC: metadata.taxId, GenArk:
23
+ // metadata.ucsc.taxId). Falls back to human via searchUniProtEntries when
24
+ // absent; a user override (taxonIdInput) always wins.
25
+ const assemblyName = view.assemblyNames[0];
26
+ const assembly = assemblyName
27
+ ? getSession(view).assemblyManager.get(assemblyName)
28
+ : undefined;
29
+ const assemblyTaxonId = assembly
30
+ ? extractTaxonId(getConf(assembly, ['sequence', 'metadata']))
31
+ : undefined;
32
+ const overrideTaxon = Number(taxonIdInput.trim());
33
+ const effectiveTaxonId = taxonIdInput.trim() !== '' &&
34
+ Number.isFinite(overrideTaxon) &&
35
+ overrideTaxon > 0
36
+ ? overrideTaxon
37
+ : assemblyTaxonId;
38
+ const [selectedQueryId, setSelectedQueryId] = useState('auto');
39
+ const [selectedUniprotId, setSelectedUniprotId] = useState();
40
+ const featureUniprotId = geneIds.uniprotId;
41
+ const effectiveLookupMode = lookupMode === 'auto' && featureUniprotId ? 'feature' : lookupMode;
42
+ const isSequenceMode = effectiveLookupMode === 'sequence';
43
+ const isAutoMode = effectiveLookupMode === 'auto';
44
+ const { entries: uniprotEntries, isLoading: isLookupLoading, error: lookupError, } = useUniProtSearch({
45
+ recognizedIds: geneIds.recognizedIds,
46
+ geneId: geneIds.geneId,
47
+ geneName: geneIds.geneName,
48
+ organismId: effectiveTaxonId,
49
+ selectedQueryId,
50
+ enabled: isAutoMode,
51
+ });
52
+ // Debounce manual entry so fetches don't fire on every keystroke and
53
+ // pollute the SWR cache with partial-ID 404s.
54
+ const debouncedManualUniprotId = useDebouncedValue(manualUniprotId, 400);
55
+ const autoUniprotId = uniprotEntries[0]?.accession;
56
+ const uniprotId = effectiveLookupMode === 'feature'
57
+ ? featureUniprotId
58
+ : isAutoMode
59
+ ? (selectedUniprotId ?? autoUniprotId)
60
+ : effectiveLookupMode === 'manual'
61
+ ? debouncedManualUniprotId
62
+ : undefined;
63
+ return {
64
+ lookupMode: effectiveLookupMode,
65
+ setLookupMode,
66
+ manualUniprotId,
67
+ setManualUniprotId,
68
+ taxonId: taxonIdInput,
69
+ setTaxonId: setTaxonIdInput,
70
+ // shown as the field placeholder so the user sees the organism in effect
71
+ effectiveTaxonId: effectiveTaxonId ?? 9606,
72
+ selectedQueryId,
73
+ setSelectedQueryId,
74
+ selectedUniprotId,
75
+ setSelectedUniprotId,
76
+ selectedTableAccession: selectedUniprotId ?? autoUniprotId,
77
+ uniprotEntries,
78
+ isLookupLoading,
79
+ lookupError,
80
+ uniprotId,
81
+ featureUniprotId,
82
+ recognizedIds: geneIds.recognizedIds,
83
+ geneName: geneIds.geneName,
84
+ isAutoMode,
85
+ isSequenceMode,
86
+ showIdentifierSelector: isAutoMode && (geneIds.recognizedIds.length > 0 || !!geneIds.geneName),
87
+ searchDescription: getSearchDescription({
88
+ selectedQueryId,
89
+ recognizedIds: geneIds.recognizedIds,
90
+ geneName: geneIds.geneName,
91
+ }),
92
+ searchDescriptionOr: getSearchDescription({
93
+ selectedQueryId,
94
+ recognizedIds: geneIds.recognizedIds,
95
+ geneName: geneIds.geneName,
96
+ joinWord: 'or',
97
+ }),
98
+ };
99
+ }