jbrowse-plugin-protein3d 0.4.10 → 0.4.12

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 (110) hide show
  1. package/dist/LaunchProteinView/components/AlphaFoldDBSearch.d.ts +5 -3
  2. package/dist/LaunchProteinView/components/AlphaFoldDBSearch.js +1 -4
  3. package/dist/LaunchProteinView/components/AlphaFoldDBSearchStatus.js +8 -13
  4. package/dist/LaunchProteinView/components/FoldseekSearch.d.ts +5 -3
  5. package/dist/LaunchProteinView/components/FoldseekSearch.js +7 -15
  6. package/dist/LaunchProteinView/components/IsoformSequencesToggle.d.ts +10 -0
  7. package/dist/LaunchProteinView/components/IsoformSequencesToggle.js +13 -0
  8. package/dist/LaunchProteinView/components/LaunchProteinViewDialog.js +6 -3
  9. package/dist/LaunchProteinView/components/StructureSourcePicker.d.ts +1 -2
  10. package/dist/LaunchProteinView/components/StructureSourcePicker.js +1 -1
  11. package/dist/LaunchProteinView/components/UserProvidedStructure.d.ts +6 -3
  12. package/dist/LaunchProteinView/components/UserProvidedStructure.js +10 -35
  13. package/dist/LaunchProteinView/hooks/swrOptions.d.ts +5 -0
  14. package/dist/LaunchProteinView/hooks/swrOptions.js +8 -0
  15. package/dist/LaunchProteinView/hooks/useAlphaFoldDBSearch.js +17 -15
  16. package/dist/LaunchProteinView/hooks/useAlphaFoldData.d.ts +1 -0
  17. package/dist/LaunchProteinView/hooks/useAlphaFoldData.js +3 -4
  18. package/dist/LaunchProteinView/hooks/useAlphaFoldSequenceSearch.d.ts +1 -0
  19. package/dist/LaunchProteinView/hooks/useAlphaFoldSequenceSearch.js +4 -4
  20. package/dist/LaunchProteinView/hooks/useFoldseekSearch.js +47 -12
  21. package/dist/LaunchProteinView/hooks/useIsoformProteinSequences.js +8 -4
  22. package/dist/LaunchProteinView/hooks/useStructureFileSequence.d.ts +9 -0
  23. package/dist/LaunchProteinView/hooks/useStructureFileSequence.js +50 -0
  24. package/dist/LaunchProteinView/hooks/useTranscriptIsoformSelection.d.ts +24 -0
  25. package/dist/LaunchProteinView/hooks/useTranscriptIsoformSelection.js +33 -0
  26. package/dist/LaunchProteinView/hooks/useUniProtSearch.js +2 -3
  27. package/dist/LaunchProteinView/services/foldseekApi.d.ts +23 -5
  28. package/dist/LaunchProteinView/services/foldseekApi.js +21 -13
  29. package/dist/LaunchProteinView/utils/calculateProteinSequence.d.ts +4 -5
  30. package/dist/LaunchProteinView/utils/calculateProteinSequence.js +2 -4
  31. package/dist/LaunchProteinView/utils/util.d.ts +0 -1
  32. package/dist/LaunchProteinView/utils/util.js +0 -3
  33. package/dist/LaunchProteinViewExtensionPoint/index.js +44 -7
  34. package/dist/LaunchProteinViewExtensionPoint/resolveShortLaunch.d.ts +26 -0
  35. package/dist/LaunchProteinViewExtensionPoint/resolveShortLaunch.js +91 -0
  36. package/dist/ProteinView/applyColorTheme.d.ts +1 -1
  37. package/dist/ProteinView/components/FeatureBar.js +2 -2
  38. package/dist/ProteinView/components/FeatureTypeLabel.js +2 -2
  39. package/dist/ProteinView/components/HeaderStructureInfo.d.ts +1 -1
  40. package/dist/ProteinView/components/HeaderStructureInfo.js +12 -6
  41. package/dist/ProteinView/components/ProteinAlignment.js +5 -5
  42. package/dist/ProteinView/components/ProteinFeatureTrack.js +3 -3
  43. package/dist/ProteinView/components/ProteinView.js +7 -2
  44. package/dist/ProteinView/components/ProteinViewHeader.js +69 -18
  45. package/dist/ProteinView/components/ResidueValueTrack.js +4 -4
  46. package/dist/ProteinView/constants.d.ts +4 -2
  47. package/dist/ProteinView/constants.js +4 -2
  48. package/dist/ProteinView/loadStructureData.d.ts +18 -0
  49. package/dist/ProteinView/loadStructureData.js +22 -0
  50. package/dist/ProteinView/model.d.ts +25 -11
  51. package/dist/ProteinView/model.js +18 -36
  52. package/dist/ProteinView/structureLoader.d.ts +30 -0
  53. package/dist/ProteinView/structureLoader.js +58 -0
  54. package/dist/ProteinView/structureModel.d.ts +26 -1
  55. package/dist/ProteinView/structureModel.js +53 -8
  56. package/dist/ProteinView/useProteinView.js +43 -8
  57. package/dist/config.json +1 -1
  58. package/dist/fetchUtils.d.ts +1 -1
  59. package/dist/fetchUtils.js +18 -2
  60. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +16 -16
  61. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  62. package/dist/molstar-chunk.js.map +1 -1
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.js +1 -1
  65. package/package.json +5 -2
  66. package/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx +5 -6
  67. package/src/LaunchProteinView/components/AlphaFoldDBSearchStatus.tsx +12 -27
  68. package/src/LaunchProteinView/components/FoldseekSearch.tsx +17 -24
  69. package/src/LaunchProteinView/components/IsoformSequencesToggle.tsx +41 -0
  70. package/src/LaunchProteinView/components/LaunchProteinViewDialog.tsx +10 -3
  71. package/src/LaunchProteinView/components/StructureSourcePicker.tsx +0 -2
  72. package/src/LaunchProteinView/components/UserProvidedStructure.tsx +24 -53
  73. package/src/LaunchProteinView/hooks/swrOptions.ts +8 -0
  74. package/src/LaunchProteinView/hooks/useAlphaFoldDBSearch.ts +30 -29
  75. package/src/LaunchProteinView/hooks/useAlphaFoldData.ts +4 -4
  76. package/src/LaunchProteinView/hooks/useAlphaFoldSequenceSearch.ts +13 -12
  77. package/src/LaunchProteinView/hooks/useFoldseekSearch.ts +49 -12
  78. package/src/LaunchProteinView/hooks/useIsoformProteinSequences.ts +8 -4
  79. package/src/LaunchProteinView/hooks/useStructureFileSequence.ts +67 -0
  80. package/src/LaunchProteinView/hooks/useTranscriptIsoformSelection.ts +47 -0
  81. package/src/LaunchProteinView/hooks/useUniProtSearch.ts +2 -3
  82. package/src/LaunchProteinView/services/foldseekApi.ts +57 -23
  83. package/src/LaunchProteinView/utils/calculateProteinSequence.ts +5 -6
  84. package/src/LaunchProteinView/utils/util.ts +0 -4
  85. package/src/LaunchProteinViewExtensionPoint/index.ts +54 -6
  86. package/src/LaunchProteinViewExtensionPoint/resolveShortLaunch.ts +143 -0
  87. package/src/ProteinView/components/FeatureBar.tsx +2 -7
  88. package/src/ProteinView/components/FeatureTypeLabel.tsx +2 -2
  89. package/src/ProteinView/components/HeaderStructureInfo.tsx +21 -10
  90. package/src/ProteinView/components/ProteinAlignment.tsx +13 -9
  91. package/src/ProteinView/components/ProteinFeatureTrack.tsx +3 -3
  92. package/src/ProteinView/components/ProteinView.tsx +11 -2
  93. package/src/ProteinView/components/ProteinViewHeader.tsx +104 -43
  94. package/src/ProteinView/components/ResidueValueTrack.tsx +4 -4
  95. package/src/ProteinView/constants.ts +4 -2
  96. package/src/ProteinView/loadStructureData.ts +36 -0
  97. package/src/ProteinView/model.ts +18 -47
  98. package/src/ProteinView/structureLoader.test.ts +102 -0
  99. package/src/ProteinView/structureLoader.ts +74 -0
  100. package/src/ProteinView/structureModel.ts +63 -8
  101. package/src/ProteinView/useProteinView.ts +49 -8
  102. package/src/fetchUtils.test.ts +27 -0
  103. package/src/fetchUtils.ts +22 -2
  104. package/src/version.ts +1 -1
  105. package/dist/LaunchProteinView/hooks/useLocalStructureFileSequence.d.ts +0 -7
  106. package/dist/LaunchProteinView/hooks/useLocalStructureFileSequence.js +0 -39
  107. package/dist/LaunchProteinView/hooks/useRemoteStructureFileSequence.d.ts +0 -7
  108. package/dist/LaunchProteinView/hooks/useRemoteStructureFileSequence.js +0 -28
  109. package/src/LaunchProteinView/hooks/useLocalStructureFileSequence.ts +0 -60
  110. package/src/LaunchProteinView/hooks/useRemoteStructureFileSequence.ts +0 -41
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import type { AlignmentAlgorithm } from '../../ProteinView/types';
3
- import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
4
- declare const AlphaFoldDBSearch: ({ feature, model, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }: {
3
+ import type { AbstractSessionModel, Feature } from '@jbrowse/core/util';
4
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
5
+ declare const AlphaFoldDBSearch: ({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }: {
5
6
  feature: Feature;
6
- model: AbstractTrackModel;
7
+ session: AbstractSessionModel;
8
+ view: LinearGenomeViewModel;
7
9
  handleClose: () => void;
8
10
  alignmentAlgorithm: AlignmentAlgorithm;
9
11
  onAlignmentAlgorithmChange: (algorithm: AlignmentAlgorithm) => void;
@@ -1,6 +1,5 @@
1
1
  import React from 'react';
2
2
  import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
- import { getContainingView, getSession } from '@jbrowse/core/util';
4
3
  import { DialogActions, DialogContent, Typography } from '@mui/material';
5
4
  import { observer } from 'mobx-react';
6
5
  import { makeStyles } from 'tss-react/mui';
@@ -30,10 +29,8 @@ const useStyles = makeStyles()({
30
29
  alignItems: 'flex-start',
31
30
  },
32
31
  });
33
- const AlphaFoldDBSearch = observer(function AlphaFoldDBSearch({ feature, model, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
32
+ const AlphaFoldDBSearch = observer(function AlphaFoldDBSearch({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
34
33
  const { classes } = useStyles();
35
- const session = getSession(model);
36
- const view = getContainingView(model);
37
34
  const state = useAlphaFoldDBSearch({ feature, view });
38
35
  return (React.createElement(React.Fragment, null,
39
36
  React.createElement(DialogContent, { className: classes.dialogContent },
@@ -1,8 +1,8 @@
1
- import React, { useState } from 'react';
2
- import { Button, Typography } from '@mui/material';
3
- import MSATable from './MSATable';
1
+ import React from 'react';
2
+ import { Typography } from '@mui/material';
3
+ import IsoformSequencesToggle from './IsoformSequencesToggle';
4
4
  import ExternalLink from '../../components/ExternalLink';
5
- import { getDisplayName } from '../utils/util';
5
+ import { getTranscriptDisplayName } from '../utils/util';
6
6
  function NotFound({ uniprotId }) {
7
7
  return (React.createElement(Typography, null,
8
8
  "No structure found for this UniProtID in AlphaFoldDB",
@@ -10,7 +10,6 @@ function NotFound({ uniprotId }) {
10
10
  React.createElement(ExternalLink, { href: `https://alphafold.ebi.ac.uk/search/text/${uniprotId}` }, "(search for results)")));
11
11
  }
12
12
  export default function AlphaFoldDBSearchStatus({ uniprotId, selectedTranscript, structureSequence, isoformSequences, url, }) {
13
- const [showAllProteinSequences, setShowAllProteinSequences] = useState(false);
14
13
  return uniprotId ? (React.createElement(React.Fragment, null,
15
14
  React.createElement("div", null,
16
15
  React.createElement(Typography, null,
@@ -20,16 +19,12 @@ export default function AlphaFoldDBSearchStatus({ uniprotId, selectedTranscript,
20
19
  React.createElement(Typography, null,
21
20
  "AlphaFoldDB link: ",
22
21
  React.createElement(ExternalLink, { href: url }, url))),
23
- structureSequence ? (React.createElement("div", { style: { margin: 20 } },
24
- React.createElement(Button, { variant: "contained", color: "primary", onClick: () => {
25
- setShowAllProteinSequences(!showAllProteinSequences);
26
- } }, showAllProteinSequences
27
- ? 'Hide all isoform protein sequences'
28
- : 'Show all isoform protein sequences'),
29
- showAllProteinSequences ? (React.createElement(MSATable, { structureSequence: structureSequence, structureName: uniprotId, isoformSequences: isoformSequences })) : null)) : (React.createElement(NotFound, { uniprotId: uniprotId })))) : (React.createElement(Typography, null,
22
+ structureSequence ? (React.createElement(IsoformSequencesToggle, { structureSequence: structureSequence, structureName: uniprotId, isoformSequences: isoformSequences })) : (React.createElement(NotFound, { uniprotId: uniprotId })))) : (React.createElement(Typography, null,
30
23
  "Searching",
31
24
  ' ',
32
- selectedTranscript ? getDisplayName(selectedTranscript) : 'transcript',
25
+ selectedTranscript
26
+ ? getTranscriptDisplayName(selectedTranscript)
27
+ : 'transcript',
33
28
  ' ',
34
29
  "for UniProt ID"));
35
30
  }
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
- import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
3
- declare const FoldseekSearch: ({ feature, model, handleClose, }: {
2
+ import type { AbstractSessionModel, Feature } from '@jbrowse/core/util';
3
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
4
+ declare const FoldseekSearch: ({ feature, session, view, handleClose, }: {
4
5
  feature: Feature;
5
- model: AbstractTrackModel;
6
+ session: AbstractSessionModel;
7
+ view: LinearGenomeViewModel;
6
8
  handleClose: () => void;
7
9
  }) => React.JSX.Element;
8
10
  export default FoldseekSearch;
@@ -1,6 +1,5 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
- import { getContainingView, getSession } from '@jbrowse/core/util';
4
3
  import { Button, DialogActions, DialogContent, TextField, Typography, } from '@mui/material';
5
4
  import { observer } from 'mobx-react';
6
5
  import { makeStyles } from 'tss-react/mui';
@@ -8,10 +7,9 @@ import FoldseekDatabaseSelector from './FoldseekDatabaseSelector';
8
7
  import FoldseekResultsTable from './FoldseekResultsTable';
9
8
  import TranscriptSelector from './TranscriptSelector';
10
9
  import useFoldseekSearch from '../hooks/useFoldseekSearch';
11
- import useIsoformProteinSequences from '../hooks/useIsoformProteinSequences';
12
- import useTranscriptSelection from '../hooks/useTranscriptSelection';
10
+ import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection';
13
11
  import { DEFAULT_DATABASES } from '../services/foldseekApi';
14
- import { getTranscriptFeatures } from '../utils/util';
12
+ import { stripStopCodon } from '../utils/util';
15
13
  const useStyles = makeStyles()({
16
14
  dialogContent: {
17
15
  width: '80em',
@@ -28,21 +26,15 @@ const useStyles = makeStyles()({
28
26
  gap: 8,
29
27
  },
30
28
  });
31
- const FoldseekSearch = observer(function FoldseekSearch({ feature, model, handleClose, }) {
29
+ const FoldseekSearch = observer(function FoldseekSearch({ feature, session, view, handleClose, }) {
32
30
  const { classes } = useStyles();
33
- const session = getSession(model);
34
- const view = getContainingView(model);
35
31
  const [userEditedSequence, setUserEditedSequence] = useState();
36
32
  const [selectedDatabases, setSelectedDatabases] = useState(DEFAULT_DATABASES);
37
33
  const { results, cleanedAaSequence, di3Sequence, isLoading, isPredicting, error, statusMessage, predictStructure, search, reset, } = useFoldseekSearch();
38
- const transcripts = getTranscriptFeatures(feature);
39
- const { isoformSequences, isLoading: isLoadingIsoforms, error: isoformError, } = useIsoformProteinSequences({ feature, view });
40
- const { userSelection: effectiveSelectedTranscriptId, setUserSelection } = useTranscriptSelection({ options: transcripts, isoformSequences });
41
- const selectedTranscript = transcripts.find(t => t.id() === effectiveSelectedTranscriptId);
42
- const selectedIsoformData = effectiveSelectedTranscriptId
43
- ? isoformSequences?.[effectiveSelectedTranscriptId]
44
- : undefined;
45
- const cleanedSequence = selectedIsoformData?.seq.replace(/\*/g, '') ?? '';
34
+ const { transcripts, isoformSequences, isLoading: isLoadingIsoforms, error: isoformError, selectedTranscriptId: effectiveSelectedTranscriptId, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: selectedIsoformData, } = useTranscriptIsoformSelection({ feature, view });
35
+ const cleanedSequence = selectedIsoformData
36
+ ? stripStopCodon(selectedIsoformData.seq)
37
+ : '';
46
38
  const sequence = userEditedSequence ?? cleanedSequence;
47
39
  const setUserSelectionWithReset = (id) => {
48
40
  setUserSelection(id);
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import type { Feature } from '@jbrowse/core/util';
3
+ export default function IsoformSequencesToggle({ structureSequence, structureName, isoformSequences, }: {
4
+ structureSequence: string;
5
+ structureName: string;
6
+ isoformSequences: Record<string, {
7
+ feature: Feature;
8
+ seq: string;
9
+ }>;
10
+ }): React.JSX.Element;
@@ -0,0 +1,13 @@
1
+ import React, { useState } from 'react';
2
+ import { Button } from '@mui/material';
3
+ import MSATable from './MSATable';
4
+ export default function IsoformSequencesToggle({ structureSequence, structureName, isoformSequences, }) {
5
+ const [show, setShow] = useState(false);
6
+ return (React.createElement("div", { style: { margin: 10 } },
7
+ React.createElement(Button, { variant: "contained", color: "primary", onClick: () => {
8
+ setShow(!show);
9
+ } }, show
10
+ ? 'Hide all isoform protein sequences'
11
+ : 'Show all isoform protein sequences'),
12
+ show ? (React.createElement(MSATable, { structureSequence: structureSequence, structureName: structureName, isoformSequences: isoformSequences })) : null));
13
+ }
@@ -1,5 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Dialog } from '@jbrowse/core/ui';
3
+ import { getContainingView, getSession } from '@jbrowse/core/util';
3
4
  import { Tab, Tabs } from '@mui/material';
4
5
  import AlphaFoldDBSearch from './AlphaFoldDBSearch';
5
6
  import FoldseekSearch from './FoldseekSearch';
@@ -11,6 +12,8 @@ import { useLocalStorage } from '../hooks/useLocalStorage';
11
12
  export default function LaunchProteinViewDialog({ handleClose, feature, model, }) {
12
13
  const [choice, setChoice] = useState(0);
13
14
  const [alignmentAlgorithm, setAlignmentAlgorithm] = useLocalStorage('jbrowse-protein3d-alignment-algorithm', DEFAULT_ALIGNMENT_ALGORITHM);
15
+ const session = getSession(model);
16
+ const view = getContainingView(model);
14
17
  return (React.createElement(Dialog, { maxWidth: "xl", title: "Launch protein view", titleNode: React.createElement(React.Fragment, null,
15
18
  "Launch protein view ",
16
19
  React.createElement(HelpButton, null)), open: true, onClose: handleClose },
@@ -21,9 +24,9 @@ export default function LaunchProteinViewDialog({ handleClose, feature, model, }
21
24
  React.createElement(Tab, { value: 1, label: "Foldseek search" }),
22
25
  React.createElement(Tab, { value: 2, label: "Open file manually" })),
23
26
  React.createElement(TabPanel, { value: choice, index: 0 },
24
- React.createElement(AlphaFoldDBSearch, { model: model, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm })),
27
+ React.createElement(AlphaFoldDBSearch, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm })),
25
28
  React.createElement(TabPanel, { value: choice, index: 1 },
26
- React.createElement(FoldseekSearch, { model: model, feature: feature, handleClose: handleClose })),
29
+ React.createElement(FoldseekSearch, { session: session, view: view, feature: feature, handleClose: handleClose })),
27
30
  React.createElement(TabPanel, { value: choice, index: 2 },
28
- React.createElement(UserProvidedStructure, { model: model, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm }))));
31
+ React.createElement(UserProvidedStructure, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm }))));
29
32
  }
@@ -1,10 +1,9 @@
1
1
  import React from 'react';
2
- export default function StructureSourcePicker({ choice, setChoice, structureURL, setStructureURL, file: _file, setFile, pdbId, setPdbId, }: {
2
+ export default function StructureSourcePicker({ choice, setChoice, structureURL, setStructureURL, setFile, pdbId, setPdbId, }: {
3
3
  choice: string;
4
4
  setChoice: (c: string) => void;
5
5
  structureURL: string;
6
6
  setStructureURL: (url: string) => void;
7
- file: File | undefined;
8
7
  setFile: (f: File) => void;
9
8
  pdbId: string;
10
9
  setPdbId: (id: string) => void;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { Button, FormControl, FormControlLabel, Radio, RadioGroup, TextField, Typography, } from '@mui/material';
3
3
  import HelpButton from './HelpButton';
4
4
  import { getPdbStructureUrl } from '../utils/launchViewUtils';
5
- export default function StructureSourcePicker({ choice, setChoice, structureURL, setStructureURL, file: _file, setFile, pdbId, setPdbId, }) {
5
+ export default function StructureSourcePicker({ choice, setChoice, structureURL, setStructureURL, setFile, pdbId, setPdbId, }) {
6
6
  return (React.createElement("div", { style: { display: 'flex', margin: 30 } },
7
7
  React.createElement(Typography, null,
8
8
  "Open your structure file ",
@@ -1,9 +1,12 @@
1
1
  import React from 'react';
2
2
  import type { AlignmentAlgorithm } from '../../ProteinView/types';
3
- import type { AbstractTrackModel, Feature } from '@jbrowse/core/util';
4
- declare const UserProvidedStructure: ({ feature, model, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }: {
3
+ import type { AbstractSessionModel, Feature } from '@jbrowse/core/util';
4
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view';
5
+ type LGV = LinearGenomeViewModel;
6
+ declare const UserProvidedStructure: ({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }: {
5
7
  feature: Feature;
6
- model: AbstractTrackModel;
8
+ session: AbstractSessionModel;
9
+ view: LGV;
7
10
  handleClose: () => void;
8
11
  alignmentAlgorithm: AlignmentAlgorithm;
9
12
  onAlignmentAlgorithmChange: (algorithm: AlignmentAlgorithm) => void;
@@ -1,20 +1,17 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
- import { getContainingView, getSession } from '@jbrowse/core/util';
4
3
  import { Button, DialogActions, DialogContent } from '@mui/material';
5
4
  import { observer } from 'mobx-react';
6
5
  import { makeStyles } from 'tss-react/mui';
7
- import MSATable from './MSATable';
6
+ import IsoformSequencesToggle from './IsoformSequencesToggle';
8
7
  import SequenceMismatchNotice from './SequenceMismatchNotice';
9
8
  import StructureSourcePicker from './StructureSourcePicker';
10
9
  import TranscriptSelector from './TranscriptSelector';
11
10
  import ExternalLink from '../../components/ExternalLink';
12
- import useIsoformProteinSequences from '../hooks/useIsoformProteinSequences';
13
- import useLocalStructureFileSequence from '../hooks/useLocalStructureFileSequence';
14
- import useRemoteStructureFileSequence from '../hooks/useRemoteStructureFileSequence';
15
- import useTranscriptSelection from '../hooks/useTranscriptSelection';
11
+ import useStructureFileSequence from '../hooks/useStructureFileSequence';
12
+ import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection';
16
13
  import { launch3DProteinView } from '../utils/launchViewUtils';
17
- import { getGeneDisplayName, getId, getTranscriptDisplayName, getTranscriptFeatures, stripStopCodon, } from '../utils/util';
14
+ import { getGeneDisplayName, getTranscriptDisplayName, stripStopCodon, } from '../utils/util';
18
15
  const useStyles = makeStyles()(theme => ({
19
16
  dialogContent: {
20
17
  marginTop: theme.spacing(6),
@@ -31,36 +28,20 @@ function HelpText() {
31
28
  React.createElement(ExternalLink, { href: "https://github.com/sokrypton/ColabFold" }, "ColabFold"),
32
29
  ". This plugin will align the protein sequence calculated from the genome to the protein sequence embedded in the structure file which allows for slight differences in these two representations."));
33
30
  }
34
- const UserProvidedStructure = observer(function UserProvidedStructure({ feature, model, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
31
+ const UserProvidedStructure = observer(function UserProvidedStructure({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
35
32
  const { classes } = useStyles();
36
- const session = getSession(model);
37
33
  const [file, setFile] = useState();
38
34
  const [pdbId, setPdbId] = useState('');
39
35
  const [choice, setChoice] = useState('file');
40
36
  const [submitError, setSubmitError] = useState();
41
37
  const [structureURL, setStructureURL] = useState('');
42
- const [showAllProteinSequences, setShowAllProteinSequences] = useState(false);
43
38
  const activeFile = choice === 'file' ? file : undefined;
44
39
  const activeURL = choice === 'file' ? '' : structureURL;
45
- const options = getTranscriptFeatures(feature);
46
- const view = getContainingView(model);
47
- const { isoformSequences, error: isoformError } = useIsoformProteinSequences({
48
- feature,
49
- view,
50
- });
51
- const { sequences: localSequences, error: localFileError } = useLocalStructureFileSequence({ file: activeFile });
52
- const { sequences: remoteSequences, error: remoteFileError } = useRemoteStructureFileSequence({ url: activeURL });
40
+ const { sequences: structureSequences, error: fileError } = useStructureFileSequence({ file: activeFile, url: activeURL });
53
41
  const structureName = activeFile?.name ?? activeURL.slice(activeURL.lastIndexOf('/') + 1);
54
- const structureSequences = activeFile ? localSequences : remoteSequences;
55
42
  const structureSequence = structureSequences?.[0];
56
- const { userSelection, setUserSelection } = useTranscriptSelection({
57
- options,
58
- isoformSequences,
59
- structureSequence,
60
- });
61
- const selectedTranscript = options.find(val => getId(val) === userSelection);
62
- const protein = userSelection ? isoformSequences?.[userSelection] : undefined;
63
- const error = isoformError ?? submitError ?? localFileError ?? remoteFileError;
43
+ const { transcripts: options, isoformSequences, selectedTranscriptId: userSelection, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: protein, error: isoformError, } = useTranscriptIsoformSelection({ feature, view, structureSequence });
44
+ const error = isoformError ?? submitError ?? fileError;
64
45
  const canLaunch = !!(activeURL || activeFile) && !!protein && !!selectedTranscript;
65
46
  const sequencesDiffer = !!protein?.seq &&
66
47
  !!structureSequence &&
@@ -94,16 +75,10 @@ const UserProvidedStructure = observer(function UserProvidedStructure({ feature,
94
75
  React.createElement(DialogContent, { className: classes.dialogContent },
95
76
  error ? React.createElement(ErrorMessage, { error: error }) : null,
96
77
  React.createElement(HelpText, null),
97
- React.createElement(StructureSourcePicker, { choice: choice, setChoice: setChoice, structureURL: structureURL, setStructureURL: setStructureURL, file: file, setFile: setFile, pdbId: pdbId, setPdbId: setPdbId }),
78
+ React.createElement(StructureSourcePicker, { choice: choice, setChoice: setChoice, structureURL: structureURL, setStructureURL: setStructureURL, setFile: setFile, pdbId: pdbId, setPdbId: setPdbId }),
98
79
  React.createElement("div", { style: { margin: 20 } }, isoformSequences ? (structureSequence ? (React.createElement(React.Fragment, null,
99
80
  React.createElement(TranscriptSelector, { val: userSelection, setVal: setUserSelection, structureSequence: structureSequence, isoforms: options, feature: feature, isoformSequences: isoformSequences }),
100
- React.createElement("div", { style: { margin: 10 } },
101
- React.createElement(Button, { variant: "contained", color: "primary", onClick: () => {
102
- setShowAllProteinSequences(!showAllProteinSequences);
103
- } }, showAllProteinSequences
104
- ? 'Hide all isoform protein sequences'
105
- : 'Show all isoform protein sequences'),
106
- showAllProteinSequences ? (React.createElement(MSATable, { structureSequence: structureSequence, structureName: structureName, isoformSequences: isoformSequences })) : null))) : null) : (React.createElement(LoadingEllipses, { title: "Loading protein sequences", variant: "h6" })))),
81
+ React.createElement(IsoformSequencesToggle, { structureSequence: structureSequence, structureName: structureName, isoformSequences: isoformSequences }))) : null) : (React.createElement(LoadingEllipses, { title: "Loading protein sequences", variant: "h6" })))),
107
82
  React.createElement(DialogActions, null,
108
83
  sequencesDiffer ? (React.createElement(SequenceMismatchNotice, { alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: onAlignmentAlgorithmChange })) : null,
109
84
  React.createElement(Button, { variant: "contained", color: "secondary", onClick: () => {
@@ -0,0 +1,5 @@
1
+ export declare const STATIC_SWR_OPTIONS: {
2
+ readonly revalidateOnFocus: false;
3
+ readonly revalidateOnReconnect: false;
4
+ readonly revalidateIfStale: false;
5
+ };
@@ -0,0 +1,8 @@
1
+ // Shared SWR config for one-shot fetches that should never auto-revalidate
2
+ // (structure files, computed protein sequences). keepPreviousData is opt-in
3
+ // per-hook since it avoids result flicker when the key changes.
4
+ export const STATIC_SWR_OPTIONS = {
5
+ revalidateOnFocus: false,
6
+ revalidateOnReconnect: false,
7
+ revalidateIfStale: false,
8
+ };
@@ -2,11 +2,10 @@ import { useState } from 'react';
2
2
  import useAlphaFoldData from './useAlphaFoldData';
3
3
  import useAlphaFoldSequenceSearch from './useAlphaFoldSequenceSearch';
4
4
  import useDebouncedValue from './useDebouncedValue';
5
- import useIsoformProteinSequences from './useIsoformProteinSequences';
6
- import useTranscriptSelection from './useTranscriptSelection';
5
+ import useTranscriptIsoformSelection from './useTranscriptIsoformSelection';
7
6
  import useUniProtSearch from './useUniProtSearch';
8
7
  import getSearchDescription from '../utils/getSearchDescription';
9
- import { extractFeatureIdentifiers, getId, getTranscriptFeatures, stripStopCodon, } from '../utils/util';
8
+ import { extractFeatureIdentifiers, stripStopCodon } from '../utils/util';
10
9
  export default function useAlphaFoldDBSearch({ feature, view, }) {
11
10
  const [lookupMode, setLookupMode] = useState('auto');
12
11
  const [manualUniprotId, setManualUniprotId] = useState('');
@@ -14,12 +13,10 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
14
13
  const [selectedQueryId, setSelectedQueryId] = useState('auto');
15
14
  const [sequenceSearchType, setSequenceSearchType] = useState('md5');
16
15
  const [selectedUniprotId, setSelectedUniprotId] = useState();
17
- const transcriptOptions = getTranscriptFeatures(feature);
18
16
  const featureUniprotId = geneIds.uniprotId;
19
17
  const effectiveLookupMode = lookupMode === 'auto' && featureUniprotId ? 'feature' : lookupMode;
20
18
  const isSequenceMode = effectiveLookupMode === 'sequence';
21
19
  const isAutoMode = effectiveLookupMode === 'auto';
22
- const { isoformSequences, isLoading: isIsoformLoading, error: isoformError, } = useIsoformProteinSequences({ feature, view });
23
20
  const { entries: uniprotEntries, isLoading: isLookupLoading, error: lookupError, } = useUniProtSearch({
24
21
  recognizedIds: geneIds.recognizedIds,
25
22
  geneId: geneIds.geneId,
@@ -38,20 +35,16 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
38
35
  : effectiveLookupMode === 'manual'
39
36
  ? debouncedManualUniprotId
40
37
  : undefined;
41
- const { isLoading: isAlphaFoldLoading, error: alphaFoldError, url: alphaFoldUrl, confidenceUrl: alphaFoldConfidenceUrl, structureSequence: alphaFoldStructureSequence, } = useAlphaFoldData({
38
+ const { isLoading: isAlphaFoldLoading, isValidating: isAlphaFoldValidating, error: alphaFoldError, url: alphaFoldUrl, confidenceUrl: alphaFoldConfidenceUrl, structureSequence: alphaFoldStructureSequence, } = useAlphaFoldData({
42
39
  uniprotId: isSequenceMode ? undefined : uniprotId,
43
40
  });
44
- const { userSelection: effectiveTranscriptId, setUserSelection } = useTranscriptSelection({
45
- options: transcriptOptions,
46
- isoformSequences,
41
+ const { transcripts: transcriptOptions, isoformSequences, isLoading: isIsoformLoading, error: isoformError, selectedTranscriptId: effectiveTranscriptId, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: userSelectedProteinSequence, } = useTranscriptIsoformSelection({
42
+ feature,
43
+ view,
47
44
  structureSequence: alphaFoldStructureSequence,
48
45
  resetKey: uniprotId,
49
46
  });
50
- const selectedTranscript = transcriptOptions.find(f => getId(f) === effectiveTranscriptId);
51
- const userSelectedProteinSequence = effectiveTranscriptId
52
- ? isoformSequences?.[effectiveTranscriptId]
53
- : undefined;
54
- const { uniprotId: seqSearchUniprotId, cifUrl: seqSearchUrl, plddtDocUrl: seqSearchConfidenceUrl, structureSequence: seqSearchStructureSequence, isLoading: isSequenceSearchLoading, error: sequenceSearchError, } = useAlphaFoldSequenceSearch({
47
+ const { uniprotId: seqSearchUniprotId, cifUrl: seqSearchUrl, plddtDocUrl: seqSearchConfidenceUrl, structureSequence: seqSearchStructureSequence, isLoading: isSequenceSearchLoading, isValidating: isSequenceSearchValidating, error: sequenceSearchError, } = useAlphaFoldSequenceSearch({
55
48
  sequence: userSelectedProteinSequence?.seq,
56
49
  searchType: sequenceSearchType,
57
50
  enabled: isSequenceMode,
@@ -65,6 +58,13 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
65
58
  ? seqSearchStructureSequence
66
59
  : alphaFoldStructureSequence;
67
60
  const finalUniprotId = isSequenceMode ? seqSearchUniprotId : uniprotId;
61
+ // While a structure fetch is in flight, finalStructureSequence may still be
62
+ // the previous selection's sequence (keepPreviousData). Comparing that stale
63
+ // sequence to the freshly-selected transcript would give a wrong match, so
64
+ // the match is treated as unknown until the fetch settles.
65
+ const isStructureValidating = isSequenceMode
66
+ ? isSequenceSearchValidating
67
+ : isAlphaFoldValidating;
68
68
  const loadingStatuses = [
69
69
  isLookupLoading && 'Looking up UniProt ID',
70
70
  isIsoformLoading && 'Loading protein sequences from transcript isoforms',
@@ -111,7 +111,9 @@ export default function useAlphaFoldDBSearch({ feature, view, }) {
111
111
  showStructureSelectors: !!isoformSequences &&
112
112
  !!selectedTranscript &&
113
113
  (isSequenceMode || !!(finalStructureSequence && finalUniprotId)),
114
- sequencesMatch: userSelectedProteinSequence?.seq && finalStructureSequence
114
+ sequencesMatch: !isStructureValidating &&
115
+ userSelectedProteinSequence?.seq &&
116
+ finalStructureSequence
115
117
  ? stripStopCodon(userSelectedProteinSequence.seq) ===
116
118
  finalStructureSequence
117
119
  : undefined,
@@ -2,6 +2,7 @@ export default function useAlphaFoldData({ uniprotId, }: {
2
2
  uniprotId?: string;
3
3
  }): {
4
4
  isLoading: boolean;
5
+ isValidating: boolean;
5
6
  error: any;
6
7
  url: string | undefined;
7
8
  confidenceUrl: string | undefined;
@@ -1,15 +1,14 @@
1
- import useRemoteStructureFileSequence from './useRemoteStructureFileSequence';
1
+ import useStructureFileSequence from './useStructureFileSequence';
2
2
  import { getAlphaFoldConfidenceUrl, getAlphaFoldStructureUrl, } from '../utils/launchViewUtils';
3
3
  export default function useAlphaFoldData({ uniprotId, }) {
4
4
  const url = uniprotId ? getAlphaFoldStructureUrl(uniprotId) : undefined;
5
5
  const confidenceUrl = uniprotId
6
6
  ? getAlphaFoldConfidenceUrl(uniprotId)
7
7
  : undefined;
8
- const { sequences, isLoading, error } = useRemoteStructureFileSequence({
9
- url,
10
- });
8
+ const { sequences, isLoading, isValidating, error } = useStructureFileSequence({ url });
11
9
  return {
12
10
  isLoading,
11
+ isValidating,
13
12
  error,
14
13
  url,
15
14
  confidenceUrl,
@@ -19,6 +19,7 @@ export default function useAlphaFoldSequenceSearch({ sequence, searchType, enabl
19
19
  enabled?: boolean;
20
20
  }): {
21
21
  isLoading: boolean;
22
+ isValidating: boolean;
22
23
  result: SequenceSummaryResponse | undefined;
23
24
  uniprotId: string | undefined;
24
25
  cifUrl: string | undefined;
@@ -1,5 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
  import useSWR from 'swr';
3
+ import { STATIC_SWR_OPTIONS } from './swrOptions';
3
4
  import { jsonfetch } from '../../fetchUtils';
4
5
  import { md5 } from '../utils/md5';
5
6
  import { stripStopCodon } from '../utils/util';
@@ -11,16 +12,15 @@ export default function useAlphaFoldSequenceSearch({ sequence, searchType, enabl
11
12
  const cleanSeq = stripStopCodon(sequence.toUpperCase());
12
13
  return searchType === 'md5' ? md5(cleanSeq) : cleanSeq;
13
14
  }, [sequence, searchType]);
14
- const { data, error, isLoading } = useSWR(enabled && searchValue
15
+ const { data, error, isLoading, isValidating } = useSWR(enabled && searchValue
15
16
  ? `https://alphafold.ebi.ac.uk/api/sequence/summary?id=${encodeURIComponent(searchValue)}&type=${searchType}`
16
17
  : null, jsonfetch, {
17
- revalidateOnFocus: false,
18
- revalidateOnReconnect: false,
19
- revalidateIfStale: false,
18
+ ...STATIC_SWR_OPTIONS,
20
19
  keepPreviousData: true,
21
20
  });
22
21
  return {
23
22
  isLoading,
23
+ isValidating,
24
24
  result: data,
25
25
  uniprotId: data?.uniprotAccession,
26
26
  cifUrl: data?.cifUrl,
@@ -1,4 +1,4 @@
1
- import { useState } from 'react';
1
+ import { useEffect, useRef, useState } from 'react';
2
2
  import { DEFAULT_DATABASES, predict3Di, submitFoldseekSearch, waitForFoldseekResults, } from '../services/foldseekApi';
3
3
  export default function useFoldseekSearch() {
4
4
  const [results, setResults] = useState();
@@ -7,46 +7,81 @@ export default function useFoldseekSearch() {
7
7
  const [isPredicting, setIsPredicting] = useState(false);
8
8
  const [error, setError] = useState();
9
9
  const [statusMessage, setStatusMessage] = useState('');
10
+ // Aborts the in-flight request (3Di prediction or the up-to-3-minute Foldseek
11
+ // poll) when the dialog closes/unmounts, so it stops hitting the external API
12
+ // and stops updating dead state.
13
+ const abortRef = useRef(null);
14
+ useEffect(() => {
15
+ return () => {
16
+ abortRef.current?.abort();
17
+ };
18
+ }, []);
19
+ const startOperation = () => {
20
+ abortRef.current?.abort();
21
+ const controller = new AbortController();
22
+ abortRef.current = controller;
23
+ return controller.signal;
24
+ };
10
25
  const predictStructure = async (aaSequence) => {
26
+ const signal = startOperation();
11
27
  setIsPredicting(true);
12
28
  setError(undefined);
13
29
  setStatusMessage('Predicting 3Di structure...');
14
30
  try {
15
- const result = await predict3Di(aaSequence);
31
+ const result = await predict3Di({ aaSequence, signal });
16
32
  setPredictData(result);
17
33
  return result;
18
34
  }
19
35
  catch (e) {
20
- console.error(e);
21
- setError(e);
36
+ if (!signal.aborted) {
37
+ console.error(e);
38
+ setError(e);
39
+ }
22
40
  return undefined;
23
41
  }
24
42
  finally {
25
- setIsPredicting(false);
26
- setStatusMessage('');
43
+ if (!signal.aborted) {
44
+ setIsPredicting(false);
45
+ setStatusMessage('');
46
+ }
27
47
  }
28
48
  };
29
49
  const search = async (aaSeq, di3Seq, databases = DEFAULT_DATABASES) => {
50
+ const signal = startOperation();
30
51
  setIsLoading(true);
31
52
  setError(undefined);
32
53
  setStatusMessage('Submitting search...');
33
54
  try {
34
- const ticket = await submitFoldseekSearch(aaSeq, di3Seq, databases);
35
- const result = await waitForFoldseekResults(ticket.id, setStatusMessage);
55
+ const ticket = await submitFoldseekSearch({
56
+ aaSequence: aaSeq,
57
+ di3Sequence: di3Seq,
58
+ databases,
59
+ signal,
60
+ });
61
+ const result = await waitForFoldseekResults({
62
+ ticketId: ticket.id,
63
+ onStatusChange: setStatusMessage,
64
+ signal,
65
+ });
36
66
  setResults(result);
37
67
  return result;
38
68
  }
39
69
  catch (e) {
40
- console.error(e);
41
- setError(e);
70
+ if (!signal.aborted) {
71
+ console.error(e);
72
+ setError(e);
73
+ }
42
74
  return undefined;
43
75
  }
44
76
  finally {
45
- setIsLoading(false);
46
- setStatusMessage('');
77
+ if (!signal.aborted) {
78
+ setIsLoading(false);
79
+ setStatusMessage('');
80
+ }
47
81
  }
48
82
  };
49
83
  const reset = () => {
84
+ abortRef.current?.abort();
50
85
  setResults(undefined);
51
86
  setPredictData(undefined);
52
87
  setError(undefined);
@@ -1,4 +1,6 @@
1
+ import { getSession } from '@jbrowse/core/util';
1
2
  import useSWR from 'swr';
3
+ import { STATIC_SWR_OPTIONS } from './swrOptions';
2
4
  import { fetchProteinSeq } from '../utils/calculateProteinSequence';
3
5
  import { getTranscriptFeatures } from '../utils/util';
4
6
  export default function useIsoformProteinSequences({ feature, view, }) {
@@ -7,7 +9,11 @@ export default function useIsoformProteinSequences({ feature, view, }) {
7
9
  const errors = [];
8
10
  const results = await Promise.all(transcripts.map(async (f) => {
9
11
  try {
10
- const seq = await fetchProteinSeq({ view, feature: f });
12
+ const seq = await fetchProteinSeq({
13
+ session: getSession(view),
14
+ assemblyName: view?.assemblyNames?.[0],
15
+ feature: f,
16
+ });
11
17
  return seq ? [f.id(), { feature: f, seq }] : undefined;
12
18
  }
13
19
  catch (e) {
@@ -27,9 +33,7 @@ export default function useIsoformProteinSequences({ feature, view, }) {
27
33
  }
28
34
  return Object.fromEntries(entries);
29
35
  }, {
30
- revalidateOnFocus: false,
31
- revalidateOnReconnect: false,
32
- revalidateIfStale: false,
36
+ ...STATIC_SWR_OPTIONS,
33
37
  keepPreviousData: true,
34
38
  });
35
39
  return { isLoading, isoformSequences: data, error };
@@ -0,0 +1,9 @@
1
+ export default function useStructureFileSequence({ file, url, }: {
2
+ file?: File;
3
+ url?: string;
4
+ }): {
5
+ error: any;
6
+ isLoading: boolean;
7
+ isValidating: boolean;
8
+ sequences: string[] | undefined;
9
+ };