@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,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
|
+
}
|