@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.9

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 (86) hide show
  1. package/dist/index.esm.js +11212 -10483
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +7726 -9014
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +18 -18
  12. package/src/ApolloInternetAccount/model.ts +123 -70
  13. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
  14. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
  15. package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
  16. package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
  17. package/src/ChangeManager.ts +36 -14
  18. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  19. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
  20. package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
  21. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  22. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
  23. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
  24. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
  25. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  26. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
  27. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  28. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  29. package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
  30. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  31. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  32. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  33. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  34. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  36. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  37. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
  38. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
  39. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  40. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  41. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  42. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
  43. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
  44. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
  45. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  46. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  47. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  48. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  49. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  50. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  51. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  52. package/src/components/AddAssembly.tsx +34 -38
  53. package/src/components/AddAssemblyAliases.tsx +1 -1
  54. package/src/components/AddChildFeature.tsx +5 -2
  55. package/src/components/AddFeature.tsx +30 -21
  56. package/src/components/AddRefSeqAliases.tsx +64 -50
  57. package/src/components/CopyFeature.tsx +4 -2
  58. package/src/components/CreateApolloAnnotation.tsx +22 -9
  59. package/src/components/DeleteAssembly.tsx +3 -10
  60. package/src/components/DownloadGFF3.tsx +2 -2
  61. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  62. package/src/components/FilterFeatures.tsx +7 -7
  63. package/src/components/FilterTranscripts.tsx +6 -6
  64. package/src/components/ImportFeatures.tsx +1 -1
  65. package/src/components/ManageChecks.tsx +3 -10
  66. package/src/components/ManageUsers.tsx +23 -22
  67. package/src/components/MergeTranscripts.tsx +12 -15
  68. package/src/components/OntologyTermAutocomplete.tsx +1 -8
  69. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  70. package/src/components/OpenLocalFile.tsx +11 -7
  71. package/src/components/ViewChangeLog.tsx +25 -50
  72. package/src/components/ViewCheckResults.tsx +2 -8
  73. package/src/components/index.ts +1 -0
  74. package/src/config.ts +6 -0
  75. package/src/index.ts +53 -115
  76. package/src/makeDisplayComponent.tsx +9 -14
  77. package/src/menus/index.ts +1 -0
  78. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  79. package/src/menus/topLevelMenuAdmin.ts +154 -0
  80. package/src/session/ClientDataStore.ts +32 -14
  81. package/src/session/session.ts +159 -121
  82. package/src/util/annotationFeatureUtils.ts +15 -21
  83. package/src/util/displayUtils.ts +149 -0
  84. package/src/util/glyphUtils.ts +329 -0
  85. package/src/util/loadAssemblyIntoClient.ts +3 -2
  86. package/src/util/mouseEventsUtils.ts +32 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo-annotation/jbrowse-plugin-apollo",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
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,15 +48,15 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@apollo-annotation/common": "^0.3.7",
52
- "@apollo-annotation/mst": "^0.3.7",
53
- "@apollo-annotation/shared": "^0.3.7",
51
+ "@apollo-annotation/common": "^0.3.9",
52
+ "@apollo-annotation/mst": "^0.3.9",
53
+ "@apollo-annotation/shared": "^0.3.9",
54
54
  "@emotion/react": "^11.10.6",
55
55
  "@emotion/styled": "^11.10.6",
56
- "@gmod/gff": "1.2.0",
57
- "@jbrowse/plugin-authentication": "^3.0.1",
58
- "@jbrowse/plugin-linear-genome-view": "^3.0.1",
59
- "@mui/icons-material": "^5.8.4",
56
+ "@gmod/gff": "^2.0.0",
57
+ "@jbrowse/plugin-authentication": "^3.6.5",
58
+ "@jbrowse/plugin-linear-genome-view": "^3.6.5",
59
+ "@mui/icons-material": "^6.5.0",
60
60
  "@types/jsonpath": "^0.2.0",
61
61
  "autosuggest-highlight": "^3.3.4",
62
62
  "bson-objectid": "^2.0.4",
@@ -71,15 +71,15 @@
71
71
  "tslib": "^2.3.1"
72
72
  },
