@apollo-annotation/jbrowse-plugin-apollo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/dist/index.esm.js +10248 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.js +7 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
- package/package.json +130 -0
- package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
- package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
- package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
- package/src/ApolloInternetAccount/configSchema.ts +26 -0
- package/src/ApolloInternetAccount/index.ts +2 -0
- package/src/ApolloInternetAccount/model.ts +448 -0
- package/src/ApolloJobModel.ts +117 -0
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
- package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
- package/src/ApolloSequenceAdapter/index.ts +21 -0
- package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
- package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
- package/src/ApolloSixFrameRenderer/index.ts +3 -0
- package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
- package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
- package/src/ApolloTextSearchAdapter/index.ts +18 -0
- package/src/BackendDrivers/BackendDriver.ts +31 -0
- package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
- package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
- package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
- package/src/BackendDrivers/index.ts +4 -0
- package/src/ChangeManager.ts +148 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
- package/src/LinearApolloDisplay/components/index.ts +1 -0
- package/src/LinearApolloDisplay/configSchema.ts +16 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
- package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
- package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
- package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
- package/src/LinearApolloDisplay/index.ts +2 -0
- package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
- package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
- package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
- package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
- package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
- package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
- package/src/LinearApolloDisplay/types.ts +1 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
- package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
- package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
- package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
- package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
- package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
- package/src/OntologyManager/OntologyStore/index.ts +526 -0
- package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
- package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
- package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
- package/src/OntologyManager/index.ts +173 -0
- package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
- package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
- package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
- package/src/SixFrameFeatureDisplay/index.ts +2 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
- package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
- package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
- package/src/TabularEditor/HybridGrid/index.ts +1 -0
- package/src/TabularEditor/TabularEditorPane.tsx +34 -0
- package/src/TabularEditor/index.ts +3 -0
- package/src/TabularEditor/model.ts +44 -0
- package/src/TabularEditor/types.ts +3 -0
- package/src/components/AddAssembly.tsx +464 -0
- package/src/components/AddChildFeature.tsx +247 -0
- package/src/components/AddFeature.tsx +252 -0
- package/src/components/CopyFeature.tsx +328 -0
- package/src/components/DeleteAssembly.tsx +185 -0
- package/src/components/DeleteFeature.tsx +90 -0
- package/src/components/Dialog.tsx +47 -0
- package/src/components/DownloadGFF3.tsx +213 -0
- package/src/components/ImportFeatures.tsx +295 -0
- package/src/components/ManageChecks.tsx +280 -0
- package/src/components/ManageUsers.tsx +218 -0
- package/src/components/ModifyFeatureAttribute.tsx +457 -0
- package/src/components/OntologyTermAutocomplete.tsx +240 -0
- package/src/components/OntologyTermMultiSelect.tsx +349 -0
- package/src/components/OpenLocalFile.tsx +178 -0
- package/src/components/ViewChangeLog.tsx +208 -0
- package/src/components/ViewCheckResults.tsx +151 -0
- package/src/components/index.ts +12 -0
- package/src/config.ts +10 -0
- package/src/declare.d.ts +3 -0
- package/src/extensions/annotationFromPileup.ts +208 -0
- package/src/extensions/index.ts +1 -0
- package/src/index.ts +394 -0
- package/src/makeDisplayComponent.tsx +244 -0
- package/src/session/ClientDataStore.ts +282 -0
- package/src/session/index.ts +1 -0
- package/src/session/session.ts +373 -0
- package/src/types.ts +10 -0
- package/src/util/index.ts +31 -0
- package/src/util/loadAssemblyIntoClient.ts +291 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import { getConf, readConfObject } from '@jbrowse/core/configuration'
|
|
2
|
+
import { ConfigurationModel } from '@jbrowse/core/configuration/types'
|
|
3
|
+
import { Region, getSession, isElectron } from '@jbrowse/core/util'
|
|
4
|
+
import { LocalPathLocation, UriLocation } from '@jbrowse/core/util/types/mst'
|
|
5
|
+
import { ClientDataStore as ClientDataStoreType } from 'apollo-common'
|
|
6
|
+
import {
|
|
7
|
+
AnnotationFeature,
|
|
8
|
+
AnnotationFeatureSnapshot,
|
|
9
|
+
ApolloAssembly,
|
|
10
|
+
ApolloRefSeq,
|
|
11
|
+
CheckResult,
|
|
12
|
+
CheckResultSnapshot,
|
|
13
|
+
} from 'apollo-mst'
|
|
14
|
+
import {
|
|
15
|
+
Instance,
|
|
16
|
+
SnapshotOut,
|
|
17
|
+
flow,
|
|
18
|
+
getParentOfType,
|
|
19
|
+
getRoot,
|
|
20
|
+
resolveIdentifier,
|
|
21
|
+
types,
|
|
22
|
+
} from 'mobx-state-tree'
|
|
23
|
+
|
|
24
|
+
import {
|
|
25
|
+
ApolloInternetAccount,
|
|
26
|
+
CollaborationServerDriver,
|
|
27
|
+
DesktopFileDriver,
|
|
28
|
+
InMemoryFileDriver,
|
|
29
|
+
} from '../BackendDrivers'
|
|
30
|
+
import { ChangeManager } from '../ChangeManager'
|
|
31
|
+
import ApolloPluginConfigurationSchema from '../config'
|
|
32
|
+
import {
|
|
33
|
+
OntologyManagerType,
|
|
34
|
+
OntologyRecordConfiguration,
|
|
35
|
+
TextIndexFieldDefinition,
|
|
36
|
+
} from '../OntologyManager'
|
|
37
|
+
import { ApolloRootModel } from '../types'
|
|
38
|
+
|
|
39
|
+
export function clientDataStoreFactory(
|
|
40
|
+
AnnotationFeatureExtended: typeof AnnotationFeature,
|
|
41
|
+
) {
|
|
42
|
+
const clientStoreType = types
|
|
43
|
+
.model('ClientDataStore', {
|
|
44
|
+
typeName: types.optional(types.literal('Client'), 'Client'),
|
|
45
|
+
assemblies: types.map(ApolloAssembly),
|
|
46
|
+
checkResults: types.map(CheckResult),
|
|
47
|
+
})
|
|
48
|
+
.views((self) => ({
|
|
49
|
+
get internetAccounts() {
|
|
50
|
+
return getRoot<ApolloRootModel>(self).internetAccounts
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
get pluginConfiguration() {
|
|
54
|
+
return getRoot<ApolloRootModel>(self).jbrowse.configuration
|
|
55
|
+
.ApolloPlugin as Instance<typeof ApolloPluginConfigurationSchema>
|
|
56
|
+
},
|
|
57
|
+
getFeature(featureId: string) {
|
|
58
|
+
return resolveIdentifier(
|
|
59
|
+
AnnotationFeatureExtended,
|
|
60
|
+
self.assemblies,
|
|
61
|
+
featureId,
|
|
62
|
+
)
|
|
63
|
+
},
|
|
64
|
+
}))
|
|
65
|
+
.actions((self) => ({
|
|
66
|
+
addAssembly(assemblyId: string) {
|
|
67
|
+
return self.assemblies.put({ _id: assemblyId, refSeqs: {} })
|
|
68
|
+
},
|
|
69
|
+
addFeature(assemblyId: string, feature: AnnotationFeatureSnapshot) {
|
|
70
|
+
const assembly = self.assemblies.get(assemblyId)
|
|
71
|
+
if (!assembly) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
const ref = assembly.refSeqs.get(feature.refSeq)
|
|
77
|
+
if (!ref) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Could not find refSeq "${feature.refSeq}" to add feature "${feature._id}"`,
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
ref.features.put(feature)
|
|
83
|
+
},
|
|
84
|
+
deleteFeature(featureId: string) {
|
|
85
|
+
const feature = self.getFeature(featureId)
|
|
86
|
+
if (!feature) {
|
|
87
|
+
throw new Error(`Could not find feature "${featureId}" to delete`)
|
|
88
|
+
}
|
|
89
|
+
const { _id, parent } = feature
|
|
90
|
+
if (parent) {
|
|
91
|
+
parent.deleteChild(featureId)
|
|
92
|
+
} else {
|
|
93
|
+
const refSeq = getParentOfType(feature, ApolloRefSeq)
|
|
94
|
+
refSeq.deleteFeature(_id)
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
deleteAssembly(assemblyId: string) {
|
|
98
|
+
self.assemblies.delete(assemblyId)
|
|
99
|
+
},
|
|
100
|
+
addCheckResult(checkResult: CheckResultSnapshot) {
|
|
101
|
+
self.checkResults.put(checkResult)
|
|
102
|
+
},
|
|
103
|
+
addCheckResults(checkResults: CheckResultSnapshot[]) {
|
|
104
|
+
for (const checkResult of checkResults) {
|
|
105
|
+
if (!self.checkResults.has(checkResult._id)) {
|
|
106
|
+
self.checkResults.put(checkResult)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
deleteCheckResult(checkResultId: string) {
|
|
111
|
+
self.checkResults.delete(checkResultId)
|
|
112
|
+
},
|
|
113
|
+
}))
|
|
114
|
+
.volatile((self) => ({
|
|
115
|
+
changeManager: new ChangeManager(self as unknown as ClientDataStoreType),
|
|
116
|
+
collaborationServerDriver: new CollaborationServerDriver(
|
|
117
|
+
self as unknown as ClientDataStoreType,
|
|
118
|
+
),
|
|
119
|
+
inMemoryFileDriver: new InMemoryFileDriver(
|
|
120
|
+
self as unknown as ClientDataStoreType,
|
|
121
|
+
),
|
|
122
|
+
desktopFileDriver: isElectron
|
|
123
|
+
? new DesktopFileDriver(self as unknown as ClientDataStoreType)
|
|
124
|
+
: undefined,
|
|
125
|
+
ontologyManager: OntologyManagerType.create(),
|
|
126
|
+
}))
|
|
127
|
+
.actions((self) => ({
|
|
128
|
+
afterCreate() {
|
|
129
|
+
// Merge in the ontologies from our plugin configuration.
|
|
130
|
+
// Ontologies of a given name that are already in the session
|
|
131
|
+
// take precedence over the ontologies in the configuration.
|
|
132
|
+
const { ontologyManager, pluginConfiguration } = self
|
|
133
|
+
const configuredOntologies =
|
|
134
|
+
pluginConfiguration.ontologies as ConfigurationModel<
|
|
135
|
+
typeof OntologyRecordConfiguration
|
|
136
|
+
>[]
|
|
137
|
+
|
|
138
|
+
for (const ont of configuredOntologies || []) {
|
|
139
|
+
const [name, version, source, indexFields] = [
|
|
140
|
+
readConfObject(ont, 'name') as string,
|
|
141
|
+
readConfObject(ont, 'version') as string,
|
|
142
|
+
readConfObject(ont, 'source') as
|
|
143
|
+
| Instance<typeof LocalPathLocation>
|
|
144
|
+
| Instance<typeof UriLocation>,
|
|
145
|
+
readConfObject(
|
|
146
|
+
ont,
|
|
147
|
+
'textIndexFields',
|
|
148
|
+
) as TextIndexFieldDefinition[],
|
|
149
|
+
]
|
|
150
|
+
if (!ontologyManager.findOntology(name)) {
|
|
151
|
+
ontologyManager.addOntology(name, version, source, {
|
|
152
|
+
textIndexing: { indexFields },
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// TODO: add in any configured ontology prefixes that we don't already
|
|
158
|
+
// have in the session (or hardcoded in the model)
|
|
159
|
+
},
|
|
160
|
+
}))
|
|
161
|
+
.views((self) => ({
|
|
162
|
+
getBackendDriver(assemblyId: string) {
|
|
163
|
+
if (!assemblyId) {
|
|
164
|
+
return self.collaborationServerDriver
|
|
165
|
+
}
|
|
166
|
+
const session = getSession(self)
|
|
167
|
+
const { assemblyManager } = session
|
|
168
|
+
const assembly = assemblyManager.get(assemblyId)
|
|
169
|
+
if (!assembly) {
|
|
170
|
+
return self.collaborationServerDriver
|
|
171
|
+
}
|
|
172
|
+
const { file, internetAccountConfigId } = getConf(assembly, [
|
|
173
|
+
'sequence',
|
|
174
|
+
'metadata',
|
|
175
|
+
]) as { internetAccountConfigId?: string; file: string }
|
|
176
|
+
if (isElectron && file) {
|
|
177
|
+
return self.desktopFileDriver
|
|
178
|
+
}
|
|
179
|
+
if (internetAccountConfigId) {
|
|
180
|
+
return self.collaborationServerDriver
|
|
181
|
+
}
|
|
182
|
+
return self.inMemoryFileDriver
|
|
183
|
+
},
|
|
184
|
+
getInternetAccount(assemblyName?: string, internetAccountId?: string) {
|
|
185
|
+
if (!(assemblyName ?? internetAccountId)) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
'Must provide either assemblyName or internetAccountId',
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
let configId = internetAccountId
|
|
191
|
+
if (assemblyName && !configId) {
|
|
192
|
+
const { assemblyManager } = getSession(self)
|
|
193
|
+
const assembly = assemblyManager.get(assemblyName)
|
|
194
|
+
if (!assembly) {
|
|
195
|
+
throw new Error(`No assembly found with name ${assemblyName}`)
|
|
196
|
+
}
|
|
197
|
+
;({ internetAccountConfigId: configId } = getConf(assembly, [
|
|
198
|
+
'sequence',
|
|
199
|
+
'metadata',
|
|
200
|
+
]) as { internetAccountConfigId: string })
|
|
201
|
+
}
|
|
202
|
+
const { internetAccounts } = self
|
|
203
|
+
const internetAccount = internetAccounts.find(
|
|
204
|
+
(ia) => ia.internetAccountId === configId,
|
|
205
|
+
) as ApolloInternetAccount | undefined
|
|
206
|
+
if (!internetAccount) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`No InternetAccount found with config id ${internetAccountId}`,
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
return internetAccount
|
|
212
|
+
},
|
|
213
|
+
}))
|
|
214
|
+
.actions((self) => ({
|
|
215
|
+
loadFeatures: flow(function* loadFeatures(regions: Region[]) {
|
|
216
|
+
for (const region of regions) {
|
|
217
|
+
const backendDriver = self.getBackendDriver(region.assemblyName)
|
|
218
|
+
if (!backendDriver) {
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
const [features, checkResults] = (yield backendDriver.getFeatures(
|
|
222
|
+
region,
|
|
223
|
+
)) as [AnnotationFeatureSnapshot[], CheckResultSnapshot[]]
|
|
224
|
+
if (features.length === 0) {
|
|
225
|
+
continue
|
|
226
|
+
}
|
|
227
|
+
const { assemblyName, refName } = region
|
|
228
|
+
let assembly = self.assemblies.get(assemblyName)
|
|
229
|
+
if (!assembly) {
|
|
230
|
+
assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} })
|
|
231
|
+
}
|
|
232
|
+
const [firstFeature] = features
|
|
233
|
+
let ref = assembly.refSeqs.get(firstFeature.refSeq)
|
|
234
|
+
if (!ref) {
|
|
235
|
+
ref = assembly.refSeqs.put({
|
|
236
|
+
_id: firstFeature.refSeq,
|
|
237
|
+
name: refName,
|
|
238
|
+
features: {},
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
for (const feature of features) {
|
|
242
|
+
if (!ref.features.has(feature._id)) {
|
|
243
|
+
ref.features.put(feature)
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
self.addCheckResults(checkResults)
|
|
247
|
+
}
|
|
248
|
+
}),
|
|
249
|
+
loadRefSeq: flow(function* loadRefSeq(regions: Region[]) {
|
|
250
|
+
for (const region of regions) {
|
|
251
|
+
const backendDriver = self.getBackendDriver(region.assemblyName)
|
|
252
|
+
if (!backendDriver) {
|
|
253
|
+
return
|
|
254
|
+
}
|
|
255
|
+
const { refSeq, seq } = yield backendDriver.getSequence(region)
|
|
256
|
+
const { assemblyName, end, refName, start } = region
|
|
257
|
+
let assembly = self.assemblies.get(assemblyName)
|
|
258
|
+
if (!assembly) {
|
|
259
|
+
assembly = self.assemblies.put({ _id: assemblyName, refSeqs: {} })
|
|
260
|
+
}
|
|
261
|
+
let ref = assembly.refSeqs.get(refSeq)
|
|
262
|
+
if (!ref) {
|
|
263
|
+
ref = assembly.refSeqs.put({
|
|
264
|
+
_id: refSeq,
|
|
265
|
+
name: refName,
|
|
266
|
+
sequence: [],
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
ref.addSequence({ start, stop: end, sequence: seq })
|
|
270
|
+
}
|
|
271
|
+
}),
|
|
272
|
+
}))
|
|
273
|
+
|
|
274
|
+
// assembly and feature data isn't actually reloaded on reload unless we delete it from the snap
|
|
275
|
+
return types.snapshotProcessor(clientStoreType, {
|
|
276
|
+
postProcessor(snap: SnapshotOut<typeof clientStoreType>) {
|
|
277
|
+
snap.assemblies = {}
|
|
278
|
+
snap.checkResults = {}
|
|
279
|
+
return snap
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './session'
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { AssemblyModel } from '@jbrowse/core/assemblyManager/assembly'
|
|
2
|
+
import { getConf } from '@jbrowse/core/configuration'
|
|
3
|
+
import { BaseInternetAccountModel } from '@jbrowse/core/pluggableElementTypes'
|
|
4
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
5
|
+
import {
|
|
6
|
+
AbstractSessionModel,
|
|
7
|
+
SessionWithConfigEditing,
|
|
8
|
+
} from '@jbrowse/core/util'
|
|
9
|
+
import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
10
|
+
import { ClientDataStore as ClientDataStoreType } from 'apollo-common'
|
|
11
|
+
import { AnnotationFeature, AnnotationFeatureI } from 'apollo-mst'
|
|
12
|
+
import { UserLocation } from 'apollo-shared'
|
|
13
|
+
import { autorun, observable } from 'mobx'
|
|
14
|
+
import { Instance, flow, getRoot, types } from 'mobx-state-tree'
|
|
15
|
+
|
|
16
|
+
import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
|
|
17
|
+
import { ApolloJobModel } from '../ApolloJobModel'
|
|
18
|
+
import { ChangeManager } from '../ChangeManager'
|
|
19
|
+
import { ApolloRootModel } from '../types'
|
|
20
|
+
import { createFetchErrorMessage } from '../util'
|
|
21
|
+
import { clientDataStoreFactory } from './ClientDataStore'
|
|
22
|
+
|
|
23
|
+
export interface ApolloSession extends AbstractSessionModel {
|
|
24
|
+
apolloDataStore: ClientDataStoreType & { changeManager: ChangeManager }
|
|
25
|
+
apolloSelectedFeature?: AnnotationFeatureI
|
|
26
|
+
apolloSetSelectedFeature(feature?: AnnotationFeatureI): void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ApolloAssemblyResponse {
|
|
30
|
+
_id: string
|
|
31
|
+
name: string
|
|
32
|
+
displayName?: string
|
|
33
|
+
description?: string
|
|
34
|
+
aliases?: string[]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ApolloRefSeqResponse {
|
|
38
|
+
_id: string
|
|
39
|
+
name: string
|
|
40
|
+
description?: string
|
|
41
|
+
length: string
|
|
42
|
+
assembly: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Collaborator {
|
|
46
|
+
name: string
|
|
47
|
+
id: string
|
|
48
|
+
locations: UserLocation[]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function extendSession(
|
|
52
|
+
pluginManager: PluginManager,
|
|
53
|
+
sessionModel: ReturnType<typeof types.model>,
|
|
54
|
+
) {
|
|
55
|
+
const aborter = new AbortController()
|
|
56
|
+
const { signal } = aborter
|
|
57
|
+
const AnnotationFeatureExtended = pluginManager.evaluateExtensionPoint(
|
|
58
|
+
'Apollo-extendAnnotationFeature',
|
|
59
|
+
AnnotationFeature,
|
|
60
|
+
) as typeof AnnotationFeature
|
|
61
|
+
const ClientDataStore = clientDataStoreFactory(AnnotationFeatureExtended)
|
|
62
|
+
return sessionModel
|
|
63
|
+
.props({
|
|
64
|
+
apolloDataStore: types.optional(ClientDataStore, { typeName: 'Client' }),
|
|
65
|
+
apolloSelectedFeature: types.safeReference(AnnotationFeatureExtended),
|
|
66
|
+
jobsManager: types.optional(ApolloJobModel, {}),
|
|
67
|
+
})
|
|
68
|
+
.extend(() => {
|
|
69
|
+
const collabs = observable.array<Collaborator>([])
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
views: {
|
|
73
|
+
get collaborators() {
|
|
74
|
+
return collabs
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
actions: {
|
|
78
|
+
addOrUpdateCollaborator(collaborator: Collaborator) {
|
|
79
|
+
const existingCollaborator = collabs.find(
|
|
80
|
+
(obj: Collaborator) => obj.id === collaborator.id,
|
|
81
|
+
)
|
|
82
|
+
if (existingCollaborator) {
|
|
83
|
+
existingCollaborator.locations = collaborator.locations
|
|
84
|
+
} else {
|
|
85
|
+
collabs.push(collaborator)
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
.actions((self) => ({
|
|
92
|
+
apolloSetSelectedFeature(feature?: AnnotationFeatureI) {
|
|
93
|
+
self.apolloSelectedFeature = feature
|
|
94
|
+
},
|
|
95
|
+
addApolloTrackConfig(assembly: AssemblyModel, baseURL?: string) {
|
|
96
|
+
const trackId = `apollo_track_${assembly.name}`
|
|
97
|
+
const hasTrack = (self as unknown as AbstractSessionModel).tracks.some(
|
|
98
|
+
(track) => track.trackId === trackId,
|
|
99
|
+
)
|
|
100
|
+
if (!hasTrack) {
|
|
101
|
+
;(self as unknown as SessionWithConfigEditing).addTrackConf({
|
|
102
|
+
type: 'ApolloTrack',
|
|
103
|
+
trackId,
|
|
104
|
+
name: `Annotations (${
|
|
105
|
+
// @ts-expect-error getConf types don't quite work here for some reason
|
|
106
|
+
getConf(assembly, 'displayName') || assembly.name
|
|
107
|
+
})`,
|
|
108
|
+
assemblyNames: [assembly.name],
|
|
109
|
+
textSearching: {
|
|
110
|
+
textSearchAdapter: {
|
|
111
|
+
type: 'ApolloTextSearchAdapter',
|
|
112
|
+
trackId,
|
|
113
|
+
assemblyNames: [assembly.name],
|
|
114
|
+
textSearchAdapterId: `apollo_search_${assembly.name}`,
|
|
115
|
+
...(baseURL
|
|
116
|
+
? { baseURL: { uri: baseURL, locationType: 'UriLocation' } }
|
|
117
|
+
: {}),
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
displays: [
|
|
121
|
+
{
|
|
122
|
+
type: 'LinearApolloDisplay',
|
|
123
|
+
displayId: `${trackId}-LinearApolloDisplay`,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: 'SixFrameFeatureDisplay',
|
|
127
|
+
displayId: `${trackId}-SixFrameFeatureDisplay`,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
broadcastLocations() {
|
|
134
|
+
const { internetAccounts } = getRoot<ApolloRootModel>(self)
|
|
135
|
+
const locations: {
|
|
136
|
+
assemblyName: string
|
|
137
|
+
refName: string
|
|
138
|
+
start: number
|
|
139
|
+
end: number
|
|
140
|
+
}[] = []
|
|
141
|
+
for (const view of (self as unknown as AbstractSessionModel).views) {
|
|
142
|
+
if (view.type !== 'LinearGenomeView') {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
const lgv = view as unknown as LinearGenomeViewModel
|
|
146
|
+
if (lgv.initialized) {
|
|
147
|
+
const { dynamicBlocks } = lgv
|
|
148
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
149
|
+
dynamicBlocks.forEach((block) => {
|
|
150
|
+
if (block.regionNumber !== undefined) {
|
|
151
|
+
const { assemblyName, end, refName, start } = block
|
|
152
|
+
locations.push({ assemblyName, refName, start, end })
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (locations.length === 0) {
|
|
158
|
+
for (const internetAccount of internetAccounts as (
|
|
159
|
+
| BaseInternetAccountModel
|
|
160
|
+
| ApolloInternetAccountModel
|
|
161
|
+
)[]) {
|
|
162
|
+
if ('baseURL' in internetAccount) {
|
|
163
|
+
internetAccount.postUserLocation([])
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const allLocations: UserLocation[] = []
|
|
170
|
+
for (const internetAccount of internetAccounts as (
|
|
171
|
+
| BaseInternetAccountModel
|
|
172
|
+
| ApolloInternetAccountModel
|
|
173
|
+
)[]) {
|
|
174
|
+
if ('baseURL' in internetAccount) {
|
|
175
|
+
for (const location of locations) {
|
|
176
|
+
const tmpLoc: UserLocation = {
|
|
177
|
+
assemblyId: location.assemblyName,
|
|
178
|
+
refSeq: location.refName,
|
|
179
|
+
start: location.start,
|
|
180
|
+
end: location.end,
|
|
181
|
+
}
|
|
182
|
+
allLocations.push(tmpLoc)
|
|
183
|
+
}
|
|
184
|
+
internetAccount.postUserLocation(allLocations)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
}))
|
|
189
|
+
.actions((self) => ({
|
|
190
|
+
afterCreate: flow(function* afterCreate() {
|
|
191
|
+
const { internetAccounts } = getRoot<ApolloRootModel>(self)
|
|
192
|
+
autorun(
|
|
193
|
+
() => {
|
|
194
|
+
// broadcastLocations() // **** This is not working and therefore we need to duplicate broadcastLocations() -method code here because autorun() does not observe changes otherwise
|
|
195
|
+
const locations: {
|
|
196
|
+
assemblyName: string
|
|
197
|
+
refName: string
|
|
198
|
+
start: number
|
|
199
|
+
end: number
|
|
200
|
+
}[] = []
|
|
201
|
+
for (const view of (self as unknown as AbstractSessionModel)
|
|
202
|
+
.views) {
|
|
203
|
+
if (view.type !== 'LinearGenomeView') {
|
|
204
|
+
return
|
|
205
|
+
}
|
|
206
|
+
const lgv = view as unknown as LinearGenomeViewModel
|
|
207
|
+
if (lgv.initialized) {
|
|
208
|
+
const { dynamicBlocks } = lgv as LinearGenomeViewModel
|
|
209
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
210
|
+
dynamicBlocks.forEach((block) => {
|
|
211
|
+
if (block.regionNumber !== undefined) {
|
|
212
|
+
const { assemblyName, end, refName, start } = block
|
|
213
|
+
locations.push({ assemblyName, refName, start, end })
|
|
214
|
+
}
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (locations.length === 0) {
|
|
219
|
+
for (const internetAccount of internetAccounts as (
|
|
220
|
+
| BaseInternetAccountModel
|
|
221
|
+
| ApolloInternetAccountModel
|
|
222
|
+
)[]) {
|
|
223
|
+
if ('baseURL' in internetAccount) {
|
|
224
|
+
internetAccount.postUserLocation([])
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const allLocations: UserLocation[] = []
|
|
231
|
+
for (const internetAccount of internetAccounts as (
|
|
232
|
+
| BaseInternetAccountModel
|
|
233
|
+
| ApolloInternetAccountModel
|
|
234
|
+
)[]) {
|
|
235
|
+
if ('baseURL' in internetAccount) {
|
|
236
|
+
for (const location of locations) {
|
|
237
|
+
const tmpLoc: UserLocation = {
|
|
238
|
+
assemblyId: location.assemblyName,
|
|
239
|
+
refSeq: location.refName,
|
|
240
|
+
start: location.start,
|
|
241
|
+
end: location.end,
|
|
242
|
+
}
|
|
243
|
+
allLocations.push(tmpLoc)
|
|
244
|
+
}
|
|
245
|
+
internetAccount.postUserLocation(allLocations)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{ name: 'ApolloSession' },
|
|
250
|
+
)
|
|
251
|
+
// END AUTORUN
|
|
252
|
+
|
|
253
|
+
// fetch and initialize assemblies for each of our Apollo internet accounts
|
|
254
|
+
for (const internetAccount of internetAccounts as ApolloInternetAccountModel[]) {
|
|
255
|
+
if (internetAccount.type !== 'ApolloInternetAccount') {
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const { baseURL, configuration } = internetAccount
|
|
260
|
+
const uri = new URL('assemblies', baseURL).href
|
|
261
|
+
const fetch = internetAccount.getFetcher({
|
|
262
|
+
locationType: 'UriLocation',
|
|
263
|
+
uri,
|
|
264
|
+
})
|
|
265
|
+
let response: Response
|
|
266
|
+
try {
|
|
267
|
+
response = yield fetch(uri, { signal })
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(error)
|
|
270
|
+
// setError(e instanceof Error ? e : new Error(String(e)))
|
|
271
|
+
continue
|
|
272
|
+
}
|
|
273
|
+
if (!response.ok) {
|
|
274
|
+
const errorMessage = yield createFetchErrorMessage(
|
|
275
|
+
response,
|
|
276
|
+
'Failed to fetch assemblies',
|
|
277
|
+
)
|
|
278
|
+
console.error(errorMessage)
|
|
279
|
+
continue
|
|
280
|
+
}
|
|
281
|
+
let fetchedAssemblies
|
|
282
|
+
try {
|
|
283
|
+
fetchedAssemblies =
|
|
284
|
+
(yield response.json()) as ApolloAssemblyResponse[]
|
|
285
|
+
} catch (error) {
|
|
286
|
+
console.error(error)
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
for (const assembly of fetchedAssemblies) {
|
|
290
|
+
const { addAssembly, addSessionAssembly, assemblyManager } =
|
|
291
|
+
self as unknown as AbstractSessionModel & {
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
293
|
+
addSessionAssembly: Function
|
|
294
|
+
}
|
|
295
|
+
const selectedAssembly = assemblyManager.get(assembly.name)
|
|
296
|
+
if (selectedAssembly) {
|
|
297
|
+
// @ts-expect-error MST type coercion problem?
|
|
298
|
+
self.addApolloTrackConfig(selectedAssembly, baseURL)
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
const url = new URL('refSeqs', baseURL)
|
|
302
|
+
const searchParams = new URLSearchParams({ assembly: assembly._id })
|
|
303
|
+
url.search = searchParams.toString()
|
|
304
|
+
const uri2 = url.toString()
|
|
305
|
+
const fetch2 = internetAccount.getFetcher({
|
|
306
|
+
locationType: 'UriLocation',
|
|
307
|
+
uri: uri2,
|
|
308
|
+
})
|
|
309
|
+
const response2 = (yield fetch2(uri2, {
|
|
310
|
+
signal,
|
|
311
|
+
})) as unknown as Response
|
|
312
|
+
if (!response2.ok) {
|
|
313
|
+
let errorMessage
|
|
314
|
+
try {
|
|
315
|
+
errorMessage = yield response2.text()
|
|
316
|
+
} catch {
|
|
317
|
+
errorMessage = ''
|
|
318
|
+
}
|
|
319
|
+
throw new Error(
|
|
320
|
+
`Failed to fetch fasta info — ${response2.status} (${
|
|
321
|
+
response2.statusText
|
|
322
|
+
})${errorMessage ? ` (${errorMessage})` : ''}`,
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
const f = (yield response2.json()) as ApolloRefSeqResponse[]
|
|
326
|
+
const ids: Record<string, string> = {}
|
|
327
|
+
const refNameAliasesFeatures = f.map((contig) => {
|
|
328
|
+
ids[contig.name] = contig._id
|
|
329
|
+
return {
|
|
330
|
+
refName: contig.name,
|
|
331
|
+
aliases: [contig._id],
|
|
332
|
+
uniqueId: `alias-${contig._id}`,
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
const assemblyConfig = {
|
|
336
|
+
name: assembly._id,
|
|
337
|
+
aliases: [assembly.name, ...(assembly.aliases ?? [])],
|
|
338
|
+
displayName: assembly.displayName ?? assembly.name,
|
|
339
|
+
sequence: {
|
|
340
|
+
trackId: `sequenceConfigId-${assembly.name}`,
|
|
341
|
+
type: 'ReferenceSequenceTrack',
|
|
342
|
+
adapter: {
|
|
343
|
+
type: 'ApolloSequenceAdapter',
|
|
344
|
+
assemblyId: assembly._id,
|
|
345
|
+
baseURL: { uri: baseURL, locationType: 'UriLocation' },
|
|
346
|
+
},
|
|
347
|
+
metadata: {
|
|
348
|
+
apollo: true,
|
|
349
|
+
internetAccountConfigId: configuration.internetAccountId,
|
|
350
|
+
ids,
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
refNameAliases: {
|
|
354
|
+
adapter: {
|
|
355
|
+
type: 'FromConfigAdapter',
|
|
356
|
+
features: refNameAliasesFeatures,
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
}
|
|
360
|
+
;(addSessionAssembly || addAssembly)(assemblyConfig)
|
|
361
|
+
const a = yield assemblyManager.waitForAssembly(assemblyConfig.name)
|
|
362
|
+
self.addApolloTrackConfig(a, baseURL)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}),
|
|
366
|
+
beforeDestroy() {
|
|
367
|
+
aborter.abort()
|
|
368
|
+
},
|
|
369
|
+
}))
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export type ApolloSessionStateModel = ReturnType<typeof extendSession>
|
|
373
|
+
export type ApolloSessionModel = Instance<ApolloSessionStateModel>
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BaseInternetAccountModel } from '@jbrowse/core/pluggableElementTypes'
|
|
2
|
+
import { AppRootModel } from '@jbrowse/core/util'
|
|
3
|
+
|
|
4
|
+
import { ApolloInternetAccountModel } from './ApolloInternetAccount/model'
|
|
5
|
+
import { ApolloSessionModel } from './session'
|
|
6
|
+
|
|
7
|
+
export interface ApolloRootModel extends Omit<AppRootModel, 'session'> {
|
|
8
|
+
session: ApolloSessionModel
|
|
9
|
+
internetAccounts: (BaseInternetAccountModel | ApolloInternetAccountModel)[]
|
|
10
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getParent } from 'mobx-state-tree'
|
|
2
|
+
|
|
3
|
+
import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
|
|
4
|
+
import { ApolloSessionModel } from '../session'
|
|
5
|
+
import { ApolloRootModel } from '../types'
|
|
6
|
+
|
|
7
|
+
export async function createFetchErrorMessage(
|
|
8
|
+
response: Response,
|
|
9
|
+
additionalText?: string,
|
|
10
|
+
): Promise<string> {
|
|
11
|
+
let errorMessage
|
|
12
|
+
try {
|
|
13
|
+
errorMessage = await response.text()
|
|
14
|
+
} catch {
|
|
15
|
+
errorMessage = ''
|
|
16
|
+
}
|
|
17
|
+
const responseMessage = `${response.status} ${response.statusText}${
|
|
18
|
+
errorMessage ? ` (${errorMessage})` : ''
|
|
19
|
+
}`
|
|
20
|
+
return `${additionalText ? `${additionalText} — ` : ''}${responseMessage}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** given a session, get our ApolloInternetAccount */
|
|
24
|
+
export function getApolloInternetAccount(session: ApolloSessionModel) {
|
|
25
|
+
const { internetAccounts } = getParent<ApolloRootModel>(session)
|
|
26
|
+
return internetAccounts.find((ia) => ia.type === 'ApolloInternetAccount') as
|
|
27
|
+
| ApolloInternetAccountModel
|
|
28
|
+
| undefined
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export * from './loadAssemblyIntoClient'
|