jbrowse-plugin-protein3d 0.11.2 → 0.12.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 (219) hide show
  1. package/dist/LaunchProteinView/codingFeature.js +33 -0
  2. package/dist/LaunchProteinView/components/AlphaFoldDBSearch.js +9 -18
  3. package/dist/LaunchProteinView/components/AlphaFoldDBSearchStatus.js +10 -13
  4. package/dist/LaunchProteinView/components/FoldseekActionMenu.js +1 -2
  5. package/dist/LaunchProteinView/components/FoldseekResultsTable.js +11 -5
  6. package/dist/LaunchProteinView/components/FoldseekSearch.js +40 -23
  7. package/dist/LaunchProteinView/components/HelpDialog.js +22 -11
  8. package/dist/LaunchProteinView/components/IdentifierSelector.js +1 -1
  9. package/dist/LaunchProteinView/components/LaunchOptionsMenu.js +11 -4
  10. package/dist/LaunchProteinView/components/LaunchProteinViewDialog.js +17 -7
  11. package/dist/LaunchProteinView/components/PartialFailureNotice.js +9 -0
  12. package/dist/LaunchProteinView/components/PdbResultsTable.js +5 -3
  13. package/dist/LaunchProteinView/components/PdbSearch.js +46 -23
  14. package/dist/LaunchProteinView/components/ProteinViewActions.js +7 -17
  15. package/dist/LaunchProteinView/components/SequenceMismatchNotice.js +5 -7
  16. package/dist/LaunchProteinView/components/StructureSourcePicker.js +4 -12
  17. package/dist/LaunchProteinView/components/TabPanel.js +11 -5
  18. package/dist/LaunchProteinView/components/TranscriptSelector.js +12 -9
  19. package/dist/LaunchProteinView/components/UniProtIdInput.js +3 -3
  20. package/dist/LaunchProteinView/components/UniProtLookupControls.js +32 -0
  21. package/dist/LaunchProteinView/components/UniProtResultsTable.js +1 -1
  22. package/dist/LaunchProteinView/components/UserProvidedStructure.js +17 -16
  23. package/dist/LaunchProteinView/components/proteinAssemblySetup.js +1 -1
  24. package/dist/LaunchProteinView/components/proteinTrackSetup.js +1 -2
  25. package/dist/LaunchProteinView/hooks/useAlphaFoldDBSearch.js +4 -5
  26. package/dist/LaunchProteinView/hooks/useAlphaFoldData.js +1 -1
  27. package/dist/LaunchProteinView/hooks/useFoldseekSearch.js +12 -5
  28. package/dist/LaunchProteinView/hooks/useIsoformProteinSequences.js +31 -27
  29. package/dist/LaunchProteinView/hooks/usePdbBestStructures.js +3 -3
  30. package/dist/LaunchProteinView/hooks/usePdbEntryMolecules.js +9 -0
  31. package/dist/LaunchProteinView/hooks/useStructureFileSequence.js +1 -1
  32. package/dist/LaunchProteinView/hooks/useTranscriptIsoformSelection.js +7 -5
  33. package/dist/LaunchProteinView/hooks/useTranscriptSelection.js +4 -4
  34. package/dist/LaunchProteinView/hooks/useUniProtIdLookup.js +38 -17
  35. package/dist/LaunchProteinView/hooks/useUniProtSearch.js +8 -3
  36. package/dist/LaunchProteinView/index.js +16 -7
  37. package/dist/LaunchProteinView/services/foldseekApi.js +14 -11
  38. package/dist/LaunchProteinView/utils/calculateProteinSequence.js +62 -12
  39. package/dist/LaunchProteinView/utils/launchHelpers.js +11 -3
  40. package/dist/LaunchProteinView/utils/util.js +19 -73
  41. package/dist/LaunchProteinViewExtensionPoint/index.js +6 -5
  42. package/dist/LaunchProteinViewExtensionPoint/resolveShortLaunch.js +10 -10
  43. package/dist/Protein1DLinkage/linkage.js +2 -1
  44. package/dist/ProteinView/attachStructureInteractions.js +70 -0
  45. package/dist/ProteinView/components/ChainSelect.js +2 -2
  46. package/dist/ProteinView/components/HeaderStructureRow.js +37 -0
  47. package/dist/ProteinView/components/ManualAlignmentDialog.js +1 -2
  48. package/dist/ProteinView/components/ProteinAlignment.js +15 -10
  49. package/dist/ProteinView/components/ProteinView.js +25 -5
  50. package/dist/ProteinView/components/ProteinViewHeader.js +10 -10
  51. package/dist/ProteinView/components/TrackLegend.js +33 -0
  52. package/dist/ProteinView/hooks/useProteinFeatureTrackData.js +1 -1
  53. package/dist/ProteinView/hooks/useStructureUniProt.js +1 -1
  54. package/dist/ProteinView/hooks/useUniProtFeatures.js +1 -2
  55. package/dist/ProteinView/loadStructureData.js +1 -2
  56. package/dist/ProteinView/model.js +129 -71
  57. package/dist/ProteinView/proteinToGenomeMapping.js +25 -1
  58. package/dist/ProteinView/proteinViewSpec.js +6 -2
  59. package/dist/ProteinView/removeStructure.js +15 -0
  60. package/dist/ProteinView/residueTracks.js +12 -0
  61. package/dist/ProteinView/showLoading.js +3 -1
  62. package/dist/ProteinView/storedSettings.js +9 -3
  63. package/dist/ProteinView/structureLoader.js +68 -6
  64. package/dist/ProteinView/structureModel.js +130 -117
  65. package/dist/ProteinView/structurePipeline.js +1 -1
  66. package/dist/ProteinView/util.js +8 -3
  67. package/dist/jbrowse-plugin-protein3d.umd.production.min.js +15 -18
  68. package/dist/jbrowse-plugin-protein3d.umd.production.min.js.map +4 -4
  69. package/dist/mappings.js +4 -179
  70. package/dist/version.js +1 -1
  71. package/package.json +2 -1
  72. package/src/LaunchProteinView/codingFeature.test.ts +95 -0
  73. package/src/LaunchProteinView/codingFeature.ts +47 -0
  74. package/src/LaunchProteinView/components/AlphaFoldDBSearch.tsx +16 -55
  75. package/src/LaunchProteinView/components/AlphaFoldDBSearchStatus.tsx +12 -26
  76. package/src/LaunchProteinView/components/FoldseekActionMenu.tsx +6 -5
  77. package/src/LaunchProteinView/components/FoldseekResultsTable.tsx +17 -4
  78. package/src/LaunchProteinView/components/FoldseekSearch.tsx +78 -42
  79. package/src/LaunchProteinView/components/HelpDialog.tsx +38 -32
  80. package/src/LaunchProteinView/components/IdentifierSelector.tsx +1 -2
  81. package/src/LaunchProteinView/components/LaunchOptionsMenu.tsx +20 -1
  82. package/src/LaunchProteinView/components/LaunchProteinViewDialog.tsx +20 -15
  83. package/src/LaunchProteinView/components/PartialFailureNotice.tsx +19 -0
  84. package/src/LaunchProteinView/components/PdbResultsTable.tsx +6 -3
  85. package/src/LaunchProteinView/components/PdbSearch.tsx +77 -56
  86. package/src/LaunchProteinView/components/ProteinViewActions.tsx +23 -42
  87. package/src/LaunchProteinView/components/SequenceMismatchNotice.tsx +5 -20
  88. package/src/LaunchProteinView/components/StructureSourcePicker.tsx +2 -22
  89. package/src/LaunchProteinView/components/TabPanel.tsx +12 -6
  90. package/src/LaunchProteinView/components/TranscriptSelector.tsx +14 -11
  91. package/src/LaunchProteinView/components/UniProtIdInput.tsx +11 -5
  92. package/src/LaunchProteinView/components/UniProtLookupControls.tsx +87 -0
  93. package/src/LaunchProteinView/components/UniProtResultsTable.tsx +2 -2
  94. package/src/LaunchProteinView/components/UserProvidedStructure.tsx +30 -40
  95. package/src/LaunchProteinView/components/proteinAssemblySetup.ts +1 -1
  96. package/src/LaunchProteinView/components/proteinTrackSetup.ts +1 -2
  97. package/src/LaunchProteinView/hooks/uniProtLookupStatus.test.ts +37 -0
  98. package/src/LaunchProteinView/hooks/useAlphaFoldDBSearch.ts +7 -3
  99. package/src/LaunchProteinView/hooks/useAlphaFoldData.ts +1 -4
  100. package/src/LaunchProteinView/hooks/useFoldseekSearch.ts +13 -5
  101. package/src/LaunchProteinView/hooks/useIsoformProteinSequences.ts +42 -32
  102. package/src/LaunchProteinView/hooks/usePdbBestStructures.ts +10 -9
  103. package/src/LaunchProteinView/hooks/usePdbEntryMolecules.ts +19 -0
  104. package/src/LaunchProteinView/hooks/useStructureFileSequence.ts +1 -1
  105. package/src/LaunchProteinView/hooks/useTranscriptIsoformSelection.ts +16 -8
  106. package/src/LaunchProteinView/hooks/useTranscriptSelection.ts +5 -4
  107. package/src/LaunchProteinView/hooks/useUniProtIdLookup.ts +49 -18
  108. package/src/LaunchProteinView/hooks/useUniProtSearch.ts +15 -7
  109. package/src/LaunchProteinView/index.ts +18 -8
  110. package/src/LaunchProteinView/services/foldseekApi.ts +14 -17
  111. package/src/LaunchProteinView/utils/calculateProteinSequence.ts +113 -15
  112. package/src/LaunchProteinView/utils/launchHelpers.test.ts +48 -0
  113. package/src/LaunchProteinView/utils/launchHelpers.ts +12 -2
  114. package/src/LaunchProteinView/utils/launchViewUtils.ts +1 -1
  115. package/src/LaunchProteinView/utils/translateTranscripts.test.ts +63 -0
  116. package/src/LaunchProteinView/utils/util.ts +26 -92
  117. package/src/LaunchProteinViewExtensionPoint/index.ts +9 -5
  118. package/src/LaunchProteinViewExtensionPoint/resolveShortLaunch.ts +9 -16
  119. package/src/Protein1DLinkage/linkage.ts +2 -1
  120. package/src/ProteinView/attachStructureInteractions.test.ts +126 -0
  121. package/src/ProteinView/attachStructureInteractions.ts +102 -0
  122. package/src/ProteinView/components/ChainSelect.tsx +2 -3
  123. package/src/ProteinView/components/HeaderStructureRow.tsx +115 -0
  124. package/src/ProteinView/components/ManualAlignmentDialog.tsx +2 -3
  125. package/src/ProteinView/components/ProteinAlignment.tsx +31 -33
  126. package/src/ProteinView/components/ProteinView.tsx +48 -12
  127. package/src/ProteinView/components/ProteinViewHeader.tsx +16 -24
  128. package/src/ProteinView/components/TrackLegend.tsx +68 -0
  129. package/src/ProteinView/geneExplorerLinkage.test.ts +1 -2
  130. package/src/ProteinView/hooks/layoutFeature.test.ts +1 -4
  131. package/src/ProteinView/hooks/useProteinFeatureTrackData.ts +3 -2
  132. package/src/ProteinView/hooks/useStructureUniProt.ts +7 -4
  133. package/src/ProteinView/hooks/useUniProtFeatures.ts +1 -2
  134. package/src/ProteinView/labelSeqIdIntegration.test.ts +4 -5
  135. package/src/ProteinView/loadStructureData.ts +4 -5
  136. package/src/ProteinView/model.test.ts +165 -0
  137. package/src/ProteinView/model.ts +143 -78
  138. package/src/ProteinView/proteinToGenomeMapping.ts +38 -4
  139. package/src/ProteinView/proteinViewSpec.ts +7 -7
  140. package/src/ProteinView/removeStructure.ts +37 -0
  141. package/src/ProteinView/residueTracks.test.ts +11 -0
  142. package/src/ProteinView/residueTracks.ts +13 -0
  143. package/src/ProteinView/showLoading.ts +3 -1
  144. package/src/ProteinView/storedSettings.ts +9 -3
  145. package/src/ProteinView/structureLoader.test.ts +211 -11
  146. package/src/ProteinView/structureLoader.ts +88 -7
  147. package/src/ProteinView/structureModel.test.ts +48 -6
  148. package/src/ProteinView/structureModel.ts +161 -165
  149. package/src/ProteinView/structurePipeline.ts +1 -1
  150. package/src/ProteinView/util.test.ts +1 -39
  151. package/src/ProteinView/util.ts +9 -4
  152. package/src/ProteinView/withStoredSettings.test.ts +23 -5
  153. package/src/mappings.test.ts +2 -182
  154. package/src/mappings.ts +4 -230
  155. package/src/version.ts +1 -1
  156. package/dist/LaunchProteinView/components/AlignmentSettingsButton.js +0 -41
  157. package/dist/LaunchProteinView/components/Checkbox2.js +0 -12
  158. package/dist/LaunchProteinView/components/IsoformSequencesToggle.js +0 -13
  159. package/dist/LaunchProteinView/components/LaunchSettingsDialog.js +0 -23
  160. package/dist/LaunchProteinView/components/MSATable.js +0 -53
  161. package/dist/LaunchProteinView/hooks/useLocalStorage.js +0 -10
  162. package/dist/LaunchProteinView/services/alphaFoldModels.js +0 -49
  163. package/dist/LaunchProteinView/services/lookupMethods.js +0 -76
  164. package/dist/LaunchProteinView/services/pdbeBestStructures.js +0 -66
  165. package/dist/LaunchProteinView/utils/caCoordsToPdb.js +0 -90
  166. package/dist/LaunchProteinView/utils/geneticCodes.js +0 -227
  167. package/dist/LaunchProteinView/utils/isoformRanking.js +0 -85
  168. package/dist/LaunchProteinView/utils/structureUrls.js +0 -122
  169. package/dist/ProteinView/__fixtures__/structureFixtures.js +0 -34
  170. package/dist/ProteinView/alignmentQuality.js +0 -75
  171. package/dist/ProteinView/chooseMappedEntity.js +0 -133
  172. package/dist/ProteinView/coordinates.js +0 -57
  173. package/dist/ProteinView/extractPerResidueConfidence.js +0 -37
  174. package/dist/ProteinView/extractStructureSequences.js +0 -204
  175. package/dist/ProteinView/pairwiseAlignment.js +0 -327
  176. package/dist/ProteinView/pdbUniProtMapping.js +0 -217
  177. package/dist/ProteinView/structureFormat.js +0 -60
  178. package/dist/ProteinView/types.js +0 -22
  179. package/dist/fetchUtils.js +0 -36
  180. package/src/LaunchProteinView/components/AlignmentSettingsButton.tsx +0 -126
  181. package/src/LaunchProteinView/components/Checkbox2.tsx +0 -32
  182. package/src/LaunchProteinView/components/IsoformSequencesToggle.tsx +0 -41
  183. package/src/LaunchProteinView/components/LaunchSettingsDialog.tsx +0 -63
  184. package/src/LaunchProteinView/components/MSATable.tsx +0 -97
  185. package/src/LaunchProteinView/hooks/useLocalStorage.ts +0 -19
  186. package/src/LaunchProteinView/services/alphaFoldModels.test.ts +0 -76
  187. package/src/LaunchProteinView/services/alphaFoldModels.ts +0 -87
  188. package/src/LaunchProteinView/services/lookupMethods.ts +0 -144
  189. package/src/LaunchProteinView/services/pdbeBestStructures.test.ts +0 -72
  190. package/src/LaunchProteinView/services/pdbeBestStructures.ts +0 -87
  191. package/src/LaunchProteinView/utils/caCoordsToPdb.ts +0 -111
  192. package/src/LaunchProteinView/utils/geneticCodes.ts +0 -298
  193. package/src/LaunchProteinView/utils/isoformRanking.ts +0 -138
  194. package/src/LaunchProteinView/utils/pickStructureSequence.test.ts +0 -68
  195. package/src/LaunchProteinView/utils/selectBestTranscript.test.ts +0 -235
  196. package/src/LaunchProteinView/utils/stripStopCodon.test.ts +0 -23
  197. package/src/LaunchProteinView/utils/structureUrls.test.ts +0 -120
  198. package/src/LaunchProteinView/utils/structureUrls.ts +0 -158
  199. package/src/ProteinView/__fixtures__/structureFixtures.ts +0 -64
  200. package/src/ProteinView/alignmentQuality.test.ts +0 -101
  201. package/src/ProteinView/alignmentQuality.ts +0 -101
  202. package/src/ProteinView/chooseMappedEntity.test.ts +0 -224
  203. package/src/ProteinView/chooseMappedEntity.ts +0 -198
  204. package/src/ProteinView/coordinates.test.ts +0 -90
  205. package/src/ProteinView/coordinates.ts +0 -119
  206. package/src/ProteinView/extractPerResidueConfidence.test.ts +0 -83
  207. package/src/ProteinView/extractPerResidueConfidence.ts +0 -88
  208. package/src/ProteinView/extractStructureSequences.test.ts +0 -294
  209. package/src/ProteinView/extractStructureSequences.ts +0 -342
  210. package/src/ProteinView/pairwiseAlignment.test.ts +0 -101
  211. package/src/ProteinView/pairwiseAlignment.ts +0 -423
  212. package/src/ProteinView/pdbUniProtMapping.test.ts +0 -327
  213. package/src/ProteinView/pdbUniProtMapping.ts +0 -296
  214. package/src/ProteinView/structureFormat.test.ts +0 -73
  215. package/src/ProteinView/structureFormat.ts +0 -71
  216. package/src/ProteinView/types.ts +0 -30
  217. package/src/fetchUtils.test.ts +0 -27
  218. package/src/fetchUtils.ts +0 -49
  219. package/src/mappings.test.characterization.test.ts +0 -92