73
73
  "devDependencies": {
74
- "@jbrowse/cli": "^3.0.1",
75
- "@jbrowse/core": "^3.0.1",
76
- "@jbrowse/development-tools": "^2.1.1",
74
+ "@jbrowse/cli": "^3.6.5",
75
+ "@jbrowse/core": "^3.6.5",
76
+ "@jbrowse/development-tools": "^2.2.1",
77
77
  "@jest/globals": "^29.0.3",
78
- "@mui/material": "^6.0.0",
79
- "@mui/x-data-grid": "^7.0.0",
78
+ "@mui/material": "^7.0.0",
79
+ "@mui/x-data-grid": "^8.0.0",
80
80
  "@types/autosuggest-highlight": "^3",
81
81
  "@types/file-saver": "^2",
82
- "@types/node": "^18.14.2",
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",
@@ -99,7 +99,7 @@
99
99
  "react": "^18.2.0",
100
100
  "react-dom": "^18.2.0",
101
101
  "rimraf": "^3.0.2",
102
- "rollup": "^2.59.0",
102
+ "rollup": "^2.79.2",
103
103
  "rxjs": "^7.4.0",
104
104
  "serve": "^14.0.1",
105
105
  "shx": "^0.3.3",
@@ -111,9 +111,9 @@
111
111
  },
112
112
  "peerDependencies": {
113
113
  "@jbrowse/core": "^3.0.1",
114
- "@mui/material": "^6.0.0",
114
+ "@mui/material": "^7.0.0",
115
115
  "mobx": "^6.6.1",
116
- "mobx-react": "^7.2.1",
116
+ "mobx-react": "^9.0.0",
117
117
  "mobx-state-tree": "^5.4.0",
118
118
  "prop-types": "^15.8.1",
119
119
  "react": "^18.2.0",
@@ -23,14 +23,14 @@ import {
23
23
  isElectron,
24
24
  } from '@jbrowse/core/util'
25
25
  import { autorun } from 'mobx'
26
- import { type Instance, flow, getRoot, types } from 'mobx-state-tree'
26
+ import { type Instance, flow, getRoot, isAlive, types } from 'mobx-state-tree'
27
27
  import { io } from 'socket.io-client'
28
28
 
29
+ import { addTopLevelAdminMenus } from '../menus/topLevelMenuAdmin'
29
30
  import { type Collaborator } from '../session'
30
31
  import { type ApolloRootModel } from '../types'
31
32
  import { createFetchErrorMessage } from '../util'
32
33
 
33
- import { addMenuItems } from './addMenuItems'
34
34
  import { AuthTypeSelector } from './components/AuthTypeSelector'
35
35
  import { type ApolloInternetAccountConfigModel } from './configSchema'
36
36
 
@@ -61,33 +61,23 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
61
61
  }))
62
62
  .volatile(() => ({
63
63
  role: undefined as Role | undefined,
64
+ controller: new AbortController(),
65
+ }))
66
+
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
+ },
64
80
  }))
