jbrowse-plugin-msaview 2.3.3 → 2.3.8

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 (76) hide show
  1. package/README.md +15 -216
  2. package/dist/AddHighlightModel/GenomeMouseoverHighlight.js.map +1 -1
  3. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +2 -11
  4. package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js.map +1 -1
  5. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +19 -4
  6. package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js.map +1 -1
  7. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +39 -53
  8. package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js.map +1 -1
  9. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js +9 -22
  10. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js.map +1 -1
  11. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js +4 -1
  12. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js.map +1 -1
  13. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js +8 -1
  14. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js.map +1 -1
  15. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +15 -7
  16. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js.map +1 -1
  17. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.js +8 -1
  18. package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.js.map +1 -1
  19. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +7 -0
  20. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +32 -0
  21. package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js.map +1 -0
  22. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +7 -2
  23. package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +1 -1
  24. package/dist/LaunchMsaView/components/TabPanel.js +1 -1
  25. package/dist/LaunchMsaView/components/TabPanel.js.map +1 -1
  26. package/dist/LaunchMsaView/components/TranscriptSelector.js +7 -3
  27. package/dist/LaunchMsaView/components/TranscriptSelector.js.map +1 -1
  28. package/dist/LaunchMsaView/components/useTranscriptSelection.js +15 -14
  29. package/dist/LaunchMsaView/components/useTranscriptSelection.js.map +1 -1
  30. package/dist/MsaViewPanel/afterCreateAutoruns.js +17 -18
  31. package/dist/MsaViewPanel/afterCreateAutoruns.js.map +1 -1
  32. package/dist/MsaViewPanel/components/ConnectStructureDialog.js +15 -10
  33. package/dist/MsaViewPanel/components/ConnectStructureDialog.js.map +1 -1
  34. package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +19 -0
  35. package/dist/MsaViewPanel/components/ErrorBoundary.js +22 -0
  36. package/dist/MsaViewPanel/components/ErrorBoundary.js.map +1 -0
  37. package/dist/MsaViewPanel/components/MsaViewPanel.js +11 -2
  38. package/dist/MsaViewPanel/components/MsaViewPanel.js.map +1 -1
  39. package/dist/MsaViewPanel/model.d.ts +2 -14
  40. package/dist/MsaViewPanel/model.js +13 -10
  41. package/dist/MsaViewPanel/model.js.map +1 -1
  42. package/dist/MsaViewPanel/pairwiseAlignment.js +15 -10
  43. package/dist/MsaViewPanel/pairwiseAlignment.js.map +1 -1
  44. package/dist/MsaViewPanel/structureConnection.d.ts +22 -0
  45. package/dist/MsaViewPanel/structureConnection.js +4 -0
  46. package/dist/MsaViewPanel/structureConnection.js.map +1 -1
  47. package/dist/jbrowse-plugin-msaview.umd.production.min.js +26 -26
  48. package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
  49. package/dist/utils/blastCache.js +2 -3
  50. package/dist/utils/blastCache.js.map +1 -1
  51. package/dist/version.d.ts +1 -1
  52. package/dist/version.js +1 -1
  53. package/package.json +33 -35
  54. package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +1 -2
  55. package/src/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.ts +2 -11
  56. package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +22 -7
  57. package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +40 -58
  58. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx +14 -23
  59. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.tsx +4 -1
  60. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.tsx +9 -1
  61. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.tsx +15 -8
  62. package/src/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.tsx +9 -1
  63. package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +52 -0
  64. package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +11 -2
  65. package/src/LaunchMsaView/components/TabPanel.tsx +1 -1
  66. package/src/LaunchMsaView/components/TranscriptSelector.tsx +7 -3
  67. package/src/LaunchMsaView/components/useTranscriptSelection.ts +25 -17
  68. package/src/MsaViewPanel/afterCreateAutoruns.ts +17 -18
  69. package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +23 -25
  70. package/src/MsaViewPanel/components/ErrorBoundary.tsx +40 -0
  71. package/src/MsaViewPanel/components/MsaViewPanel.tsx +22 -11
  72. package/src/MsaViewPanel/model.ts +49 -33
  73. package/src/MsaViewPanel/pairwiseAlignment.ts +15 -10
  74. package/src/MsaViewPanel/structureConnection.ts +23 -0
  75. package/src/utils/blastCache.ts +2 -3
  76. package/src/version.ts +1 -1
