@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.
Files changed (116) hide show
  1. package/README.md +76 -0
  2. package/dist/index.esm.js +10248 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +7 -0
  5. package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
  6. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
  7. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
  8. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
  9. package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
  10. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
  11. package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
  12. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
  13. package/package.json +130 -0
  14. package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
  15. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
  16. package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
  17. package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
  18. package/src/ApolloInternetAccount/configSchema.ts +26 -0
  19. package/src/ApolloInternetAccount/index.ts +2 -0
  20. package/src/ApolloInternetAccount/model.ts +448 -0
  21. package/src/ApolloJobModel.ts +117 -0
  22. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
  23. package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
  24. package/src/ApolloSequenceAdapter/index.ts +21 -0
  25. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
  26. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
  27. package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
  28. package/src/ApolloSixFrameRenderer/index.ts +3 -0
  29. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
  30. package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
  31. package/src/ApolloTextSearchAdapter/index.ts +18 -0
  32. package/src/BackendDrivers/BackendDriver.ts +31 -0
  33. package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
  34. package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
  35. package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
  36. package/src/BackendDrivers/index.ts +4 -0
  37. package/src/ChangeManager.ts +148 -0
  38. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
  39. package/src/LinearApolloDisplay/components/index.ts +1 -0
  40. package/src/LinearApolloDisplay/configSchema.ts +16 -0
  41. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
  42. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
  43. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
  44. package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
  45. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
  46. package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
  47. package/src/LinearApolloDisplay/index.ts +2 -0
  48. package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
  49. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
  50. package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
  51. package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
  52. package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
  53. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
  54. package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
  55. package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
  56. package/src/LinearApolloDisplay/types.ts +1 -0
  57. package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
  58. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
  59. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
  60. package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
  61. package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
  62. package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
  63. package/src/OntologyManager/OntologyStore/index.ts +526 -0
  64. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
  65. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
  66. package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
  67. package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
  68. package/src/OntologyManager/index.ts +173 -0
  69. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
  70. package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
  71. package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
  72. package/src/SixFrameFeatureDisplay/index.ts +2 -0
  73. package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
  74. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
  75. package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
  76. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
  77. package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
  78. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
  79. package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
  80. package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
  81. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
  82. package/src/TabularEditor/HybridGrid/index.ts +1 -0
  83. package/src/TabularEditor/TabularEditorPane.tsx +34 -0
  84. package/src/TabularEditor/index.ts +3 -0
  85. package/src/TabularEditor/model.ts +44 -0
  86. package/src/TabularEditor/types.ts +3 -0
  87. package/src/components/AddAssembly.tsx +464 -0
  88. package/src/components/AddChildFeature.tsx +247 -0
  89. package/src/components/AddFeature.tsx +252 -0
  90. package/src/components/CopyFeature.tsx +328 -0
  91. package/src/components/DeleteAssembly.tsx +185 -0
  92. package/src/components/DeleteFeature.tsx +90 -0
  93. package/src/components/Dialog.tsx +47 -0
  94. package/src/components/DownloadGFF3.tsx +213 -0
  95. package/src/components/ImportFeatures.tsx +295 -0
  96. package/src/components/ManageChecks.tsx +280 -0
  97. package/src/components/ManageUsers.tsx +218 -0
  98. package/src/components/ModifyFeatureAttribute.tsx +457 -0
  99. package/src/components/OntologyTermAutocomplete.tsx +240 -0
  100. package/src/components/OntologyTermMultiSelect.tsx +349 -0
  101. package/src/components/OpenLocalFile.tsx +178 -0
  102. package/src/components/ViewChangeLog.tsx +208 -0
  103. package/src/components/ViewCheckResults.tsx +151 -0
  104. package/src/components/index.ts +12 -0
  105. package/src/config.ts +10 -0
  106. package/src/declare.d.ts +3 -0
  107. package/src/extensions/annotationFromPileup.ts +208 -0
  108. package/src/extensions/index.ts +1 -0
  109. package/src/index.ts +394 -0
  110. package/src/makeDisplayComponent.tsx +244 -0
  111. package/src/session/ClientDataStore.ts +282 -0
  112. package/src/session/index.ts +1 -0
  113. package/src/session/session.ts +373 -0
  114. package/src/types.ts +10 -0
  115. package/src/util/index.ts +31 -0
  116. package/src/util/loadAssemblyIntoClient.ts +291 -0
