jbrowse-plugin-msaview 2.2.2 → 2.2.4
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 +727 -226
- package/dist/MsaViewPanel/model.js +496 -52
- package/dist/MsaViewPanel/model.js.map +1 -1
- package/dist/MsaViewPanel/msaCoordToGenomeCoord.d.ts +10 -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 +617 -58
- package/src/MsaViewPanel/msaCoordToGenomeCoord.test.ts +256 -0
- package/src/MsaViewPanel/msaCoordToGenomeCoord.ts +42 -30
- 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 -55367
- package/src/LaunchMsaView/components/PreLoadedMSA/findValidTranscriptId.ts +0 -25
|
@@ -1,12 +1,56 @@
|
|
|
1
|
+
import { lazy } from 'react';
|
|
1
2
|
import { BaseViewModel } from '@jbrowse/core/pluggableElementTypes';
|
|
2
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
|
+
import { addDisposer, cast, types } from '@jbrowse/mobx-state-tree';
|
|
3
5
|
import { genomeToTranscriptSeqMapping } from 'g2p_mapper';
|
|
4
6
|
import { autorun } from 'mobx';
|
|
5
|
-
import { addDisposer, cast, types } from 'mobx-state-tree';
|
|
6
7
|
import { MSAModelF } from 'react-msaview';
|
|
7
8
|
import { doLaunchBlast } from './doLaunchBlast';
|
|
8
9
|
import { genomeToMSA } from './genomeToMSA';
|
|
9
10
|
import { msaCoordToGenomeCoord } from './msaCoordToGenomeCoord';
|
|
11
|
+
import { cleanupOldData, generateDataStoreId, retrieveMsaData, storeMsaData, } from './msaDataStore';
|
|
12
|
+
import { buildAlignmentMaps, runPairwiseAlignment } from './pairwiseAlignment';
|
|
13
|
+
import { gappedToUngappedPosition, mapToRecord, ungappedToGappedPosition, } from './structureConnection';
|
|
14
|
+
import { getUniprotIdFromAlphaFoldUrl } from './util';
|
|
15
|
+
const ConnectStructureDialog = lazy(() => import('./components/ConnectStructureDialog'));
|
|
16
|
+
/**
|
|
17
|
+
* Highlights residues in connected protein structures based on current MSA hover position
|
|
18
|
+
*/
|
|
19
|
+
function highlightConnectedStructures(self) {
|
|
20
|
+
const { mouseCol, connectedProteinViews } = self;
|
|
21
|
+
if (connectedProteinViews.length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
for (const conn of connectedProteinViews) {
|
|
25
|
+
const structure = conn.proteinView?.structures?.[conn.structureIdx];
|
|
26
|
+
if (!structure) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Clear highlight if mouse left MSA
|
|
30
|
+
if (mouseCol === undefined) {
|
|
31
|
+
structure.clearHighlightFromExternal?.();
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const seq = self.getSequenceByRowName(conn.msaRowName);
|
|
35
|
+
if (!seq) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Convert gapped MSA column to ungapped position
|
|
39
|
+
const msaUngapped = gappedToUngappedPosition(seq, mouseCol);
|
|
40
|
+
if (msaUngapped === undefined) {
|
|
41
|
+
structure.clearHighlightFromExternal?.();
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Map to structure position and highlight
|
|
45
|
+
const structurePos = conn.msaToStructure[msaUngapped];
|
|
46
|
+
if (structurePos === undefined) {
|
|
47
|
+
structure.clearHighlightFromExternal?.();
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
structure.highlightFromExternal?.(structurePos);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
10
54
|
/**
|
|
11
55
|
* #stateModel MsaViewPlugin
|
|
12
56
|
* extends
|
|
@@ -39,10 +83,43 @@ export default function stateModelFactory() {
|
|
|
39
83
|
* #property
|
|
40
84
|
*/
|
|
41
85
|
querySeqName: 'QUERY',
|
|
86
|
+
/**
|
|
87
|
+
* #property
|
|
88
|
+
* UniProt ID extracted from AlphaFold MSA URL
|
|
89
|
+
*/
|
|
90
|
+
uniprotId: types.maybe(types.string),
|
|
42
91
|
/**
|
|
43
92
|
* #property
|
|
44
93
|
*/
|
|
45
94
|
zoomToBaseLevel: false,
|
|
95
|
+
/**
|
|
96
|
+
* #property
|
|
97
|
+
* used for loading the MSA view via session snapshots, e.g.
|
|
98
|
+
* {
|
|
99
|
+
* "type": "MsaView",
|
|
100
|
+
* "init": {
|
|
101
|
+
* "msaUrl": "https://example.com/alignment.fa",
|
|
102
|
+
* "treeUrl": "https://example.com/tree.nh",
|
|
103
|
+
* "querySeqName": "ENST00000123_hg38"
|
|
104
|
+
* }
|
|
105
|
+
* }
|
|
106
|
+
*/
|
|
107
|
+
init: types.frozen(),
|
|
108
|
+
/**
|
|
109
|
+
* #property
|
|
110
|
+
* connections to protein 3D structure views for synchronized highlighting
|
|
111
|
+
*/
|
|
112
|
+
connectedStructures: types.array(types.frozen()),
|
|
113
|
+
/**
|
|
114
|
+
* #property
|
|
115
|
+
* Reference ID for MSA data stored in IndexedDB (for large datasets)
|
|
116
|
+
*/
|
|
117
|
+
dataStoreId: types.maybe(types.string),
|
|
118
|
+
/**
|
|
119
|
+
* #property
|
|
120
|
+
* MAF region for coordinate mapping (used when launched from MAF viewer)
|
|
121
|
+
*/
|
|
122
|
+
mafRegion: types.frozen(),
|
|
46
123
|
}))
|
|
47
124
|
.volatile(() => ({
|
|
48
125
|
/**
|
|
@@ -57,25 +134,26 @@ export default function stateModelFactory() {
|
|
|
57
134
|
* #volatile
|
|
58
135
|
*/
|
|
59
136
|
error: undefined,
|
|
137
|
+
/**
|
|
138
|
+
* #volatile
|
|
139
|
+
* True when loading MSA data from IndexedDB
|
|
140
|
+
*/
|
|
141
|
+
loadingStoredData: false,
|
|
60
142
|
}))
|
|
61
143
|
.views(self => ({
|
|
62
144
|
/**
|
|
63
145
|
* #method
|
|
146
|
+
* Get a row by name, returns [name, sequence] or undefined
|
|
64
147
|
*/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return i;
|
|
77
|
-
}
|
|
78
|
-
return undefined;
|
|
148
|
+
getRowByName(rowName) {
|
|
149
|
+
return self.rows.find(r => r[0] === rowName);
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* #method
|
|
153
|
+
* Get the sequence for a row by name
|
|
154
|
+
*/
|
|
155
|
+
getSequenceByRowName(rowName) {
|
|
156
|
+
return self.rows.find(r => r[0] === rowName)?.[1];
|
|
79
157
|
},
|
|
80
158
|
}))
|
|
81
159
|
.views(self => ({
|
|
@@ -87,34 +165,81 @@ export default function stateModelFactory() {
|
|
|
87
165
|
? genomeToTranscriptSeqMapping(self.connectedFeature)
|
|
88
166
|
: undefined;
|
|
89
167
|
},
|
|
90
|
-
}))
|
|
91
|
-
.views(self => ({
|
|
92
168
|
/**
|
|
93
169
|
* #getter
|
|
94
170
|
*/
|
|
95
|
-
get
|
|
96
|
-
return
|
|
171
|
+
get processing() {
|
|
172
|
+
return !!self.progress;
|
|
97
173
|
},
|
|
98
174
|
/**
|
|
99
175
|
* #getter
|
|
100
176
|
*/
|
|
101
|
-
get
|
|
177
|
+
get connectedView() {
|
|
178
|
+
const { views } = getSession(self);
|
|
179
|
+
return views.find(f => f.id === self.connectedViewId);
|
|
180
|
+
},
|
|
181
|
+
/**
|
|
182
|
+
* #getter
|
|
183
|
+
* Get connected protein views with their full model references
|
|
184
|
+
*/
|
|
185
|
+
get connectedProteinViews() {
|
|
186
|
+
const { views } = getSession(self);
|
|
187
|
+
return self.connectedStructures
|
|
188
|
+
.map(conn => {
|
|
189
|
+
const proteinView = views.find((v) => v.id === conn.proteinViewId);
|
|
190
|
+
return proteinView ? { ...conn, proteinView } : undefined;
|
|
191
|
+
})
|
|
192
|
+
.filter((c) => !!c);
|
|
193
|
+
},
|
|
194
|
+
}))
|
|
195
|
+
.views(self => ({
|
|
196
|
+
/**
|
|
197
|
+
* #getter
|
|
198
|
+
* Get the MSA column that corresponds to the currently hovered structure position
|
|
199
|
+
* Returns the first match from any connected structure
|
|
200
|
+
*/
|
|
201
|
+
get structureHoverCol() {
|
|
202
|
+
for (const conn of self.connectedProteinViews) {
|
|
203
|
+
const structure = conn.proteinView?.structures?.[conn.structureIdx];
|
|
204
|
+
const structurePos = structure?.hoverPosition?.structureSeqPos;
|
|
205
|
+
if (structurePos !== undefined) {
|
|
206
|
+
const msaUngapped = conn.structureToMsa[structurePos];
|
|
207
|
+
if (msaUngapped !== undefined) {
|
|
208
|
+
const seq = self.getSequenceByRowName(conn.msaRowName);
|
|
209
|
+
if (seq) {
|
|
210
|
+
// Convert ungapped position to global column, then to visible column
|
|
211
|
+
const globalCol = ungappedToGappedPosition(seq, msaUngapped);
|
|
212
|
+
if (globalCol !== undefined) {
|
|
213
|
+
return self.globalColToVisibleCol(globalCol);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
102
219
|
return undefined;
|
|
103
220
|
},
|
|
104
221
|
}))
|
|
105
222
|
.views(self => ({
|
|
106
223
|
/**
|
|
107
224
|
* #getter
|
|
225
|
+
* Returns a secondary highlight column from either:
|
|
226
|
+
* 1. Structure hover (from connected protein 3D view)
|
|
227
|
+
* 2. Genome hover (from connected linear genome view)
|
|
108
228
|
*/
|
|
109
|
-
get
|
|
110
|
-
|
|
229
|
+
get mouseCol2() {
|
|
230
|
+
// Check structure hover first
|
|
231
|
+
const structureCol = self.structureHoverCol;
|
|
232
|
+
if (structureCol !== undefined) {
|
|
233
|
+
return structureCol;
|
|
234
|
+
}
|
|
235
|
+
// Fall back to genome hover
|
|
236
|
+
return genomeToMSA({ model: self });
|
|
111
237
|
},
|
|
112
238
|
/**
|
|
113
239
|
* #getter
|
|
114
240
|
*/
|
|
115
|
-
get
|
|
116
|
-
|
|
117
|
-
return views.find(f => f.id === self.connectedViewId);
|
|
241
|
+
get clickCol2() {
|
|
242
|
+
return undefined;
|
|
118
243
|
},
|
|
119
244
|
}))
|
|
120
245
|
.actions(self => ({
|
|
@@ -166,7 +291,132 @@ export default function stateModelFactory() {
|
|
|
166
291
|
setBlastParams(args) {
|
|
167
292
|
self.blastParams = args;
|
|
168
293
|
},
|
|
294
|
+
/**
|
|
295
|
+
* #action
|
|
296
|
+
*/
|
|
297
|
+
setInit(arg) {
|
|
298
|
+
self.init = arg;
|
|
299
|
+
},
|
|
300
|
+
/**
|
|
301
|
+
* #action
|
|
302
|
+
*/
|
|
303
|
+
setQuerySeqName(arg) {
|
|
304
|
+
self.querySeqName = arg;
|
|
305
|
+
},
|
|
306
|
+
/**
|
|
307
|
+
* #action
|
|
308
|
+
*/
|
|
309
|
+
setUniprotId(arg) {
|
|
310
|
+
self.uniprotId = arg;
|
|
311
|
+
},
|
|
312
|
+
/**
|
|
313
|
+
* #action
|
|
314
|
+
*/
|
|
315
|
+
setDataStoreId(arg) {
|
|
316
|
+
self.dataStoreId = arg;
|
|
317
|
+
},
|
|
318
|
+
/**
|
|
319
|
+
* #action
|
|
320
|
+
*/
|
|
321
|
+
setMafRegion(arg) {
|
|
322
|
+
self.mafRegion = arg;
|
|
323
|
+
},
|
|
324
|
+
/**
|
|
325
|
+
* #action
|
|
326
|
+
*/
|
|
327
|
+
setLoadingStoredData(arg) {
|
|
328
|
+
self.loadingStoredData = arg;
|
|
329
|
+
},
|
|
330
|
+
/**
|
|
331
|
+
* #action
|
|
332
|
+
*/
|
|
333
|
+
handleMsaClick(coord) {
|
|
334
|
+
const { connectedView, zoomToBaseLevel } = self;
|
|
335
|
+
const { assemblyManager } = getSession(self);
|
|
336
|
+
const r2 = msaCoordToGenomeCoord({ model: self, coord });
|
|
337
|
+
if (!r2 || !connectedView) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (zoomToBaseLevel) {
|
|
341
|
+
connectedView.navTo(r2);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
const r = assemblyManager
|
|
345
|
+
.get(connectedView.assemblyNames[0])
|
|
346
|
+
?.getCanonicalRefName(r2.refName) ?? r2.refName;
|
|
347
|
+
connectedView.centerAt(r2.start, r);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
/**
|
|
351
|
+
* #action
|
|
352
|
+
* Connect to a protein structure for synchronized highlighting
|
|
353
|
+
*/
|
|
354
|
+
connectToStructure(proteinViewId, structureIdx, msaRowName) {
|
|
355
|
+
const rowName = msaRowName ?? self.querySeqName;
|
|
356
|
+
const msaSequence = self.getSequenceByRowName(rowName);
|
|
357
|
+
if (!msaSequence) {
|
|
358
|
+
throw new Error(`MSA row "${rowName}" not found`);
|
|
359
|
+
}
|
|
360
|
+
const ungappedMsaSequence = msaSequence.replaceAll('-', '');
|
|
361
|
+
const { views } = getSession(self);
|
|
362
|
+
const proteinView = views.find((v) => v.id === proteinViewId);
|
|
363
|
+
if (!proteinView) {
|
|
364
|
+
throw new Error(`ProteinView "${proteinViewId}" not found`);
|
|
365
|
+
}
|
|
366
|
+
const structure = proteinView.structures?.[structureIdx];
|
|
367
|
+
if (!structure) {
|
|
368
|
+
throw new Error(`Structure at index ${structureIdx} not found`);
|
|
369
|
+
}
|
|
370
|
+
const structureSequence = structure.structureSequences?.[0];
|
|
371
|
+
if (!structureSequence) {
|
|
372
|
+
throw new Error('Structure sequence not available');
|
|
373
|
+
}
|
|
374
|
+
const alignment = runPairwiseAlignment(ungappedMsaSequence, structureSequence);
|
|
375
|
+
const { seq1ToSeq2, seq2ToSeq1 } = buildAlignmentMaps(alignment);
|
|
376
|
+
const connection = {
|
|
377
|
+
proteinViewId,
|
|
378
|
+
structureIdx,
|
|
379
|
+
msaRowName: rowName,
|
|
380
|
+
msaToStructure: mapToRecord(seq1ToSeq2),
|
|
381
|
+
structureToMsa: mapToRecord(seq2ToSeq1),
|
|
382
|
+
};
|
|
383
|
+
self.connectedStructures.push(connection);
|
|
384
|
+
},
|
|
385
|
+
/**
|
|
386
|
+
* #action
|
|
387
|
+
* Disconnect from a protein structure
|
|
388
|
+
*/
|
|
389
|
+
disconnectFromStructure(proteinViewId, structureIdx) {
|
|
390
|
+
const idx = self.connectedStructures.findIndex(c => c.proteinViewId === proteinViewId &&
|
|
391
|
+
c.structureIdx === structureIdx);
|
|
392
|
+
if (idx !== -1) {
|
|
393
|
+
self.connectedStructures.splice(idx, 1);
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
/**
|
|
397
|
+
* #action
|
|
398
|
+
* Disconnect from all protein structures
|
|
399
|
+
*/
|
|
400
|
+
disconnectAllStructures() {
|
|
401
|
+
self.connectedStructures.clear();
|
|
402
|
+
},
|
|
169
403
|
}))
|
|
404
|
+
.actions(self => {
|
|
405
|
+
// store reference to the original action from react-msaview
|
|
406
|
+
const superSetMouseClickPos = self.setMouseClickPos.bind(self);
|
|
407
|
+
return {
|
|
408
|
+
/**
|
|
409
|
+
* #action
|
|
410
|
+
* overrides base setMouseClickPos to trigger navigation
|
|
411
|
+
*/
|
|
412
|
+
setMouseClickPos(col, row) {
|
|
413
|
+
superSetMouseClickPos(col, row);
|
|
414
|
+
if (col !== undefined) {
|
|
415
|
+
self.handleMsaClick(col);
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
})
|
|
170
420
|
.views(self => ({
|
|
171
421
|
/**
|
|
172
422
|
* #method
|
|
@@ -182,24 +432,95 @@ export default function stateModelFactory() {
|
|
|
182
432
|
self.setZoomToBaseLevel(!self.zoomToBaseLevel);
|
|
183
433
|
},
|
|
184
434
|
},
|
|
435
|
+
{
|
|
436
|
+
label: 'Connect to protein structure...',
|
|
437
|
+
onClick: () => {
|
|
438
|
+
getSession(self).queueDialog(handleClose => [
|
|
439
|
+
ConnectStructureDialog,
|
|
440
|
+
{
|
|
441
|
+
model: self,
|
|
442
|
+
handleClose,
|
|
443
|
+
},
|
|
444
|
+
]);
|
|
445
|
+
},
|
|
446
|
+
},
|
|
447
|
+
...(self.connectedStructures.length > 0
|
|
448
|
+
? [
|
|
449
|
+
{
|
|
450
|
+
label: 'Disconnect from protein structures',
|
|
451
|
+
onClick: () => {
|
|
452
|
+
self.disconnectAllStructures();
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
]
|
|
456
|
+
: []),
|
|
185
457
|
];
|
|
186
458
|
},
|
|
187
|
-
/**
|
|
188
|
-
* #getter
|
|
189
|
-
*/
|
|
190
|
-
get processing() {
|
|
191
|
-
return !!self.progress;
|
|
192
|
-
},
|
|
193
|
-
/**
|
|
194
|
-
* #getter
|
|
195
|
-
*/
|
|
196
|
-
get connectedView() {
|
|
197
|
-
const { views } = getSession(self);
|
|
198
|
-
return views.find(f => f.id === self.connectedViewId);
|
|
199
|
-
},
|
|
200
459
|
}))
|
|
201
460
|
.actions(self => ({
|
|
202
461
|
afterCreate() {
|
|
462
|
+
// Clean up old IndexedDB entries on startup
|
|
463
|
+
cleanupOldData().catch((e) => {
|
|
464
|
+
console.error('Failed to cleanup old MSA data:', e);
|
|
465
|
+
});
|
|
466
|
+
// Load MSA data from IndexedDB if dataStoreId exists and no data loaded
|
|
467
|
+
addDisposer(self, autorun(async () => {
|
|
468
|
+
const { dataStoreId, rows } = self;
|
|
469
|
+
if (dataStoreId && rows.length === 0) {
|
|
470
|
+
try {
|
|
471
|
+
self.setLoadingStoredData(true);
|
|
472
|
+
const storedData = await retrieveMsaData(dataStoreId);
|
|
473
|
+
if (storedData) {
|
|
474
|
+
if (storedData.msa) {
|
|
475
|
+
self.setMSA(storedData.msa);
|
|
476
|
+
}
|
|
477
|
+
if (storedData.tree) {
|
|
478
|
+
self.setTree(storedData.tree);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
catch (e) {
|
|
483
|
+
console.error('Failed to load MSA data from IndexedDB:', e);
|
|
484
|
+
}
|
|
485
|
+
finally {
|
|
486
|
+
self.setLoadingStoredData(false);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}));
|
|
490
|
+
// Store MSA data to IndexedDB when loaded from inline data (no filehandle)
|
|
491
|
+
// This ensures data persists across page refreshes even when
|
|
492
|
+
// react-msaview's postProcessSnapshot would strip it
|
|
493
|
+
addDisposer(self, autorun(async () => {
|
|
494
|
+
const { rows, dataStoreId } = self;
|
|
495
|
+
// Only store if we have data and don't already have a dataStoreId
|
|
496
|
+
if (rows.length > 0 && !dataStoreId) {
|
|
497
|
+
// Only store if there's no filehandle (filehandles can reload from source)
|
|
498
|
+
const hasFilehandle = !!(self.msaFilehandle ?? self.treeFilehandle);
|
|
499
|
+
if (hasFilehandle) {
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const msaData = self.data.msa;
|
|
503
|
+
const treeData = self.data.tree;
|
|
504
|
+
// Only store if we have actual data
|
|
505
|
+
if (msaData || treeData) {
|
|
506
|
+
try {
|
|
507
|
+
const newId = generateDataStoreId();
|
|
508
|
+
const success = await storeMsaData(newId, {
|
|
509
|
+
msa: msaData,
|
|
510
|
+
tree: treeData,
|
|
511
|
+
treeMetadata: self.data.treeMetadata,
|
|
512
|
+
});
|
|
513
|
+
// Only set dataStoreId if storage was successful
|
|
514
|
+
if (success) {
|
|
515
|
+
self.setDataStoreId(newId);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
catch (e) {
|
|
519
|
+
console.error('Failed to store MSA data to IndexedDB:', e);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}));
|
|
203
524
|
addDisposer(self, autorun(async () => {
|
|
204
525
|
if (self.blastParams) {
|
|
205
526
|
try {
|
|
@@ -220,6 +541,56 @@ export default function stateModelFactory() {
|
|
|
220
541
|
}
|
|
221
542
|
}
|
|
222
543
|
}));
|
|
544
|
+
// process init parameter for loading MSA from session snapshots
|
|
545
|
+
addDisposer(self, autorun(async () => {
|
|
546
|
+
const { init } = self;
|
|
547
|
+
if (init) {
|
|
548
|
+
try {
|
|
549
|
+
self.setError(undefined);
|
|
550
|
+
const { msaData, msaUrl, treeData, treeUrl, querySeqName } = init;
|
|
551
|
+
// Extract uniprotId from AlphaFold MSA URL and set querySeqName
|
|
552
|
+
if (msaUrl) {
|
|
553
|
+
const id = getUniprotIdFromAlphaFoldUrl(msaUrl);
|
|
554
|
+
if (id) {
|
|
555
|
+
self.setUniprotId(id);
|
|
556
|
+
// AlphaFold MSA files use 'query' as the row name
|
|
557
|
+
self.setQuerySeqName('query');
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
// User-provided querySeqName takes precedence
|
|
561
|
+
if (querySeqName) {
|
|
562
|
+
self.setQuerySeqName(querySeqName);
|
|
563
|
+
}
|
|
564
|
+
if (msaData) {
|
|
565
|
+
self.setMSA(msaData);
|
|
566
|
+
}
|
|
567
|
+
else if (msaUrl) {
|
|
568
|
+
const response = await fetch(msaUrl);
|
|
569
|
+
if (!response.ok) {
|
|
570
|
+
throw new Error(`Failed to fetch MSA: ${response.status}`);
|
|
571
|
+
}
|
|
572
|
+
const data = await response.text();
|
|
573
|
+
self.setMSA(data);
|
|
574
|
+
}
|
|
575
|
+
if (treeData) {
|
|
576
|
+
self.setTree(treeData);
|
|
577
|
+
}
|
|
578
|
+
else if (treeUrl) {
|
|
579
|
+
const response = await fetch(treeUrl);
|
|
580
|
+
if (!response.ok) {
|
|
581
|
+
throw new Error(`Failed to fetch tree: ${response.status}`);
|
|
582
|
+
}
|
|
583
|
+
const data = await response.text();
|
|
584
|
+
self.setTree(data);
|
|
585
|
+
}
|
|
586
|
+
self.setInit(undefined);
|
|
587
|
+
}
|
|
588
|
+
catch (e) {
|
|
589
|
+
self.setError(e);
|
|
590
|
+
console.error(e);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}));
|
|
223
594
|
// this adds highlights to the genome view when mouse-ing over the MSA
|
|
224
595
|
addDisposer(self, autorun(() => {
|
|
225
596
|
const { mouseCol, mouseClickCol } = self;
|
|
@@ -231,25 +602,98 @@ export default function stateModelFactory() {
|
|
|
231
602
|
: msaCoordToGenomeCoord({ model: self, coord: mouseClickCol });
|
|
232
603
|
self.setConnectedHighlights([r1, r2].filter(f => !!f));
|
|
233
604
|
}));
|
|
234
|
-
//
|
|
605
|
+
// this highlights residues in connected protein structures when mousing over the MSA
|
|
235
606
|
addDisposer(self, autorun(() => {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
607
|
+
highlightConnectedStructures(self);
|
|
608
|
+
}));
|
|
609
|
+
// auto-connect to compatible ProteinViews
|
|
610
|
+
addDisposer(self, autorun(() => {
|
|
611
|
+
const { views } = getSession(self);
|
|
612
|
+
const { connectedViewId, uniprotId, rows, connectedStructures } = self;
|
|
613
|
+
// Need MSA loaded and a uniprotId to auto-connect
|
|
614
|
+
if (!uniprotId || rows.length === 0) {
|
|
242
615
|
return;
|
|
243
616
|
}
|
|
244
|
-
|
|
245
|
-
|
|
617
|
+
// Find ProteinViews that share the same connectedViewId
|
|
618
|
+
for (const view of views) {
|
|
619
|
+
const v = view;
|
|
620
|
+
if (v.type !== 'ProteinView' || !v.structures) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
for (let structureIdx = 0; structureIdx < v.structures.length; structureIdx++) {
|
|
624
|
+
const structure = v.structures[structureIdx];
|
|
625
|
+
// Check if structure shares the same genome view connection
|
|
626
|
+
if (structure.connectedViewId !== connectedViewId) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
// Check if structure has matching uniprotId
|
|
630
|
+
if (structure.uniprotId !== uniprotId) {
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
633
|
+
// Check if already connected
|
|
634
|
+
const alreadyConnected = connectedStructures.some(c => c.proteinViewId === v.id && c.structureIdx === structureIdx);
|
|
635
|
+
if (alreadyConnected) {
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
// Check if structure sequence is available
|
|
639
|
+
if (!structure.structureSequences?.[0]) {
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
// Auto-connect
|
|
643
|
+
try {
|
|
644
|
+
self.connectToStructure(v.id, structureIdx);
|
|
645
|
+
}
|
|
646
|
+
catch (e) {
|
|
647
|
+
console.error('Failed to auto-connect to ProteinView:', e);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}));
|
|
652
|
+
// Observe protein3d genome highlights and update MSA highlighted columns
|
|
653
|
+
// This enables communication via the linear genome view coordinates
|
|
654
|
+
addDisposer(self, autorun(() => {
|
|
655
|
+
const { views } = getSession(self);
|
|
656
|
+
const { connectedViewId, transcriptToMsaMap, querySeqName } = self;
|
|
657
|
+
if (!connectedViewId || !transcriptToMsaMap) {
|
|
658
|
+
return;
|
|
246
659
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
660
|
+
const columns = [];
|
|
661
|
+
// Find ProteinViews that share the same connected genome view
|
|
662
|
+
for (const view of views) {
|
|
663
|
+
const v = view;
|
|
664
|
+
if (v.type !== 'ProteinView' || !v.structures) {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
for (const structure of v.structures) {
|
|
668
|
+
// Check if structure is connected to same genome view
|
|
669
|
+
if (structure.connectedViewId !== connectedViewId) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
// Check if structure has hover genome highlights
|
|
673
|
+
const highlights = structure.hoverGenomeHighlights;
|
|
674
|
+
if (!highlights || highlights.length === 0) {
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
// Map genome coordinates to MSA columns
|
|
678
|
+
const { g2p } = transcriptToMsaMap;
|
|
679
|
+
for (const highlight of highlights) {
|
|
680
|
+
for (let coord = highlight.start; coord < highlight.end; coord++) {
|
|
681
|
+
const proteinPos = g2p[coord];
|
|
682
|
+
if (proteinPos !== undefined) {
|
|
683
|
+
const col = self.seqPosToGlobalCol(querySeqName, proteinPos);
|
|
684
|
+
if (!columns.includes(col)) {
|
|
685
|
+
columns.push(col);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
252
691
|
}
|
|
692
|
+
// Convert global column indices to visible column indices
|
|
693
|
+
const visibleColumns = columns
|
|
694
|
+
.map(col => self.globalColToVisibleCol(col))
|
|
695
|
+
.filter((col) => col !== undefined);
|
|
696
|
+
self.setHighlightedColumns(visibleColumns.length > 0 ? visibleColumns : undefined);
|
|
253
697
|
}));
|
|
254
698
|
},
|
|
255
699
|
}));
|