@apollo-annotation/jbrowse-plugin-apollo 0.3.8 → 0.3.10
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/dist/index.esm.js +10914 -10799
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +10979 -10865
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +8799 -11326
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +7 -7
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -1
- package/src/ApolloInternetAccount/model.ts +87 -65
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
- package/src/BackendDrivers/CollaborationServerDriver.ts +60 -23
- package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
- package/src/ChangeManager.ts +22 -5
- package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
- package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +68 -212
- package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +6 -102
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +33 -232
- package/src/LinearApolloDisplay/glyphs/util.ts +36 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +174 -0
- package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +200 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +62 -386
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +6 -0
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +122 -70
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +33 -2
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +101 -3
- package/src/components/AddAssembly.tsx +34 -38
- package/src/components/AddFeature.tsx +21 -18
- package/src/components/AddRefSeqAliases.tsx +56 -42
- package/src/components/CopyFeature.tsx +1 -1
- package/src/components/CreateApolloAnnotation.tsx +22 -10
- package/src/components/DeleteAssembly.tsx +2 -9
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +2 -9
- package/src/components/ManageUsers.tsx +23 -22
- package/src/components/OntologyTermAutocomplete.tsx +3 -10
- package/src/components/OntologyTermMultiSelect.tsx +2 -2
- package/src/components/ViewChangeLog.tsx +25 -50
- package/src/components/ViewCheckResults.tsx +1 -7
- package/src/config.ts +3 -3
- package/src/index.ts +17 -16
- package/src/makeDisplayComponent.tsx +9 -13
- package/src/session/ClientDataStore.ts +33 -15
- package/src/session/session.ts +23 -27
- package/src/util/displayUtils.ts +28 -0
- package/src/util/glyphUtils.ts +196 -1
- package/src/util/loadAssemblyIntoClient.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@apollo-annotation/jbrowse-plugin-apollo",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "Apollo plugin for JBrowse 2",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"start:watch": "JB_NPM=false NODE_ENV=development rollup --config --watch",
|
|
28
28
|
"start:server": "serve --no-request-logging --cors --listen 9000 --no-port-switching .",
|
|
29
29
|
"build": "yarn build:shared && yarn clean && rollup --config",
|
|
30
|
-
"browse": "serve --no-request-logging --listen 8999 --no-port-switching .jbrowse",
|
|
30
|
+
"browse": "serve --no-request-logging --listen 8999 --no-port-switching --symlinks .jbrowse",
|
|
31
31
|
"test": "jest",
|
|
32
32
|
"test:ci": "jest --coverage",
|
|
33
33
|
"start:collab-cypress": "yarn workspace @apollo-annotation/collaboration-server run cypress:start",
|
|
@@ -48,12 +48,12 @@
|
|
|
48
48
|
}
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@apollo-annotation/common": "^0.3.
|
|
52
|
-
"@apollo-annotation/mst": "^0.3.
|
|
53
|
-
"@apollo-annotation/shared": "^0.3.
|
|
51
|
+
"@apollo-annotation/common": "^0.3.10",
|
|
52
|
+
"@apollo-annotation/mst": "^0.3.10",
|
|
53
|
+
"@apollo-annotation/shared": "^0.3.10",
|
|
54
54
|
"@emotion/react": "^11.10.6",
|
|
55
55
|
"@emotion/styled": "^11.10.6",
|
|
56
|
-
"@gmod/gff": "
|
|
56
|
+
"@gmod/gff": "^2.0.0",
|
|
57
57
|
"@jbrowse/plugin-authentication": "^3.6.5",
|
|
58
58
|
"@jbrowse/plugin-linear-genome-view": "^3.6.5",
|
|
59
59
|
"@mui/icons-material": "^6.5.0",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"@mui/x-data-grid": "^8.0.0",
|
|
80
80
|
"@types/autosuggest-highlight": "^3",
|
|
81
81
|
"@types/file-saver": "^2",
|
|
82
|
-
"@types/node": "^
|
|
82
|
+
"@types/node": "^20.19.15",
|
|
83
83
|
"@types/prop-types": "^15",
|
|
84
84
|
"@types/react": "^18.3.4",
|
|
85
85
|
"@types/react-dom": "^18",
|
|
@@ -64,32 +64,20 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
|
|
|
64
64
|
controller: new AbortController(),
|
|
65
65
|
}))
|
|
66
66
|
|
|
67
|
-
.actions((self) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
'You have registered as a user but have not been given access. Ask your administrator to enable access for your account.',
|
|
82
|
-
'warning',
|
|
83
|
-
)
|
|
84
|
-
// notify
|
|
85
|
-
roleNotificationSent = true
|
|
86
|
-
}
|
|
87
|
-
if (self.role !== role) {
|
|
88
|
-
self.role = role
|
|
89
|
-
}
|
|
90
|
-
},
|
|
91
|
-
}
|
|
92
|
-
})
|
|
67
|
+
.actions((self) => ({
|
|
68
|
+
setRole() {
|
|
69
|
+
const token = self.retrieveToken()
|
|
70
|
+
if (!token) {
|
|
71
|
+
self.role = undefined
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
const dec = getDecodedToken(token)
|
|
75
|
+
const { role } = dec
|
|
76
|
+
if (self.role !== role) {
|
|
77
|
+
self.role = role
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
}))
|
|
93
81
|
.actions((self) => {
|
|
94
82
|
let listener: (event: MessageEvent) => void
|
|
95
83
|
return {
|
|
@@ -380,7 +368,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
|
|
|
380
368
|
}))
|
|
381
369
|
.actions((self) => {
|
|
382
370
|
async function postUserLocation(userLoc: UserLocation[]) {
|
|
383
|
-
if (!isAlive(self)) {
|
|
371
|
+
if (!isAlive(self) || self.role === 'none') {
|
|
384
372
|
return
|
|
385
373
|
}
|
|
386
374
|
const { baseURL, controller } = self
|
|
@@ -418,45 +406,73 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
|
|
|
418
406
|
}
|
|
419
407
|
return { postUserLocation: debouncePostUserLocation(postUserLocation) }
|
|
420
408
|
})
|
|
421
|
-
.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
// Get and set server last change sequence into session storage
|
|
430
|
-
yield self.updateLastChangeSequenceNumber()
|
|
431
|
-
// Open socket listeners
|
|
432
|
-
self.addSocketListeners()
|
|
433
|
-
// request user locations
|
|
434
|
-
const { baseURL } = self
|
|
435
|
-
const uri = new URL('users/locations', baseURL).href
|
|
436
|
-
const apolloFetch = self.getFetcher({
|
|
437
|
-
locationType: 'UriLocation',
|
|
438
|
-
uri,
|
|
439
|
-
})
|
|
440
|
-
yield apolloFetch(uri, {
|
|
441
|
-
method: 'GET',
|
|
442
|
-
signal: self.controller.signal,
|
|
443
|
-
})
|
|
444
|
-
window.addEventListener('beforeunload', () => {
|
|
409
|
+
.volatile(() => ({ roleNotificationSent: false }))
|
|
410
|
+
.actions((self) => {
|
|
411
|
+
function beforeUnloadListener() {
|
|
412
|
+
self.postUserLocation([])
|
|
413
|
+
}
|
|
414
|
+
function visibilityChangeListener() {
|
|
415
|
+
// fires when user switches tabs, apps, goes to homescreen, etc.
|
|
416
|
+
if (document.visibilityState === 'hidden') {
|
|
445
417
|
self.postUserLocation([])
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
418
|
+
}
|
|
419
|
+
// fires when app transitions from prerender, user returns to the app / tab.
|
|
420
|
+
if (document.visibilityState === 'visible') {
|
|
421
|
+
const { session } = getRoot<ApolloRootModel>(self)
|
|
422
|
+
session.broadcastLocations()
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
initialize: flow(function* initialize(role: Role) {
|
|
427
|
+
if (role === 'none') {
|
|
428
|
+
if (!self.roleNotificationSent) {
|
|
429
|
+
const { session } = getRoot<ApolloRootModel>(self)
|
|
430
|
+
;(session as unknown as AbstractSessionModel).notify(
|
|
431
|
+
'You have registered as an Apollo user but have not been given access. Ask your administrator to enable access for your account.',
|
|
432
|
+
'warning',
|
|
433
|
+
)
|
|
434
|
+
self.roleNotificationSent = true
|
|
435
|
+
}
|
|
436
|
+
return
|
|
451
437
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
438
|
+
if (role === 'admin') {
|
|
439
|
+
const rootModel = getRoot(self)
|
|
440
|
+
if (isAbstractMenuManager(rootModel)) {
|
|
441
|
+
addTopLevelAdminMenus(rootModel)
|
|
442
|
+
}
|
|
456
443
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
444
|
+
// Get and set server last change sequence into session storage
|
|
445
|
+
yield self.updateLastChangeSequenceNumber()
|
|
446
|
+
// Open socket listeners
|
|
447
|
+
self.addSocketListeners()
|
|
448
|
+
// request user locations
|
|
449
|
+
const { baseURL } = self
|
|
450
|
+
const uri = new URL('users/locations', baseURL).href
|
|
451
|
+
const apolloFetch = self.getFetcher({
|
|
452
|
+
locationType: 'UriLocation',
|
|
453
|
+
uri,
|
|
454
|
+
})
|
|
455
|
+
yield apolloFetch(uri, {
|
|
456
|
+
method: 'GET',
|
|
457
|
+
signal: self.controller.signal,
|
|
458
|
+
})
|
|
459
|
+
window.addEventListener('beforeunload', beforeUnloadListener)
|
|
460
|
+
document.addEventListener(
|
|
461
|
+
'visibilitychange',
|
|
462
|
+
visibilityChangeListener,
|
|
463
|
+
)
|
|
464
|
+
}),
|
|
465
|
+
removeBeforeUnloadListener() {
|
|
466
|
+
window.removeEventListener('beforeunload', beforeUnloadListener)
|
|
467
|
+
},
|
|
468
|
+
removeVisibilityChangeListener() {
|
|
469
|
+
document.removeEventListener(
|
|
470
|
+
'visibilitychange',
|
|
471
|
+
visibilityChangeListener,
|
|
472
|
+
)
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
})
|
|
460
476
|
.actions((self) => ({
|
|
461
477
|
afterAttach() {
|
|
462
478
|
self.setRole()
|
|
@@ -473,14 +489,20 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
|
|
|
473
489
|
return
|
|
474
490
|
}
|
|
475
491
|
if (self.role) {
|
|
476
|
-
|
|
477
|
-
|
|
492
|
+
try {
|
|
493
|
+
await self.initialize(self.role)
|
|
494
|
+
reaction.dispose()
|
|
495
|
+
} catch {
|
|
496
|
+
// if initialize fails, do nothing so the autorun runs again
|
|
497
|
+
}
|
|
478
498
|
}
|
|
479
499
|
},
|
|
480
500
|
{ name: 'ApolloInternetAccount' },
|
|
481
501
|
)
|
|
482
502
|
},
|
|
483
503
|
beforeDestroy() {
|
|
504
|
+
self.removeBeforeUnloadListener()
|
|
505
|
+
self.removeVisibilityChangeListener()
|
|
484
506
|
self.controller.abort('internet account beforeDestroy')
|
|
485
507
|
self.socket.close()
|
|
486
508
|
},
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
import type RpcServer from 'librpc-web-mod/dist/server'
|
|
7
7
|
import { nanoid } from 'nanoid'
|
|
8
8
|
|
|
9
|
-
import { type BackendDriver } from '../BackendDrivers'
|
|
10
9
|
import { type ApolloSessionModel } from '../session'
|
|
11
10
|
|
|
12
11
|
import { type RefNameAliases } from './../BackendDrivers/BackendDriver'
|
|
@@ -50,9 +49,10 @@ export default class RefNameAliasAdapter
|
|
|
50
49
|
if (!dataStore) {
|
|
51
50
|
throw new Error('No Apollo data store found')
|
|
52
51
|
}
|
|
53
|
-
const backendDriver = dataStore.getBackendDriver(
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
const backendDriver = dataStore.getBackendDriver(assemblyId)
|
|
53
|
+
if (!backendDriver) {
|
|
54
|
+
throw new Error('No backend driver found')
|
|
55
|
+
}
|
|
56
56
|
const refNameAliases = await backendDriver.getRefNameAliases(assemblyId)
|
|
57
57
|
return refNameAliases
|
|
58
58
|
}
|
|
@@ -10,7 +10,6 @@ import SimpleFeature, { type Feature } from '@jbrowse/core/util/simpleFeature'
|
|
|
10
10
|
import { type NoAssemblyRegion, type Region } from '@jbrowse/core/util/types'
|
|
11
11
|
import { nanoid } from 'nanoid'
|
|
12
12
|
|
|
13
|
-
import { type BackendDriver } from '../BackendDrivers'
|
|
14
13
|
import { type ApolloSessionModel } from '../session'
|
|
15
14
|
|
|
16
15
|
// declare global {
|
|
@@ -62,9 +61,10 @@ export class ApolloSequenceAdapter extends BaseSequenceAdapter {
|
|
|
62
61
|
if (!dataStore) {
|
|
63
62
|
throw new Error('No Apollo data store found')
|
|
64
63
|
}
|
|
65
|
-
const backendDriver = dataStore.getBackendDriver(
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
const backendDriver = dataStore.getBackendDriver(assemblyId)
|
|
65
|
+
if (!backendDriver) {
|
|
66
|
+
throw new Error('No backend driver found')
|
|
67
|
+
}
|
|
68
68
|
const regions = await backendDriver.getRegions(assemblyId)
|
|
69
69
|
this.regions = regions
|
|
70
70
|
return regions
|
|
@@ -124,9 +124,11 @@ export class ApolloSequenceAdapter extends BaseSequenceAdapter {
|
|
|
124
124
|
observer.error('No Apollo data store found')
|
|
125
125
|
return
|
|
126
126
|
}
|
|
127
|
-
const backendDriver = dataStore.getBackendDriver(
|
|
128
|
-
|
|
129
|
-
|
|
127
|
+
const backendDriver = dataStore.getBackendDriver(assemblyId)
|
|
128
|
+
if (!backendDriver) {
|
|
129
|
+
observer.error('No backend driver found')
|
|
130
|
+
return
|
|
131
|
+
}
|
|
130
132
|
const regions = await backendDriver.getRegions(
|
|
131
133
|
regionWithAssemblyName.assemblyName,
|
|
132
134
|
)
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
type ChangeMessage,
|
|
20
20
|
ValidationResultSet,
|
|
21
|
+
makeUserSessionId,
|
|
21
22
|
} from '@apollo-annotation/shared'
|
|
22
23
|
import { getConf } from '@jbrowse/core/configuration'
|
|
23
24
|
import { type BaseInternetAccountModel } from '@jbrowse/core/pluggableElementTypes'
|
|
@@ -38,6 +39,14 @@ export interface ApolloRefSeqResponse {
|
|
|
38
39
|
assembly: string
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
interface RefSeq {
|
|
43
|
+
refName: string
|
|
44
|
+
id: string
|
|
45
|
+
aliases: string[]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type RefSeqMap = Map<string, RefSeq>
|
|
49
|
+
|
|
41
50
|
export interface ApolloInternetAccount extends BaseInternetAccountModel {
|
|
42
51
|
baseURL: string
|
|
43
52
|
socket: Socket
|
|
@@ -48,6 +57,8 @@ export interface ApolloInternetAccount extends BaseInternetAccountModel {
|
|
|
48
57
|
export class CollaborationServerDriver extends BackendDriver {
|
|
49
58
|
private inFlight = new Map<string, Promise<string>>()
|
|
50
59
|
|
|
60
|
+
private refSeqMaps = new Map<string, RefSeqMap>()
|
|
61
|
+
|
|
51
62
|
private async fetch(
|
|
52
63
|
internetAccount: ApolloInternetAccount,
|
|
53
64
|
info: RequestInfo,
|
|
@@ -97,13 +108,12 @@ export class CollaborationServerDriver extends BackendDriver {
|
|
|
97
108
|
if (!assembly) {
|
|
98
109
|
throw new Error(`Could not find assembly with name "${assemblyName}"`)
|
|
99
110
|
}
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const refSeq = ids[refName]
|
|
104
|
-
if (!refSeq) {
|
|
111
|
+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
|
|
112
|
+
const refSeqEntry = refSeqMap.get(refName)
|
|
113
|
+
if (!refSeqEntry) {
|
|
105
114
|
throw new Error(`Could not find refSeq "${refName}"`)
|
|
106
115
|
}
|
|
116
|
+
const refSeq = refSeqEntry.id
|
|
107
117
|
const internetAccount = this.clientStore.getInternetAccount(
|
|
108
118
|
assemblyName,
|
|
109
119
|
) as ApolloInternetAccount
|
|
@@ -145,6 +155,10 @@ export class CollaborationServerDriver extends BackendDriver {
|
|
|
145
155
|
) {
|
|
146
156
|
const { socket } = internetAccount
|
|
147
157
|
const token = internetAccount.retrieveToken()
|
|
158
|
+
if (!token) {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
const localSessionId = makeUserSessionId(token)
|
|
148
162
|
const channel = `${assembly}-${refSeq}`
|
|
149
163
|
const changeManager = new ChangeManager(this.clientStore)
|
|
150
164
|
|
|
@@ -154,11 +168,12 @@ export class CollaborationServerDriver extends BackendDriver {
|
|
|
154
168
|
internetAccount.setLastChangeSequenceNumber(
|
|
155
169
|
Number(message.changeSequence),
|
|
156
170
|
)
|
|
157
|
-
if (message.userSessionId
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
171
|
+
if (message.userSessionId === localSessionId) {
|
|
172
|
+
return // we did this change, no need to apply it again
|
|
173
|
+
}
|
|
174
|
+
const change = Change.fromJSON(message.changeInfo)
|
|
175
|
+
if (isFeatureChange(change) && this.haveDataForChange(change)) {
|
|
176
|
+
await changeManager.submit(change, { submitToBackend: false })
|
|
162
177
|
}
|
|
163
178
|
})
|
|
164
179
|
}
|
|
@@ -192,13 +207,12 @@ export class CollaborationServerDriver extends BackendDriver {
|
|
|
192
207
|
if (!assembly) {
|
|
193
208
|
throw new Error(`Could not find assembly with name "${assemblyName}"`)
|
|
194
209
|
}
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const refSeq = ids[refName]
|
|
199
|
-
if (!refSeq) {
|
|
210
|
+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
|
|
211
|
+
const refSeqEntry = refSeqMap.get(refName)
|
|
212
|
+
if (!refSeqEntry) {
|
|
200
213
|
throw new Error(`Could not find refSeq "${refName}"`)
|
|
201
214
|
}
|
|
215
|
+
const refSeq = refSeqEntry.id
|
|
202
216
|
if (inFlightPromise) {
|
|
203
217
|
const seq = await inFlightPromise
|
|
204
218
|
return { seq, refSeq }
|
|
@@ -269,7 +283,11 @@ export class CollaborationServerDriver extends BackendDriver {
|
|
|
269
283
|
return seq
|
|
270
284
|
}
|
|
271
285
|
|
|
272
|
-
async
|
|
286
|
+
async getRefSeqMapping(assemblyName: string): Promise<RefSeqMap> {
|
|
287
|
+
const cachedRefSeqMap = this.refSeqMaps.get(assemblyName)
|
|
288
|
+
if (cachedRefSeqMap) {
|
|
289
|
+
return cachedRefSeqMap
|
|
290
|
+
}
|
|
273
291
|
const { assemblyManager } = getSession(this.clientStore)
|
|
274
292
|
const assembly = assemblyManager.get(assemblyName)
|
|
275
293
|
if (!assembly) {
|
|
@@ -299,13 +317,32 @@ export class CollaborationServerDriver extends BackendDriver {
|
|
|
299
317
|
)
|
|
300
318
|
}
|
|
301
319
|
const refSeqs = (await response.json()) as ApolloRefSeqResponse[]
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
320
|
+
const refSeqMap = new Map<string, RefSeq>(
|
|
321
|
+
refSeqs.map((refSeq) => [
|
|
322
|
+
refSeq.name,
|
|
323
|
+
{ refName: refSeq.name, id: refSeq._id, aliases: refSeq.aliases },
|
|
324
|
+
]),
|
|
325
|
+
)
|
|
326
|
+
this.refSeqMaps.set(assemblyName, refSeqMap)
|
|
327
|
+
return refSeqMap
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
|
|
331
|
+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
|
|
332
|
+
return [...refSeqMap.values()].map((refSeq) => ({
|
|
333
|
+
refName: refSeq.refName,
|
|
334
|
+
aliases: [...new Set([refSeq.id, ...refSeq.aliases])],
|
|
335
|
+
uniqueId: `alias-${refSeq.id}`,
|
|
336
|
+
}))
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async getRefSeqId(assemblyName: string, refName: string) {
|
|
340
|
+
const refSeqMap = await this.getRefSeqMapping(assemblyName)
|
|
341
|
+
if (!refSeqMap) {
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
const refSeq = refSeqMap.get(refName)
|
|
345
|
+
return refSeq?.id
|
|
309
346
|
}
|
|
310
347
|
|
|
311
348
|
async getRegions(assemblyName: string): Promise<Region[]> {
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
annotationFeatureToGFF3,
|
|
14
14
|
splitStringIntoChunks,
|
|
15
15
|
} from '@apollo-annotation/shared'
|
|
16
|
-
import
|
|
16
|
+
import { type GFF3Item, formatSync } from '@gmod/gff'
|
|
17
17
|
import { getConf } from '@jbrowse/core/configuration'
|
|
18
18
|
import { type Region, getSession } from '@jbrowse/core/util'
|
|
19
19
|
import { getSnapshot } from 'mobx-state-tree'
|
|
@@ -165,7 +165,7 @@ export class DesktopFileDriver extends BackendDriver {
|
|
|
165
165
|
})
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
const gff3Contents =
|
|
168
|
+
const gff3Contents = formatSync(gff3Items)
|
|
169
169
|
|
|
170
170
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/consistent-type-imports
|
|
171
171
|
const fs = require('node:fs') as typeof import('fs')
|
package/src/ChangeManager.ts
CHANGED
|
@@ -42,21 +42,31 @@ export class ChangeManager {
|
|
|
42
42
|
const session = getSession(this.dataStore)
|
|
43
43
|
const controller = new AbortController()
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
46
|
+
const { jobsManager, isLocked, changeInProgress, setChangeInProgress } =
|
|
47
|
+
getSession(this.dataStore) as unknown as ApolloSessionModel
|
|
48
48
|
|
|
49
49
|
if (isLocked) {
|
|
50
50
|
session.notify('Cannot submit changes in locked mode')
|
|
51
|
+
setChangeInProgress(false)
|
|
51
52
|
return
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
if (changeInProgress) {
|
|
56
|
+
session.notify(
|
|
57
|
+
'Could not submit change, there is another change still in progress',
|
|
58
|
+
)
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setChangeInProgress(true)
|
|
63
|
+
|
|
54
64
|
const job = {
|
|
55
65
|
name: change.typeName,
|
|
56
66
|
statusMessage: 'Pre-validating',
|
|
57
67
|
progressPct: 0,
|
|
58
68
|
cancelCallback: () => {
|
|
59
|
-
controller.abort()
|
|
69
|
+
controller.abort('ChangeManager')
|
|
60
70
|
},
|
|
61
71
|
}
|
|
62
72
|
|
|
@@ -71,6 +81,7 @@ export class ChangeManager {
|
|
|
71
81
|
jobsManager.abortJob(job.name, msg)
|
|
72
82
|
}
|
|
73
83
|
session.notify(msg, 'error')
|
|
84
|
+
setChangeInProgress(false)
|
|
74
85
|
return
|
|
75
86
|
}
|
|
76
87
|
|
|
@@ -86,6 +97,7 @@ export class ChangeManager {
|
|
|
86
97
|
`Error encountered in client: ${String(error)}. Data may be out of sync, please refresh the page`,
|
|
87
98
|
'error',
|
|
88
99
|
)
|
|
100
|
+
setChangeInProgress(false)
|
|
89
101
|
return
|
|
90
102
|
}
|
|
91
103
|
|
|
@@ -106,7 +118,9 @@ export class ChangeManager {
|
|
|
106
118
|
// submit to driver
|
|
107
119
|
const { collaborationServerDriver, getBackendDriver } = this.dataStore
|
|
108
120
|
const backendDriver = isAssemblySpecificChange(change)
|
|
109
|
-
?
|
|
121
|
+
? // for assembly-specific change, fall back in case it's an
|
|
122
|
+
// add-assembly change, since that won't exist in the driver yet
|
|
123
|
+
getBackendDriver(change.assembly) ?? collaborationServerDriver
|
|
110
124
|
: collaborationServerDriver
|
|
111
125
|
let backendResult: ValidationResultSet
|
|
112
126
|
try {
|
|
@@ -118,6 +132,7 @@ export class ChangeManager {
|
|
|
118
132
|
console.error(error)
|
|
119
133
|
session.notify(String(error), 'error')
|
|
120
134
|
await this.undo(change, false)
|
|
135
|
+
setChangeInProgress(false)
|
|
121
136
|
return
|
|
122
137
|
}
|
|
123
138
|
if (!backendResult.ok) {
|
|
@@ -127,6 +142,7 @@ export class ChangeManager {
|
|
|
127
142
|
}
|
|
128
143
|
session.notify(msg, 'error')
|
|
129
144
|
await this.undo(change, false)
|
|
145
|
+
setChangeInProgress(false)
|
|
130
146
|
return
|
|
131
147
|
}
|
|
132
148
|
if (change.notification) {
|
|
@@ -141,6 +157,7 @@ export class ChangeManager {
|
|
|
141
157
|
if (updateJobsManager) {
|
|
142
158
|
jobsManager.done(job)
|
|
143
159
|
}
|
|
160
|
+
setChangeInProgress(false)
|
|
144
161
|
}
|
|
145
162
|
|
|
146
163
|
async undo(change: Change, submitToBackend = true) {
|
|
@@ -66,7 +66,7 @@ export const BasicInformation = observer(function BasicInformation({
|
|
|
66
66
|
return changeManager.submit(change)
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function handleStartChange(newStart: number) {
|
|
69
|
+
function handleStartChange(newStart: number): boolean {
|
|
70
70
|
newStart--
|
|
71
71
|
const change = new LocationStartChange({
|
|
72
72
|
typeName: 'LocationStartChange',
|
|
@@ -76,10 +76,11 @@ export const BasicInformation = observer(function BasicInformation({
|
|
|
76
76
|
newStart,
|
|
77
77
|
assembly,
|
|
78
78
|
})
|
|
79
|
-
|
|
79
|
+
void changeManager.submit(change)
|
|
80
|
+
return true
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
function handleEndChange(newEnd: number) {
|
|
83
|
+
function handleEndChange(newEnd: number): boolean {
|
|
83
84
|
const change = new LocationEndChange({
|
|
84
85
|
typeName: 'LocationEndChange',
|
|
85
86
|
changedIds: [_id],
|
|
@@ -88,7 +89,8 @@ export const BasicInformation = observer(function BasicInformation({
|
|
|
88
89
|
newEnd,
|
|
89
90
|
assembly,
|
|
90
91
|
})
|
|
91
|
-
|
|
92
|
+
void changeManager.submit(change)
|
|
93
|
+
return true
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
async function fetchValidTerms(
|
|
@@ -15,7 +15,7 @@ interface NumberTextFieldProps
|
|
|
15
15
|
| 'error'
|
|
16
16
|
| 'helperText'
|
|
17
17
|
> {
|
|
18
|
-
onChangeCommitted(newValue: number):
|
|
18
|
+
onChangeCommitted(newValue: number): boolean
|
|
19
19
|
value: unknown
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -65,7 +65,10 @@ export const NumberTextField = observer(function NumberTextField({
|
|
|
65
65
|
if (Number.isNaN(valueAsNumber)) {
|
|
66
66
|
setValue(String(initialValue))
|
|
67
67
|
} else {
|
|
68
|
-
onChangeCommitted(valueAsNumber)
|
|
68
|
+
const success = onChangeCommitted(valueAsNumber)
|
|
69
|
+
if (!success) {
|
|
70
|
+
setValue(String(initialValue))
|
|
71
|
+
}
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
}}
|