@@ -0,0 +1,52 @@
1
+ import useSWR from 'swr'
2
+
3
+ import {
4
+ clearAllCachedResults,
5
+ deleteCachedResult,
6
+ getAllCachedResults,
7
+ } from '../../../utils/blastCache'
8
+
9
+ const swrConfig = {
10
+ revalidateOnFocus: false,
11
+ revalidateOnReconnect: false,
12
+ revalidateIfStale: false,
13
+ refreshWhenHidden: false,
14
+ refreshWhenOffline: false,
15
+ shouldRetryOnError: false,
16
+ }
17
+
18
+ export function useCachedBlastResults(geneIds: string[]) {
19
+ const {
20
+ data: results,
21
+ error,
22
+ mutate,
23
+ } = useSWR(
24
+ `cached-blast-${geneIds.join(',')}`,
25
+ async () => {
26
+ const cached = await getAllCachedResults()
27
+ return cached.filter(r => r.geneId && geneIds.includes(r.geneId))
28
+ },
29
+ swrConfig,
30
+ )
31
+
32
+ const handleDelete = async (id: string) => {
33
+ await deleteCachedResult(id)
34
+ await mutate(
35
+ results => results?.filter(result => result.id !== id) ?? [],
36
+ false,
37
+ )
38
+ }
39
+
40
+ const handleClearAll = async () => {
41
+ await clearAllCachedResults()
42
+ await mutate([], false)
43
+ }
44
+
45
+ return {
46
+ results: results ?? [],
47
+ error,
48
+ isLoading: !results && !error,
49
+ handleDelete,
50
+ handleClearAll,
51
+ }
52
+ }
@@ -24,6 +24,9 @@ const useStyles = makeStyles()({
24
24
  dialogContent: {
25
25
  width: '80em',
26
26
  },
27
+ selectedContainer: {
28
+ marginTop: 50,
29
+ },
27
30
  })
28
31
 