@@ -0,0 +1,33 @@
1
+ // Copied from @jbrowse/core's featureTypes: the barrel export reads undefined
2
+ // on a v4 host, and the deep path is not in core's exports map.
3
+ const GENE_LIKE_TYPE = /gene(_segment)?$|rna$|transcript/;
4
+ export function isGeneLikeType(type) {
5
+ return type !== undefined && GENE_LIKE_TYPE.test(type.toLowerCase());
6
+ }
7
+ export function isCDS(feature) {
8
+ return feature.get('type')?.toLowerCase() === 'cds';
9
+ }
10
+ function hasDirectCDS(feature) {
11
+ return !!feature.get('subfeatures')?.some(isCDS);
12
+ }
13
+ // The transcripts the translator can read: the feature itself when its CDS
14
+ // records hang directly off it, else each gene-like child that carries them.
15
+ // One definition serves the menu gate, the isoform picker and the translator,
16
+ // so the menu never promises a protein the dialog cannot compute.
17
+ export function codingTranscripts(feature) {
18
+ return hasDirectCDS(feature)
19
+ ? [feature]
20
+ : (feature.get('subfeatures') ?? []).filter(f => isGeneLikeType(f.get('type')) && hasDirectCDS(f));
21
+ }
22
+ export function isCodingFeature(feature) {
23
+ return codingTranscripts(feature).length > 0;
24
+ }
25
+ // The outermost gene-like ancestor, so a click on an isoform opens the dialog
26
+ // on the gene with every transcript to choose from, as the canvas host does.
27
+ export function geneLikeRoot(feature) {
28
+ let root = feature;
29
+ for (let parent = root.parent?.(); parent && isGeneLikeType(parent.get('type')); parent = parent.parent?.()) {
30
+ root = parent;
31
+ }
32
+ return root;
33
+ }
@@ -1,13 +1,13 @@
1
1
  import React from 'react';
