@apollo-annotation/jbrowse-plugin-apollo 0.3.0 → 0.3.2
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 +2072 -1496
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2069 -1493
- 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 +2256 -1533
- 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 +13 -11
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +7 -10
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +3 -0
- package/src/FeatureDetailsWidget/Attributes.tsx +27 -27
- package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +65 -0
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +6 -1
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +25 -2
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -1
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +8 -1
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +88 -40
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +8 -1
- package/src/LinearApolloDisplay/stateModel/base.ts +28 -2
- package/src/LinearApolloDisplay/stateModel/layouts.ts +65 -11
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +25 -6
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -2
- package/src/OntologyManager/OntologyStore/index.ts +6 -2
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +41 -13
- package/src/OntologyManager/index.ts +35 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +11 -2
- package/src/TabularEditor/HybridGrid/Feature.tsx +1 -2
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +0 -1
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +8 -1
- package/src/components/AddRefSeqAliases.tsx +7 -8
- package/src/components/CopyFeature.tsx +1 -1
- package/src/components/CreateApolloAnnotation.tsx +304 -0
- package/src/components/DownloadGFF3.tsx +5 -1
- package/src/components/FilterFeatures.tsx +120 -0
- package/src/components/ModifyFeatureAttribute.tsx +27 -27
- package/src/components/OntologyTermMultiSelect.tsx +5 -5
- package/src/extensions/annotationFromJBrowseFeature.test.ts +119 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +171 -0
- package/src/extensions/annotationFromPileup.ts +1 -1
- package/src/extensions/index.ts +1 -0
- package/src/index.ts +8 -2
- package/src/session/ClientDataStore.ts +29 -0
- package/src/session/session.ts +2 -5
- 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>
|
|
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>
|
|
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
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
309
|
-
<
|
|
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
|
-
</
|
|
314
|
-
<
|
|
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
|
-
</
|
|
321
|
-
<
|
|
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
|
-
</
|
|
333
|
-
</
|
|
332
|
+
</Grid2>
|
|
333
|
+
</Grid2>
|
|
334
334
|
)
|
|
335
335
|
})}
|
|
336
|
-
<
|
|
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
|
-
</
|
|
347
|
+
</Grid2>
|
|
348
348
|
{showAddNewForm ? (
|
|
349
|
-
<
|
|
349
|
+
<Grid2>
|
|
350
350
|
<Paper elevation={8} className={classes.newAttributePaper}>
|
|
351
|
-
<
|
|
352
|
-
<
|
|
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
|
-
<
|
|
369
|
-
<
|
|
368
|
+
<Grid2 container spacing={1} alignItems="center">
|
|
369
|
+
<Grid2>
|
|
370
370
|
<Typography>Custom</Typography>
|
|
371
|
-
</
|
|
372
|
-
<
|
|
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
|
-
</
|
|
387
|
-
</
|
|
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
|
-
</
|
|
401
|
-
<
|
|
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
|
-
</
|
|
427
|
-
</
|
|
426
|
+
</Grid2>
|
|
427
|
+
</Grid2>
|
|
428
428
|
</Paper>
|
|
429
|
-
</
|
|
429
|
+
</Grid2>
|
|
430
430
|
) : null}
|
|
431
|
-
</
|
|
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
|
-
|
|
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
|
-
<
|
|
337
|
-
<
|
|
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
|
-
</
|
|
348
|
-
</
|
|
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) {
|
package/src/extensions/index.ts
CHANGED
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 {
|
|
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
|
}
|
package/src/session/session.ts
CHANGED
|
@@ -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
|
|
103
|
+
;(self as unknown as SessionWithAddTracks).addTrackConf({
|
|
107
104
|
type: 'ApolloTrack',
|
|
108
105
|
trackId,
|
|
109
106
|
name: `Annotations (${
|