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.
- package/README.md +15 -216
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js.map +1 -1
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js +2 -11
- package/dist/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.js.map +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +19 -4
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +39 -53
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js +9 -22
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js +4 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js +8 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js +15 -7
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.js +8 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.d.ts +7 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js +32 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.js.map +1 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +7 -2
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/TabPanel.js +1 -1
- package/dist/LaunchMsaView/components/TabPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/TranscriptSelector.js +7 -3
- package/dist/LaunchMsaView/components/TranscriptSelector.js.map +1 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.js +15 -14
- package/dist/LaunchMsaView/components/useTranscriptSelection.js.map +1 -1
- package/dist/MsaViewPanel/afterCreateAutoruns.js +17 -18
- package/dist/MsaViewPanel/afterCreateAutoruns.js.map +1 -1
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js +15 -10
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js.map +1 -1
- package/dist/MsaViewPanel/components/ErrorBoundary.d.ts +19 -0
- package/dist/MsaViewPanel/components/ErrorBoundary.js +22 -0
- package/dist/MsaViewPanel/components/ErrorBoundary.js.map +1 -0
- package/dist/MsaViewPanel/components/MsaViewPanel.js +11 -2
- package/dist/MsaViewPanel/components/MsaViewPanel.js.map +1 -1
- package/dist/MsaViewPanel/model.d.ts +2 -14
- package/dist/MsaViewPanel/model.js +13 -10
- package/dist/MsaViewPanel/model.js.map +1 -1
- package/dist/MsaViewPanel/pairwiseAlignment.js +15 -10
- package/dist/MsaViewPanel/pairwiseAlignment.js.map +1 -1
- package/dist/MsaViewPanel/structureConnection.d.ts +22 -0
- package/dist/MsaViewPanel/structureConnection.js +4 -0
- package/dist/MsaViewPanel/structureConnection.js.map +1 -1
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +26 -26
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.js +2 -3
- package/dist/utils/blastCache.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +33 -35
- package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +1 -2
- package/src/BgzipFastaMsaAdapter/BgzipFastaMsaAdapter.ts +2 -11
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +22 -7
- package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +40 -58
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx +14 -23
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.tsx +4 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastPanel.tsx +9 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastRIDPanel.tsx +15 -8
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBISettingsDialog.tsx +9 -1
- package/src/LaunchMsaView/components/NCBIBlastQuery/useCachedBlastResults.ts +52 -0
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +11 -2
- package/src/LaunchMsaView/components/TabPanel.tsx +1 -1
- package/src/LaunchMsaView/components/TranscriptSelector.tsx +7 -3
- package/src/LaunchMsaView/components/useTranscriptSelection.ts +25 -17
- package/src/MsaViewPanel/afterCreateAutoruns.ts +17 -18
- package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +23 -25
- package/src/MsaViewPanel/components/ErrorBoundary.tsx +40 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +22 -11
- package/src/MsaViewPanel/model.ts +49 -33
- package/src/MsaViewPanel/pairwiseAlignment.ts +15 -10
- package/src/MsaViewPanel/structureConnection.ts +23 -0
- package/src/utils/blastCache.ts +2 -3
- 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
|
|
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
|
|
189
|
+
<Button
|
|
190
|
+
color="secondary"
|
|
191
|
+
variant="contained"
|
|
192
|
+
onClick={() => {
|
|
193
|
+
handleClose()
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
187
196
|
Cancel
|
|
188
197
|
</Button>
|
|
189
198
|
</DialogActions>
|
|
@@ -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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 <
|
|
211
|
+
structureIdx < view.structures.length;
|
|
213
212
|
structureIdx++
|
|
214
213
|
) {
|
|
215
|
-
const structure =
|
|
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 ===
|
|
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(
|
|
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
|
|
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
|
|
255
|
+
const columns = new Set<number>()
|
|
254
256
|
|
|
255
257
|
for (const view of views) {
|
|
256
|
-
|
|
257
|
-
if (v.type !== 'ProteinView' || !v.structures) {
|
|
258
|
+
if (!isProteinView(view)) {
|
|
258
259
|
continue
|
|
259
260
|
}
|
|
260
261
|
|
|
261
|
-
for (const structure of
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 {
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
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:
|
|
374
|
-
)
|
|
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
|
|
395
|
+
const structure = proteinView.structures[structureIdx]
|
|
380
396
|
if (!structure) {
|
|
381
397
|
throw new Error(`Structure at index ${structureIdx} not found`)
|
|
382
398
|
}
|