@apollo-annotation/jbrowse-plugin-apollo 0.3.8 → 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 (48) hide show
  1. package/dist/index.esm.js +10932 -10932
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +10845 -10846
  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 +18619 -21342
  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 +7 -7
  12. package/src/ApolloInternetAccount/model.ts +81 -63
  13. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
  14. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
  15. package/src/BackendDrivers/CollaborationServerDriver.ts +49 -18
  16. package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
  17. package/src/ChangeManager.ts +3 -1
  18. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
  19. package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
  20. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +39 -203
  21. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
  22. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +6 -102
  23. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +31 -230
  24. package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
  25. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
  26. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
  27. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +62 -386
  28. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +6 -0
  29. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +122 -70
  30. package/src/components/AddAssembly.tsx +33 -37
  31. package/src/components/AddFeature.tsx +21 -18
  32. package/src/components/AddRefSeqAliases.tsx +56 -42
  33. package/src/components/CopyFeature.tsx +1 -1
  34. package/src/components/CreateApolloAnnotation.tsx +22 -10
  35. package/src/components/DeleteAssembly.tsx +2 -9
  36. package/src/components/DownloadGFF3.tsx +2 -2
  37. package/src/components/ManageChecks.tsx +2 -9
  38. package/src/components/ManageUsers.tsx +23 -22
  39. package/src/components/OntologyTermAutocomplete.tsx +1 -8
  40. package/src/components/ViewChangeLog.tsx +25 -50
  41. package/src/components/ViewCheckResults.tsx +1 -7
  42. package/src/config.ts +3 -3
  43. package/src/index.ts +17 -16
  44. package/src/makeDisplayComponent.tsx +9 -13
  45. package/src/session/ClientDataStore.ts +32 -14
  46. package/src/session/session.ts +19 -27
  47. package/src/util/glyphUtils.ts +178 -1
  48. package/src/util/loadAssemblyIntoClient.ts +3 -2
@@ -193,11 +193,8 @@ export function CreateApolloAnnotation({
193
193
  continue
194
194
  }
195
195
 
196
- // Destination feature should be of type gene amd should be on the same strand as the source feature
197
- if (
198
- featureTypeOntology?.isTypeOf(f.type, 'gene') &&
199
- f.strand === annotationFeature.strand
200
- ) {
196
+ // Destination feature should be of type gene
197
+ if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
201
198
  const featureSnapshot = getSnapshot(f)
202
199
  filteredFeatures.push(featureSnapshot)
203
200
  }
@@ -373,7 +370,7 @@ export function CreateApolloAnnotation({
373
370
  })
374
371
  }
375
372
 
376
- await submitChange(change)
373
+ await submitChange(change, annotationFeature._id)
377
374
  }
378
375
 
379
376
  const copyTranscriptsToDestinationGene = async (
@@ -384,6 +381,15 @@ export function CreateApolloAnnotation({
384
381
  }
385
382
  for (const transcriptId of Object.keys(transcripts)) {
386
383
  const transcript = transcripts[transcriptId]
384
+ transcript.strand = selectedDestinationFeature.strand
385
+
386
+ // update strand of transcript children if they exist
387
+ if (transcript.children) {
388
+ for (const childId of Object.keys(transcript.children)) {
389
+ transcript.children[childId].strand =
390
+ selectedDestinationFeature.strand
391
+ }
392
+ }
387
393
  const change = new AddFeatureChange({
388
394
  parentFeatureId: selectedDestinationFeature._id,
389
395
  changedIds: [selectedDestinationFeature._id],
@@ -391,7 +397,8 @@ export function CreateApolloAnnotation({
391
397
  assembly: assembly.name,
392
398
  addedFeature: transcript,
393
399
  })
394
- await submitChange(change)
400
+ // selects the last added transcript
401
+ await submitChange(change, transcriptId)
395
402
  }
396
403
  }
397
404
 
@@ -419,8 +426,7 @@ export function CreateApolloAnnotation({
419
426
  },
420
427
  },
421
428
  })
422
- await submitChange(change)
423
- apolloSessionModel.apolloSetSelectedFeature(newGeneId)
429
+ await submitChange(change, newGeneId)
424
430
  }