@@ -0,0 +1,64 @@
1
+ import { readConfObject } from '@jbrowse/core/configuration'
2
+ import {
3
+ BaseAdapter,
4
+ BaseTextSearchAdapter,
5
+ BaseTextSearchArgs,
6
+ } from '@jbrowse/core/data_adapters/BaseAdapter'
7
+ import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
8
+
9
+ export class ApolloTextSearchAdapter
10
+ extends BaseAdapter
11
+ implements BaseTextSearchAdapter
12
+ {
13
+ get baseURL() {
14
+ return readConfObject(this.config, 'baseURL').uri
15
+ }
16
+
17
+ get trackId() {
18
+ return readConfObject(this.config, 'trackId')
19
+ }
20
+
21
+ get assemblyNames() {
22
+ return readConfObject(this.config, 'assemblyNames')
23
+ }
24
+
25
+ mapBaseResult(
26
+ features: {
27
+ refSeq: any // eslint-disable-line @typescript-eslint/no-explicit-any
28
+ start: number
29
+ end: number
30
+ }[],
31
+ query: string,
32
+ ) {
33
+ return features.map(
34
+ (feature) =>
35
+ new BaseResult({
36
+ label: query,
37
+ trackId: this.trackId,
38
+ locString: `${feature.refSeq?.name}:${feature.start + 1}..${
39
+ feature.end
40
+ }`,
41
+ }),
42
+ )
43
+ }
44
+
45
+ async searchIndex(args: BaseTextSearchArgs): Promise<BaseResult[]> {
46
+ const results = []
47
+ for (const assemblyName of this.assemblyNames) {
48
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
49
+ const session = this.pluginManager?.rootModel?.session as any
50
+ const backendDriver =
51
+ session?.apolloDataStore.getBackendDriver(assemblyName)
52
+ const r = await backendDriver.searchFeatures(args.queryString, [
53
+ assemblyName,
54
+ ])
55
+ results.push(...r)
56
+ }
57
+
58
+ const query = args.queryString
59
+ return this.mapBaseResult(results, query)
60
+ }
61
+
62
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
63
+ freeResources() {}
64
+ }
@@ -0,0 +1,24 @@
1
+ import { ConfigurationSchema } from '@jbrowse/core/configuration'
2
+
3
+ export default ConfigurationSchema(
4
+ 'ApolloTextSearchAdapter',
5
+ {
6
+ assemblyNames: {
7
+ type: 'stringArray',
8
+ defaultValue: [],
9
+ description: 'List of assemblies covered by text search adapter',
10
+ },
11
+ trackId: {
12
+ type: 'string',
13
+ defaultValue: '',
14
+ },
15
+ baseURL: {
16
+ type: 'fileLocation',
17
+ defaultValue: {
18
+ uri: '',
19
+ locationType: 'UriLocation',
20
+ },
21
+ },
22
+ },
23
+ { explicitlyTyped: true, explicitIdentifier: 'textSearchAdapterId' },
24
+ )
@@ -0,0 +1,18 @@
1
+ import { TextSearchAdapterType } from '@jbrowse/core/pluggableElementTypes'
2
+ import PluginManager from '@jbrowse/core/PluginManager'
3
+
4
+ import { ApolloTextSearchAdapter } from './ApolloTextSearchAdapter'
5
+ import configSchema from './configSchema'
6
+
7
+ export function installApolloTextSearchAdapter(pluginManager: PluginManager) {
8
+ pluginManager.addTextSearchAdapterType(
9
+ () =>
10
+ new TextSearchAdapterType({
11
+ name: 'ApolloTextSearchAdapter',
12
+ displayName: 'Apollo text search adapter',
13
+ configSchema,
14
+ AdapterClass: ApolloTextSearchAdapter,
15
+ description: 'Apollo Text Search adapter',
16
+ }),
17
+ )
18
+ }
@@ -0,0 +1,31 @@
1
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
2
+ import { Region } from '@jbrowse/core/util'
3
+ import { Change, ClientDataStore } from 'apollo-common'
4
+ import { AnnotationFeatureSnapshot, CheckResultSnapshot } from 'apollo-mst'
5
+ import { ValidationResultSet } from 'apollo-shared'
6
+
7
+ import { SubmitOpts } from '../ChangeManager'
8
+
9
+ export abstract class BackendDriver {
10
+ constructor(protected clientStore: ClientDataStore) {}
11
+
12
+ abstract getFeatures(
13
+ region: Region,
14
+ ): Promise<[AnnotationFeatureSnapshot[], CheckResultSnapshot[]]>
15
+
16
+ abstract getSequence(region: Region): Promise<{ seq: string; refSeq: string }>
17
+
18
+ abstract getRegions(assemblyName: string): Promise<Region[]>
19
+
20
+ abstract getAssemblies(internetAccountConfigId?: string): Assembly[]
21
+
22
+ abstract submitChange(
23
+ change: Change,
24
+ opts: SubmitOpts,
25
+ ): Promise<ValidationResultSet>
26
+
27
+ abstract searchFeatures(
28
+ term: string,
29
+ assemblies: string[],
30
+ ): Promise<AnnotationFeatureSnapshot[]>
31
+ }
@@ -0,0 +1,318 @@
1
+ import { getConf } from '@jbrowse/core/configuration'
2
+ import { BaseInternetAccountModel } from '@jbrowse/core/pluggableElementTypes'
3
+ import { Region, getSession } from '@jbrowse/core/util'
4
+ import { AssemblySpecificChange, Change } from 'apollo-common'
5
+ import {
6
+ AnnotationFeatureSnapshot,
7
+ ApolloRefSeqI,
8
+ CheckResultSnapshot,
9
+ } from 'apollo-mst'
10
+ import { ChangeMessage, ValidationResultSet } from 'apollo-shared'
11
+ import { Socket } from 'socket.io-client'
12
+
13
+ import { ChangeManager, SubmitOpts } from '../ChangeManager'
14
+ import { createFetchErrorMessage } from '../util'
15
+ import { BackendDriver } from './BackendDriver'
16
+
17
+ export interface ApolloInternetAccount extends BaseInternetAccountModel {
18
+ baseURL: string
19
+ socket: Socket
20
+ setLastChangeSequenceNumber(sequenceNumber: number): void
21
+ getMissingChanges(): void
22
+ }
23
+
24
+ export class CollaborationServerDriver extends BackendDriver {
25
+ private inFlight = new Map<string, Promise<string>>()
26
+
27
+ private async fetch(
28
+ internetAccount: ApolloInternetAccount,
29
+ info: RequestInfo,
30
+ init?: RequestInit,
31
+ ) {
32
+ const customFetch = internetAccount.getFetcher({
33
+ locationType: 'UriLocation',
34
+ uri: info.toString(),
35
+ })
36
+ return customFetch(info, init)
37
+ }
38
+
39
+ async searchFeatures(term: string, assemblies: string[]) {
40
+ const internetAccount = this.clientStore.getInternetAccount(
41
+ assemblies[0],
42
+ ) as ApolloInternetAccount
43
+ const { baseURL } = internetAccount
44
+
45
+ const url = new URL('features/searchFeatures', baseURL)
46
+ const searchParams = new URLSearchParams({
47
+ assemblies: assemblies.join(','),
48
+ term,
49
+ })
50
+ url.search = searchParams.toString()
51
+ const uri = url.toString()
52
+
53
+ const response = await this.fetch(internetAccount, uri)
54
+ if (!response.ok) {
55
+ const errorMessage = await createFetchErrorMessage(
56
+ response,
57
+ 'searchFeatures failed',
58
+ )
59
+ throw new Error(errorMessage)
60
+ }
61
+ return response.json() as Promise<AnnotationFeatureSnapshot[]>
62
+ }
63
+
64
+ /**
65
+ * Call backend endpoint to get features by criteria
66
+ * @param region - Searchable region containing refSeq, start and end
67
+ * @returns
68
+ */
69
+ async getFeatures(region: Region) {
70
+ const { assemblyName, end, refName, start } = region
71
+ const { assemblyManager } = getSession(this.clientStore)
72
+ const assembly = assemblyManager.get(assemblyName)
73
+ if (!assembly) {
74
+ throw new Error(`Could not find assembly with name "${assemblyName}"`)
75
+ }
76
+ const { ids } = getConf(assembly, ['sequence', 'metadata']) as {
77
+ ids: Record<string, string>
78
+ }
79
+ const refSeq = ids[refName]
80
+ if (!refSeq) {
81
+ throw new Error(`Could not find refSeq "${refName}"`)
82
+ }
83
+ const internetAccount = this.clientStore.getInternetAccount(
84
+ assemblyName,
85
+ ) as ApolloInternetAccount
86
+ const { baseURL } = internetAccount
87
+
88
+ const url = new URL('features/getFeatures', baseURL)
89
+ const searchParams = new URLSearchParams({
90
+ refSeq,
91
+ start: String(start),
92
+ end: String(end),
93
+ })
94
+ url.search = searchParams.toString()
95
+ const uri = url.toString()
96
+
97
+ const response = await this.fetch(internetAccount, uri)
98
+ if (!response.ok) {
99
+ const errorMessage = await createFetchErrorMessage(
100
+ response,
101
+ 'getFeatures failed',
102
+ )
103
+ throw new Error(errorMessage)
104
+ }
105
+ await this.checkSocket(assemblyName, refName, internetAccount)
106
+ return response.json() as Promise<
107
+ [AnnotationFeatureSnapshot[], CheckResultSnapshot[]]
108
+ >
109
+ }
110
+
111
+ /**
112
+ * Checks if there is assembly-refSeq specific socket. If not, it opens one
113
+ * @param assembly - assemblyId
114
+ * @param refSeq - refSeqName
115
+ * @param internetAccount - internet account
116
+ */
117
+ async checkSocket(
118
+ assembly: string,
119
+ refSeq: string,
120
+ internetAccount: ApolloInternetAccount,
121
+ ) {
122
+ const { socket } = internetAccount
123
+ const token = internetAccount.retrieveToken()
124
+ const channel = `${assembly}-${refSeq}`
125
+ const changeManager = new ChangeManager(this.clientStore)
126
+
127
+ if (!socket.hasListeners(channel)) {
128
+ socket.on(channel, async (message: ChangeMessage) => {
129
+ // Save server last change sequence into session storage
130
+ internetAccount.setLastChangeSequenceNumber(
131
+ Number(message.changeSequence),
132
+ )
133
+ if (message.userSessionId !== token && message.channel === channel) {
134
+ const change = Change.fromJSON(message.changeInfo)
135
+ await changeManager.submit(change, { submitToBackend: false })
136
+ }
137
+ })
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Call backend endpoint to get sequence by criteria
143
+ * @param region - Searchable region containing refSeq, start and end
144
+ * @returns
145
+ */
146
+ async getSequence(region: Region): Promise<{ seq: string; refSeq: string }> {
147
+ const inFlightKey = `${region.refName}:${region.start}-${region.end}`
148
+ const inFlightPromise = this.inFlight.get(inFlightKey)
149
+ const { assemblyName, end, refName, start } = region
150
+ const { assemblyManager } = getSession(this.clientStore)
151
+ const assembly = assemblyManager.get(assemblyName)
152
+ if (!assembly) {
153
+ throw new Error(`Could not find assembly with name "${assemblyName}"`)
154
+ }
155
+ const { ids } = getConf(assembly, ['sequence', 'metadata']) as {
156
+ ids: Record<string, string>
157
+ }
158
+ const refSeq = ids[refName]
159
+ if (!refSeq) {
160
+ throw new Error(`Could not find refSeq "${refName}"`)
161
+ }
162
+ if (inFlightPromise) {
163
+ const seq = await inFlightPromise
164
+ return { seq, refSeq }
165
+ }
166
+ let apolloAssembly = this.clientStore.assemblies.get(assemblyName)
167
+ if (!apolloAssembly) {
168
+ apolloAssembly = this.clientStore.addAssembly(assemblyName)
169
+ }
170
+ let apolloRefSeq = apolloAssembly?.refSeqs.get(refSeq)
171
+ if (!apolloRefSeq) {
172
+ apolloRefSeq = apolloAssembly.addRefSeq(refSeq, refName)
173
+ }
174
+ const clientStoreSequence = apolloRefSeq.getSequence(start, end)
175
+ if (clientStoreSequence.length === end - start) {
176
+ return { seq: clientStoreSequence, refSeq }
177
+ }
178
+ const internetAccount = this.clientStore.getInternetAccount(
179
+ assemblyName,
180
+ ) as ApolloInternetAccount
181
+ const { baseURL } = internetAccount
182
+
183
+ const url = new URL('sequence', baseURL)
184
+ const searchParams = new URLSearchParams({
185
+ refSeq,
186
+ start: String(start),
187
+ end: String(end),
188
+ })
189
+ url.search = searchParams.toString()
190
+ const uri = url.toString()
191
+
192
+ const seqPromise = this.getSeqFromServer(
193
+ internetAccount,
194
+ uri,
195
+ apolloRefSeq,
196
+ start,
197
+ end,
198
+ )
199
+ this.inFlight.set(inFlightKey, seqPromise)
200
+ const seq = await seqPromise
201
+ await this.checkSocket(assemblyName, refName, internetAccount)
202
+ this.inFlight.delete(inFlightKey)
203
+ return { seq, refSeq }
204
+ }
205
+
206
+ private async getSeqFromServer(
207
+ internetAccount: ApolloInternetAccount,
208
+ uri: string,
209
+ apolloRefSeq: ApolloRefSeqI,
210
+ start: number,
211
+ stop: number,
212
+ ) {
213
+ const response = await this.fetch(internetAccount, uri)
214
+ if (!response.ok) {
215
+ let errorMessage
216
+ try {
217
+ errorMessage = await response.text()
218
+ } catch {
219
+ errorMessage = ''
220
+ }
221
+ throw new Error(
222
+ `getSequence failed: ${response.status} (${response.statusText})${
223
+ errorMessage ? ` (${errorMessage})` : ''
224
+ }`,
225
+ )
226
+ }
227
+ const seq = await response.text()
228
+ apolloRefSeq.addSequence({ sequence: seq, start, stop })
229
+ return seq
230
+ }
231
+
232
+ async getRegions(assemblyName: string): Promise<Region[]> {
233
+ const { assemblyManager } = getSession(this.clientStore)
234
+ const assembly = assemblyManager.get(assemblyName)
235
+ if (!assembly) {
236
+ throw new Error(`Could not find assembly with name "${assemblyName}"`)
237
+ }
238
+ const internetAccount = this.clientStore.getInternetAccount(
239
+ assemblyName,
240
+ ) as ApolloInternetAccount
241
+ const { baseURL } = internetAccount
242
+ const url = new URL('refSeqs', baseURL)
243
+ const searchParams = new URLSearchParams({ assembly: assemblyName })
244
+ url.search = searchParams.toString()
245
+ const uri = url.toString()
246
+
247
+ const response = await this.fetch(internetAccount, uri)
248
+ if (!response.ok) {
249
+ let errorMessage
250
+ try {
251
+ errorMessage = await response.text()
252
+ } catch {
253
+ errorMessage = ''
254
+ }
255
+ throw new Error(
256
+ `getRegions failed: ${response.status} (${response.statusText})${
257
+ errorMessage ? ` (${errorMessage})` : ''
258
+ }`,
259
+ )
260
+ }
261
+ const refSeqs = await response.json()
262
+ return refSeqs.map((refSeq: { name: string; length: number }) => ({
263
+ refName: refSeq.name,
264
+ start: 0,
265
+ end: refSeq.length,
266
+ }))
267
+ }
268
+
269
+ getAssemblies(internetAccountId?: string) {
270
+ const { assemblyManager } = getSession(this.clientStore)
271
+ return assemblyManager.assemblies.filter((assembly) => {
272
+ const sequenceMetadata = getConf(assembly, ['sequence', 'metadata']) as
273
+ | { apollo: boolean; internetAccountConfigId?: string }
274
+ | undefined
275
+ if (
276
+ sequenceMetadata &&
277
+ sequenceMetadata.apollo &&
278
+ sequenceMetadata.internetAccountConfigId
279
+ ) {
280
+ if (internetAccountId) {
281
+ return sequenceMetadata.internetAccountConfigId === internetAccountId
282
+ }
283
+ return true
284
+ }
285
+ return false
286
+ })
287
+ }
288
+
289
+ async submitChange(
290
+ change: Change | AssemblySpecificChange,
291
+ opts: SubmitOpts = {},
292
+ ) {
293
+ const { internetAccountId } = opts
294
+ const internetAccount = this.clientStore.getInternetAccount(
295
+ 'assembly' in change ? change.assembly : undefined,
296
+ internetAccountId,
297
+ ) as ApolloInternetAccount
298
+ const { baseURL } = internetAccount
299
+ const url = new URL('changes', baseURL).href
300
+ const response = await this.fetch(internetAccount, url, {
301
+ method: 'POST',
302
+ body: JSON.stringify(change.toJSON()),
303
+ headers: { 'Content-Type': 'application/json' },
304
+ })
305
+ if (!response.ok) {
306
+ const errorMessage = await createFetchErrorMessage(
307
+ response,
308
+ 'submitChange failed',
309
+ )
310
+ throw new Error(errorMessage)
311
+ }
312
+ const results = new ValidationResultSet()
313
+ if (!response.ok) {
314
+ results.ok = false
315
+ }
316
+ return results
317
+ }
318
+ }
@@ -0,0 +1,170 @@
1
+ import gff, { GFF3Item } from '@gmod/gff'
2
+ import { getConf } from '@jbrowse/core/configuration'
3
+ import { Region, getSession } from '@jbrowse/core/util'
4
+ import {
5
+ AssemblySpecificChange,
6
+ Change,
7
+ isAssemblySpecificChange,
8
+ } from 'apollo-common'
9
+ import { AnnotationFeatureSnapshot, CheckResultSnapshot } from 'apollo-mst'
10
+ import { ValidationResultSet, makeGFF3Feature } from 'apollo-shared'
11
+ import { getSnapshot } from 'mobx-state-tree'
12
+
13
+ import { checkFeatures, loadAssemblyIntoClient } from '../util'
14
+ import { BackendDriver } from './BackendDriver'
15
+
16
+ function splitStringIntoChunks(input: string, chunkSize: number): string[] {
17
+ const chunks: string[] = []
18
+ for (let i = 0; i < input.length; i += chunkSize) {
19
+ const chunk = input.slice(i, i + chunkSize)
20
+ chunks.push(chunk)
21
+ }
22
+ return chunks
23
+ }
24
+
25
+ export class DesktopFileDriver extends BackendDriver {
26
+ async loadAssembly(assemblyName: string) {
27
+ const { assemblyManager } = getSession(this.clientStore)
28
+ const assembly = assemblyManager.get(assemblyName)
29
+ if (!assembly) {
30
+ throw new Error(`Assembly ${assemblyName} not found`)
31
+ }
32
+ const { file } = getConf(assembly, ['sequence', 'metadata']) as {
33
+ file: string
34
+ }
35
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
36
+ const fs = require('node:fs') as typeof import('fs')
37
+ const fileContents = await fs.promises.readFile(file, 'utf8')
38
+ return loadAssemblyIntoClient(assemblyName, fileContents, this.clientStore)
39
+ }
40
+
41
+ async getAssembly(assemblyName: string) {
42
+ let assembly = this.clientStore.assemblies.get(assemblyName)
43
+ if (!assembly) {
44
+ assembly = await this.loadAssembly(assemblyName)
45
+ }
46
+ return assembly
47
+ }
48
+
49
+ async getFeatures(
50
+ region: Region,
51
+ ): Promise<[AnnotationFeatureSnapshot[], CheckResultSnapshot[]]> {
52
+ await this.getAssembly(region.assemblyName)
53
+ return [[], []]
54
+ }
55
+
56
+ async getSequence(region: Region) {
57
+ const { assemblyName, end, refName, start } = region
58
+ const assembly = await this.getAssembly(assemblyName)
59
+ const refSeq = assembly.refSeqs.get(refName)
60
+ if (!refSeq) {
61
+ throw new Error(`refSeq ${refName} not found in client data store`)
62
+ }
63
+ const seq = refSeq.getSequence(start, end)
64
+ return { seq, refSeq: refName }
65
+ }
66
+
67
+ async getRegions(assemblyName: string): Promise<Region[]> {
68
+ const assembly = await this.getAssembly(assemblyName)
69
+ const regions: Region[] = []
70
+ for (const [, refSeq] of assembly.refSeqs) {
71
+ regions.push({
72
+ assemblyName,
73
+ refName: refSeq.name,
74
+ start: refSeq.sequence[0].start,
75
+ end: refSeq.sequence[0].stop,
76
+ })
77
+ }
78
+ return regions
79
+ }
80
+
81
+ getAssemblies() {
82
+ const { assemblyManager } = getSession(this.clientStore)
83
+ return assemblyManager.assemblies.filter((assembly) => {
84
+ const sequenceMetadata = getConf(assembly, ['sequence', 'metadata']) as
85
+ | { apollo: boolean; internetAccountConfigId?: string; file?: string }
86
+ | undefined
87
+ return Boolean(
88
+ sequenceMetadata &&
89
+ sequenceMetadata.apollo &&
90
+ !sequenceMetadata.internetAccountConfigId &&
91
+ sequenceMetadata.file,
92
+ )
93
+ })
94
+ }
95
+
96
+ async submitChange(change: Change | AssemblySpecificChange) {
97
+ if (!isAssemblySpecificChange(change)) {
98
+ throw new Error(
99
+ `Cannot use this type of change with local file: "${change.typeName}"`,
100
+ )
101
+ }
102
+ const { assemblyManager } = getSession(this.clientStore)
103
+ const assembly = assemblyManager.get(change.assembly)
104
+ if (!assembly) {
105
+ throw new Error(`Could not find assembly with name "${change.assembly}"`)
106
+ }
107
+ const { file } = getConf(assembly, ['sequence', 'metadata']) as {
108
+ file: string
109
+ }
110
+ const clientAssembly = this.clientStore.assemblies.get(change.assembly)
111
+ if (!clientAssembly) {
112
+ throw new Error(
113
+ `Could not find assembly in client with name "${change.assembly}"`,
114
+ )
115
+ }
116
+ const refSeqs = new Set(...clientAssembly.refSeqs.keys())
117
+ const { checkResults } = this.clientStore
118
+ for (const checkResult of checkResults.values()) {
119
+ if (refSeqs.has(checkResult.refSeq)) {
120
+ checkResults.delete(checkResult._id)
121
+ }
122
+ }
123
+ const newCheckResults = await checkFeatures(clientAssembly)
124
+ this.clientStore.addCheckResults(newCheckResults)
125
+ const gff3Items: GFF3Item[] = [{ directive: 'gff-version', value: '3' }]
126
+ for (const [, refSeq] of clientAssembly.refSeqs) {
127
+ gff3Items.push({
128
+ directive: 'sequence-region',
129
+ value: `${refSeq.name} 1 ${refSeq.sequence[0].stop}`,
130
+ })
131
+ }
132
+ for (const comment of clientAssembly.comments) {
133
+ gff3Items.push({ comment })
134
+ }
135
+ for (const [, refSeq] of clientAssembly.refSeqs) {
136
+ const { features } = refSeq
137
+ for (const [, feature] of features) {
138
+ gff3Items.push(makeGFF3Feature(getSnapshot(feature)))
139
+ }
140
+ }
141
+ for (const [, refSeq] of clientAssembly.refSeqs) {
142
+ const [sequence] = refSeq.sequence
143
+ const formattedSequence = splitStringIntoChunks(
144
+ sequence.sequence,
145
+ 80,
146
+ ).join('\n')
147
+ gff3Items.push({
148
+ id: refSeq.name,
149
+ description: refSeq.description,
150
+ sequence: formattedSequence,
151
+ })
152
+ }
153
+
154
+ const gff3Contents = gff.formatSync(gff3Items)
155
+
156
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
157
+ const fs = require('node:fs') as typeof import('fs')
158
+ await fs.promises.writeFile(file, gff3Contents, 'utf8')
159
+
160
+ const results = new ValidationResultSet()
161
+ return results
162
+ }
163
+
164
+ async searchFeatures(
165
+ _term: string,
166
+ _assemblies: string[],
167
+ ): Promise<AnnotationFeatureSnapshot[]> {
168
+ return []
169
+ }
170
+ }
@@ -0,0 +1,76 @@
1
+ import { getConf } from '@jbrowse/core/configuration'
2
+ import { Region, getSession } from '@jbrowse/core/util'
3
+ import { AssemblySpecificChange, Change } from 'apollo-common'
4
+ import { AnnotationFeatureSnapshot, CheckResultSnapshot } from 'apollo-mst'
5
+ import { ValidationResultSet } from 'apollo-shared'
6
+
7
+ import { SubmitOpts } from '../ChangeManager'
8
+ import { BackendDriver } from './BackendDriver'
9
+
10
+ export class InMemoryFileDriver extends BackendDriver {
11
+ async getFeatures(): Promise<
12
+ [AnnotationFeatureSnapshot[], CheckResultSnapshot[]]
13
+ > {
14
+ return [[], []]
15
+ }
16
+
17
+ async getSequence(region: Region) {
18
+ const { assemblyName, end, refName, start } = region
19
+ const assembly = this.clientStore.assemblies.get(assemblyName)
20
+ if (!assembly) {
21
+ return { seq: '', refSeq: refName }
22
+ }
23
+ const refSeq = assembly.refSeqs.get(refName)
24
+ if (!refSeq) {
25
+ return { seq: '', refSeq: refName }
26
+ }
27
+ const seq = refSeq.getSequence(start, end)
28
+ return { seq, refSeq: refName }
29
+ }
30
+
31
+ async getRegions(assemblyName: string): Promise<Region[]> {
32
+ const assembly = this.clientStore.assemblies.get(assemblyName)
33
+ if (!assembly) {
34
+ return []
35
+ }
36
+ const regions: Region[] = []
37
+ for (const [, refSeq] of assembly.refSeqs) {
38
+ regions.push({
39
+ assemblyName,
40
+ refName: refSeq.name,
41
+ start: refSeq.sequence[0].start,
42
+ end: refSeq.sequence[0].stop,
43
+ })
44
+ }
45
+ return regions
46
+ }
47
+
48
+ getAssemblies() {
49
+ const { assemblyManager } = getSession(this.clientStore)
50
+ return assemblyManager.assemblies.filter((assembly) => {
51
+ const sequenceMetadata = getConf(assembly, ['sequence', 'metadata']) as
52
+ | { apollo: boolean; internetAccountConfigId?: string; file?: string }
53
+ | undefined
54
+ return Boolean(
55
+ sequenceMetadata &&
56
+ sequenceMetadata.apollo &&
57
+ !sequenceMetadata.file &&
58
+ !sequenceMetadata.internetAccountConfigId,
59
+ )
60
+ })
61
+ }
62
+
63
+ async submitChange(
64
+ _change: Change | AssemblySpecificChange,
65
+ _opts: SubmitOpts = {},
66
+ ) {
67
+ return new ValidationResultSet()
68
+ }
69
+
70
+ async searchFeatures(
71
+ _term: string,
72
+ _assemblies: string[],
73
+ ): Promise<AnnotationFeatureSnapshot[]> {
74
+ return []
75
+ }
76
+ }