@apollo-annotation/jbrowse-plugin-apollo 0.3.1 → 0.3.3

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 (47) hide show
  1. package/dist/index.esm.js +2072 -1496
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +2069 -1493
  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 +2256 -1533
  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 +13 -11
  12. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +7 -10
  13. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +3 -0
  14. package/src/FeatureDetailsWidget/Attributes.tsx +27 -27
  15. package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +65 -0
  16. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +6 -1
  17. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +25 -2
  18. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -1
  19. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +8 -1
  20. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +88 -40
  21. package/src/LinearApolloDisplay/glyphs/Glyph.ts +8 -1
  22. package/src/LinearApolloDisplay/stateModel/base.ts +28 -2
  23. package/src/LinearApolloDisplay/stateModel/layouts.ts +65 -11
  24. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +25 -6
  25. package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -2
  26. package/src/OntologyManager/OntologyStore/index.ts +6 -2
  27. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +41 -13
  28. package/src/OntologyManager/index.ts +35 -0
  29. package/src/SixFrameFeatureDisplay/stateModel.ts +11 -2
  30. package/src/TabularEditor/HybridGrid/Feature.tsx +1 -2
  31. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +0 -1
  32. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +8 -1
  33. package/src/components/AddRefSeqAliases.tsx +7 -8
  34. package/src/components/CopyFeature.tsx +1 -1
  35. package/src/components/CreateApolloAnnotation.tsx +304 -0
  36. package/src/components/DownloadGFF3.tsx +5 -1
  37. package/src/components/FilterFeatures.tsx +120 -0
  38. package/src/components/ModifyFeatureAttribute.tsx +27 -27
  39. package/src/components/OntologyTermMultiSelect.tsx +5 -5
  40. package/src/extensions/annotationFromJBrowseFeature.test.ts +119 -0
  41. package/src/extensions/annotationFromJBrowseFeature.ts +171 -0
  42. package/src/extensions/annotationFromPileup.ts +1 -1
  43. package/src/extensions/index.ts +1 -0
  44. package/src/index.ts +8 -2
  45. package/src/session/ClientDataStore.ts +29 -0
  46. package/src/session/session.ts +2 -5
  47. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +0 -40