29
32
  const PreLoadedMSA = observer(function ({
@@ -120,7 +123,7 @@ const PreLoadedMSA = observer(function ({
120
123
  </TextField2>
121
124
 
122
125
  {selectedDataset ? (
123
- <div style={{ marginTop: 50 }}>
126
+ <div className={classes.selectedContainer}>
124
127
  {!msaListLoading && msaDataLoading ? (
125
128
  <LoadingEllipses
126
129
  variant="h6"
@@ -183,7 +186,13 @@ const PreLoadedMSA = observer(function ({
183
186
  >
184
187
  Submit
185
188
  </Button>
186
- <Button color="secondary" variant="contained" onClick={handleClose}>
189
+ <Button
190
+ color="secondary"
191
+ variant="contained"
192
+ onClick={() => {
193
+ handleClose()
194
+ }}
195
+ >
187
196
  Cancel
188
197
  </Button>
189
198
  </DialogActions>
@@ -13,7 +13,7 @@ export default function TabPanel({
13
13
  }) {
14
14
  return (
15
15
  <div role="tabpanel" hidden={value !== index} {...other}>
16
- {value === index && <div>{children}</div>}
16
+ {value === index ? <div>{children}</div> : null}
17
17
  </div>
18
18
  )
19
19
  }
@@ -21,6 +21,10 @@ const useStyles = makeStyles()({
21
21
  minWidth: {
22
22
  minWidth: 300,
23
23
  },
24
+ centered: {
25
+ alignContent: 'center',
26
+ marginLeft: 20,
27
+ },
24
28
  })
25
29
 
26
30
  export default function TranscriptSelector({
@@ -70,7 +74,7 @@ export default function TranscriptSelector({
70
74
  )
71
75
  })}
72
76
  </TextField>
73
- <div style={{ alignContent: 'center', marginLeft: 20 }}>
77
+ <div className={classes.centered}>
74
78
  <Button
75
79
  variant="contained"
76
80
  color="primary"
@@ -83,7 +87,7 @@ export default function TranscriptSelector({
83
87
  </div>
84
88
  </div>
85
89
 
86
- {showSequence && (
90
+ {showSequence ? (
87
91
  <ReadOnlyTextField2
88
92
  value={
89
93
  proteinSequence
@@ -91,7 +95,7 @@ export default function TranscriptSelector({
91
95
  : 'Loading...'
92
96
  }
93
97
  />
94
- )}
98
+ ) : null}
95
99
  </>
96
100
  )
97
101
  }
@@ -1,4 +1,4 @@
1
- import { useEffect, useMemo, useState } from 'react'
1
+ import { useMemo, useState } from 'react'
2
2
 
3
3
  import { featureMatchesId, getId, getSortedTranscriptFeatures } from '../util'
4
4
  import { useFeatureSequence } from './useFeatureSequence'
@@ -9,6 +9,24 @@ function featureInValidIds(feature: Feature, validIds: string[]): boolean {
9
9
  return validIds.some(id => featureMatchesId(feature, id))
10
10
  }
11
11
 
12
+ function findValidSelection(
13
+ currentId: string,
14
+ options: Feature[],
15
+ validIds: string[] | undefined,
16
+ ): string | undefined {
17
+ if (!validIds || validIds.length === 0) {
18
+ return undefined
19
+ }
20
+
21
+ const currentFeature = options.find(opt => getId(opt) === currentId)
22
+ if (!currentFeature || featureInValidIds(currentFeature, validIds)) {
23
+ return undefined
24
+ }
25
+
26
+ const validOption = options.find(opt => featureInValidIds(opt, validIds))
27
+ return validOption ? getId(validOption) : undefined
28
+ }
29
+
12
30
  export function useTranscriptSelection({
13
31
  feature,
14
32
  view,
@@ -20,29 +38,19 @@ export function useTranscriptSelection({
20
38
  }) {
21
39
  const options = useMemo(() => getSortedTranscriptFeatures(feature), [feature])
22
40
  const [selectedId, setSelectedId] = useState(() => getId(options[0]))
23
- const selectedTranscript = options.find(val => getId(val) === selectedId)
41
+ const validatedSelectedId =
42
+ findValidSelection(selectedId, options, validIds) || selectedId
43
+ const selectedTranscript = options.find(
44
+ val => getId(val) === validatedSelectedId,
45
+ )
24
46
  const { proteinSequence, error } = useFeatureSequence({
25
47
  view,
26
48
  feature: selectedTranscript,
27
49
  })
28
50
 
29
- useEffect(() => {
30
- if (validIds && validIds.length > 0) {
31
- const currentFeature = options.find(opt => getId(opt) === selectedId)
32
- if (currentFeature && !featureInValidIds(currentFeature, validIds)) {
33
- const validOption = options.find(opt =>
34
- featureInValidIds(opt, validIds),
35
- )
36
- if (validOption) {
37
- setSelectedId(getId(validOption))
38
- }
39
- }
40
- }
41
- }, [validIds, options, selectedId])
42
-
43
51
  return {
44
52
  options,
45
- selectedId,
53
+ selectedId: validatedSelectedId,
46
54
  setSelectedId,
47
55
  selectedTranscript,
48
56
  proteinSequence,
@@ -8,7 +8,7 @@ import {
8
8
  retrieveMsaData,
9
9
  storeMsaData,
10
10
  } from './msaDataStore'
11
- import { gappedToUngappedPosition } from './structureConnection'
11
+ import { gappedToUngappedPosition, isProteinView } from './structureConnection'
12
12
  import { getUniprotIdFromAlphaFoldUrl } from './util'
13
13
 
14
14
  import type { JBrowsePluginMsaViewModel } from './model'
@@ -163,7 +163,7 @@ export function highlightConnectedStructures(self: JBrowsePluginMsaViewModel) {
163
163
  }
164
164
 
165
165
  for (const conn of connectedProteinViews) {
166
- const structure = conn.proteinView?.structures?.[conn.structureIdx]
166
+ const structure = conn.proteinView.structures[conn.structureIdx]
167
167
  if (!structure) {
168
168
  continue
169
169
  }
@@ -194,7 +194,7 @@ export function highlightConnectedStructures(self: JBrowsePluginMsaViewModel) {
194
194
  }
195
195
 
196
196
  export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
197
- const { views } = getSession(self)
197
+ const views = getSession(self).views as unknown[]
198
198
  const { connectedViewId, uniprotId, rows, connectedStructures } = self
199
199
 
200
200
  if (!uniprotId || rows.length === 0) {
@@ -202,17 +202,19 @@ export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
202
202
  }
203
203
 
204
204
  for (const view of views) {
205
- const v = view as any
206
- if (v.type !== 'ProteinView' || !v.structures) {
205
+ if (!isProteinView(view)) {
207
206
  continue
208
207
  }
209
208
 
210
209
  for (
211
210
  let structureIdx = 0;
212
- structureIdx < v.structures.length;
211
+ structureIdx < view.structures.length;
213
212
  structureIdx++
214
213
  ) {
215
- const structure = v.structures[structureIdx]
214
+ const structure = view.structures[structureIdx]
215
+ if (!structure) {
216
+ continue
217
+ }
216
218
 
217
219
  if (structure.connectedViewId !== connectedViewId) {
218
220
  continue
@@ -223,7 +225,7 @@ export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
223
225
  }
224
226
 
225
227
  const alreadyConnected = connectedStructures.some(
226
- c => c.proteinViewId === v.id && c.structureIdx === structureIdx,
228
+ c => c.proteinViewId === view.id && c.structureIdx === structureIdx,
227
229
  )
228
230
  if (alreadyConnected) {
229
231
  continue
@@ -234,7 +236,7 @@ export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
234
236
  }
235
237
 
236
238
  try {
237
- self.connectToStructure(v.id, structureIdx)
239
+ self.connectToStructure(view.id, structureIdx)
238
240
  } catch (e) {
239
241
  console.error('Failed to auto-connect to ProteinView:', e)
240
242
  }
@@ -243,22 +245,21 @@ export function autoConnectStructures(self: JBrowsePluginMsaViewModel) {
243
245
  }
244
246
 
245
247
  export function observeProteinHighlights(self: JBrowsePluginMsaViewModel) {
246
- const { views } = getSession(self)
248
+ const views = getSession(self).views as unknown[]
247
249
  const { connectedViewId, transcriptToMsaMap, querySeqName } = self
248
250
 
249
251
  if (!connectedViewId || !transcriptToMsaMap) {
250
252
  return
251
253
  }
252
254
 
253
- const columns: number[] = []
255
+ const columns = new Set<number>()
254
256
 
255
257
  for (const view of views) {
256
- const v = view as any
257
- if (v.type !== 'ProteinView' || !v.structures) {
258
+ if (!isProteinView(view)) {
258
259
  continue
259
260
  }
260
261
 
261
- for (const structure of v.structures) {
262
+ for (const structure of view.structures) {
262
263
  if (structure.connectedViewId !== connectedViewId) {
263
264
  continue
264
265
  }
@@ -274,16 +275,14 @@ export function observeProteinHighlights(self: JBrowsePluginMsaViewModel) {
274
275
  const proteinPos = g2p[coord]
275
276
  if (proteinPos !== undefined) {
276
277
  const col = self.seqPosToGlobalCol(querySeqName, proteinPos)
277
- if (!columns.includes(col)) {
278
- columns.push(col)
279
- }
278
+ columns.add(col)
280
279
  }
281
280
  }
282
281
  }
283
282
  }
284
283
  }
285
284
 
286
- const visibleColumns = columns
285
+ const visibleColumns = Array.from(columns)
287
286
  .map(col => self.globalColToVisibleCol(col))
288
287
  .filter((col): col is number => col !== undefined)
289
288
 
@@ -1,6 +1,6 @@
1
1
  import React, { useState } from 'react'
2
2
 
3
- import { Dialog } from '@jbrowse/core/ui'
3
+ import { Dialog, ErrorMessage } from '@jbrowse/core/ui'
4
4
  import { getSession } from '@jbrowse/core/util'
5
5
  import {
6
6
  Button,
@@ -13,9 +13,18 @@ import {
13
13
  Typography,
14
14
  } from '@mui/material'
15
15
  import { observer } from 'mobx-react'
16
+ import { makeStyles } from 'tss-react/mui'
17
+
18
+ import { isProteinView } from '../structureConnection'
16
19
 
17
20
  import type { JBrowsePluginMsaViewModel } from '../model'
18
21
 
22
+ const useStyles = makeStyles()(theme => ({
23
+ formControl: {
24
+ marginBottom: theme.spacing(2),
25
+ },
26
+ }))
27
+
19
28
  const ConnectStructureDialog = observer(function ConnectStructureDialog({
20
29
  model,
21
30
  handleClose,
@@ -23,23 +32,18 @@ const ConnectStructureDialog = observer(function ConnectStructureDialog({
23
32
  model: JBrowsePluginMsaViewModel
24
33
  handleClose: () => void
25
34
  }) {
35
+ const { classes } = useStyles()
26
36
  const session = getSession(model)
27
37
  const [selectedViewId, setSelectedViewId] = useState('')
28
38
  const [selectedStructureIdx, setSelectedStructureIdx] = useState(0)
29
39
  const [selectedMsaRow, setSelectedMsaRow] = useState(model.querySeqName)
30
40
  const [error, setError] = useState<string>()
31
41
 
32
- // Find all ProteinViews in the session
33
-
34
- const proteinViews = session.views.filter(
35
- (v: any) => v.type === 'ProteinView',
36
- ) as any[]
42
+ const proteinViews = (session.views as unknown[]).filter(isProteinView)
37
43
 
38
- // Get structures for the selected view
39
44
  const selectedView = proteinViews.find(v => v.id === selectedViewId)
40
45
  const structures = selectedView?.structures ?? []
41
46
 
42
- // Get MSA row names
43
47
  const msaRowNames = model.rows.map(r => r[0])
44
48
 
45
49
  const handleConnect = () => {
@@ -75,7 +79,7 @@ const ConnectStructureDialog = observer(function ConnectStructureDialog({
75
79
  </Typography>
76
80
  ) : (
77
81
  <>
78
- <FormControl fullWidth sx={{ mb: 2 }}>
82
+ <FormControl fullWidth className={classes.formControl}>
79
83
  <InputLabel>Protein View</InputLabel>
80
84
  <Select
81
85
  value={selectedViewId}
@@ -93,8 +97,8 @@ const ConnectStructureDialog = observer(function ConnectStructureDialog({
93
97
  </Select>
94
98
  </FormControl>
95
99
 
96
- {structures.length > 1 && (
97
- <FormControl fullWidth sx={{ mb: 2 }}>
100
+ {structures.length > 1 ? (
101
+ <FormControl fullWidth className={classes.formControl}>
98
102
  <InputLabel>Structure</InputLabel>
99
103
  <Select
100
104
  value={selectedStructureIdx}
@@ -103,18 +107,16 @@ const ConnectStructureDialog = observer(function ConnectStructureDialog({
103
107
  setSelectedStructureIdx(e.target.value)
104
108
  }}
105
109
  >
106
- {structures.map(
107
- (structure: { url?: string }, idx: number) => (
108
- <MenuItem key={idx} value={idx}>
109
- {structure.url ?? `Structure ${idx + 1}`}
110
- </MenuItem>
111
- ),
112
- )}
110
+ {structures.map((structure, idx) => (
111
+ <MenuItem key={idx} value={idx}>
112
+ {structure.url ?? `Structure ${idx + 1}`}
113
+ </MenuItem>
114
+ ))}
113
115
  </Select>
114
116
  </FormControl>
115
- )}
117
+ ) : null}
116
118
 
117
- <FormControl fullWidth sx={{ mb: 2 }}>
119
+ <FormControl fullWidth className={classes.formControl}>
118
120
  <InputLabel>MSA Row</InputLabel>
119
121
  <Select
120
122
  value={selectedMsaRow}
@@ -131,11 +133,7 @@ const ConnectStructureDialog = observer(function ConnectStructureDialog({
131
133
  </Select>
132
134
  </FormControl>
133
135
 
134
- {error && (
135
- <Typography color="error" sx={{ mt: 1 }}>
136
- {error}
137
- </Typography>
138
- )}
136
+ {error ? <ErrorMessage error={error} /> : null}
139
137
  </>
140
138
  )}
141
139
  </DialogContent>
@@ -0,0 +1,40 @@
1
+ import type { ReactNode } from 'react'
2
+ import React, { Component } from 'react'
3
+
4
+ import { ErrorMessage } from '@jbrowse/core/ui'
5
+
6
+ interface Props {
7
+ children: ReactNode
8
+ }
9
+
10
+ interface State {
11
+ hasError: boolean
12
+ error: unknown
13
+ }
14
+
15
+ export class ErrorBoundary extends Component<Props, State> {
16
+ constructor(props: Props) {
17
+ super(props)
18
+ this.state = { hasError: false, error: null }
19
+ }
20
+
21
+ static getDerivedStateFromError(error: unknown) {
22
+ return { hasError: true, error }
23
+ }
24
+
25
+ componentDidCatch(error: unknown, info: React.ErrorInfo) {
26
+ console.error('MsaViewPanel error:', error, info.componentStack)
27
+ }
28
+
29
+ render() {
30
+ if (this.state.hasError) {
31
+ return (
32
+ <div style={{ padding: 20 }}>
33
+ <ErrorMessage error={this.state.error} />
34
+ </div>
35
+ )
36
+ }
37
+
38
+ return this.props.children
39
+ }
40
+ }
@@ -3,29 +3,40 @@ import React from 'react'
3
3
  import { LoadingEllipses } from '@jbrowse/core/ui'
4
4
  import { observer } from 'mobx-react'
5
5
  import { MSAView } from 'react-msaview'
6
+ import { makeStyles } from 'tss-react/mui'
6
7
 
8
+ import { ErrorBoundary } from './ErrorBoundary'
7
9
  import LoadingBLAST from './LoadingBLAST'
8
10
 
9
11
  import type { JBrowsePluginMsaViewModel } from '../model'
10
12
 
13
+ const useStyles = makeStyles()({
14
+ loadingContainer: {
15
+ padding: 20,
16
+ },
17
+ })
18
+
11
19
  const MsaViewPanel = observer(function MsaViewPanel2({
12
20
  model,
13
21
  }: {
14
22
  model: JBrowsePluginMsaViewModel
15
23
  }) {
24
+ const { classes } = useStyles()
16
25
  const { blastParams, loadingStoredData } = model
17
26
  return (
18
- <div>
19
- {blastParams ? (
20
- <LoadingBLAST model={model} baseUrl={blastParams.baseUrl} />
21
- ) : loadingStoredData ? (
22
- <div style={{ padding: 20 }}>
23
- <LoadingEllipses message="Loading MSA data" variant="h6" />
24
- </div>
25
- ) : (
26
- <MSAView model={model} />
27
- )}
28
- </div>
27
+ <ErrorBoundary>
28
+ <div>
29
+ {blastParams ? (
30
+ <LoadingBLAST model={model} baseUrl={blastParams.baseUrl} />
31
+ ) : loadingStoredData ? (
32
+ <div className={classes.loadingContainer}>
33
+ <LoadingEllipses message="Loading MSA data" variant="h6" />
34
+ </div>
35
+ ) : (
36
+ <MSAView model={model} />
37
+ )}
38
+ </div>
39
+ </ErrorBoundary>
29
40
  )
30
41
  })
31
42
 
@@ -21,9 +21,13 @@ import {
21
21
  import { genomeToMSA } from './genomeToMSA'
22
22
  import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord'
23
23
  import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment'
24
- import { mapToRecord, ungappedToGappedPosition } from './structureConnection'
24
+ import {
25
+ isProteinView,
26
+ mapToRecord,
27
+ ungappedToGappedPosition,
28
+ } from './structureConnection'
25
29
 
26
- import type { StructureConnection } from './structureConnection'
30
+ import type { ProteinView, StructureConnection } from './structureConnection'
27
31
  import type { MafRegion, MsaViewInitState } from './types'
28
32
  import type { Feature } from '@jbrowse/core/util'
29
33
  import type { Instance } from '@jbrowse/mobx-state-tree'
@@ -123,24 +127,31 @@ export default function stateModelFactory() {
123
127
  }),
124
128
  )
125
129
 
126
- .volatile(() => ({
127
- /**
128
- * #volatile
129
- */
130
- rid: undefined as string | undefined,
131
- /**
132
- * #volatile
133
- */
134
- progress: '',
135
- /**
136
- * #volatile
137
- */
138
- error: undefined as unknown,
139
- /**
140
- * #volatile
141
- */
142
- loadingStoredData: false,
143
- }))
130
+ .volatile(
131
+ (): {
132
+ rid: string | undefined
133
+ progress: string
134
+ error: unknown
135
+ loadingStoredData: boolean
136
+ } => ({
137
+ /**
138
+ * #volatile
139
+ */
140
+ rid: undefined,
141
+ /**
142
+ * #volatile
143
+ */
144
+ progress: '',
145
+ /**
146
+ * #volatile
147
+ */
148
+ error: undefined,
149
+ /**
150
+ * #volatile
151
+ */
152
+ loadingStoredData: false,
153
+ }),
154
+ )
144
155
 
145
156
  .views(self => ({
146
157
  /**
@@ -188,14 +199,19 @@ export default function stateModelFactory() {
188
199
  */
189
200
  get connectedProteinViews() {
190
201
  const { views } = getSession(self)
191
- return self.connectedStructures
192
- .map(conn => {
193
- const proteinView = views.find(
194
- (v: any) => v.id === conn.proteinViewId,
195
- ) as any
196
- return proteinView ? { ...conn, proteinView } : undefined
197
- })
198
- .filter((c): c is StructureConnection & { proteinView: any } => !!c)
202
+ const unknownViews = views as unknown[]
203
+ const result: (StructureConnection & { proteinView: ProteinView })[] =
204
+ []
205
+ for (const conn of self.connectedStructures) {
206
+ const proteinView = unknownViews.find(
207
+ (v): v is ProteinView =>
208
+ isProteinView(v) && v.id === conn.proteinViewId,
209
+ )
210
+ if (proteinView) {
211
+ result.push({ ...conn, proteinView })
212
+ }
213
+ }
214
+ return result
199
215
  },
200
216
  }))
201
217
 
@@ -205,7 +221,7 @@ export default function stateModelFactory() {
205
221
  */
206
222
  get structureHoverCol(): number | undefined {
207
223
  for (const conn of self.connectedProteinViews) {
208
- const structure = conn.proteinView?.structures?.[conn.structureIdx]
224
+ const structure = conn.proteinView.structures[conn.structureIdx]
209
225
  const structurePos = structure?.hoverPosition?.structureSeqPos
210
226
  if (structurePos !== undefined) {
211
227
  const msaUngapped = conn.structureToMsa[structurePos]
@@ -369,14 +385,14 @@ export default function stateModelFactory() {
369
385
 
370
386
  const { views } = getSession(self)
371
387
 
372
- const proteinView = views.find(
373
- (v: any) => v.id === proteinViewId,
374
- ) as any
388
+ const proteinView = (views as unknown[]).find(
389
+ (v): v is ProteinView => isProteinView(v) && v.id === proteinViewId,
390
+ )
375
391
  if (!proteinView) {
376
392
  throw new Error(`ProteinView "${proteinViewId}" not found`)
377
393
  }
378
394
 
379
- const structure = proteinView.structures?.[structureIdx]
395
+ const structure = proteinView.structures[structureIdx]
380
396
  if (!structure) {
381
397
  throw new Error(`Structure at index ${structureIdx} not found`)
382
398
  }