2
2
  import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
- import { DialogActions, DialogContent, TextField, Typography, } from '@mui/material';
3
+ import { DialogActions, DialogContent, Typography } from '@mui/material';
4
4
  import { observer } from 'mobx-react';
5
5
  import { makeStyles } from 'tss-react/mui';
6
6
  import AlphaFoldDBSearchStatus from './AlphaFoldDBSearchStatus';
7
- import IdentifierSelector from './IdentifierSelector';
7
+ import PartialFailureNotice from './PartialFailureNotice';
8
8
  import ProteinViewActions from './ProteinViewActions';
9
9
  import TranscriptSelector from './TranscriptSelector';
10
- import UniProtIdInput from './UniProtIdInput';
10
+ import UniProtLookupControls from './UniProtLookupControls';
11
11
  import UniProtResultsTable from './UniProtResultsTable';
12
12
  import ExternalLink from '../../components/ExternalLink';
13
13
  import useAlphaFoldDBSearch from '../hooks/useAlphaFoldDBSearch';
@@ -27,16 +27,10 @@ const useStyles = makeStyles()({
27
27
  gap: 20,
28
28
  alignItems: 'flex-start',
29
29
  },
30
- endRow: {
31
- display: 'flex',
32
- flexDirection: 'row',
33
- gap: 12,
34
- alignItems: 'flex-start',
35
- },
36
30
  });