425
431
 
426
432
  const extendSelectedDestinationFeatureLocation = async (
@@ -462,8 +468,14 @@ export function CreateApolloAnnotation({
462
468
 
463
469
  const submitChange = async (
464
470
  change: AddFeatureChange | LocationStartChange | LocationEndChange,
471
+ selectedFeatureId?: string,
465
472
  ) => {
466
- await apolloSessionModel.apolloDataStore.changeManager.submit(change)
473
+ await apolloSessionModel.apolloDataStore.changeManager
474
+ .submit(change)
475
+ .then(() => {
476
+ // Selects the newly added/modified feature
477
+ apolloSessionModel.apolloSetSelectedFeature(selectedFeatureId)
478
+ })
467
479
  }
468
480
 
469
481
  const handleCreateNewGeneChange = (
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
2
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
3
  import { DeleteAssemblyChange } from '@apollo-annotation/shared'
4
- import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
5
4
  import {
6
5
  Button,
7
6
  Checkbox,
@@ -15,7 +14,7 @@ import {
15
14
  type SelectChangeEvent,
16
15
  } from '@mui/material'
17
16
  import { getRoot } from 'mobx-state-tree'
18
- import React, { useEffect, useState } from 'react'
17
+ import React, { useState } from 'react'
19
18
 
20
19
  import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
21
20
  import {
@@ -40,7 +39,6 @@ export function DeleteAssembly({
40
39
  session,
41
40
  }: DeleteAssemblyProps) {
42
41
  const { internetAccounts } = getRoot<ApolloRootModel>(session)
43
- const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
44
42
  const [errorMessage, setErrorMessage] = useState('')
45
43
  const [confirmDelete, setconfirmDelete] = useState(false)
46
44
  const [submitted, setSubmitted] = useState(false)
@@ -63,12 +61,7 @@ export function DeleteAssembly({
63
61
  }
64
62
 
65
63
  const assemblies = collaborationServerDriver.getAssemblies()
66
-
67
- useEffect(() => {
68
- if (assemblies.length > 0 && selectedAssembly === undefined) {
69
- setSelectedAssembly(assemblies[0])
70
- }
71
- }, [assemblies, selectedAssembly])
64
+ const [selectedAssembly, setSelectedAssembly] = useState(assemblies.at(0))
72
65
 
73
66
  function handleChangeInternetAccount(e: SelectChangeEvent) {
74
67
  setSubmitted(false)
@@ -5,7 +5,7 @@
5
5
  /* eslint-disable @typescript-eslint/no-misused-promises */
6
6
  import { type ApolloAssembly } from '@apollo-annotation/mst'
7
7
  import { annotationFeatureToGFF3 } from '@apollo-annotation/shared'
8
- import gff, { type GFF3Item } from '@gmod/gff'
8
+ import { type GFF3Item, formatSync } from '@gmod/gff'
9
9
  import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
10
10
  import { getConf } from '@jbrowse/core/configuration'
11
11
  import {
@@ -169,7 +169,7 @@ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
169
169
  const { refName, seq } = sequenceFeature
170
170
  gff3Items.push({ id: refName, description: '', sequence: seq })
171
171
  }
172
- const gff3 = gff.formatSync(gff3Items)
172
+ const gff3 = formatSync(gff3Items)
173
173
  const gff3Blob = new Blob([gff3], { type: 'text/plain;charset=utf-8' })
174
174
  saveAs(
175
175
  gff3Blob,
@@ -1,7 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
2
2
  /* eslint-disable @typescript-eslint/unbound-method */
3
3
  /* eslint-disable @typescript-eslint/no-misused-promises */
4
- import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
5
4
  import { type AbstractSessionModel } from '@jbrowse/core/util'
6
5
  import {
7
6
  Button,
@@ -52,7 +51,6 @@ interface CheckDocument {
52
51
 
53
52
  export function ManageChecks({ handleClose, session }: ManageChecksProps) {
54
53
  const { internetAccounts } = getRoot<ApolloRootModel>(session)
55
- const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
56
54
  const [errorMessage, setErrorMessage] = useState('')
57
55
  const [submitted, setSubmitted] = useState(false)
58
56
  const apolloInternetAccounts = internetAccounts.filter(
@@ -76,6 +74,7 @@ export function ManageChecks({ handleClose, session }: ManageChecksProps) {
76
74
  }
77
75
 
78
76
  const assemblies = collaborationServerDriver.getAssemblies()
77
+ const [selectedAssembly, setSelectedAssembly] = useState(assemblies.at(0))
79
78
 
80
79
  useEffect(() => {
81
80
  async function getChecks() {
@@ -99,19 +98,13 @@ export function ManageChecks({ handleClose, session }: ManageChecksProps) {
99
98
  })
100
99
  }, [selectedInternetAccount])
101
100
 
102
- useEffect(() => {
103
- if (assemblies.length > 0 && selectedAssembly === undefined) {
104
- setSelectedAssembly(assemblies[0])
105
- }
106
- }, [assemblies, selectedAssembly])
107
-
108
101
  useEffect(() => {
109
102
  async function getChecks() {
110
103
  if (!selectedAssembly) {
111
104
  return
112
105
  }
113
106
  const { baseURL, getFetcher } = selectedInternetAccount
114
- const uri = new URL(`/assemblies/${selectedAssembly.name}`, baseURL).href
107
+ const uri = new URL(`assemblies/${selectedAssembly.name}`, baseURL).href
115
108
  const apolloFetch = getFetcher({ locationType: 'UriLocation', uri })
116
109
  const response = await apolloFetch(uri, { method: 'GET' })
117
110
  if (!response.ok) {
@@ -28,7 +28,7 @@ import {
28
28
  GridToolbar,
29
29
  } from '@mui/x-data-grid'
30
30
  import { getRoot } from 'mobx-state-tree'
31
- import React, { useCallback, useEffect, useState } from 'react'
31
+ import React, { useEffect, useState } from 'react'
32
32
 
33
33
  import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
34
34
  import { type ChangeManager } from '../ChangeManager'
@@ -72,33 +72,34 @@ export function ManageUsers({
72
72
  )
73
73
  const [users, setUsers] = useState<UserResponse[]>([])
74
74
 
75
- const getUsers = useCallback(async () => {
76
- const { baseURL } = selectedInternetAccount
77
- const uri = new URL('users', baseURL).href
78
- const apolloFetch = selectedInternetAccount.getFetcher({
79
- locationType: 'UriLocation',
80
- uri,
81
- })
82
- if (apolloFetch) {
83
- const response = await apolloFetch(uri, { method: 'GET' })
84
- if (!response.ok) {
85
- const newErrorMessage = await createFetchErrorMessage(
86
- response,
87
- 'Error when getting user data from db',
75
+ useEffect(() => {
76
+ async function getUsers() {
77
+ const { baseURL } = selectedInternetAccount
78
+ const uri = new URL('users', baseURL).href
79
+ const apolloFetch = selectedInternetAccount.getFetcher({
80
+ locationType: 'UriLocation',
81
+ uri,
82
+ })
83
+ if (apolloFetch) {
84
+ const response = await apolloFetch(uri, { method: 'GET' })
85
+ if (!response.ok) {
86
+ const newErrorMessage = await createFetchErrorMessage(
87
+ response,
88
+ 'Error when getting user data from db',
89
+ )
90
+ setErrorMessage(newErrorMessage)
91
+ return
92
+ }
93
+ const data = (await response.json()) as UserResponse[]
94
+ setUsers(
95
+ data.map((u) => (u.role === undefined ? { ...u, role: '' } : u)),
88
96
  )
89
- setErrorMessage(newErrorMessage)
90
- return
91
97
  }
92
- const data = (await response.json()) as UserResponse[]
93
- setUsers(data.map((u) => (u.role === undefined ? { ...u, role: '' } : u)))
94
98
  }
95
- }, [selectedInternetAccount])
96
-
97
- useEffect(() => {
98
99
  getUsers().catch((error) => {
99
100
  setErrorMessage(String(error))
100
101
  })
101
- }, [getUsers])
102
+ }, [selectedInternetAccount])
102
103
 
103
104
  async function deleteUser(id: GridRowId) {
104
105
  const change = new DeleteUserChange({
@@ -73,21 +73,14 @@ export function OntologyTermAutocomplete({
73
73
  [filterTermsProp, includeDeprecated],
74
74
  )
75
75
 
76
- // effect for clearing choices when not open
77
- useEffect(() => {
78
- if (!open) {
79
- setTermChoices(undefined)
80
- }
81
- }, [open])
82
-
83
76
  // effect for matching the current value with an ontology term
84
77
  useEffect(() => {
85
78
  const controller = new AbortController()
86
79
  const { signal } = controller
87
80
  if (needToLoadCurrentTerm) {
88
- setCurrentOntologyTermInvalid('')
89
81
  getCurrentTerm(ontologyStore, valueString, filterTerms, signal).then(
90
82
  (term) => {
83
+ setCurrentOntologyTermInvalid('')
91
84
  if (!signal.aborted) {
92
85
  setCurrentOntologyTerm(term)
93
86
  }
@@ -26,6 +26,10 @@ import React, { useEffect, useState } from 'react'
26
26
  import { makeStyles } from 'tss-react/mui'
27
27
 
28
28
  import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
29
+ import {
30
+ type ApolloInternetAccount,
31
+ type CollaborationServerDriver,
32
+ } from '../BackendDrivers'
29
33
  import { type ApolloSessionModel } from '../session'
30
34
  import { type ApolloRootModel } from '../types'
31
35
  import { createFetchErrorMessage } from '../util'
@@ -37,11 +41,6 @@ interface ViewChangeLogProps {
37
41
  handleClose(): void
38
42
  }
39
43
 
40
- interface AssemblyDocument {
41
- _id: string
42
- name: string
43
- }
44
-
45
44
  const useStyles = makeStyles()((theme) => ({
46
45
  changeTextarea: {
47
46
  fontFamily: 'monospace',
@@ -63,12 +62,18 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
63
62
  const { baseURL } = apolloInternetAccount
64
63
  const { classes } = useStyles()
65
64
  const [errorMessage, setErrorMessage] = useState<string>()
66
- const [assemblyCollection, setAssemblyCollection] = useState<
67
- AssemblyDocument[]
68
- >([])
69
- const [assemblyId, setAssemblyId] = useState<string>('')
70
65
  const [displayGridData, setDisplayGridData] = useState<GridRowsProp[]>([])
71
66
 
67
+ const { collaborationServerDriver } = session.apolloDataStore as {
68
+ collaborationServerDriver: CollaborationServerDriver
69
+ getInternetAccount(
70
+ assemblyName?: string,
71
+ internetAccountId?: string,
72
+ ): ApolloInternetAccount
73
+ }
74
+ const assemblies = collaborationServerDriver.getAssemblies()
75
+ const [selectedAssembly, setSelectedAssembly] = useState(assemblies.at(0))
76
+
72
77
  const gridColumns: GridColDef[] = [
73
78
  { field: 'sequence' },
74
79
  {
@@ -90,7 +95,6 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
90
95
  readOnly
91
96
  />
92
97
  ),
93
- valueFormatter: ({ value }) => JSON.stringify(value),
94
98
  },
95
99
  { field: 'user', headerName: 'User', width: 140 },
96
100
  {
@@ -102,47 +106,17 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
102
106
  },
103
107
  ]
104
108
 
105
- useEffect(() => {
106
- async function getAssemblies() {
107
- const uri = new URL('assemblies', baseURL).href
108
- const apolloFetch = apolloInternetAccount?.getFetcher({
109
- locationType: 'UriLocation',
110
- uri,
111
- })
112
- if (apolloFetch) {
113
- const response = await apolloFetch(uri, { method: 'GET' })
114
- if (!response.ok) {
115
- const newErrorMessage = await createFetchErrorMessage(
116
- response,
117
- 'Error when retrieving assemblies from server',
118
- )
119
- setErrorMessage(newErrorMessage)
120
- return
121
- }
122
- const data = (await response.json()) as AssemblyDocument[]
123
- setAssemblyCollection(data)
124
- }
125
- }
126
- getAssemblies().catch((error) => {
127
- setErrorMessage(String(error))
128
- })
129
- }, [apolloInternetAccount, baseURL])
130
-
131
- useEffect(() => {
132
- if (!assemblyId && assemblyCollection.length > 0) {
133
- setAssemblyId(assemblyCollection[0]._id)
134
- }
135
- }, [assemblyId, assemblyCollection])
136
-
137
109
  useEffect(() => {
138
110
  async function getGridData() {
139
- if (!assemblyId) {
111
+ if (!selectedAssembly) {
140
112
  return
141
113
  }
142
114
 
143
115
  // Get changes
144
116
  const url = new URL('changes', baseURL)
145
- const searchParams = new URLSearchParams({ assembly: assemblyId })
117
+ const searchParams = new URLSearchParams({
118
+ assembly: selectedAssembly.name,
119
+ })
146
120
  url.search = searchParams.toString()
147
121
  const uri = url.toString()
148
122
  const apolloFetch = apolloInternetAccount?.getFetcher({
@@ -168,10 +142,11 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
168
142
  getGridData().catch((error) => {
169
143
  setErrorMessage(String(error))
170
144
  })
171
- }, [assemblyId, apolloInternetAccount, baseURL])
145
+ }, [apolloInternetAccount, baseURL, selectedAssembly])
172
146
 
173
147
  function handleChangeAssembly(e: SelectChangeEvent) {
174
- setAssemblyId(e.target.value)
148
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
149
+ setSelectedAssembly(newAssembly)
175
150
  }
176
151
 
177
152
  return (
@@ -184,12 +159,12 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
184
159
  >
185
160
  <Select
186
161
  style={{ width: 200, marginLeft: 40 }}
187
- value={assemblyId}
162
+ value={selectedAssembly?.name ?? ''}
188
163
  onChange={handleChangeAssembly}
189
164
  >
190
- {assemblyCollection.map((option) => (
191
- <MenuItem key={option._id} value={option._id}>
192
- {option.name}
165
+ {assemblies.map((option) => (
166
+ <MenuItem key={option.name} value={option.name}>
167
+ {option.displayName || option.name}
193
168
  </MenuItem>
194
169
  ))}
195
170
  </Select>
@@ -4,7 +4,6 @@
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
5
5
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
6
6
  /* eslint-disable @typescript-eslint/no-unsafe-return */
7
- import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
8
7
  import {
9
8
  Button,
10
9
  DialogActions,
@@ -49,7 +48,6 @@ export function ViewCheckResults({
49
48
  }
50
49
  const { baseURL } = apolloInternetAccount
51
50
  const [errorMessage, setErrorMessage] = useState<string>()
52
- const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
53
51
  const [displayGridData, setDisplayGridData] = useState<GridRowsProp[]>([])
54
52
 
55
53
  const gridColumns: GridColDef[] = [
@@ -65,11 +63,7 @@ export function ViewCheckResults({
65
63
  ]
66
64
 
67
65
  const assemblies = collaborationServerDriver.getAssemblies()
68
- useEffect(() => {
69
- if (!selectedAssembly && assemblies.length > 0) {
70
- setSelectedAssembly(assemblies[0])
71
- }
72
- }, [assemblies, selectedAssembly])
66
+ const [selectedAssembly, setSelectedAssembly] = useState(assemblies.at(0))
73
67
 
74
68
  useEffect(() => {
75
69
  async function getGridData() {
package/src/config.ts CHANGED
@@ -15,10 +15,10 @@ const ApolloPluginConfigurationSchema = ConfigurationSchema('ApolloPlugin', {
15
15
  type: 'boolean',
16
16
  defaultValue: false,
17
17
  },
18
- backgroundColorForFeature: {
19
- description: 'Color ',
18
+ geneBackgroundColor: {
19
+ description: 'Color for feature background',
20
20
  type: 'string',
21
- defaultValue: 'jexl:colorFeature(featureType)',
21
+ defaultValue: 'jexl:geneBackgroundColor(featureType)',
22
22
  contextVariable: ['featureType'],
23
23
  },
24
24
  })
package/src/index.ts CHANGED
@@ -42,7 +42,6 @@ import {
42
42
  import { installApolloRefNameAliasAdapter } from './ApolloRefNameAliasAdapter'
43
43
  import { installApolloSequenceAdapter } from './ApolloSequenceAdapter'
44
44
  import { installApolloTextSearchAdapter } from './ApolloTextSearchAdapter'
45
- import { type BackendDriver } from './BackendDrivers'
46
45
  import {
47
46
  ApolloFeatureDetailsWidget,
48
47
  ApolloFeatureDetailsWidgetModel,
@@ -309,9 +308,10 @@ export default class ApolloPlugin extends Plugin {
309
308
  if (!dataStore) {
310
309
  break
311
310
  }
312
- const backendDriver = dataStore.getBackendDriver(
313
- assemblyName,
314
- ) as BackendDriver
311
+ const backendDriver = dataStore.getBackendDriver(assemblyName)
312
+ if (!backendDriver) {
313
+ break
314
+ }
315
315
  const { seq: sequence } =
316
316
  await backendDriver.getSequence(region)
317
317
  handle.workers[0].postMessage({
@@ -331,9 +331,10 @@ export default class ApolloPlugin extends Plugin {
331
331
  if (!dataStore) {
332
332
  break
333
333
  }
334
- const backendDriver = dataStore.getBackendDriver(
335
- assembly,
336
- ) as BackendDriver
334
+ const backendDriver = dataStore.getBackendDriver(assembly)
335
+ if (!backendDriver) {
336
+ break
337
+ }
337
338
  const regions = await backendDriver.getRegions(assembly)
338
339
  handle.workers[0].postMessage({
339
340
  apollo,
@@ -352,9 +353,10 @@ export default class ApolloPlugin extends Plugin {
352
353
  if (!dataStore) {
353
354
  break
354
355
  }
355
- const backendDriver = dataStore.getBackendDriver(
356
- assembly,
357
- ) as BackendDriver
356
+ const backendDriver = dataStore.getBackendDriver(assembly)
357
+ if (!backendDriver) {
358
+ break
359
+ }
358
360
  const refNameAliases =
359
361
  await backendDriver.getRefNameAliases(assembly)
360
362
  handle.workers[0].postMessage({
@@ -378,18 +380,17 @@ export default class ApolloPlugin extends Plugin {
378
380
  configure(pluginManager: PluginManager) {
379
381
  if (isAbstractMenuManager(pluginManager.rootModel)) {
380
382
  pluginManager.jexl.addFunction(
381
- 'colorFeature',
382
- (featureType: 'pseudogenic_transcript' | 'nonCodingTranscript') => {
383
- if (featureType === 'pseudogenic_transcript') {
383
+ 'geneBackgroundColor',
384
+ (featureType: string) => {
385
+ if (featureType === 'pseudogene') {
384
386
  return alpha('rgb(148, 203, 236)', 0.6)
385
387
  }
386
- if (featureType === 'nonCodingTranscript') {
388
+ if (featureType === 'ncRNA_gene') {
387
389
  return alpha('rgb(194, 106, 119)', 0.6)
388
390
  }
389
- throw new Error('Invalid type')
391
+ return
390
392
  },
391
393
  )
392
-
393
394
  addTopLevelMenus(pluginManager.rootModel)
394
395
  }
395
396
  }
@@ -87,25 +87,21 @@ const ResizeHandle = ({
87
87
  },
88
88
  [onResize],
89
89
  )
90
- const cancelDrag: (event: MouseEvent) => void = useCallback(
91
- (event: MouseEvent) => {
92
- event.stopPropagation()
93
- event.preventDefault()
94
- globalThis.removeEventListener('mousemove', mouseMove)
95
- globalThis.removeEventListener('mouseup', cancelDrag)
96
- globalThis.removeEventListener('mouseleave', cancelDrag)
97
- },
98
- [mouseMove],
99
- )
90
+
100
91
  return (
101
92
  // TODO: a11y
102
93
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
103
94
  <div
104
95
  onMouseDown={(event: React.MouseEvent) => {
105
96
  event.stopPropagation()
106
- globalThis.addEventListener('mousemove', mouseMove)
107
- globalThis.addEventListener('mouseup', cancelDrag)
108
- globalThis.addEventListener('mouseleave', cancelDrag)
97
+ const controller = new AbortController()
98
+ const { signal } = controller
99
+ function abortDrag() {
100
+ controller.abort()
101
+ }
102
+ globalThis.addEventListener('mousemove', mouseMove, { signal })
103
+ globalThis.addEventListener('mouseup', abortDrag, { signal })
104
+ globalThis.addEventListener('mouseleave', abortDrag, { signal })
109
105
  }}
110
106
  onClick={(e) => {
111
107
  e.stopPropagation()
@@ -33,6 +33,7 @@ import {
33
33
 
34
34
  import {
35
35
  type ApolloInternetAccount,
36
+ type BackendDriver,
36
37
  CollaborationServerDriver,
37
38
  DesktopFileDriver,
38
39
  InMemoryFileDriver,
@@ -86,18 +87,38 @@ export function clientDataStoreFactory(
86
87
  }
87
88
  return self.assemblies.put(assemblySnapshot)
88
89
  },
90
+ }))
91
+ .actions((self) => ({
89
92
  addFeature(assemblyId: string, feature: AnnotationFeatureSnapshot) {
90
- const assembly = self.assemblies.get(assemblyId)
91
- if (!assembly) {
92
- throw new Error(
93
- `Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
94
- )
93
+ const session = getSession(self)
94
+ const { assemblyManager } = session
95
+ let apolloAssembly = self.assemblies.get(assemblyId)
96
+ if (!apolloAssembly) {
97
+ // maybe it's a valid assembly that we haven't loaded yet
98
+ const assembly = assemblyManager.get(assemblyId)
99
+ if (!assembly) {
100
+ throw new Error(
101
+ `Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
102
+ )
103
+ }
104
+ apolloAssembly = self.addAssembly(assemblyId)
95
105
  }
96
- const ref = assembly.refSeqs.get(feature.refSeq)
106
+ let ref = apolloAssembly.refSeqs.get(feature.refSeq)
97
107
  if (!ref) {
98
- throw new Error(
99
- `Could not find refSeq "${feature.refSeq}" to add feature "${feature._id}"`,
100
- )
108
+ // maybe it's a valid refName that we haven't loaded yet
109
+ const assembly = assemblyManager.get(assemblyId)
110
+ if (!assembly) {
111
+ throw new Error(
112
+ `Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
113
+ )
114
+ }
115
+ const canonicalRefName = assembly.getCanonicalRefName(feature.refSeq)
116
+ if (!canonicalRefName) {
117
+ throw new Error(
118
+ `Could not find refSeq "${feature.refSeq}" to add feature "${feature._id}"`,
119
+ )
120
+ }
121
+ ref = apolloAssembly.addRefSeq(feature.refSeq, canonicalRefName)
101
122
  }
102
123
  ref.features.put(feature)
103
124
  },
@@ -212,15 +233,12 @@ export function clientDataStoreFactory(
212
233
  },
213
234
  }))
214
235
  .views((self) => ({
215
- getBackendDriver(assemblyId: string) {
216
- if (!assemblyId) {
217
- return self.collaborationServerDriver
218
- }
236
+ getBackendDriver(assemblyId: string): BackendDriver | undefined {
219
237
  const session = getSession(self)
220
238
  const { assemblyManager } = session
221
239
  const assembly = assemblyManager.get(assemblyId)
222
240
  if (!assembly) {
223
- return self.collaborationServerDriver
241
+ return
224
242
  }
225
243
  const { file, internetAccountConfigId } = getConf(assembly, [
226
244
  'sequence',