jbrowse-plugin-msaview 2.2.3 → 2.2.5
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/CHANGELOG.md +1 -1
- package/README.md +229 -0
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js +23 -18
- package/dist/AddHighlightModel/GenomeMouseoverHighlight.js.map +1 -1
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js +23 -13
- package/dist/AddHighlightModel/MsaToGenomeHighlight.js.map +1 -1
- package/dist/AddHighlightModel/index.js +8 -1
- package/dist/AddHighlightModel/index.js.map +1 -1
- package/dist/AddHighlightModel/util.d.ts +2 -2
- package/dist/BgzipFastaMsaAdapter/configSchema.d.ts +2 -2
- package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js +5 -11
- package/dist/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.js.map +1 -1
- package/dist/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.js +5 -1
- package/dist/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.js.map +1 -1
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js +16 -16
- package/dist/LaunchMsaView/components/LaunchMsaViewDialog.js.map +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js +38 -46
- package/dist/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.js.map +1 -1
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.d.ts +4 -3
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js +4 -3
- package/dist/LaunchMsaView/components/ManualMSALoader/launchView.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.d.ts +9 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js +76 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.js.map +1 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js +35 -13
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js +6 -12
- package/dist/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.d.ts +6 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js +15 -0
- package/dist/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.js.map +1 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js +12 -34
- package/dist/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.js.map +1 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/consts.d.ts +1 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/consts.js +1 -0
- package/dist/LaunchMsaView/components/PreLoadedMSA/consts.js.map +1 -1
- package/dist/LaunchMsaView/components/TabPanel.d.ts +2 -2
- package/dist/LaunchMsaView/components/TranscriptSelector.d.ts +2 -2
- package/dist/LaunchMsaView/components/TranscriptSelector.js +3 -6
- package/dist/LaunchMsaView/components/TranscriptSelector.js.map +1 -1
- package/dist/LaunchMsaView/components/useSWRFeatureSequence.js +6 -4
- package/dist/LaunchMsaView/components/useSWRFeatureSequence.js.map +1 -1
- package/dist/LaunchMsaView/components/useTranscriptSelection.d.ts +16 -0
- package/dist/LaunchMsaView/components/useTranscriptSelection.js +31 -0
- package/dist/LaunchMsaView/components/useTranscriptSelection.js.map +1 -0
- package/dist/LaunchMsaView/components/util.d.ts +3 -1
- package/dist/LaunchMsaView/components/util.js +12 -2
- package/dist/LaunchMsaView/components/util.js.map +1 -1
- package/dist/LaunchMsaView/util.d.ts +2 -0
- package/dist/LaunchMsaView/util.js +16 -4
- package/dist/LaunchMsaView/util.js.map +1 -1
- package/dist/LaunchMsaViewExtensionPoint/index.d.ts +2 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js +31 -0
- package/dist/LaunchMsaViewExtensionPoint/index.js.map +1 -0
- package/dist/MsaViewPanel/components/ConnectStructureDialog.d.ts +7 -0
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js +56 -0
- package/dist/MsaViewPanel/components/ConnectStructureDialog.js.map +1 -0
- package/dist/MsaViewPanel/components/MsaViewPanel.js +4 -2
- package/dist/MsaViewPanel/components/MsaViewPanel.js.map +1 -1
- package/dist/MsaViewPanel/doLaunchBlast.d.ts +1 -0
- package/dist/MsaViewPanel/doLaunchBlast.js +65 -19
- package/dist/MsaViewPanel/doLaunchBlast.js.map +1 -1
- package/dist/MsaViewPanel/genomeToMSA.d.ts +6 -0
- package/dist/MsaViewPanel/genomeToMSA.js +38 -8
- package/dist/MsaViewPanel/genomeToMSA.js.map +1 -1
- package/dist/MsaViewPanel/genomeToMSA.test.d.ts +1 -0
- package/dist/MsaViewPanel/genomeToMSA.test.js +244 -0
- package/dist/MsaViewPanel/genomeToMSA.test.js.map +1 -0
- package/dist/MsaViewPanel/model.d.ts +719 -226
- package/dist/MsaViewPanel/model.js +467 -39
- package/dist/MsaViewPanel/model.js.map +1 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +7 -2
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js +26 -27
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.js.map +1 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.d.ts +1 -0
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js +240 -0
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.test.js.map +1 -0
- package/dist/MsaViewPanel/msaDataStore.d.ts +14 -0
- package/dist/MsaViewPanel/msaDataStore.js +197 -0
- package/dist/MsaViewPanel/msaDataStore.js.map +1 -0
- package/dist/MsaViewPanel/pairwiseAlignment.d.ts +27 -0
- package/dist/MsaViewPanel/pairwiseAlignment.js +776 -0
- package/dist/MsaViewPanel/pairwiseAlignment.js.map +1 -0
- package/dist/MsaViewPanel/pairwiseAlignment.test.d.ts +1 -0
- package/dist/MsaViewPanel/pairwiseAlignment.test.js +112 -0
- package/dist/MsaViewPanel/pairwiseAlignment.test.js.map +1 -0
- package/dist/MsaViewPanel/structureConnection.d.ts +27 -0
- package/dist/MsaViewPanel/structureConnection.js +46 -0
- package/dist/MsaViewPanel/structureConnection.js.map +1 -0
- package/dist/MsaViewPanel/structureConnection.test.d.ts +1 -0
- package/dist/MsaViewPanel/structureConnection.test.js +122 -0
- package/dist/MsaViewPanel/structureConnection.test.js.map +1 -0
- package/dist/MsaViewPanel/types.d.ts +13 -0
- package/dist/MsaViewPanel/types.js +2 -0
- package/dist/MsaViewPanel/types.js.map +1 -0
- package/dist/MsaViewPanel/util.d.ts +7 -0
- package/dist/MsaViewPanel/util.js +10 -0
- package/dist/MsaViewPanel/util.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/jbrowse-plugin-msaview.umd.production.min.js +39 -90
- package/dist/jbrowse-plugin-msaview.umd.production.min.js.map +4 -4
- package/dist/utils/blastCache.d.ts +34 -0
- package/dist/utils/blastCache.js +58 -0
- package/dist/utils/blastCache.js.map +1 -0
- package/dist/utils/fetch.d.ts +1 -1
- package/dist/utils/fetch.js +1 -1
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/ncbiBlast.d.ts +1 -5
- package/dist/utils/taxonomyNames.d.ts +5 -0
- package/dist/utils/taxonomyNames.js +79 -0
- package/dist/utils/taxonomyNames.js.map +1 -0
- package/dist/utils/types.d.ts +8 -5
- package/package.json +50 -54
- package/src/AddHighlightModel/GenomeMouseoverHighlight.tsx +37 -21
- package/src/AddHighlightModel/MsaToGenomeHighlight.tsx +38 -17
- package/src/AddHighlightModel/index.tsx +9 -4
- package/src/LaunchMsaView/components/EnsemblGeneTree/EnsemblGeneTree.tsx +13 -13
- package/src/LaunchMsaView/components/EnsemblGeneTree/useGeneTree.ts +6 -0
- package/src/LaunchMsaView/components/LaunchMsaViewDialog.tsx +30 -23
- package/src/LaunchMsaView/components/ManualMSALoader/ManualMSALoader.tsx +64 -51
- package/src/LaunchMsaView/components/ManualMSALoader/launchView.ts +9 -6
- package/src/LaunchMsaView/components/NCBIBlastQuery/CachedBlastResults.tsx +146 -0
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastAutomaticPanel.tsx +53 -22
- package/src/LaunchMsaView/components/NCBIBlastQuery/NCBIBlastManualPanel.tsx +8 -13
- package/src/LaunchMsaView/components/NCBIBlastQuery/blastLaunchView.ts +25 -0
- package/src/LaunchMsaView/components/PreLoadedMSA/PreLoadedMSADataPanel.tsx +27 -47
- package/src/LaunchMsaView/components/PreLoadedMSA/consts.ts +1 -0
- package/src/LaunchMsaView/components/TabPanel.tsx +2 -2
- package/src/LaunchMsaView/components/TranscriptSelector.tsx +13 -20
- package/src/LaunchMsaView/components/useSWRFeatureSequence.ts +5 -5
- package/src/LaunchMsaView/components/useTranscriptSelection.ts +48 -0
- package/src/LaunchMsaView/components/util.ts +17 -2
- package/src/LaunchMsaView/index.ts +1 -1
- package/src/LaunchMsaView/util.ts +25 -6
- package/src/LaunchMsaViewExtensionPoint/index.ts +74 -0
- package/src/MsaViewPanel/components/ConnectStructureDialog.tsx +156 -0
- package/src/MsaViewPanel/components/MsaViewPanel.tsx +6 -1
- package/src/MsaViewPanel/doLaunchBlast.ts +83 -23
- package/src/MsaViewPanel/genomeToMSA.test.ts +281 -0
- package/src/MsaViewPanel/genomeToMSA.ts +43 -10
- package/src/MsaViewPanel/model.ts +590 -43
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +256 -0
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +43 -29
- package/src/MsaViewPanel/msaDataStore.ts +236 -0
- package/src/MsaViewPanel/pairwiseAlignment.test.ts +140 -0
- package/src/MsaViewPanel/pairwiseAlignment.ts +818 -0
- package/src/MsaViewPanel/structureConnection.test.ts +143 -0
- package/src/MsaViewPanel/structureConnection.ts +72 -0
- package/src/MsaViewPanel/types.ts +14 -0
- package/src/MsaViewPanel/util.ts +11 -0
- package/src/index.ts +3 -1
- package/src/utils/blastCache.ts +114 -0
- package/src/utils/fetch.ts +1 -1
- package/src/utils/taxonomyNames.ts +111 -0
- package/src/utils/types.ts +9 -1
- package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.d.ts +0 -5
- package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.js +0 -16
- package/dist/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.js.map +0 -1
- package/dist/out.js +0 -55381
- package/src/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.ts +0 -25
|
@@ -1,22 +1,86 @@
|
|
|
1
|
+
import { lazy } from 'react'
|
|
2
|
+
|
|
1
3
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes'
|
|
2
4
|
import { getSession } from '@jbrowse/core/util'
|
|
5
|
+
import { addDisposer, cast, types } from '@jbrowse/mobx-state-tree'
|
|
3
6
|
import { genomeToTranscriptSeqMapping } from 'g2p_mapper'
|
|
4
7
|
import { autorun } from 'mobx'
|
|
5
|
-
import { addDisposer, cast, types } from 'mobx-state-tree'
|
|
6
8
|
import { MSAModelF } from 'react-msaview'
|
|
7
9
|
|
|
8
10
|
import { doLaunchBlast } from './doLaunchBlast'
|
|
9
11
|
import { genomeToMSA } from './genomeToMSA'
|
|
10
12
|
import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord'
|
|
13
|
+
import {
|
|
14
|
+
cleanupOldData,
|
|
15
|
+
generateDataStoreId,
|
|
16
|
+
retrieveMsaData,
|
|
17
|
+
storeMsaData,
|
|
18
|
+
} from './msaDataStore'
|
|
19
|
+
import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment'
|
|
20
|
+
import {
|
|
21
|
+
gappedToUngappedPosition,
|
|
22
|
+
mapToRecord,
|
|
23
|
+
ungappedToGappedPosition,
|
|
24
|
+
} from './structureConnection'
|
|
25
|
+
import { getUniprotIdFromAlphaFoldUrl } from './util'
|
|
11
26
|
|
|
27
|
+
import type { StructureConnection } from './structureConnection'
|
|
28
|
+
import type { MafRegion, MsaViewInitState } from './types'
|
|
12
29
|
import type { Feature } from '@jbrowse/core/util'
|
|
30
|
+
import type { Instance } from '@jbrowse/mobx-state-tree'
|
|
13
31
|
import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
14
|
-
|
|
32
|
+
|
|
33
|
+
const ConnectStructureDialog = lazy(
|
|
34
|
+
() => import('./components/ConnectStructureDialog'),
|
|
35
|
+
)
|
|
15
36
|
|
|
16
37
|
type LGV = LinearGenomeViewModel
|
|
17
38
|
|
|
18
39
|
type MaybeLGV = LGV | undefined
|
|
19
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Highlights residues in connected protein structures based on current MSA hover position
|
|
43
|
+
*/
|
|
44
|
+
function highlightConnectedStructures(self: JBrowsePluginMsaViewModel) {
|
|
45
|
+
const { mouseCol, connectedProteinViews } = self
|
|
46
|
+
if (connectedProteinViews.length === 0) {
|
|
47
|
+
return
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const conn of connectedProteinViews) {
|
|
51
|
+
const structure = conn.proteinView?.structures?.[conn.structureIdx]
|
|
52
|
+
if (!structure) {
|
|
53
|
+
continue
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Clear highlight if mouse left MSA
|
|
57
|
+
if (mouseCol === undefined) {
|
|
58
|
+
structure.clearHighlightFromExternal?.()
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const seq = self.getSequenceByRowName(conn.msaRowName)
|
|
63
|
+
if (!seq) {
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Convert gapped MSA column to ungapped position
|
|
68
|
+
const msaUngapped = gappedToUngappedPosition(seq, mouseCol)
|
|
69
|
+
if (msaUngapped === undefined) {
|
|
70
|
+
structure.clearHighlightFromExternal?.()
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Map to structure position and highlight
|
|
75
|
+
const structurePos = conn.msaToStructure[msaUngapped]
|
|
76
|
+
if (structurePos === undefined) {
|
|
77
|
+
structure.clearHighlightFromExternal?.()
|
|
78
|
+
} else {
|
|
79
|
+
structure.highlightFromExternal?.(structurePos)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
20
84
|
export interface IRegion {
|
|
21
85
|
refName: string
|
|
22
86
|
start: number
|
|
@@ -28,7 +92,7 @@ export interface BlastParams {
|
|
|
28
92
|
blastDatabase: string
|
|
29
93
|
msaAlgorithm: string
|
|
30
94
|
blastProgram: string
|
|
31
|
-
selectedTranscript
|
|
95
|
+
selectedTranscript?: Feature
|
|
32
96
|
proteinSequence: string
|
|
33
97
|
}
|
|
34
98
|
|
|
@@ -70,10 +134,48 @@ export default function stateModelFactory() {
|
|
|
70
134
|
*/
|
|
71
135
|
querySeqName: 'QUERY',
|
|
72
136
|
|
|
137
|
+
/**
|
|
138
|
+
* #property
|
|
139
|
+
* UniProt ID extracted from AlphaFold MSA URL
|
|
140
|
+
*/
|
|
141
|
+
uniprotId: types.maybe(types.string),
|
|
142
|
+
|
|
73
143
|
/**
|
|
74
144
|
* #property
|
|
75
145
|
*/
|
|
76
146
|
zoomToBaseLevel: false,
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* #property
|
|
150
|
+
* used for loading the MSA view via session snapshots, e.g.
|
|
151
|
+
* {
|
|
152
|
+
* "type": "MsaView",
|
|
153
|
+
* "init": {
|
|
154
|
+
* "msaUrl": "https://example.com/alignment.fa",
|
|
155
|
+
* "treeUrl": "https://example.com/tree.nh",
|
|
156
|
+
* "querySeqName": "ENST00000123_hg38"
|
|
157
|
+
* }
|
|
158
|
+
* }
|
|
159
|
+
*/
|
|
160
|
+
init: types.frozen<MsaViewInitState | undefined>(),
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* #property
|
|
164
|
+
* connections to protein 3D structure views for synchronized highlighting
|
|
165
|
+
*/
|
|
166
|
+
connectedStructures: types.array(types.frozen<StructureConnection>()),
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* #property
|
|
170
|
+
* Reference ID for MSA data stored in IndexedDB (for large datasets)
|
|
171
|
+
*/
|
|
172
|
+
dataStoreId: types.maybe(types.string),
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* #property
|
|
176
|
+
* MAF region for coordinate mapping (used when launched from MAF viewer)
|
|
177
|
+
*/
|
|
178
|
+
mafRegion: types.frozen<MafRegion | undefined>(),
|
|
77
179
|
}),
|
|
78
180
|
)
|
|
79
181
|
|
|
@@ -90,28 +192,31 @@ export default function stateModelFactory() {
|
|
|
90
192
|
* #volatile
|
|
91
193
|
*/
|
|
92
194
|
error: undefined as unknown,
|
|
195
|
+
/**
|
|
196
|
+
* #volatile
|
|
197
|
+
* True when loading MSA data from IndexedDB
|
|
198
|
+
*/
|
|
199
|
+
loadingStoredData: false,
|
|
93
200
|
}))
|
|
94
201
|
|
|
95
202
|
.views(self => ({
|
|
96
203
|
/**
|
|
97
204
|
* #method
|
|
205
|
+
* Get a row by name, returns [name, sequence] or undefined
|
|
98
206
|
*/
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
return i
|
|
111
|
-
}
|
|
112
|
-
return undefined
|
|
207
|
+
getRowByName(rowName: string) {
|
|
208
|
+
return self.rows.find(r => r[0] === rowName)
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* #method
|
|
213
|
+
* Get the sequence for a row by name
|
|
214
|
+
*/
|
|
215
|
+
getSequenceByRowName(rowName: string) {
|
|
216
|
+
return self.rows.find(r => r[0] === rowName)?.[1]
|
|
113
217
|
},
|
|
114
218
|
}))
|
|
219
|
+
|
|
115
220
|
.views(self => ({
|
|
116
221
|
/**
|
|
117
222
|
* #getter
|
|
@@ -121,38 +226,91 @@ export default function stateModelFactory() {
|
|
|
121
226
|
? genomeToTranscriptSeqMapping(self.connectedFeature)
|
|
122
227
|
: undefined
|
|
123
228
|
},
|
|
124
|
-
|
|
125
|
-
.views(self => ({
|
|
229
|
+
|
|
126
230
|
/**
|
|
127
231
|
* #getter
|
|
128
232
|
*/
|
|
129
|
-
get
|
|
130
|
-
return
|
|
233
|
+
get processing() {
|
|
234
|
+
return !!self.progress
|
|
131
235
|
},
|
|
236
|
+
|
|
132
237
|
/**
|
|
133
238
|
* #getter
|
|
134
239
|
*/
|
|
135
|
-
get
|
|
136
|
-
|
|
240
|
+
get connectedView() {
|
|
241
|
+
const { views } = getSession(self)
|
|
242
|
+
return views.find(f => f.id === self.connectedViewId) as MaybeLGV
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* #getter
|
|
247
|
+
* Get connected protein views with their full model references
|
|
248
|
+
*/
|
|
249
|
+
get connectedProteinViews() {
|
|
250
|
+
const { views } = getSession(self)
|
|
251
|
+
return self.connectedStructures
|
|
252
|
+
.map(conn => {
|
|
253
|
+
const proteinView = views.find(
|
|
254
|
+
(v: any) => v.id === conn.proteinViewId,
|
|
255
|
+
) as any
|
|
256
|
+
return proteinView ? { ...conn, proteinView } : undefined
|
|
257
|
+
})
|
|
258
|
+
.filter((c): c is StructureConnection & { proteinView: any } => !!c)
|
|
137
259
|
},
|
|
138
260
|
}))
|
|
139
261
|
|
|
140
262
|
.views(self => ({
|
|
141
263
|
/**
|
|
142
264
|
* #getter
|
|
265
|
+
* Get the MSA column that corresponds to the currently hovered structure position
|
|
266
|
+
* Returns the first match from any connected structure
|
|
143
267
|
*/
|
|
144
|
-
get
|
|
145
|
-
|
|
268
|
+
get structureHoverCol(): number | undefined {
|
|
269
|
+
for (const conn of self.connectedProteinViews) {
|
|
270
|
+
const structure = conn.proteinView?.structures?.[conn.structureIdx]
|
|
271
|
+
const structurePos = structure?.hoverPosition?.structureSeqPos
|
|
272
|
+
if (structurePos !== undefined) {
|
|
273
|
+
const msaUngapped = conn.structureToMsa[structurePos]
|
|
274
|
+
if (msaUngapped !== undefined) {
|
|
275
|
+
const seq = self.getSequenceByRowName(conn.msaRowName)
|
|
276
|
+
if (seq) {
|
|
277
|
+
// Convert ungapped position to global column, then to visible column
|
|
278
|
+
const globalCol = ungappedToGappedPosition(seq, msaUngapped)
|
|
279
|
+
if (globalCol !== undefined) {
|
|
280
|
+
return self.globalColToVisibleCol(globalCol)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return undefined
|
|
146
287
|
},
|
|
288
|
+
}))
|
|
147
289
|
|
|
290
|
+
.views(self => ({
|
|
148
291
|
/**
|
|
149
292
|
* #getter
|
|
293
|
+
* Returns a secondary highlight column from either:
|
|
294
|
+
* 1. Structure hover (from connected protein 3D view)
|
|
295
|
+
* 2. Genome hover (from connected linear genome view)
|
|
150
296
|
*/
|
|
151
|
-
get
|
|
152
|
-
|
|
153
|
-
|
|
297
|
+
get mouseCol2(): number | undefined {
|
|
298
|
+
// Check structure hover first
|
|
299
|
+
const structureCol = self.structureHoverCol
|
|
300
|
+
if (structureCol !== undefined) {
|
|
301
|
+
return structureCol
|
|
302
|
+
}
|
|
303
|
+
// Fall back to genome hover
|
|
304
|
+
return genomeToMSA({ model: self as JBrowsePluginMsaViewModel })
|
|
305
|
+
},
|
|
306
|
+
/**
|
|
307
|
+
* #getter
|
|
308
|
+
*/
|
|
309
|
+
get clickCol2() {
|
|
310
|
+
return undefined
|
|
154
311
|
},
|
|
155
312
|
}))
|
|
313
|
+
|
|
156
314
|
.actions(self => ({
|
|
157
315
|
/**
|
|
158
316
|
* #action
|
|
@@ -202,6 +360,42 @@ export default function stateModelFactory() {
|
|
|
202
360
|
setBlastParams(args?: BlastParams) {
|
|
203
361
|
self.blastParams = args
|
|
204
362
|
},
|
|
363
|
+
/**
|
|
364
|
+
* #action
|
|
365
|
+
*/
|
|
366
|
+
setInit(arg?: MsaViewInitState) {
|
|
367
|
+
self.init = arg
|
|
368
|
+
},
|
|
369
|
+
/**
|
|
370
|
+
* #action
|
|
371
|
+
*/
|
|
372
|
+
setQuerySeqName(arg: string) {
|
|
373
|
+
self.querySeqName = arg
|
|
374
|
+
},
|
|
375
|
+
/**
|
|
376
|
+
* #action
|
|
377
|
+
*/
|
|
378
|
+
setUniprotId(arg?: string) {
|
|
379
|
+
self.uniprotId = arg
|
|
380
|
+
},
|
|
381
|
+
/**
|
|
382
|
+
* #action
|
|
383
|
+
*/
|
|
384
|
+
setDataStoreId(arg?: string) {
|
|
385
|
+
self.dataStoreId = arg
|
|
386
|
+
},
|
|
387
|
+
/**
|
|
388
|
+
* #action
|
|
389
|
+
*/
|
|
390
|
+
setMafRegion(arg?: MafRegion) {
|
|
391
|
+
self.mafRegion = arg
|
|
392
|
+
},
|
|
393
|
+
/**
|
|
394
|
+
* #action
|
|
395
|
+
*/
|
|
396
|
+
setLoadingStoredData(arg: boolean) {
|
|
397
|
+
self.loadingStoredData = arg
|
|
398
|
+
},
|
|
205
399
|
/**
|
|
206
400
|
* #action
|
|
207
401
|
*/
|
|
@@ -224,10 +418,86 @@ export default function stateModelFactory() {
|
|
|
224
418
|
connectedView.centerAt(r2.start, r)
|
|
225
419
|
}
|
|
226
420
|
},
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* #action
|
|
424
|
+
* Connect to a protein structure for synchronized highlighting
|
|
425
|
+
*/
|
|
426
|
+
connectToStructure(
|
|
427
|
+
proteinViewId: string,
|
|
428
|
+
structureIdx: number,
|
|
429
|
+
msaRowName?: string,
|
|
430
|
+
) {
|
|
431
|
+
const rowName = msaRowName ?? self.querySeqName
|
|
432
|
+
const msaSequence = self.getSequenceByRowName(rowName)
|
|
433
|
+
if (!msaSequence) {
|
|
434
|
+
throw new Error(`MSA row "${rowName}" not found`)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const ungappedMsaSequence = msaSequence.replaceAll('-', '')
|
|
438
|
+
|
|
439
|
+
const { views } = getSession(self)
|
|
440
|
+
|
|
441
|
+
const proteinView = views.find(
|
|
442
|
+
(v: any) => v.id === proteinViewId,
|
|
443
|
+
) as any
|
|
444
|
+
if (!proteinView) {
|
|
445
|
+
throw new Error(`ProteinView "${proteinViewId}" not found`)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const structure = proteinView.structures?.[structureIdx]
|
|
449
|
+
if (!structure) {
|
|
450
|
+
throw new Error(`Structure at index ${structureIdx} not found`)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const structureSequence = structure.structureSequences?.[0]
|
|
454
|
+
if (!structureSequence) {
|
|
455
|
+
throw new Error('Structure sequence not available')
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const alignment = runPairwiseAlignment(
|
|
459
|
+
ungappedMsaSequence,
|
|
460
|
+
structureSequence,
|
|
461
|
+
)
|
|
462
|
+
const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment)
|
|
463
|
+
|
|
464
|
+
const connection: StructureConnection = {
|
|
465
|
+
proteinViewId,
|
|
466
|
+
structureIdx,
|
|
467
|
+
msaRowName: rowName,
|
|
468
|
+
msaToStructure: mapToRecord(seq1ToSeq2),
|
|
469
|
+
structureToMsa: mapToRecord(seq2ToSeq1),
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
self.connectedStructures.push(connection)
|
|
473
|
+
},
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* #action
|
|
477
|
+
* Disconnect from a protein structure
|
|
478
|
+
*/
|
|
479
|
+
disconnectFromStructure(proteinViewId: string, structureIdx: number) {
|
|
480
|
+
const idx = self.connectedStructures.findIndex(
|
|
481
|
+
c =>
|
|
482
|
+
c.proteinViewId === proteinViewId &&
|
|
483
|
+
c.structureIdx === structureIdx,
|
|
484
|
+
)
|
|
485
|
+
if (idx !== -1) {
|
|
486
|
+
self.connectedStructures.splice(idx, 1)
|
|
487
|
+
}
|
|
488
|
+
},
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* #action
|
|
492
|
+
* Disconnect from all protein structures
|
|
493
|
+
*/
|
|
494
|
+
disconnectAllStructures() {
|
|
495
|
+
self.connectedStructures.clear()
|
|
496
|
+
},
|
|
227
497
|
}))
|
|
228
498
|
.actions(self => {
|
|
229
499
|
// store reference to the original action from react-msaview
|
|
230
|
-
const superSetMouseClickPos = self.setMouseClickPos
|
|
500
|
+
const superSetMouseClickPos = self.setMouseClickPos.bind(self)
|
|
231
501
|
|
|
232
502
|
return {
|
|
233
503
|
/**
|
|
@@ -258,26 +528,106 @@ export default function stateModelFactory() {
|
|
|
258
528
|
self.setZoomToBaseLevel(!self.zoomToBaseLevel)
|
|
259
529
|
},
|
|
260
530
|
},
|
|
531
|
+
{
|
|
532
|
+
label: 'Connect to protein structure...',
|
|
533
|
+
onClick: () => {
|
|
534
|
+
getSession(self).queueDialog(handleClose => [
|
|
535
|
+
ConnectStructureDialog,
|
|
536
|
+
{
|
|
537
|
+
model: self,
|
|
538
|
+
handleClose,
|
|
539
|
+
},
|
|
540
|
+
])
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
...(self.connectedStructures.length > 0
|
|
544
|
+
? [
|
|
545
|
+
{
|
|
546
|
+
label: 'Disconnect from protein structures',
|
|
547
|
+
onClick: () => {
|
|
548
|
+
self.disconnectAllStructures()
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
]
|
|
552
|
+
: []),
|
|
261
553
|
]
|
|
262
554
|
},
|
|
263
|
-
/**
|
|
264
|
-
* #getter
|
|
265
|
-
*/
|
|
266
|
-
get processing() {
|
|
267
|
-
return !!self.progress
|
|
268
|
-
},
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* #getter
|
|
272
|
-
*/
|
|
273
|
-
get connectedView() {
|
|
274
|
-
const { views } = getSession(self)
|
|
275
|
-
return views.find(f => f.id === self.connectedViewId) as MaybeLGV
|
|
276
|
-
},
|
|
277
555
|
}))
|
|
278
556
|
|
|
279
557
|
.actions(self => ({
|
|
280
558
|
afterCreate() {
|
|
559
|
+
// Clean up old IndexedDB entries on startup
|
|
560
|
+
cleanupOldData().catch((e: unknown) => {
|
|
561
|
+
console.error('Failed to cleanup old MSA data:', e)
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
// Load MSA data from IndexedDB if dataStoreId exists and no data loaded
|
|
565
|
+
addDisposer(
|
|
566
|
+
self,
|
|
567
|
+
autorun(async () => {
|
|
568
|
+
const { dataStoreId, rows } = self
|
|
569
|
+
if (dataStoreId && rows.length === 0) {
|
|
570
|
+
try {
|
|
571
|
+
self.setLoadingStoredData(true)
|
|
572
|
+
const storedData = await retrieveMsaData(dataStoreId)
|
|
573
|
+
if (storedData) {
|
|
574
|
+
if (storedData.msa) {
|
|
575
|
+
self.setMSA(storedData.msa)
|
|
576
|
+
}
|
|
577
|
+
if (storedData.tree) {
|
|
578
|
+
self.setTree(storedData.tree)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
} catch (e) {
|
|
582
|
+
console.error('Failed to load MSA data from IndexedDB:', e)
|
|
583
|
+
} finally {
|
|
584
|
+
self.setLoadingStoredData(false)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}),
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
// Store MSA data to IndexedDB when loaded from inline data (no filehandle)
|
|
591
|
+
// This ensures data persists across page refreshes even when
|
|
592
|
+
// react-msaview's postProcessSnapshot would strip it
|
|
593
|
+
addDisposer(
|
|
594
|
+
self,
|
|
595
|
+
autorun(async () => {
|
|
596
|
+
const { rows, dataStoreId } = self
|
|
597
|
+
// Only store if we have data and don't already have a dataStoreId
|
|
598
|
+
if (rows.length > 0 && !dataStoreId) {
|
|
599
|
+
// Only store if there's no filehandle (filehandles can reload from source)
|
|
600
|
+
const hasFilehandle = !!(
|
|
601
|
+
self.msaFilehandle ?? self.treeFilehandle
|
|
602
|
+
)
|
|
603
|
+
if (hasFilehandle) {
|
|
604
|
+
return
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
const msaData = self.data.msa
|
|
608
|
+
const treeData = self.data.tree
|
|
609
|
+
|
|
610
|
+
// Only store if we have actual data
|
|
611
|
+
if (msaData || treeData) {
|
|
612
|
+
try {
|
|
613
|
+
const newId = generateDataStoreId()
|
|
614
|
+
const success = await storeMsaData(newId, {
|
|
615
|
+
msa: msaData,
|
|
616
|
+
tree: treeData,
|
|
617
|
+
treeMetadata: self.data.treeMetadata,
|
|
618
|
+
})
|
|
619
|
+
// Only set dataStoreId if storage was successful
|
|
620
|
+
if (success) {
|
|
621
|
+
self.setDataStoreId(newId)
|
|
622
|
+
}
|
|
623
|
+
} catch (e) {
|
|
624
|
+
console.error('Failed to store MSA data to IndexedDB:', e)
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}),
|
|
629
|
+
)
|
|
630
|
+
|
|
281
631
|
addDisposer(
|
|
282
632
|
self,
|
|
283
633
|
autorun(async () => {
|
|
@@ -300,6 +650,63 @@ export default function stateModelFactory() {
|
|
|
300
650
|
}),
|
|
301
651
|
)
|
|
302
652
|
|
|
653
|
+
// process init parameter for loading MSA from session snapshots
|
|
654
|
+
addDisposer(
|
|
655
|
+
self,
|
|
656
|
+
autorun(async () => {
|
|
657
|
+
const { init } = self
|
|
658
|
+
if (init) {
|
|
659
|
+
try {
|
|
660
|
+
self.setError(undefined)
|
|
661
|
+
const { msaData, msaUrl, treeData, treeUrl, querySeqName } =
|
|
662
|
+
init
|
|
663
|
+
|
|
664
|
+
// Extract uniprotId from AlphaFold MSA URL and set querySeqName
|
|
665
|
+
if (msaUrl) {
|
|
666
|
+
const id = getUniprotIdFromAlphaFoldUrl(msaUrl)
|
|
667
|
+
if (id) {
|
|
668
|
+
self.setUniprotId(id)
|
|
669
|
+
// AlphaFold MSA files use 'query' as the row name
|
|
670
|
+
self.setQuerySeqName('query')
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// User-provided querySeqName takes precedence
|
|
675
|
+
if (querySeqName) {
|
|
676
|
+
self.setQuerySeqName(querySeqName)
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (msaData) {
|
|
680
|
+
self.setMSA(msaData)
|
|
681
|
+
} else if (msaUrl) {
|
|
682
|
+
const response = await fetch(msaUrl)
|
|
683
|
+
if (!response.ok) {
|
|
684
|
+
throw new Error(`Failed to fetch MSA: ${response.status}`)
|
|
685
|
+
}
|
|
686
|
+
const data = await response.text()
|
|
687
|
+
self.setMSA(data)
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
if (treeData) {
|
|
691
|
+
self.setTree(treeData)
|
|
692
|
+
} else if (treeUrl) {
|
|
693
|
+
const response = await fetch(treeUrl)
|
|
694
|
+
if (!response.ok) {
|
|
695
|
+
throw new Error(`Failed to fetch tree: ${response.status}`)
|
|
696
|
+
}
|
|
697
|
+
const data = await response.text()
|
|
698
|
+
self.setTree(data)
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
self.setInit(undefined)
|
|
702
|
+
} catch (e) {
|
|
703
|
+
self.setError(e)
|
|
704
|
+
console.error(e)
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}),
|
|
708
|
+
)
|
|
709
|
+
|
|
303
710
|
// this adds highlights to the genome view when mouse-ing over the MSA
|
|
304
711
|
addDisposer(
|
|
305
712
|
self,
|
|
@@ -313,9 +720,147 @@ export default function stateModelFactory() {
|
|
|
313
720
|
mouseClickCol === undefined
|
|
314
721
|
? undefined
|
|
315
722
|
: msaCoordToGenomeCoord({ model: self, coord: mouseClickCol })
|
|
723
|
+
|
|
316
724
|
self.setConnectedHighlights([r1, r2].filter(f => !!f))
|
|
317
725
|
}),
|
|
318
726
|
)
|
|
727
|
+
|
|
728
|
+
// this highlights residues in connected protein structures when mousing over the MSA
|
|
729
|
+
addDisposer(
|
|
730
|
+
self,
|
|
731
|
+
autorun(() => {
|
|
732
|
+
highlightConnectedStructures(self)
|
|
733
|
+
}),
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
// auto-connect to compatible ProteinViews
|
|
737
|
+
addDisposer(
|
|
738
|
+
self,
|
|
739
|
+
autorun(() => {
|
|
740
|
+
const { views } = getSession(self)
|
|
741
|
+
const { connectedViewId, uniprotId, rows, connectedStructures } =
|
|
742
|
+
self
|
|
743
|
+
|
|
744
|
+
// Need MSA loaded and a uniprotId to auto-connect
|
|
745
|
+
if (!uniprotId || rows.length === 0) {
|
|
746
|
+
return
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Find ProteinViews that share the same connectedViewId
|
|
750
|
+
for (const view of views) {
|
|
751
|
+
const v = view as any
|
|
752
|
+
if (v.type !== 'ProteinView' || !v.structures) {
|
|
753
|
+
continue
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
for (
|
|
757
|
+
let structureIdx = 0;
|
|
758
|
+
structureIdx < v.structures.length;
|
|
759
|
+
structureIdx++
|
|
760
|
+
) {
|
|
761
|
+
const structure = v.structures[structureIdx]
|
|
762
|
+
|
|
763
|
+
// Check if structure shares the same genome view connection
|
|
764
|
+
if (structure.connectedViewId !== connectedViewId) {
|
|
765
|
+
continue
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// Check if structure has matching uniprotId
|
|
769
|
+
if (structure.uniprotId !== uniprotId) {
|
|
770
|
+
continue
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// Check if already connected
|
|
774
|
+
const alreadyConnected = connectedStructures.some(
|
|
775
|
+
c =>
|
|
776
|
+
c.proteinViewId === v.id && c.structureIdx === structureIdx,
|
|
777
|
+
)
|
|
778
|
+
if (alreadyConnected) {
|
|
779
|
+
continue
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Check if structure sequence is available
|
|
783
|
+
if (!structure.structureSequences?.[0]) {
|
|
784
|
+
continue
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Auto-connect
|
|
788
|
+
try {
|
|
789
|
+
self.connectToStructure(v.id, structureIdx)
|
|
790
|
+
} catch (e) {
|
|
791
|
+
console.error('Failed to auto-connect to ProteinView:', e)
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}),
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
// Observe protein3d genome highlights and update MSA highlighted columns
|
|
799
|
+
// This enables communication via the linear genome view coordinates
|
|
800
|
+
addDisposer(
|
|
801
|
+
self,
|
|
802
|
+
autorun(() => {
|
|
803
|
+
const { views } = getSession(self)
|
|
804
|
+
const { connectedViewId, transcriptToMsaMap, querySeqName } = self
|
|
805
|
+
|
|
806
|
+
if (!connectedViewId || !transcriptToMsaMap) {
|
|
807
|
+
return
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const columns: number[] = []
|
|
811
|
+
|
|
812
|
+
// Find ProteinViews that share the same connected genome view
|
|
813
|
+
for (const view of views) {
|
|
814
|
+
const v = view as any
|
|
815
|
+
if (v.type !== 'ProteinView' || !v.structures) {
|
|
816
|
+
continue
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
for (const structure of v.structures) {
|
|
820
|
+
// Check if structure is connected to same genome view
|
|
821
|
+
if (structure.connectedViewId !== connectedViewId) {
|
|
822
|
+
continue
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// Check if structure has hover genome highlights
|
|
826
|
+
const highlights = structure.hoverGenomeHighlights
|
|
827
|
+
if (!highlights || highlights.length === 0) {
|
|
828
|
+
continue
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Map genome coordinates to MSA columns
|
|
832
|
+
const { g2p } = transcriptToMsaMap
|
|
833
|
+
for (const highlight of highlights) {
|
|
834
|
+
for (
|
|
835
|
+
let coord = highlight.start;
|
|
836
|
+
coord < highlight.end;
|
|
837
|
+
coord++
|
|
838
|
+
) {
|
|
839
|
+
const proteinPos = g2p[coord]
|
|
840
|
+
if (proteinPos !== undefined) {
|
|
841
|
+
const col = self.seqPosToGlobalCol(
|
|
842
|
+
querySeqName,
|
|
843
|
+
proteinPos,
|
|
844
|
+
)
|
|
845
|
+
if (!columns.includes(col)) {
|
|
846
|
+
columns.push(col)
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// Convert global column indices to visible column indices
|
|
855
|
+
const visibleColumns = columns
|
|
856
|
+
.map(col => self.globalColToVisibleCol(col))
|
|
857
|
+
.filter((col): col is number => col !== undefined)
|
|
858
|
+
|
|
859
|
+
self.setHighlightedColumns(
|
|
860
|
+
visibleColumns.length > 0 ? visibleColumns : undefined,
|
|
861
|
+
)
|
|
862
|
+
}),
|
|
863
|
+
)
|
|
319
864
|
},
|
|
320
865
|
}))
|
|
321
866
|
}
|
|
@@ -324,3 +869,5 @@ export type JBrowsePluginMsaViewStateModel = ReturnType<
|
|
|
324
869
|
typeof stateModelFactory
|
|
325
870
|
>
|
|
326
871
|
export type JBrowsePluginMsaViewModel = Instance<JBrowsePluginMsaViewStateModel>
|
|
872
|
+
|
|
873
|
+
export { type MafRegion, type MsaViewInitState } from './types'
|