@@ -0,0 +1,120 @@
1
+ import React, { useState } from 'react'
2
+ import { ApolloSessionModel } from '../session'
3
+ import { Dialog } from './Dialog'
4
+ import {
5
+ Box,
6
+ Button,
7
+ Chip,
8
+ DialogContent,
9
+ DialogContentText,
10
+ Grid2,
11
+ TextField,
12
+ } from '@mui/material'
13
+ import { isOntologyClass } from '../OntologyManager'
14
+ import { OntologyTermAutocomplete } from './OntologyTermAutocomplete'
15
+ import { observer } from 'mobx-react'
16
+
17
+ interface FilterFeaturesProps {
18
+ onUpdate: (types: string[]) => void
19
+ featureTypes: string[]
20
+ handleClose: () => void
21
+ session: ApolloSessionModel
22
+ }
23
+
24
+ export const FilterFeatures = observer(function FilterFeatures({
25
+ featureTypes,
26
+ handleClose,
27
+ onUpdate,
28
+ session,
29
+ }: FilterFeaturesProps) {
30
+ const [type, setType] = useState('')
31
+ const [selectedFeatureTypes, setSelectedFeatureTypes] =
32
+ useState<string[]>(featureTypes)
33
+ const handleChange = (value: string): void => {
34
+ setType(value)
35
+ }
36
+ const handleAddFeatureType = () => {
37
+ if (type) {
38
+ if (selectedFeatureTypes.includes(type)) {
39
+ return
40
+ }
41
+ onUpdate([...selectedFeatureTypes, type])
42
+ setSelectedFeatureTypes([...selectedFeatureTypes, type])
43
+ }
44
+ }
45
+ const handleFeatureTypeDelete = (value: string) => {
46
+ const newTypes = selectedFeatureTypes.filter((type) => type !== value)
47
+ onUpdate(newTypes)
48
+ setSelectedFeatureTypes(newTypes)
49
+ }
50
+
51
+ return (
52
+ <Dialog
53
+ open
54
+ maxWidth={false}
55
+ data-testid="filter-features-dialog"
56
+ title="Filter features by type"
57
+ handleClose={handleClose}
58
+ >
59
+ <DialogContent>
60
+ <DialogContentText>
61
+ Select the feature types you want to display in the apollo track
62
+ </DialogContentText>
63
+ <Grid2 container spacing={2}>
64
+ <Grid2 size={8}>
65
+ <OntologyTermAutocomplete
66
+ session={session}
67
+ ontologyName="Sequence Ontology"
68
+ style={{ width: '100%' }}
69
+ value={type}
70
+ filterTerms={isOntologyClass}
71
+ renderInput={(params) => (
72
+ <TextField
73
+ {...params}
74
+ label="Feature type"
75
+ variant="outlined"
76
+ fullWidth
77
+ />
78
+ )}
79
+ onChange={(oldValue, newValue) => {
80
+ if (newValue) {
81
+ handleChange(newValue)
82
+ }
83
+ }}
84
+ />
85
+ </Grid2>
86
+ <Grid2 size={4}>
87
+ <Button
88
+ variant="contained"
89
+ onClick={handleAddFeatureType}
90
+ disabled={!type}
91
+ style={{ marginTop: 9 }}
92
+ size="medium"
93
+ >
94
+ Add
95
+ </Button>
96
+ </Grid2>
97
+ </Grid2>
98
+ {selectedFeatureTypes.length > 0 && (
99
+ <div>
100
+ <hr />
101
+ <div style={{ width: 300 }}>
102
+ <DialogContentText>Selected feature types:</DialogContentText>
103
+ <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
104
+ {selectedFeatureTypes.map((value) => (
105
+ <Chip
106
+ key={value}
107
+ label={value}
108
+ onDelete={() => {
109
+ handleFeatureTypeDelete(value)
110
+ }}
111
+ />
112
+ ))}
113
+ </Box>
114
+ </div>
115
+ </div>
116
+ )}
117
+ </DialogContent>
118
+ </Dialog>
119
+ )
120
+ })
@@ -13,7 +13,7 @@ import {
13
13
  FormControl,
14
14
  FormControlLabel,
15
15
  FormLabel,
16
- Grid,
16
+ Grid2,
17
17
  IconButton,
18
18
  Paper,
19
19
  Radio,
@@ -300,25 +300,25 @@ export function ModifyFeatureAttribute({
300
300
  >
301
301
  <form onSubmit={onSubmit}>
302
302
  <DialogContent>
303
- <Grid container direction="column" spacing={1}>
303
+ <Grid2 container direction="column" spacing={1}>
304
304
  {Object.entries(attributes).map(([key, value]) => {
305
305
  const EditorComponent =
306
306
  reservedKeys.get(key) ?? CustomAttributeValueEditor
307
307
  return (
308
- <Grid container item spacing={3} alignItems="center" key={key}>
309
- <Grid item xs="auto">
308
+ <Grid2 container spacing={3} alignItems="center" key={key}>
309
+ <Grid2>
310
310
  <Paper variant="outlined" className={classes.attributeName}>
311
311
  <Typography>{key}</Typography>
312
312
  </Paper>
313
- </Grid>
314
- <Grid item flexGrow={1}>
313
+ </Grid2>
314
+ <Grid2 flexGrow={1}>
315
315
  <EditorComponent
316
316
  session={session}
317
317
  value={value}
318
318
  onChange={makeOnChange(key)}
319
319
  />
320
- </Grid>
321
- <Grid item xs={1}>
320
+ </Grid2>
321
+ <Grid2>
322
322
  <IconButton
323
323
  aria-label="delete"
324
324
  size="medium"
@@ -329,11 +329,11 @@ export function ModifyFeatureAttribute({
329
329
  >
330
330
  <DeleteIcon fontSize="medium" key={key} />
331
331
  </IconButton>
332
- </Grid>
333
- </Grid>
332
+ </Grid2>
333
+ </Grid2>
334
334
  )
335
335
  })}
336
- <Grid item>
336
+ <Grid2>
337
337
  <Button
338
338
  color="primary"
339
339
  variant="contained"
@@ -344,12 +344,12 @@ export function ModifyFeatureAttribute({
344
344
  >
345
345
  Add new
346
346
  </Button>
347
- </Grid>
347
+ </Grid2>
348
348
  {showAddNewForm ? (
349
- <Grid item>
349
+ <Grid2>
350
350
  <Paper elevation={8} className={classes.newAttributePaper}>
351
- <Grid container direction="column">
352
- <Grid item>
351
+ <Grid2 container direction="column">
352
+ <Grid2>
353
353
  <FormControl>
354
354
  <FormLabel id="attribute-radio-button-group">
355
355
  Select attribute type
@@ -365,11 +365,11 @@ export function ModifyFeatureAttribute({
365
365
  control={<Radio />}
366
366
  disableTypography
367
367
  label={
368
- <Grid container spacing={1} alignItems="center">
369
- <Grid item>
368
+ <Grid2 container spacing={1} alignItems="center">
369
+ <Grid2>
370
370
  <Typography>Custom</Typography>
371
- </Grid>
372
- <Grid item>
371
+ </Grid2>
372
+ <Grid2>
373
373
  <TextField
374
374
  label="Custom attribute key"
375
375
  variant="outlined"
@@ -383,8 +383,8 @@ export function ModifyFeatureAttribute({
383
383
  setNewAttributeKey(event.target.value)
384
384
  }}
385
385
  />
386
- </Grid>
387
- </Grid>
386
+ </Grid2>
387
+ </Grid2>
388
388
  }
389
389
  />
390
390
  {[...reservedKeys.keys()].map((key) => (
@@ -397,8 +397,8 @@ export function ModifyFeatureAttribute({
397
397
  ))}
398
398
  </RadioGroup>
399
399
  </FormControl>
400
- </Grid>
401
- <Grid item>
400
+ </Grid2>
401
+ <Grid2>
402
402
  <DialogActions>
403
403
  <Button
404
404
  key="addButton"
@@ -423,12 +423,12 @@ export function ModifyFeatureAttribute({
423
423
  Cancel
424
424
  </Button>
425
425
  </DialogActions>
426
- </Grid>
427
- </Grid>
426
+ </Grid2>
427
+ </Grid2>
428
428
  </Paper>
429
- </Grid>
429
+ </Grid2>
430
430
  ) : null}
431
- </Grid>
431
+ </Grid2>
432
432
  {errorMessage ? (
433
433
  <DialogContentText color="error">{errorMessage}</DialogContentText>
434
434
  ) : null}
@@ -6,7 +6,7 @@ import {
6
6
  Autocomplete,
7
7
  AutocompleteRenderGetTagProps,
8
8
  Chip,
9
- Grid,
9
+ Grid2,
10
10
  TextField,
11
11
  Tooltip,
12
12
  Typography,
@@ -333,8 +333,8 @@ function Option(props: {
333
333
  // .join(', ')
334
334
  return (
335
335
  <li {...other}>
336
- <Grid container>
337
- <Grid item>
336
+ <Grid2 container>
337
+ <Grid2>
338
338
  <Typography component="span">
339
339
  {ontologyManager.applyPrefixes(option.term.id)}
340
340
  </Typography>{' '}
@@ -344,8 +344,8 @@ function Option(props: {
344
344
  />{' '}
345
345
  {/* ({lblScore}) */}
346
346
  <dl>{fields}</dl>
347
- </Grid>
348
- </Grid>
347
+ </Grid2>
348
+ </Grid2>
349
349
  </li>
350
350
  )
351
351
  }
@@ -0,0 +1,119 @@
1
+ import SimpleFeature from '@jbrowse/core/util/simpleFeature'
2
+ import { describe, expect, it } from '@jest/globals'
3
+ import { jbrowseFeatureToAnnotationFeature } from './annotationFromJBrowseFeature'
4
+
5
+ describe('Convert JBrowse feature to annotation feature', () => {
6
+ // it('Handle reserved fields', () => {
7
+ // const feature = new SimpleFeature({
8
+ // uniqueId: 'a',
9
+ // id: 'gene01',
10
+ // start: 3,
11
+ // end: 27,
12
+ // strand: 1,
13
+ // type: 'gene',
14
+ // source: 'mySource',
15
+ // score: 10,
16
+ // refName: 'chr1',
17
+ // derived_features: [],
18
+ // subfeatures: [],
19
+ // })
20
+ // const ff = simpleFeatureToGFF3Feature(feature, 'abcd')
21
+ // const af = jbrowseFeatureToAnnotationFeature(feature, 'abcd')
22
+ // console.log(JSON.stringify(feature, null, 2))
23
+ // console.log(JSON.stringify(ff, null, 2))
24
+ // console.log(JSON.stringify(af, null, 2))
25
+
26
+ // // expect(af.attributes?.score).toBeUndefined()
27
+ // // expect(af.attributes?.gff_score).toBeUndefined()
28
+ // // expect(af.attributes?.source).toBeUndefined()
29
+ // // expect(af.attributes?.gff_source?.at(0)).toStrictEqual('mySource')
30
+ // // console.log(JSON.stringify(af, null, 2))
31
+ // })
32
+
33
+ it('Convert gff', () => {
34
+ const feature = new SimpleFeature({
35
+ uniqueId: 'a',
36
+ id: 'gene01',
37
+ start: 3,
38
+ end: 27,
39
+ strand: 1,
40
+ type: 'gene',
41
+ source: 'mySource',
42
+ refName: 'chr1',
43
+ derived_features: [],
44
+ subfeatures: [
45
+ {
46
+ uniqueId: 'b',
47
+ id: 'mrna01',
48
+ parent: 'gene01',
49
+ start: 3,
50
+ end: 27,
51
+ strand: 1,
52
+ type: 'mRNA',
53
+ source: 'mySource',
54
+ refName: 'chr1',
55
+ derived_features: [],
56
+ subfeatures: [
57
+ {
58
+ uniqueId: 'c',
59
+ id: 'exon01',
60
+ parent: 'mrna01',
61
+ start: 3,
62
+ end: 27,
63
+ strand: 1,
64
+ type: 'exon',
65
+ source: 'mySource',
66
+ refName: 'chr1',
67
+ derived_features: [],
68
+ subfeatures: [],
69
+ },
70
+ {
71
+ uniqueId: 'd',
72
+ id: 'cds01',
73
+ parent: 'mrna01',
74
+ name: 'XYZ',
75
+ description: 'Stuff',
76
+ xkey: 'xvalue',
77
+ start: 15,
78
+ end: 27,
79
+ strand: 1,
80
+ type: 'CDS',
81
+ source: 'mySource',
82
+ score: 0,
83
+ refName: 'chr1',
84
+ derived_features: [],
85
+ phase: 0,
86
+ subfeatures: [],
87
+ },
88
+ ],
89
+ },
90
+ ],
91
+ })
92
+ const af = jbrowseFeatureToAnnotationFeature(feature, 'abcd')
93
+ expect(af.refSeq).toStrictEqual('abcd')
94
+ expect(af.strand).toEqual(1)
95
+ expect(af.min).toEqual(3)
96
+ expect(af.attributes?.score).toBeUndefined()
97
+ expect(af.attributes?.gff_score).toBeUndefined()
98
+
99
+ const mrna = af.children ? Object.values(af.children) : undefined
100
+ if (mrna) {
101
+ expect(mrna.length).toEqual(1)
102
+ } else {
103
+ throw new Error('Children expected')
104
+ }
105
+ const cds = mrna.at(0)?.children
106
+ if (cds) {
107
+ expect(Object.values(cds).length).toEqual(2)
108
+ const xcds = Object.values(cds).at(1)
109
+ expect(xcds?.type).toStrictEqual('CDS')
110
+ expect(xcds?.min).toStrictEqual(15)
111
+ expect(xcds?.max).toStrictEqual(27)
112
+ expect(xcds?.attributes?.name?.at(0)).toStrictEqual('XYZ')
113
+ expect(xcds?.attributes?.gff_score?.at(0)).toStrictEqual('0')
114
+ expect(xcds?.attributes?.gff_source?.at(0)).toStrictEqual('mySource')
115
+ } else {
116
+ throw new Error('Children expected')
117
+ }
118
+ })
119
+ })
@@ -0,0 +1,171 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-argument */
2
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
3
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
+ /* eslint-disable @typescript-eslint/no-unsafe-call */
5
+ /* eslint-disable @typescript-eslint/no-unsafe-return */
6
+ import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
7
+ import PluggableElementBase from '@jbrowse/core/pluggableElementTypes/PluggableElementBase'
8
+ import AddIcon from '@mui/icons-material/Add'
9
+ import {
10
+ AbstractSessionModel,
11
+ getContainingView,
12
+ getSession,
13
+ } from '@jbrowse/core/util'
14
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
15
+ import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
16
+ import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
17
+ import { Feature } from '@jbrowse/core/util/simpleFeature'
18
+ import { CreateApolloAnnotation } from '../components/CreateApolloAnnotation'
19
+ import { GFF3Feature } from '@gmod/gff'
20
+ import { gff3ToAnnotationFeature } from '@apollo-annotation/shared'
21
+
22
+ function simpleFeatureToGFF3Feature(
23
+ feature: Feature,
24
+ refSeqId: string,
25
+ ): GFF3Feature {
26
+ const xfeature = JSON.parse(JSON.stringify(feature))
27
+ const children = xfeature.subfeatures
28
+ const gff3Feature = [
29
+ {
30
+ start: (xfeature.start as number) + 1,
31
+ end: xfeature.end as number,
32
+ seq_id: refSeqId,
33
+ source: xfeature.source ?? null,
34
+ type: xfeature.type ?? null,
35
+ score: xfeature.score ?? null,
36
+ strand: xfeature.strand ? (xfeature.strand === 1 ? '+' : '-') : null,
37
+ phase:
38
+ xfeature.phase !== null || xfeature.phase !== undefined
39
+ ? (xfeature.phase as string)
40
+ : null,
41
+ attributes: convertFeatureAttributes(xfeature),
42
+ derived_features: [],
43
+ child_features: children
44
+ ? children.map((x: Feature) => simpleFeatureToGFF3Feature(x, refSeqId))
45
+ : [],
46
+ },
47
+ ]
48
+ return gff3Feature
49
+ }
50
+
51
+ export function jbrowseFeatureToAnnotationFeature(
52
+ feature: Feature,
53
+ refSeqId: string,
54
+ ): AnnotationFeatureSnapshot {
55
+ return gff3ToAnnotationFeature(simpleFeatureToGFF3Feature(feature, refSeqId))
56
+ }
57
+
58
+ function convertFeatureAttributes(feature: Feature): Record<string, string[]> {
59
+ const attributes: Record<string, string[]> = {}
60
+ const defaultFields = new Set([
61
+ 'start',
62
+ 'end',
63
+ 'type',
64
+ 'strand',
65
+ 'refName',
66
+ 'subfeatures',
67
+ 'derived_features',
68
+ 'phase',
69
+ 'source',
70
+ 'score',
71
+ ])
72
+ for (const [key, value] of Object.entries(feature)) {
73
+ if (defaultFields.has(key)) {
74
+ continue
75
+ }
76
+ attributes[key] = Array.isArray(value) ? value.map(String) : [String(value)]
77
+ }
78
+ return attributes
79
+ }
80
+
81
+ export function annotationFromJBrowseFeature(
82
+ pluggableElement: PluggableElementBase,
83
+ ) {
84
+ if (pluggableElement.name !== 'LinearBasicDisplay') {
85
+ return pluggableElement
86
+ }
87
+ const { stateModel } = pluggableElement as DisplayType
88
+
89
+ const newStateModel = stateModel
90
+ .views((self) => ({
91
+ getFirstRegion() {
92
+ const lgv = getContainingView(self) as unknown as LinearGenomeViewModel
93
+ return lgv.dynamicBlocks.contentBlocks[0]
94
+ },
95
+ getAssembly() {
96
+ const firstRegion = self.getFirstRegion()
97
+ const session = getSession(self)
98
+ const { assemblyManager } = session
99
+ const { assemblyName } = firstRegion
100
+ const assembly = assemblyManager.get(assemblyName)
101
+ if (!assembly) {
102
+ throw new Error(`Could not find assembly named ${assemblyName}`)
103
+ }
104
+ return assembly
105
+ },
106
+ getRefSeqId(assembly: Assembly) {
107
+ const firstRegion = self.getFirstRegion()
108
+ const { refName } = firstRegion
109
+ const { refNameAliases } = assembly
110
+ if (!refNameAliases) {
111
+ throw new Error(`Could not find aliases for ${assembly.name}`)
112
+ }
113
+ const newRefNames = [...Object.entries(refNameAliases)]
114
+ .filter(([id, refName]) => id !== refName)
115
+ .map(([id, refName]) => ({
116
+ _id: id,
117
+ name: refName,
118
+ }))
119
+ const refSeqId = newRefNames.find((item) => item.name === refName)?._id
120
+ if (!refSeqId) {
121
+ throw new Error(`Could not find refSeqId named ${refName}`)
122
+ }
123
+ return refSeqId
124
+ },
125
+ getAnnotationFeature(assembly: Assembly) {
126
+ const refSeqId = self.getRefSeqId(assembly)
127
+ const sfeature: Feature = self.contextMenuFeature.data
128
+ return jbrowseFeatureToAnnotationFeature(sfeature, refSeqId)
129
+ },
130
+ }))
131
+ .views((self) => {
132
+ const superContextMenuItems = self.contextMenuItems
133
+ const session = getSession(self)
134
+ const assembly = self.getAssembly()
135
+
136
+ return {
137
+ contextMenuItems() {
138
+ const feature = self.contextMenuFeature
139
+ if (!feature) {
140
+ return superContextMenuItems()
141
+ }
142
+ return [
143
+ ...superContextMenuItems(),
144
+ {
145
+ label: 'Create Apollo annotation',
146
+ icon: AddIcon,
147
+ onClick: () => {
148
+ ;(session as unknown as AbstractSessionModel).queueDialog(
149
+ (doneCallback) => [
150
+ CreateApolloAnnotation,
151
+ {
152
+ session,
153
+ handleClose: () => {
154
+ doneCallback()
155
+ },
156
+ annotationFeature: self.getAnnotationFeature(assembly),
157
+ assembly,
158
+ refSeqId: self.getRefSeqId(assembly),
159
+ },
160
+ ],
161
+ )
162
+ },
163
+ },
164
+ ]
165
+ },
166
+ }
167
+ })
168
+
169
+ ;(pluggableElement as DisplayType).stateModel = newStateModel
170
+ return pluggableElement
171
+ }
@@ -54,7 +54,7 @@ export function annotationFromPileup(pluggableElement: PluggableElementBase) {
54
54
  .filter(([id, refName]) => id !== refName)
55
55
  .map(([id, refName]) => ({
56
56
  _id: id,
57
- name: refName ?? '',
57
+ name: refName,
58
58
  }))
59
59
  const refSeqId = newRefNames.find((item) => item.name === refName)?._id
60
60
  if (!refSeqId) {
@@ -1 +1,2 @@
1
1
  export * from './annotationFromPileup'
2
+ export * from './annotationFromJBrowseFeature'
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
1
  /* eslint-disable @typescript-eslint/unbound-method */
3
2
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
3
  /* eslint-disable @typescript-eslint/no-misused-promises */
@@ -55,7 +54,10 @@ import {
55
54
  ViewCheckResults,
56
55
  } from './components'
57
56
  import ApolloPluginConfigurationSchema from './config'
58
- import { annotationFromPileup } from './extensions'
57
+ import {
58
+ annotationFromPileup,
59
+ annotationFromJBrowseFeature,
60
+ } from './extensions'
59
61
  import {
60
62
  ApolloFeatureDetailsWidget,
61
63
  ApolloFeatureDetailsWidgetModel,
@@ -276,6 +278,10 @@ export default class ApolloPlugin extends Plugin {
276
278
  'Core-extendPluggableElement',
277
279
  annotationFromPileup,
278
280
  )
281
+ pluginManager.addToExtensionPoint(
282
+ 'Core-extendPluggableElement',
283
+ annotationFromJBrowseFeature,
284
+ )
279
285
  if (!inWebWorker) {
280
286
  pluginManager.addToExtensionPoint(
281
287
  'Core-extendWorker',
@@ -42,6 +42,7 @@ import {
42
42
  } from '../OntologyManager'
43
43
  import { ApolloRootModel } from '../types'
44
44
  import { autorun } from 'mobx'
45
+ import { ApolloSessionModel } from './session'
45
46
 
46
47
  export function clientDataStoreFactory(
47
48
  AnnotationFeatureExtended: typeof AnnotationFeatureModel,
@@ -167,8 +168,36 @@ export function clientDataStoreFactory(
167
168
  ) as TextIndexFieldDefinition[],
168
169
  ]
169
170
  if (!ontologyManager.findOntology(name)) {
171
+ const session = getSession(
172
+ self,
173
+ ) as unknown as ApolloSessionModel
174
+ const { jobsManager } = session
175
+ const controller = new AbortController()
176
+ const jobName = `Loading ontology "${name}"`
177
+ const job = {
178
+ name: jobName,
179
+ statusMessage: `Loading ontology "${name}", version "${version}", this may take a while`,
180
+ progressPct: 0,
181
+ cancelCallback: () => {
182
+ controller.abort()
183
+ jobsManager.abortJob(job.name)
184
+ },
185
+ }
186
+ const update = (message: string, progress: number): void => {
187
+ if (progress === 0) {
188
+ jobsManager.runJob(job)
189
+ return
190
+ }
191
+ if (progress === 100) {
192
+ jobsManager.done(job)
193
+ return
194
+ }
195
+ jobsManager.update(jobName, message, progress)
196
+ return
197
+ }
170
198
  ontologyManager.addOntology(name, version, source, {
171
199
  textIndexing: { indexFields },
200
+ update,
172
201
  })
173
202
  }
174
203
  }
@@ -15,10 +15,7 @@ import {
15
15
  import { readConfObject, getConf } from '@jbrowse/core/configuration'
16
16
  import { BaseTrackConfig } from '@jbrowse/core/pluggableElementTypes'
17
17
  import PluginManager from '@jbrowse/core/PluginManager'
18
- import {
19
- AbstractSessionModel,
20
- SessionWithConfigEditing,
21
- } from '@jbrowse/core/util'
18
+ import { AbstractSessionModel, SessionWithAddTracks } from '@jbrowse/core/util'
22
19
  import { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
23
20
  import SaveIcon from '@mui/icons-material/Save'
24
21
  import { autorun, observable } from 'mobx'
@@ -103,7 +100,7 @@ export function extendSession(
103
100
  (track) => track.trackId === trackId,
104
101
  )
105
102
  if (!hasTrack) {
106
- ;(self as unknown as SessionWithConfigEditing).addTrackConf({
103
+ ;(self as unknown as SessionWithAddTracks).addTrackConf({
107
104
  type: 'ApolloTrack',
108
105
  trackId,
109
106
  name: `Annotations (${