65
- .actions((self) => {
66
- let roleNotificationSent = false
67
- return {
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 (!role && !roleNotificationSent) {
77
- const { session } = getRoot<ApolloRootModel>(self)
78
- ;(session as unknown as AbstractSessionModel).notify(
79
- 'You have registered as a user but have not been given access. Ask your administrator to enable access for your account.',
80
- 'warning',
81
- )
82
- // notify
83
- roleNotificationSent = true
84
- }
85
- if (self.role !== role) {
86
- self.role = role
87
- }
88
- },
89
- }
90
- })
91
81
  .actions((self) => {
92
82
  let listener: (event: MessageEvent) => void
93
83
  return {
@@ -205,7 +195,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
205
195
  const searchParams = new URLSearchParams({ type: authType })
206
196
  url.search = searchParams.toString()
207
197
  const uri = url.toString()
208
- const response = await fetch(uri)
198
+ const response = await fetch(uri, { signal: self.controller.signal })
209
199
  if (!response.ok) {
210
200
  const errorMessage = await createFetchErrorMessage(
211
201
  response,
@@ -239,7 +229,18 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
239
229
  uri,
240
230
  })
241
231
 
242
- const response = yield apolloFetch(uri, { method: 'GET' })
232
+ let response: Response
233
+ try {
234
+ response = yield apolloFetch(uri, {
235
+ method: 'GET',
236
+ signal: self.controller.signal,
237
+ })
238
+ } catch (error) {
239
+ if (!self.controller.signal.aborted) {
240
+ console.error(error)
241
+ }
242
+ return
243
+ }
243
244
  if (!response.ok) {
244
245
  const errorMessage = yield createFetchErrorMessage(
245
246
  response,
@@ -274,7 +275,18 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
274
275
  uri,
275
276
  })
276
277
 
277
- const response = yield apolloFetch(uri, { method: 'GET' })
278
+ let response: Response
279
+ try {
280
+ response = yield apolloFetch(uri, {
281
+ method: 'GET',
282
+ signal: self.controller.signal,
283
+ })
284
+ } catch (error) {
285
+ if (!self.controller.signal.aborted) {
286
+ console.error(error)
287
+ }
288
+ return
289
+ }
278
290
  if (!response.ok) {
279
291
  console.error(
280
292
  `Error when fetching the last updates to recover socket connection — ${response.status}`,
@@ -300,11 +312,13 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
300
312
  if (!token) {
301
313
  throw new Error('No Token found')
302
314
  }
315
+ const user = getDecodedToken(token)
316
+ const localSessionId = makeUserSessionId(user)
303
317
  const { socket } = self
304
318
  const { addCheckResult, changeManager, deleteCheckResult } =
305
319
  session.apolloDataStore
306
- socket.on('connect', async () => {
307
- await self.getMissingChanges()
320
+ socket.on('connect', () => {
321
+ void self.getMissingChanges()
308
322
  })
309
323
  socket.on('connect_error', (error) => {
310
324
  console.error(error)
@@ -324,7 +338,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
324
338
  'LastChangeSequence',
325
339
  String(message.changeSequence),
326
340
  )
327
- if (message.userSessionId === token) {
341
+ if (message.userSessionId === localSessionId) {
328
342
  return // we did this change, no need to apply it again
329
343
  }
330
344
  const change = Change.fromJSON(message.changeInfo)
@@ -332,8 +346,6 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
332
346
  })
333
347
  socket.on('USER_LOCATION', (message: UserLocationMessage) => {
334
348
  const { channel, locations, userName, userSessionId } = message
335
- const user = getDecodedToken(token)
336
- const localSessionId = makeUserSessionId(user)
337
349
  if (channel === 'USER_LOCATION' && userSessionId !== localSessionId) {
338
350
  const collaborator: Collaborator = {
339
351
  name: userName,
@@ -356,7 +368,10 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
356
368
  }))
357
369
  .actions((self) => {
358
370
  async function postUserLocation(userLoc: UserLocation[]) {
359
- const { baseURL } = self
371
+ if (!isAlive(self) || self.role === 'none') {
372
+ return
373
+ }
374
+ const { baseURL, controller } = self
360
375
  const url = new URL('users/userLocation', baseURL).href
361
376
  const userLocation = new URLSearchParams(JSON.stringify(userLoc))
362
377
 
@@ -368,6 +383,7 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
368
383
  const response = await apolloFetch(url, {
369
384
  method: 'POST',
370
385
  body: userLocation,
386
+ signal: controller.signal,
371
387
  })
372
388
  if (!response.ok) {
373
389
  throw new Error('ignore') // ignore message, will get caught by "catch"
@@ -390,42 +406,73 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
390
406
  }
391
407
  return { postUserLocation: debouncePostUserLocation(postUserLocation) }
392
408
  })
393
- .actions((self) => ({
394
- initialize: flow(function* initialize(role: Role) {
395
- if (role === 'admin') {
396
- const rootModel = getRoot(self)
397
- if (isAbstractMenuManager(rootModel)) {
398
- addMenuItems(rootModel)
399
- }
400
- }
401
- // Get and set server last change sequence into session storage
402
- yield self.updateLastChangeSequenceNumber()
403
- // Open socket listeners
404
- self.addSocketListeners()
405
- // request user locations
406
- const { baseURL } = self
407
- const uri = new URL('users/locations', baseURL).href
408
- const apolloFetch = self.getFetcher({
409
- locationType: 'UriLocation',
410
- uri,
411
- })
412
- yield apolloFetch(uri, { method: 'GET' })
413
- 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') {
414
417
  self.postUserLocation([])
415
- })
416
- document.addEventListener('visibilitychange', () => {
417
- // fires when user switches tabs, apps, goes to homescreen, etc.
418
- if (document.visibilityState === 'hidden') {
419
- self.postUserLocation([])
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
420
437
  }
421
- // fires when app transitions from prerender, user returns to the app / tab.
422
- if (document.visibilityState === 'visible') {
423
- const { session } = getRoot<ApolloRootModel>(self)
424
- session.broadcastLocations()
438
+ if (role === 'admin') {
439
+ const rootModel = getRoot(self)
440
+ if (isAbstractMenuManager(rootModel)) {
441
+ addTopLevelAdminMenus(rootModel)
442
+ }
425
443
  }
426
- })
427
- }),
428
- }))
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
+ })
429
476
  .actions((self) => ({
430
477
  afterAttach() {
431
478
  self.setRole()
@@ -449,6 +496,12 @@ const stateModelFactory = (configSchema: ApolloInternetAccountConfigModel) => {
449
496
  { name: 'ApolloInternetAccount' },
450
497
  )
451
498
  },
499
+ beforeDestroy() {
500
+ self.removeBeforeUnloadListener()
501
+ self.removeVisibilityChangeListener()
502
+ self.controller.abort('internet account beforeDestroy')
503
+ self.socket.close()
504
+ },
452
505
  }))
453
506
  }
454
507
 
@@ -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
- assemblyId,
55
- ) as BackendDriver
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
- assemblyId,
67
- ) as BackendDriver
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
- assemblyId,
129
- ) as BackendDriver
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
  )
@@ -4,7 +4,12 @@
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
5
5
  /* eslint-disable @typescript-eslint/no-unsafe-call */
6
6
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
7
- import { type AssemblySpecificChange, Change } from '@apollo-annotation/common'
7
+ import {
8
+ type AssemblySpecificChange,
9
+ Change,
10
+ type FeatureChange,
11
+ isFeatureChange,
12
+ } from '@apollo-annotation/common'
8
13
  import {
9
14
  type AnnotationFeatureSnapshot,
10
15
  type ApolloRefSeqI,
@@ -33,6 +38,14 @@ export interface ApolloRefSeqResponse {
33
38
  assembly: string
34
39
  }
35
40
 
41
+ interface RefSeq {
42
+ refName: string
43
+ id: string
44
+ aliases: string[]
45
+ }
46
+
47
+ type RefSeqMap = Map<string, RefSeq>
48
+
36
49
  export interface ApolloInternetAccount extends BaseInternetAccountModel {
37
50
  baseURL: string
38
51
  socket: Socket
@@ -43,6 +56,8 @@ export interface ApolloInternetAccount extends BaseInternetAccountModel {
43
56
  export class CollaborationServerDriver extends BackendDriver {
44
57
  private inFlight = new Map<string, Promise<string>>()
45
58
 
59
+ private refSeqMaps = new Map<string, RefSeqMap>()
60
+
46
61
  private async fetch(
47
62
  internetAccount: ApolloInternetAccount,
48
63
  info: RequestInfo,
@@ -92,13 +107,12 @@ export class CollaborationServerDriver extends BackendDriver {
92
107
  if (!assembly) {
93
108
  throw new Error(`Could not find assembly with name "${assemblyName}"`)
94
109
  }
95
- const { ids } = getConf(assembly, ['sequence', 'metadata']) as {
96
- ids: Record<string, string>
97
- }
98
- const refSeq = ids[refName]
99
- if (!refSeq) {
110
+ const refSeqMap = await this.getRefSeqMapping(assemblyName)
111
+ const refSeqEntry = refSeqMap.get(refName)
112
+ if (!refSeqEntry) {
100
113
  throw new Error(`Could not find refSeq "${refName}"`)
101
114
  }
115
+ const refSeq = refSeqEntry.id
102
116
  const internetAccount = this.clientStore.getInternetAccount(
103
117
  assemblyName,
104
118
  ) as ApolloInternetAccount
@@ -151,12 +165,28 @@ export class CollaborationServerDriver extends BackendDriver {
151
165
  )
152
166
  if (message.userSessionId !== token && message.channel === channel) {
153
167
  const change = Change.fromJSON(message.changeInfo)
154
- await changeManager.submit(change, { submitToBackend: false })
168
+ if (isFeatureChange(change) && this.haveDataForChange(change)) {
169
+ await changeManager.submit(change, { submitToBackend: false })
170
+ }
155
171
  }
156
172
  })
157
173
  }
158
174
  }
159
175
 
176
+ private haveDataForChange(change: FeatureChange): boolean {
177
+ const { assembly, changedIds } = change
178
+ const apolloAssembly = this.clientStore.assemblies.get(assembly)
179
+ if (!apolloAssembly) {
180
+ return false
181
+ }
182
+ for (const changedId of changedIds) {
183
+ if (this.clientStore.getFeature(changedId)) {
184
+ return true
185
+ }
186
+ }
187
+ return false
188
+ }
189
+
160
190
  /**
161
191
  * Call backend endpoint to get sequence by criteria
162
192
  * @param region - Searchable region containing refSeq, start and end
@@ -171,13 +201,12 @@ export class CollaborationServerDriver extends BackendDriver {
171
201
  if (!assembly) {
172
202
  throw new Error(`Could not find assembly with name "${assemblyName}"`)
173
203
  }
174
- const { ids } = getConf(assembly, ['sequence', 'metadata']) as {
175
- ids: Record<string, string>
176
- }
177
- const refSeq = ids[refName]
178
- if (!refSeq) {
204
+ const refSeqMap = await this.getRefSeqMapping(assemblyName)
205
+ const refSeqEntry = refSeqMap.get(refName)
206
+ if (!refSeqEntry) {
179
207
  throw new Error(`Could not find refSeq "${refName}"`)
180
208
  }
209
+ const refSeq = refSeqEntry.id
181
210
  if (inFlightPromise) {
182
211
  const seq = await inFlightPromise
183
212
  return { seq, refSeq }
@@ -248,7 +277,11 @@ export class CollaborationServerDriver extends BackendDriver {
248
277
  return seq
249
278
  }
250
279
 
251
- async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
280
+ async getRefSeqMapping(assemblyName: string): Promise<RefSeqMap> {
281
+ const cachedRefSeqMap = this.refSeqMaps.get(assemblyName)
282
+ if (cachedRefSeqMap) {
283
+ return cachedRefSeqMap
284
+ }
252
285
  const { assemblyManager } = getSession(this.clientStore)
253
286
  const assembly = assemblyManager.get(assemblyName)
254
287
  if (!assembly) {
@@ -278,13 +311,32 @@ export class CollaborationServerDriver extends BackendDriver {
278
311
  )
279
312
  }
280
313
  const refSeqs = (await response.json()) as ApolloRefSeqResponse[]
281
- return refSeqs.map((refSeq) => {
282
- return {
283
- refName: refSeq.name,
284
- aliases: [refSeq._id, ...refSeq.aliases],
285
- uniqueId: `alias-${refSeq._id}`,
286
- }
287
- }) as RefNameAliases[]
314
+ const refSeqMap = new Map<string, RefSeq>(
315
+ refSeqs.map((refSeq) => [
316
+ refSeq.name,
317
+ { refName: refSeq.name, id: refSeq._id, aliases: refSeq.aliases },
318
+ ]),
319
+ )
320
+ this.refSeqMaps.set(assemblyName, refSeqMap)
321
+ return refSeqMap
322
+ }
323
+
324
+ async getRefNameAliases(assemblyName: string): Promise<RefNameAliases[]> {
325
+ const refSeqMap = await this.getRefSeqMapping(assemblyName)
326
+ return [...refSeqMap.values()].map((refSeq) => ({
327
+ refName: refSeq.refName,
328
+ aliases: [...new Set([refSeq.id, ...refSeq.aliases])],
329
+ uniqueId: `alias-${refSeq.id}`,
330
+ }))
331
+ }
332
+
333
+ async getRefSeqId(assemblyName: string, refName: string) {
334
+ const refSeqMap = await this.getRefSeqMapping(assemblyName)
335
+ if (!refSeqMap) {
336
+ return
337
+ }
338
+ const refSeq = refSeqMap.get(refName)
339
+ return refSeq?.id
288
340
  }
289
341
 
290
342
  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 gff, { type GFF3Item } from '@gmod/gff'
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 = gff.formatSync(gff3Items)
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')