37
- const AlphaFoldDBSearch = observer(function AlphaFoldDBSearch({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
31
+ const AlphaFoldDBSearch = observer(function AlphaFoldDBSearch({ feature, session, view, handleClose, lookup, sideBySide, onSideBySideChange, }) {
38
32
  const { classes } = useStyles();
39
- const state = useAlphaFoldDBSearch({ feature, view });
33
+ const state = useAlphaFoldDBSearch({ feature, view, lookup });
40
34
  return (React.createElement(React.Fragment, null,
41
35
  React.createElement(DialogContent, { className: classes.dialogContent },
42
36
  state.error ? React.createElement(ErrorMessage, { error: state.error }) : null,
@@ -44,12 +38,9 @@ const AlphaFoldDBSearch = observer(function AlphaFoldDBSearch({ feature, session
44
38
  "AlphaFold DB has no model for ",
45
39
  state.uniprotId,
46
40
  ". The PDB and Foldseek tabs may have a structure.")) : null,
47
- React.createElement(UniProtIdInput, { lookupMode: state.lookupMode, onLookupModeChange: state.setLookupMode, manualUniprotId: state.manualUniprotId, onManualUniprotIdChange: state.setManualUniprotId, featureUniprotId: state.featureUniprotId, endContent: state.showIdentifierSelector ? (React.createElement("div", { className: classes.endRow },
48
- React.createElement(IdentifierSelector, { recognizedIds: state.recognizedIds, geneName: state.geneName, selectedId: state.selectedQueryId, onSelectedIdChange: state.setSelectedQueryId }),
49
- React.createElement(TextField, { size: "small", label: "Organism (NCBI taxon)", helperText: "Scopes the gene-name search", value: state.taxonId, onChange: event => {
50
- state.setTaxonId(event.target.value);
51
- }, placeholder: String(state.effectiveTaxonId), slotProps: { inputLabel: { shrink: true } }, sx: { width: 180 } }))) : null }),
41
+ React.createElement(UniProtLookupControls, { lookup: lookup }),
52
42
  state.loadingStatuses.map(status => (React.createElement(LoadingEllipses, { key: status, variant: "subtitle2", message: status }))),
43
+ React.createElement(PartialFailureNotice, { message: state.isoformPartialFailure }),
53
44
  state.showUniprotResults && (React.createElement(React.Fragment, null,
54
45
  React.createElement(Typography, { variant: "body2", color: "textSecondary" },
55
46
  "Searched UniProt by ",
@@ -75,8 +66,8 @@ const AlphaFoldDBSearch = observer(function AlphaFoldDBSearch({ feature, session
75
66
  state.uniprotId ? (React.createElement(React.Fragment, null,
76
67
  React.createElement("div", { className: classes.selectorsRow },
77
68
  React.createElement(TranscriptSelector, { val: state.userSelection, setVal: state.setUserSelection, structureSequence: state.structureSequence, feature: feature, isoforms: state.transcriptOptions, isoformSequences: state.isoformSequences })),
78
- React.createElement(AlphaFoldDBSearchStatus, { uniprotId: state.modelAccession ?? state.uniprotId, structureSequence: state.structureSequence, isoformSequences: state.isoformSequences, url: state.url }))) : null),
69
+ React.createElement(AlphaFoldDBSearchStatus, { uniprotId: state.modelAccession ?? state.uniprotId, url: state.url }))) : null),
79
70
  React.createElement(DialogActions, null,
80
- React.createElement(ProteinViewActions, { handleClose: handleClose, uniprotId: state.modelAccession ?? state.uniprotId, userSelectedProteinSequence: state.userSelectedProteinSequence, selectedTranscript: state.selectedTranscript, url: state.url, confidenceUrl: state.confidenceUrl, feature: feature, view: view, session: session, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: onAlignmentAlgorithmChange, sequencesMatch: state.sequencesMatch, isLoading: state.isLoading, error: state.error }))));
71
+ React.createElement(ProteinViewActions, { handleClose: handleClose, uniprotId: state.modelAccession ?? state.uniprotId, userSelectedProteinSequence: state.userSelectedProteinSequence, selectedTranscript: state.selectedTranscript, url: state.url, confidenceUrl: state.confidenceUrl, feature: feature, view: view, session: session, sideBySide: sideBySide, onSideBySideChange: onSideBySideChange, sequencesMatch: state.sequencesMatch, isLoading: state.isLoading, error: state.error }))));
81
72
  });
82
73
  export default AlphaFoldDBSearch;
@@ -1,17 +1,14 @@
1
1
  import React from 'react';
2
2
  import { Typography } from '@mui/material';
3
- import IsoformSequencesToggle from './IsoformSequencesToggle';
3
+ import { uniprotEntryUrl } from 'p2s_mapper';
4
4
  import ExternalLink from '../../components/ExternalLink';
5
- import { uniprotEntryUrl } from '../utils/structureUrls';
6
- export default function AlphaFoldDBSearchStatus({ uniprotId, structureSequence, isoformSequences, url, }) {
7
- return (React.createElement(React.Fragment, null,
8
- React.createElement("div", null,
9
- React.createElement(Typography, null,
10
- "UniProt link:",
11
- ' ',
12
- React.createElement(ExternalLink, { href: uniprotEntryUrl(uniprotId) }, uniprotId)),
13
- React.createElement(Typography, null,
14
- "AlphaFoldDB link: ",
15
- React.createElement(ExternalLink, { href: url }, url))),
16
- React.createElement(IsoformSequencesToggle, { structureSequence: structureSequence, structureName: uniprotId, isoformSequences: isoformSequences })));
5
+ export default function AlphaFoldDBSearchStatus({ uniprotId, url, }) {
6
+ return (React.createElement("div", null,
7
+ React.createElement(Typography, null,
8
+ "UniProt link:",
9
+ ' ',
10
+ React.createElement(ExternalLink, { href: uniprotEntryUrl(uniprotId) }, uniprotId)),
11
+ React.createElement(Typography, null,
12
+ "AlphaFoldDB link: ",
13
+ React.createElement(ExternalLink, { href: url }, url))));
17
14
  }
@@ -1,10 +1,9 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ErrorMessage } from '@jbrowse/core/ui';
3
3
  import { Button, Menu, MenuItem } from '@mui/material';
4
+ import { caCoordsToPdb, getConfidenceUrlFromTarget, getUniprotIdFromAlphaFoldTarget, hasValidCaCoords, } from 'p2s_mapper';
4
5
  import { useSafeLaunch } from '../hooks/useSafeLaunch';
5
- import { caCoordsToPdb, hasValidCaCoords } from '../utils/caCoordsToPdb';
6
6
  import { PROTEIN_LAUNCH_LABELS, getConditionalProteinLaunches, launch3DProteinView, } from '../utils/launchViewUtils';
7
- import { getConfidenceUrlFromTarget, getUniprotIdFromAlphaFoldTarget, } from '../utils/structureUrls';
8
7
  export default function FoldseekActionMenu({ hit, session, view, feature, selectedTranscript, userProvidedTranscriptSequence, onClose, }) {
9
8
  const [anchorEl, setAnchorEl] = useState(null);
10
9
  const open = Boolean(anchorEl);
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from '@mui/material';
2
+ import { Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, } from '@mui/material';
3
+ import { getStructureUrlFromTarget } from 'p2s_mapper';
3
4
  import { makeStyles } from 'tss-react/mui';
4
5
  import FoldseekActionMenu from './FoldseekActionMenu';
5
- import { getStructureUrlFromTarget } from '../utils/structureUrls';
6
6
  const useStyles = makeStyles()(theme => ({
7
7
  root: {
8
8
  display: 'flex',
@@ -56,10 +56,16 @@ export default function FoldseekResultsTable({ results, session, view, feature,
56
56
  React.createElement(TableCell, { className: classes.headerCell }, "Database"),
57
57
  React.createElement(TableCell, { className: classes.headerCell }, "Target"),
58
58
  React.createElement(TableCell, { className: classes.headerCell }, "Organism"),
59
- React.createElement(TableCell, { className: classes.headerCell }, "Prob"),
60
- React.createElement(TableCell, { className: classes.headerCell }, "Seq. Id."),
59
+ React.createElement(TableCell, { className: classes.headerCell },
60
+ React.createElement(Tooltip, { title: "Foldseek's estimated probability that the hit is a true structural homolog" },
61
+ React.createElement("span", null, "Prob"))),
62
+ React.createElement(TableCell, { className: classes.headerCell },
63
+ React.createElement(Tooltip, { title: "Percentage of aligned residues whose amino acid is identical; structural hits are often low here" },
64
+ React.createElement("span", null, "Seq. Id."))),
61
65
  React.createElement(TableCell, { className: classes.headerCell }, "Coverage"),
62
- React.createElement(TableCell, { className: classes.headerCell }, "E-value"),
66
+ React.createElement(TableCell, { className: classes.headerCell },
67
+ React.createElement(Tooltip, { title: "Hits of this score expected by chance in a database this size; smaller is stronger" },
68
+ React.createElement("span", null, "E-value"))),
63
69
  React.createElement(TableCell, { className: classes.headerCell }, "Actions"))),
64
70
  React.createElement(TableBody, null, flatHits.map((hit, idx) => (React.createElement(TableRow, { key: `${hit.db}-${hit.target}-${idx}` },
65
71
  React.createElement(TableCell, null, hit.db),
@@ -1,15 +1,16 @@
1
1
  import React, { useState } from 'react';
2
2
  import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
- import { Button, DialogActions, DialogContent, TextField, Typography, } from '@mui/material';
3
+ import { Button, DialogActions, DialogContent, Link, TextField, Typography, } from '@mui/material';
4
4
  import { observer } from 'mobx-react';
5
+ import { stripAllStopCodons } from 'p2s_mapper';
5
6
  import { makeStyles } from 'tss-react/mui';
6
7
  import FoldseekDatabaseSelector from './FoldseekDatabaseSelector';
7
8
  import FoldseekResultsTable from './FoldseekResultsTable';
9
+ import PartialFailureNotice from './PartialFailureNotice';
8
10
  import TranscriptSelector from './TranscriptSelector';
9
11
  import useFoldseekSearch from '../hooks/useFoldseekSearch';
10
12
  import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection';
11
13
  import { DEFAULT_DATABASES } from '../services/foldseekApi';
12
- import { stripAllStopCodons } from '../utils/util';
13
14
  const useStyles = makeStyles()({
14
15
  dialogContent: {
15
16
  width: '80em',
@@ -24,22 +25,23 @@ const useStyles = makeStyles()({
24
25
  display: 'flex',
25
26
  flexDirection: 'column',
26
27
  gap: 8,
28
+ alignItems: 'flex-start',
27
29
  },
28
30
  });
29
31
  const FoldseekSearch = observer(function FoldseekSearch({ feature, session, view, handleClose, }) {
30
32
  const { classes } = useStyles();
31
33
  const [userEditedSequence, setUserEditedSequence] = useState();
32
34
  const [selectedDatabases, setSelectedDatabases] = useState(DEFAULT_DATABASES);
33
- const { results, cleanedAaSequence, di3Sequence, isLoading, isPredicting, error, statusMessage, predictStructure, search, reset, } = useFoldseekSearch();
34
- const { transcripts, isoformSequences, isLoading: isLoadingIsoforms, error: isoformError, selectedTranscriptId: effectiveSelectedTranscriptId, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: selectedIsoformData, } = useTranscriptIsoformSelection({ feature, view });
35
+ const [show3Di, setShow3Di] = useState(false);
36
+ const { results, cleanedAaSequence, di3Sequence, isLoading, isPredicting, error, statusMessage, predictStructure, search, cancel, reset, } = useFoldseekSearch();
37
+ const { transcripts, isoformSequences, isLoading: isLoadingIsoforms, error: isoformError, partialFailure: isoformPartialFailure, selectedTranscriptId: effectiveSelectedTranscriptId, setSelectedTranscriptId: setUserSelection, selectedTranscript, selectedIsoform: selectedIsoformData, } = useTranscriptIsoformSelection({ feature, view });
35
38
  const cleanedSequence = selectedIsoformData
36
39
  ? stripAllStopCodons(selectedIsoformData.seq)
37
40
  : '';
38
41
  const sequence = userEditedSequence ?? cleanedSequence;
39
42
  // Any change to the input sequence makes an existing 3Di prediction (and any
40
- // results derived from it) stale. Clearing it returns the UI to the Predict
41
- // step so a search can't silently run against the previously-predicted
42
- // sequence after the user switches transcript or edits the residues.
43
+ // results derived from it) stale, so it goes back to being predicted on the
44
+ // next search rather than a search running against the old residues.
43
45
  const invalidatePrediction = () => {
44
46
  if (di3Sequence !== undefined || results !== undefined) {
45
47
  reset();
@@ -50,17 +52,24 @@ const FoldseekSearch = observer(function FoldseekSearch({ feature, session, view
50
52
  setUserEditedSequence(undefined);
51
53
  invalidatePrediction();
52
54
  };
53
- const canPredict = sequence.trim().length > 0 && !isPredicting && !isLoading;
54
- const canSearch = !!cleanedAaSequence &&
55
- !!di3Sequence &&
56
- selectedDatabases.length > 0 &&
57
- !isLoading;
58
- const combinedError = error ?? isoformError;
59
55
  const isBusy = isLoading || isPredicting;
56
+ const canSearch = sequence.trim().length > 0 && selectedDatabases.length > 0 && !isBusy;
57
+ // One button: predicting the 3Di alphabet is a step of the search, not a
58
+ // decision, and making the user click twice only invited a stale prediction.
59
+ const runSearch = async () => {
60
+ const predicted = cleanedAaSequence && di3Sequence
61
+ ? { aaSequence: cleanedAaSequence, di3Sequence }
62
+ : await predictStructure(sequence.trim());
63
+ if (predicted) {
64
+ await search(predicted.aaSequence, predicted.di3Sequence, selectedDatabases);
65
+ }
66
+ };
67
+ const combinedError = error ?? isoformError;
60
68
  return (React.createElement(React.Fragment, null,
61
69
  React.createElement(DialogContent, { className: classes.dialogContent },
62
70
  combinedError && !isLoadingIsoforms ? (React.createElement(ErrorMessage, { error: combinedError })) : null,
63
71
  isLoadingIsoforms ? (React.createElement(LoadingEllipses, { variant: "subtitle2", message: "Loading transcript sequences" })) : null,
72
+ React.createElement(PartialFailureNotice, { message: isoformPartialFailure }),
64
73
  isoformSequences ? (React.createElement(React.Fragment, null,
65
74
  React.createElement(TranscriptSelector, { val: effectiveSelectedTranscriptId, setVal: setUserSelectionWithReset, isoforms: transcripts, isoformSequences: isoformSequences, feature: feature, disabled: isBusy }),
66
75
  React.createElement(TextField, { label: "Protein sequence (amino acids)", multiline: true, rows: 4, value: sequence, onChange: e => {
@@ -70,24 +79,32 @@ const FoldseekSearch = observer(function FoldseekSearch({ feature, session, view
70
79
  input: { className: classes.sequenceInput },
71
80
  } }))) : null,
72
81
  di3Sequence ? (React.createElement("div", { className: classes.di3Section },
73
- React.createElement(Typography, { variant: "subtitle2" }, "3Di structural alphabet (used for searching):"),
74
- React.createElement(TextField, { multiline: true, rows: 4, value: di3Sequence, slotProps: {
82
+ React.createElement(Link, { component: "button", type: "button", variant: "body2", onClick: () => {
83
+ setShow3Di(!show3Di);
84
+ } }, show3Di ? 'Hide 3Di' : 'Show 3Di'),
85
+ show3Di ? (React.createElement(TextField, { label: "3Di structural alphabet (what the search runs on)", multiline: true, rows: 4, fullWidth: true, value: di3Sequence, slotProps: {
75
86
  input: { className: classes.sequenceInput, readOnly: true },
76
- } }))) : null,
87
+ } })) : null)) : null,
77
88
  React.createElement(FoldseekDatabaseSelector, { selected: selectedDatabases, onChange: setSelectedDatabases, disabled: isBusy }),
78
89
  statusMessage ? (React.createElement(LoadingEllipses, { variant: "subtitle2", message: statusMessage })) : null,
79
- results ? (React.createElement(FoldseekResultsTable, { results: results, session: session, view: view, feature: feature, selectedTranscript: selectedTranscript, userProvidedTranscriptSequence: selectedIsoformData?.seq, onClose: handleClose })) : null),
90
+ results ? (React.createElement(FoldseekResultsTable, { results: results, session: session, view: view, feature: feature, selectedTranscript: selectedTranscript, userProvidedTranscriptSequence: selectedIsoformData?.seq, onClose: handleClose })) : null,
91
+ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "Searching sends the protein sequence above to the foldseek.com servers, which predict its 3Di alphabet and run the structure search.")),
80
92
  React.createElement(DialogActions, null,
81
93
  React.createElement(Button, { variant: "contained", color: "secondary", onClick: () => {
82
94
  handleClose();
83
- } }, "Cancel"),
95
+ } }, "Close"),
96
+ isBusy ? (React.createElement(Button, { variant: "outlined", onClick: () => {
97
+ cancel();
98
+ } }, "Cancel search")) : null,
84
99
  results ? (React.createElement(Button, { variant: "outlined", onClick: () => {
85
100
  reset();
86
101
  } }, "New search")) : null,
87
- !di3Sequence ? (React.createElement(Button, { variant: "contained", color: "primary", disabled: !canPredict, onClick: () => {
88
- void predictStructure(sequence.trim());
89
- } }, isPredicting ? 'Predicting...' : 'Predict 3Di structure')) : (React.createElement(Button, { variant: "contained", color: "primary", disabled: !canSearch, onClick: () => {
90
- void search(cleanedAaSequence, di3Sequence, selectedDatabases);
91
- } }, isLoading ? 'Searching...' : 'Search Foldseek')))));
102
+ React.createElement(Button, { variant: "contained", color: "primary", disabled: !canSearch, onClick: () => {
103
+ void runSearch();
104
+ } }, isPredicting
105
+ ? 'Predicting 3Di...'
106
+ : isLoading
107
+ ? 'Searching...'
108
+ : 'Search Foldseek'))));
92
109
  });
93
110
  export default FoldseekSearch;
@@ -1,21 +1,32 @@
1
1
  import React from 'react';
2
2
  import { Dialog } from '@jbrowse/core/ui';
3
3
  import { Button, DialogActions, DialogContent, Divider, Typography, } from '@mui/material';
4
- function Typography2({ children }) {
5
- return React.createElement(Typography, { style: { margin: 4 } }, children);
6
- }
4
+ import ExternalLink from '../../components/ExternalLink';
5
+ const ISSUES_URL = 'https://github.com/GMOD/jbrowse-plugin-protein3d/issues';
7
6
  export default function HelpDialog({ handleClose, }) {
8
7
  return (React.createElement(Dialog, { open: true, maxWidth: "lg", onClose: handleClose, title: "Help" },
9
8
  React.createElement(DialogContent, null,
10
- React.createElement(Typography2, null,
11
- "The procedure for the protein lookup is as follows:",
9
+ React.createElement(Typography, { sx: { mb: 2 } }, "Each tab finds a structure a different way. All of them end in the same place: the plugin aligns the structure's residues to the protein sequence it translates from the transcript you pick, and that alignment maps genome coordinates onto positions in the 3D view."),
10
+ React.createElement(Typography, { component: "div" },
12
11
  React.createElement("ul", null,
13
- React.createElement("li", null, "(Automatic lookup) Searches UniProt for the transcript ID or gene name to retrieve the UniProt ID, which is then used to lookup the structure in AlphaFoldDB"),
14
- React.createElement("li", null, "(Manual) Allows you to choose your own structure file from your local machine (e.g. a PDB file predicted by e.g. ColabFold) or supply a specific URL"),
15
- React.createElement("li", null, "The residues from the structure are downloaded, and then you can choose the transcript isoform from the selected gene that best represents the structure. Asterisks are displayed if there is an exact sequence match"),
16
- React.createElement("li", null, "The residues from the structure are finally aligned to the to the selected transcript's protein sequence representation, and this creates a mapping from the reference genome coordinates to positions in the 3-D structure"),
17
- React.createElement("li", null, "Finally the molstar panel is opened, and this contains many specialized features features, plus additional mouseover and selection features supplied by the plugin to connect mouse click actions and mouse hover with coordinates on the linear genome view"))),
18
- React.createElement(Typography2, null, "If you run into challenges with this workflow e.g. your transcripts are not being found in UniProt then you can use the Manual import form, or contact colin.diesh@gmail.com for troubleshooting")),
12
+ React.createElement("li", null,
13
+ React.createElement("b", null, "AlphaFoldDB search"),
14
+ " resolves the feature to a UniProt accession \u2014 from its own identifiers, or one you type \u2014 and opens AlphaFold's predicted model for it."),
15
+ React.createElement("li", null,
16
+ React.createElement("b", null, "PDB search"),
17
+ " lists the experimental structures PDBe maps to that accession, ranked on coverage and resolution. A crystal is usually one domain, often with binding partners, so the view picks the chain the transcript belongs to once the structure loads."),
18
+ React.createElement("li", null,
19
+ React.createElement("b", null, "Foldseek search"),
20
+ " sends the protein sequence to the foldseek.com servers and lists structures similar in shape, including ones with little sequence similarity."),
21
+ React.createElement("li", null,
22
+ React.createElement("b", null, "File or URL"),
23
+ " opens a structure you already have \u2014 the output of ColabFold or another modelling tool, or any PDB/mmCIF file reachable by URL."))),
24
+ React.createElement(Typography, { sx: { mb: 2 } }, "The isoform list marks which transcripts match the structure's residues exactly, and counts identical residues for the rest. You do not need an exact match; the alignment absorbs the differences between the two representations."),
25
+ React.createElement(Typography, null,
26
+ "If a gene will not resolve, or something looks wrong, please open an issue at",
27
+ ' ',
28
+ React.createElement(ExternalLink, { href: ISSUES_URL }, "github.com/GMOD/jbrowse-plugin-protein3d"),
29
+ ".")),
19
30
  React.createElement(Divider, null),
20
31
  React.createElement(DialogActions, null,
21
32
  React.createElement(Button, { onClick: handleClose, color: "primary" }, "Close"))));
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react';
2
2
  import { Button, FormControl, InputLabel, MenuItem, Select, } from '@mui/material';
3
- import { getDbIdLabel } from '../utils/util';
3
+ import { getDbIdLabel } from 'p2s_mapper';
4
4
  export default function IdentifierSelector({ recognizedIds, geneName, selectedId, onSelectedIdChange, }) {
5
5
  const [expanded, setExpanded] = useState(false);
6
6
  // Build list of selectable options
@@ -1,6 +1,13 @@
1
1
  import React from 'react';
2
- import { ListItemText, Menu, MenuItem } from '@mui/material';
3
- export default function LaunchOptionsMenu({ anchorEl, onClose, options, }) {
4
- return (React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: onClose, "data-testid": "protein-launch-options-menu" }, options.map(opt => (React.createElement(MenuItem, { key: opt.key, "data-testid": `protein-launch-option-${opt.key}`, onClick: opt.onClick },
5
- React.createElement(ListItemText, { primary: opt.title, secondary: opt.description }))))));
2
+ import { Checkbox, Divider, ListItemText, Menu, MenuItem } from '@mui/material';
3
+ export default function LaunchOptionsMenu({ anchorEl, onClose, options, sideBySide, onSideBySideChange, }) {
4
+ return (React.createElement(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: onClose, "data-testid": "protein-launch-options-menu" },
5
+ options.map(opt => (React.createElement(MenuItem, { key: opt.key, "data-testid": `protein-launch-option-${opt.key}`, onClick: opt.onClick },
6
+ React.createElement(ListItemText, { primary: opt.title, secondary: opt.description })))),
7
+ React.createElement(Divider, null),
8
+ React.createElement(MenuItem, { "data-testid": "protein-launch-side-by-side", onClick: () => {
9
+ onSideBySideChange(!sideBySide);
10
+ } },
11
+ React.createElement(Checkbox, { checked: sideBySide, size: "small" }),
12
+ React.createElement(ListItemText, { primary: "Open side by side", secondary: "Place the protein view right of the genome view instead of below it" }))));
6
13
  }
@@ -8,13 +8,23 @@ import HelpButton from './HelpButton';
8
8
  import PdbSearch from './PdbSearch';
9
9
  import TabPanel from './TabPanel';
10
10
  import UserProvidedStructure from './UserProvidedStructure';
11
- import { DEFAULT_ALIGNMENT_ALGORITHM } from '../../ProteinView/types';
12
- import { useLocalStorage } from '../hooks/useLocalStorage';
11
+ import useUniProtIdLookup from '../hooks/useUniProtIdLookup';
12
+ import { getLaunchSideBySide, setLaunchSideBySide } from '../utils/sideBySide';
13
13
  export default function LaunchProteinViewDialog({ handleClose, feature, model, }) {
14
14
  const [choice, setChoice] = useState(0);
15
- const [alignmentAlgorithm, setAlignmentAlgorithm] = useLocalStorage('jbrowse-protein3d-alignment-algorithm', DEFAULT_ALIGNMENT_ALGORITHM);
16
15
  const session = getSession(model);
17
16
  const view = getContainingView(model);
17
+ // One lookup for the whole dialog: the tabs stay mounted once visited, so a
18
+ // lookup per tab meant the same UniProt search ran twice and a row picked on
19
+ // one tab left the other pointing at a different gene.
20
+ const lookup = useUniProtIdLookup({ feature, view });
21
+ // Also the dialog's, for the same reason: a tab that has been mounted since
22
+ // before the user changed this would otherwise launch with its own stale copy.
23
+ const [sideBySide, setSideBySide] = useState(() => getLaunchSideBySide());
24
+ const changeSideBySide = (value) => {
25
+ setSideBySide(value);
26
+ setLaunchSideBySide(value);
27
+ };
18
28
  return (React.createElement(Dialog, { "data-testid": "launch-protein-view-dialog", maxWidth: "xl", title: "Launch protein view", titleNode: React.createElement(React.Fragment, null,
19
29
  "Launch protein view ",
20
30
  React.createElement(HelpButton, null)), open: true, onClose: handleClose },
@@ -24,13 +34,13 @@ export default function LaunchProteinViewDialog({ handleClose, feature, model, }
24
34
  React.createElement(Tab, { value: 0, label: "AlphaFoldDB search" }),
25
35
  React.createElement(Tab, { value: 1, label: "PDB search" }),
26
36
  React.createElement(Tab, { value: 2, label: "Foldseek search" }),
27
- React.createElement(Tab, { value: 3, label: "Open file manually" })),
37
+ React.createElement(Tab, { value: 3, label: "File or URL" })),
28
38
  React.createElement(TabPanel, { value: choice, index: 0 },
29
- React.createElement(AlphaFoldDBSearch, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm })),
39
+ React.createElement(AlphaFoldDBSearch, { session: session, view: view, feature: feature, handleClose: handleClose, lookup: lookup, sideBySide: sideBySide, onSideBySideChange: changeSideBySide })),
30
40
  React.createElement(TabPanel, { value: choice, index: 1 },
31
- React.createElement(PdbSearch, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm })),
41
+ React.createElement(PdbSearch, { session: session, view: view, feature: feature, handleClose: handleClose, lookup: lookup, sideBySide: sideBySide, onSideBySideChange: changeSideBySide })),
32
42
  React.createElement(TabPanel, { value: choice, index: 2 },
33
43
  React.createElement(FoldseekSearch, { session: session, view: view, feature: feature, handleClose: handleClose })),
34
44
  React.createElement(TabPanel, { value: choice, index: 3 },
35
- React.createElement(UserProvidedStructure, { session: session, view: view, feature: feature, handleClose: handleClose, alignmentAlgorithm: alignmentAlgorithm, onAlignmentAlgorithmChange: setAlignmentAlgorithm }))));
45
+ React.createElement(UserProvidedStructure, { session: session, view: view, feature: feature, handleClose: handleClose }))));
36
46
  }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import { Typography } from '@mui/material';
3
+ // What came back short of what was asked for: identifiers a UniProt outage
4
+ // swallowed, transcripts with no protein sequence. Not an error — the dialog
5
+ // still works — but not silence either, which is what makes 18 isoforms of a
6
+ // 20-isoform gene read as the whole gene.
7
+ export default function PartialFailureNotice({ message, }) {
8
+ return message ? (React.createElement(Typography, { variant: "body2", color: "warning.main" }, message)) : null;
9
+ }
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { Paper, Radio, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, } from '@mui/material';
2
+ import { Paper, Radio, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, } from '@mui/material';
3
+ import { rcsbEntryUrl } from 'p2s_mapper';
3
4
  import { makeStyles } from 'tss-react/mui';
4
5
  import ExternalLink from '../../components/ExternalLink';
5
- import { rcsbEntryUrl } from '../services/pdbeBestStructures';
6
6
  const useStyles = makeStyles()(theme => ({
7
7
  tableContainer: {
8
8
  maxHeight: 300,
@@ -42,7 +42,9 @@ export default function PdbResultsTable({ entries, selectedPdbId, onSelect, }) {
42
42
  React.createElement(TableCell, { className: classes.headerCell }, "PDB ID"),
43
43
  React.createElement(TableCell, { className: classes.headerCell }, "Method"),
44
44
  React.createElement(TableCell, { className: classes.headerCell }, "Resolution"),
45
- React.createElement(TableCell, { className: classes.headerCell }, "UniProt residues"),
45
+ React.createElement(TableCell, { className: classes.headerCell },
46
+ React.createElement(Tooltip, { title: "The span of the UniProt sequence this entry resolves, by SIFTS. A crystal is usually one domain rather than the whole protein." },
47
+ React.createElement("span", null, "UniProt residues"))),
46
48
  React.createElement(TableCell, { className: classes.headerCell }, "Coverage"),
47
49
  React.createElement(TableCell, { className: classes.headerCell }, "Chains"))),
48
50
  React.createElement(TableBody, null, shown.map(entry => {
@@ -2,18 +2,19 @@ import React, { useState } from 'react';
2
2
  import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
3
3
  import { DialogActions, DialogContent, TextField, Typography, } from '@mui/material';
4
4
  import { observer } from 'mobx-react';
5
+ import { getPdbStructureUrl, isPdbId, uniprotEntryUrl } from 'p2s_mapper';
5
6
  import { makeStyles } from 'tss-react/mui';
6
- import IdentifierSelector from './IdentifierSelector';
7
+ import PartialFailureNotice from './PartialFailureNotice';
7
8
  import PdbResultsTable from './PdbResultsTable';
8
9
  import ProteinViewActions from './ProteinViewActions';
9
10
  import TranscriptSelector from './TranscriptSelector';
10
- import UniProtIdInput from './UniProtIdInput';
11
+ import UniProtLookupControls from './UniProtLookupControls';
11
12
  import UniProtResultsTable from './UniProtResultsTable';
12
13
  import ExternalLink from '../../components/ExternalLink';
14
+ import useDebouncedValue from '../hooks/useDebouncedValue';
13
15
  import usePdbBestStructures from '../hooks/usePdbBestStructures';
16
+ import usePdbEntryMolecules from '../hooks/usePdbEntryMolecules';
14
17
  import useTranscriptIsoformSelection from '../hooks/useTranscriptIsoformSelection';
15
- import useUniProtIdLookup from '../hooks/useUniProtIdLookup';
16
- import { getPdbStructureUrl, uniprotEntryUrl } from '../utils/structureUrls';
17
18
  const useStyles = makeStyles()({
18
19
  dialogContent: {
19
20
  width: '80em',
@@ -24,12 +25,6 @@ const useStyles = makeStyles()({
24
25
  marginBottom: 0,
25
26
  },
26
27
  },
27
- endRow: {
28
- display: 'flex',
29
- flexDirection: 'row',
30
- gap: 12,
31
- alignItems: 'flex-start',
32
- },
33
28
  });
34
29
  // Experimental structures of the gene's protein, found through SIFTS: PDBe
35
30
  // lists every entry mapped to the UniProt accession, ranked on coverage and
@@ -37,16 +32,42 @@ const useStyles = makeStyles()({
37
32
  // A crystal is usually a fragment, often with partners, so the view aligns
38
33
  // the transcript to it after launch rather than expecting a sequence match
39
34
  // here.
40
- const PdbSearch = observer(function PdbSearch({ feature, session, view, handleClose, alignmentAlgorithm, onAlignmentAlgorithmChange, }) {
35
+ const PdbSearch = observer(function PdbSearch({ feature, session, view, handleClose, lookup, sideBySide, onSideBySideChange, }) {
41
36
  const { classes } = useStyles();
42
- const lookup = useUniProtIdLookup({ feature, view });
43
37
  const { uniprotId, isAutoMode, isLookupLoading } = lookup;
44
38
  const { entries, error: pdbError, isLoading: isPdbLoading, } = usePdbBestStructures(uniprotId);
45
39
  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;
40
+ // A typed id reaches entries PDBe's SIFTS listing never offers: a structure
41
+ // of a complex filed under a partner, anything a paper names. Debounced, so
42
+ // the three characters on the way to four are not three fetches.
43
+ const [typedPdbId, setTypedPdbId] = useState('');
44
+ const trimmedTypedPdbId = typedPdbId.trim();
45
+ const debouncedTypedPdbId = useDebouncedValue(trimmedTypedPdbId, 400);
46
+ const typedPdbIdInvalid = trimmedTypedPdbId !== '' && !isPdbId(trimmedTypedPdbId);
47
+ const selectedPdbId = isPdbId(debouncedTypedPdbId)
48
+ ? debouncedTypedPdbId.toLowerCase()
49
+ : userPdbId && entries?.some(e => e.pdbId === userPdbId)
50
+ ? userPdbId
51
+ : entries?.[0]?.pdbId;
52
+ const structureUrl = selectedPdbId
53
+ ? getPdbStructureUrl(selectedPdbId)
54
+ : undefined;
55
+ // The chosen entry's residues, so the isoform picker can say which transcript
56
+ // matches it — the same annotation the AlphaFold tab shows. It is a label and
57
+ // nothing more: Launch never waits on it, and its failure costs the label
58
+ // rather than the launch, which reads the structure file itself.
59
+ //
60
+ // While another entry's answer is in flight keepPreviousData still holds the
61
+ // last one, so isValidating withholds it rather than labelling these rows
62
+ // with the previous entry's chains.
63
+ const { sequences, isValidating: isMoleculesValidating } = usePdbEntryMolecules(selectedPdbId);
64
+ const structureSequences = isMoleculesValidating ? undefined : sequences;
65
+ const { transcripts, isoformSequences, structureSequence, isLoading: isIsoformLoading, error: isoformError, partialFailure: isoformPartialFailure, selectedTranscriptId, setSelectedTranscriptId, selectedTranscript, selectedIsoform, } = useTranscriptIsoformSelection({
66
+ feature,
67
+ view,
68
+ structureSequences,
69
+ resetKey: uniprotId,
70
+ });
50
71
  const loadingStatuses = [
51
72
  isLookupLoading && 'Looking up UniProt ID',
52
73
  isIsoformLoading && 'Loading protein sequences from transcript isoforms',
@@ -59,12 +80,9 @@ const PdbSearch = observer(function PdbSearch({ feature, session, view, handleCl
59
80
  return (React.createElement(React.Fragment, null,
60
81
  React.createElement(DialogContent, { className: classes.dialogContent },
61
82
  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 }),
83
+ React.createElement(UniProtLookupControls, { lookup: lookup }),
67
84
  loadingStatuses.map(status => (React.createElement(LoadingEllipses, { key: status, variant: "subtitle2", message: status }))),
85
+ React.createElement(PartialFailureNotice, { message: isoformPartialFailure }),
68
86
  isAutoMode && lookup.uniprotEntries.length > 0 ? (React.createElement(React.Fragment, null,
69
87
  React.createElement(Typography, { variant: "body2", color: "textSecondary" },
70
88
  "Searched UniProt by ",
@@ -80,13 +98,18 @@ const PdbSearch = observer(function PdbSearch({ feature, session, view, handleCl
80
98
  React.createElement(ExternalLink, { href: "https://www.uniprot.org/" }, "UniProt"),
81
99
  ' ',
82
100
  "directly and use \"Enter manually\".")) : null,
101
+ React.createElement(TextField, { size: "small", label: "PDB ID", placeholder: "e.g. 1TUP", helperText: typedPdbIdInvalid
102
+ ? 'A PDB ID is four characters beginning with a digit'
103
+ : 'Overrides the selection below', error: typedPdbIdInvalid, value: typedPdbId, onChange: event => {
104
+ setTypedPdbId(event.target.value);
105
+ }, slotProps: { inputLabel: { shrink: true } }, sx: { width: 240 } }),
83
106
  uniprotId && entries && !isPdbLoading ? (entries.length > 0 ? (React.createElement(PdbResultsTable, { entries: entries, selectedPdbId: selectedPdbId, onSelect: setUserPdbId })) : (React.createElement(Typography, null,
84
107
  "PDBe lists no experimental structure for",
85
108
  ' ',
86
109
  React.createElement(ExternalLink, { href: uniprotEntryUrl(uniprotId) }, uniprotId),
87
110
  ". 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),
111
+ isoformSequences && selectedTranscript ? (React.createElement(TranscriptSelector, { val: selectedTranscriptId, setVal: setSelectedTranscriptId, structureSequence: structureSequence, feature: feature, isoforms: transcripts, isoformSequences: isoformSequences })) : null),
89
112
  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 }))));
113
+ React.createElement(ProteinViewActions, { handleClose: handleClose, uniprotId: uniprotId, userSelectedProteinSequence: selectedIsoform, selectedTranscript: selectedTranscript, url: structureUrl, pdbId: selectedPdbId, feature: feature, view: view, session: session, sideBySide: sideBySide, onSideBySideChange: onSideBySideChange, isLoading: isLoading, error: error }))));
91
114
  });
92
115
  export default PdbSearch;