@apollo-annotation/jbrowse-plugin-apollo 0.3.1 → 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
|
@@ -81,6 +81,8 @@ function serializeWords(foundWords: Iterable<[string, string]>): string[] {
|
|
|
81
81
|
export async function loadOboGraphJson(this: OntologyStore, db: Database) {
|
|
82
82
|
const startTime = Date.now()
|
|
83
83
|
|
|
84
|
+
let percentProgress = 1
|
|
85
|
+
this.options.update?.('Parsing JSON', percentProgress)
|
|
84
86
|
// TODO: using file streaming along with an event-based json parser
|
|
85
87
|
// instead of JSON.parse and .readFile could probably make this faster
|
|
86
88
|
// and less memory intensive
|
|
@@ -93,6 +95,9 @@ export async function loadOboGraphJson(this: OntologyStore, db: Database) {
|
|
|
93
95
|
throw new Error('Error in loading ontology')
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
percentProgress += 5
|
|
99
|
+
this.options.update?.('Parsing JSON complete', percentProgress)
|
|
100
|
+
|
|
96
101
|
const parseTime = Date.now()
|
|
97
102
|
|
|
98
103
|
const [graph, ...additionalGraphs] = oboGraph.graphs ?? []
|
|
@@ -114,29 +119,52 @@ export async function loadOboGraphJson(this: OntologyStore, db: Database) {
|
|
|
114
119
|
const fullTextIndexPaths = getTextIndexFields
|
|
115
120
|
.call(this)
|
|
116
121
|
.map((def) => def.jsonPath)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
if (graph.nodes) {
|
|
123
|
+
let lastProgress = Math.round(percentProgress)
|
|
124
|
+
for (const [, node] of graph.nodes.entries()) {
|
|
125
|
+
percentProgress += 64 * (1 / graph.nodes.length)
|
|
126
|
+
if (
|
|
127
|
+
Math.round(percentProgress) != lastProgress &&
|
|
128
|
+
percentProgress < 100
|
|
129
|
+
) {
|
|
130
|
+
this.options.update?.('Processing nodes', percentProgress)
|
|
131
|
+
lastProgress = Math.round(percentProgress)
|
|
132
|
+
}
|
|
133
|
+
if (isOntologyDBNode(node)) {
|
|
134
|
+
await nodeStore.add({
|
|
135
|
+
...node,
|
|
136
|
+
fullTextWords: serializeWords(
|
|
137
|
+
getWords(node, fullTextIndexPaths, this.prefixes),
|
|
138
|
+
),
|
|
139
|
+
})
|
|
140
|
+
}
|
|
125
141
|
}
|
|
126
142
|
}
|
|
127
143
|
|
|
128
144
|
// load edges
|
|
129
145
|
const edgeStore = tx.objectStore('edges')
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
146
|
+
if (graph.edges) {
|
|
147
|
+
let lastProgress = Math.round(percentProgress)
|
|
148
|
+
for (const [, edge] of graph.edges.entries()) {
|
|
149
|
+
percentProgress += 30 * (1 / graph.edges.length)
|
|
150
|
+
if (
|
|
151
|
+
Math.round(percentProgress) != lastProgress &&
|
|
152
|
+
percentProgress < 100
|
|
153
|
+
) {
|
|
154
|
+
this.options.update?.('Processing edges', percentProgress)
|
|
155
|
+
lastProgress = Math.round(percentProgress)
|
|
156
|
+
}
|
|
157
|
+
if (isOntologyDBEdge(edge)) {
|
|
158
|
+
await edgeStore.add(edge)
|
|
159
|
+
}
|
|
133
160
|
}
|
|
134
161
|
}
|
|
135
|
-
|
|
136
162
|
await tx.done
|
|
137
163
|
|
|
138
164
|
// record some metadata about this ontology and load operation
|
|
139
165
|
const tx2 = db.transaction('meta', 'readwrite')
|
|
166
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
167
|
+
const { update, ...otherOptions } = this.options
|
|
140
168
|
await tx2.objectStore('meta').add(
|
|
141
169
|
{
|
|
142
170
|
ontologyRecord: {
|
|
@@ -144,7 +172,7 @@ export async function loadOboGraphJson(this: OntologyStore, db: Database) {
|
|
|
144
172
|
version: this.ontologyVersion,
|
|
145
173
|
sourceLocation: this.sourceLocation,
|
|
146
174
|
},
|
|
147
|
-
storeOptions:
|
|
175
|
+
storeOptions: otherOptions,
|
|
148
176
|
graphMeta: graph.meta,
|
|
149
177
|
timestamp: String(new Date()),
|
|
150
178
|
schemaVersion,
|
|
@@ -12,6 +12,7 @@ import { autorun } from 'mobx'
|
|
|
12
12
|
import {
|
|
13
13
|
Instance,
|
|
14
14
|
addDisposer,
|
|
15
|
+
flow,
|
|
15
16
|
getRoot,
|
|
16
17
|
getSnapshot,
|
|
17
18
|
types,
|
|
@@ -30,6 +31,7 @@ export const OntologyRecordType = types
|
|
|
30
31
|
version: 'unversioned',
|
|
31
32
|
source: types.union(LocalPathLocation, UriLocation, BlobLocation),
|
|
32
33
|
options: types.frozen<OntologyStoreOptions>(),
|
|
34
|
+
equivalentTypes: types.map(types.array(types.string)),
|
|
33
35
|
})
|
|
34
36
|
.volatile((_self) => ({
|
|
35
37
|
dataStore: undefined as undefined | OntologyStore,
|
|
@@ -55,6 +57,39 @@ export const OntologyRecordType = types
|
|
|
55
57
|
}),
|
|
56
58
|
)
|
|
57
59
|
},
|
|
60
|
+
setEquivalentTypes(type: string, equivalentTypes: string[]) {
|
|
61
|
+
self.equivalentTypes.set(type, equivalentTypes)
|
|
62
|
+
},
|
|
63
|
+
}))
|
|
64
|
+
.actions((self) => ({
|
|
65
|
+
loadEquivalentTypes: flow(function* loadEquivalentTypes(type: string) {
|
|
66
|
+
if (!self.dataStore) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
const terms = (yield self.dataStore.getTermsWithLabelOrSynonym(
|
|
70
|
+
type,
|
|
71
|
+
)) as unknown as OntologyTerm[]
|
|
72
|
+
const equivalents: string[] = terms
|
|
73
|
+
.map((term) => term.lbl)
|
|
74
|
+
.filter((term) => term != undefined)
|
|
75
|
+
self.setEquivalentTypes(type, equivalents)
|
|
76
|
+
}),
|
|
77
|
+
}))
|
|
78
|
+
.views((self) => ({
|
|
79
|
+
isTypeOf(queryType: string, typeOf: string): boolean {
|
|
80
|
+
if (queryType === typeOf) {
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
if (!self.dataStore) {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
const equivalents = self.equivalentTypes.get(typeOf)
|
|
87
|
+
if (!equivalents) {
|
|
88
|
+
void self.loadEquivalentTypes(typeOf)
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
return equivalents.includes(queryType)
|
|
92
|
+
},
|
|
58
93
|
}))
|
|
59
94
|
|
|
60
95
|
export const OntologyManagerType = types
|
|
@@ -270,6 +270,11 @@ export function stateModelFactory(
|
|
|
270
270
|
return codonLayout
|
|
271
271
|
},
|
|
272
272
|
get featureLayout() {
|
|
273
|
+
const { featureTypeOntology } =
|
|
274
|
+
self.session.apolloDataStore.ontologyManager
|
|
275
|
+
if (!featureTypeOntology) {
|
|
276
|
+
throw new Error('featureTypeOntology is undefined')
|
|
277
|
+
}
|
|
273
278
|
const featureLayout = new Map<number, [string, AnnotationFeature][]>()
|
|
274
279
|
for (const [refSeq, featuresForRefSeq] of this.features || []) {
|
|
275
280
|
if (!featuresForRefSeq) {
|
|
@@ -295,11 +300,15 @@ export function stateModelFactory(
|
|
|
295
300
|
},
|
|
296
301
|
)) {
|
|
297
302
|
for (const [, childFeature] of feature.children ?? new Map()) {
|
|
298
|
-
if (
|
|
303
|
+
if (
|
|
304
|
+
featureTypeOntology.isTypeOf(childFeature.type, 'transcript')
|
|
305
|
+
) {
|
|
299
306
|
for (const [, grandChildFeature] of childFeature.children ||
|
|
300
307
|
new Map()) {
|
|
301
308
|
let startingRow
|
|
302
|
-
if (
|
|
309
|
+
if (
|
|
310
|
+
featureTypeOntology.isTypeOf(grandChildFeature.type, 'CDS')
|
|
311
|
+
) {
|
|
303
312
|
let discontinuousLocations
|
|
304
313
|
if (grandChildFeature.discontinuousLocations.length > 0) {
|
|
305
314
|
;({ discontinuousLocations } = grandChildFeature)
|
|
@@ -21,7 +21,6 @@ import { FeatureAttributes } from './FeatureAttributes'
|
|
|
21
21
|
import { featureContextMenuItems } from './featureContextMenuItems'
|
|
22
22
|
import type { ContextMenuState } from './HybridGrid'
|
|
23
23
|
import { NumberCell } from './NumberCell'
|
|
24
|
-
import { getGlyph } from '../../LinearApolloDisplay/stateModel/getGlyph'
|
|
25
24
|
|
|
26
25
|
const useStyles = makeStyles()((theme) => ({
|
|
27
26
|
typeContent: {
|
|
@@ -134,7 +133,7 @@ export const Feature = observer(function Feature({
|
|
|
134
133
|
displayState.setApolloHover({
|
|
135
134
|
feature,
|
|
136
135
|
topLevelFeature: getTopLevelFeature(feature),
|
|
137
|
-
glyph: getGlyph(getTopLevelFeature(feature)),
|
|
136
|
+
glyph: displayState.getGlyph(getTopLevelFeature(feature)),
|
|
138
137
|
})
|
|
139
138
|
}}
|
|
140
139
|
className={
|
|
@@ -137,7 +137,14 @@ export function featureContextMenuItems(
|
|
|
137
137
|
},
|
|
138
138
|
},
|
|
139
139
|
)
|
|
140
|
-
|
|
140
|
+
const { featureTypeOntology } = session.apolloDataStore.ontologyManager
|
|
141
|
+
if (!featureTypeOntology) {
|
|
142
|
+
throw new Error('featureTypeOntology is undefined')
|
|
143
|
+
}
|
|
144
|
+
if (
|
|
145
|
+
featureTypeOntology.isTypeOf(feature.type, 'transcript') &&
|
|
146
|
+
isSessionModelWithWidgets(session)
|
|
147
|
+
) {
|
|
141
148
|
menuItems.push({
|
|
142
149
|
label: 'Edit transcript details',
|
|
143
150
|
onClick: () => {
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
DialogContent,
|
|
9
9
|
DialogContentText,
|
|
10
10
|
FormControl,
|
|
11
|
-
|
|
11
|
+
Grid2,
|
|
12
12
|
InputLabel,
|
|
13
13
|
MenuItem,
|
|
14
14
|
Select,
|
|
@@ -210,8 +210,8 @@ export function AddRefSeqAliases({
|
|
|
210
210
|
fullWidth
|
|
211
211
|
>
|
|
212
212
|
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
213
|
-
<
|
|
214
|
-
<
|
|
213
|
+
<Grid2 container spacing={2}>
|
|
214
|
+
<Grid2>
|
|
215
215
|
<FormControl disabled={enableSubmit && !errorMessage} fullWidth>
|
|
216
216
|
<InputLabel id="demo-simple-select-label">Assembly</InputLabel>
|
|
217
217
|
<Select
|
|
@@ -228,9 +228,8 @@ export function AddRefSeqAliases({
|
|
|
228
228
|
))}
|
|
229
229
|
</Select>
|
|
230
230
|
</FormControl>
|
|
231
|
-
</
|
|
232
|
-
<
|
|
233
|
-
<Grid item xs={7}>
|
|
231
|
+
</Grid2>
|
|
232
|
+
<Grid2>
|
|
234
233
|
<InputLabel>Load RefName alias</InputLabel>
|
|
235
234
|
<input
|
|
236
235
|
type="file"
|
|
@@ -238,8 +237,8 @@ export function AddRefSeqAliases({
|
|
|
238
237
|
ref={fileRef}
|
|
239
238
|
disabled={(enableSubmit && !errorMessage) || !selectedAssembly}
|
|
240
239
|
/>
|
|
241
|
-
</
|
|
242
|
-
</
|
|
240
|
+
</Grid2>
|
|
241
|
+
</Grid2>
|
|
243
242
|
{selectedAssembly && refNameAliasMap.size > 0 ? (
|
|
244
243
|
<div style={{ height: 200, width: '100%', marginTop: 20 }}>
|
|
245
244
|
<InputLabel>
|
|
@@ -114,7 +114,7 @@ export function CopyFeature({
|
|
|
114
114
|
}
|
|
115
115
|
const newRefNames = [...Object.entries(refNameAliases)]
|
|
116
116
|
.filter(([id, refName]) => id !== refName)
|
|
117
|
-
.map(([id, refName]) => ({ _id: id, name: refName
|
|
117
|
+
.map(([id, refName]) => ({ _id: id, name: refName }))
|
|
118
118
|
setRefNames(newRefNames)
|
|
119
119
|
setSelectedRefSeqId(newRefNames[0]?._id || '')
|
|
120
120
|
}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* eslint-disable react-hooks/exhaustive-deps */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
3
|
+
/* eslint-disable @typescript-eslint/unbound-method */
|
|
4
|
+
import React, { useEffect, useMemo, useState } from 'react'
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
Box,
|
|
8
|
+
Button,
|
|
9
|
+
Checkbox,
|
|
10
|
+
DialogActions,
|
|
11
|
+
DialogContent,
|
|
12
|
+
DialogContentText,
|
|
13
|
+
DialogTitle,
|
|
14
|
+
FormControlLabel,
|
|
15
|
+
MenuItem,
|
|
16
|
+
Select,
|
|
17
|
+
SelectChangeEvent,
|
|
18
|
+
Typography,
|
|
19
|
+
} from '@mui/material'
|
|
20
|
+
|
|
21
|
+
import { Dialog } from './Dialog'
|
|
22
|
+
import { ApolloSessionModel } from '../session'
|
|
23
|
+
import { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
|
|
24
|
+
import { getSnapshot } from 'mobx-state-tree'
|
|
25
|
+
import { AddFeatureChange } from '@apollo-annotation/shared'
|
|
26
|
+
import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
|
|
27
|
+
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
28
|
+
|
|
29
|
+
interface CreateApolloAnnotationProps {
|
|
30
|
+
session: AbstractSessionModel
|
|
31
|
+
handleClose(): void
|
|
32
|
+
annotationFeature: AnnotationFeatureSnapshot
|
|
33
|
+
assembly: Assembly
|
|
34
|
+
refSeqId: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const isGeneOrTranscript = (
|
|
38
|
+
annotationFeature: AnnotationFeatureSnapshot,
|
|
39
|
+
apolloSessionModel: ApolloSessionModel,
|
|
40
|
+
) => {
|
|
41
|
+
const { featureTypeOntology } =
|
|
42
|
+
apolloSessionModel.apolloDataStore.ontologyManager
|
|
43
|
+
if (!featureTypeOntology) {
|
|
44
|
+
throw new Error('featureTypeOntology is undefined')
|
|
45
|
+
}
|
|
46
|
+
return (
|
|
47
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'gene') ||
|
|
48
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'mRNA') ||
|
|
49
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript')
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isTranscript = (
|
|
54
|
+
annotationFeature: AnnotationFeatureSnapshot,
|
|
55
|
+
apolloSessionModel: ApolloSessionModel,
|
|
56
|
+
) => {
|
|
57
|
+
const { featureTypeOntology } =
|
|
58
|
+
apolloSessionModel.apolloDataStore.ontologyManager
|
|
59
|
+
if (!featureTypeOntology) {
|
|
60
|
+
throw new Error('featureTypeOntology is undefined')
|
|
61
|
+
}
|
|
62
|
+
return (
|
|
63
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'mRNA') ||
|
|
64
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript')
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function CreateApolloAnnotation({
|
|
69
|
+
annotationFeature,
|
|
70
|
+
assembly,
|
|
71
|
+
handleClose,
|
|
72
|
+
refSeqId,
|
|
73
|
+
session,
|
|
74
|
+
}: CreateApolloAnnotationProps) {
|
|
75
|
+
const apolloSessionModel = session as unknown as ApolloSessionModel
|
|
76
|
+
const childIds = useMemo(
|
|
77
|
+
() => Object.keys(annotationFeature.children ?? {}),
|
|
78
|
+
[annotationFeature],
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
const features = useMemo(() => {
|
|
82
|
+
for (const [, asm] of apolloSessionModel.apolloDataStore.assemblies) {
|
|
83
|
+
if (asm._id === assembly.name) {
|
|
84
|
+
for (const [, refSeq] of asm.refSeqs) {
|
|
85
|
+
if (refSeq._id === refSeqId) {
|
|
86
|
+
return refSeq.features
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return []
|
|
92
|
+
}, [])
|
|
93
|
+
|
|
94
|
+
const [parentFeatureChecked, setParentFeatureChecked] = useState(true)
|
|
95
|
+
const [checkedChildrens, setCheckedChildrens] = useState<string[]>(childIds)
|
|
96
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
97
|
+
const [destinationFeatures, setDestinationFeatures] = useState<
|
|
98
|
+
AnnotationFeatureSnapshot[]
|
|
99
|
+
>([])
|
|
100
|
+
const [selectedDestinationFeature, setSelectedDestinationFeature] =
|
|
101
|
+
useState<AnnotationFeatureSnapshot>()
|
|
102
|
+
|
|
103
|
+
const getFeatures = (min: number, max: number) => {
|
|
104
|
+
const filteredFeatures: AnnotationFeatureSnapshot[] = []
|
|
105
|
+
|
|
106
|
+
for (const [, f] of features) {
|
|
107
|
+
const featureSnapshot = getSnapshot(f)
|
|
108
|
+
if (min >= featureSnapshot.min && max <= featureSnapshot.max) {
|
|
109
|
+
filteredFeatures.push(featureSnapshot)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return filteredFeatures
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
setErrorMessage('')
|
|
118
|
+
if (checkedChildrens.length === 0) {
|
|
119
|
+
setParentFeatureChecked(false)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (annotationFeature.children) {
|
|
124
|
+
const checkedAnnotationFeatureChildren = Object.values(
|
|
125
|
+
annotationFeature.children,
|
|
126
|
+
)
|
|
127
|
+
.filter((child) => isTranscript(child, apolloSessionModel))
|
|
128
|
+
.filter((child) => checkedChildrens.includes(child._id))
|
|
129
|
+
const mins = checkedAnnotationFeatureChildren.map((f) => f.min)
|
|
130
|
+
const maxes = checkedAnnotationFeatureChildren.map((f) => f.max)
|
|
131
|
+
const min = Math.min(...mins)
|
|
132
|
+
const max = Math.max(...maxes)
|
|
133
|
+
const filteredFeatures = getFeatures(min, max)
|
|
134
|
+
setDestinationFeatures(filteredFeatures)
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
filteredFeatures.length === 0 &&
|
|
138
|
+
checkedChildrens.length > 0 &&
|
|
139
|
+
!parentFeatureChecked
|
|
140
|
+
) {
|
|
141
|
+
setErrorMessage('No destination features found')
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}, [checkedChildrens])
|
|
145
|
+
|
|
146
|
+
const handleParentFeatureCheck = (
|
|
147
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
148
|
+
) => {
|
|
149
|
+
const isChecked = event.target.checked
|
|
150
|
+
setParentFeatureChecked(isChecked)
|
|
151
|
+
setCheckedChildrens(isChecked ? childIds : [])
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const handleChildFeatureCheck = (
|
|
155
|
+
event: React.ChangeEvent<HTMLInputElement>,
|
|
156
|
+
child: AnnotationFeatureSnapshot,
|
|
157
|
+
) => {
|
|
158
|
+
setCheckedChildrens((prevChecked) =>
|
|
159
|
+
event.target.checked
|
|
160
|
+
? [...prevChecked, child._id]
|
|
161
|
+
: prevChecked.filter((childId) => childId !== child._id),
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const handleDestinationFeatureChange = (e: SelectChangeEvent) => {
|
|
166
|
+
const selectedFeature = destinationFeatures.find(
|
|
167
|
+
(f) => f._id === e.target.value,
|
|
168
|
+
)
|
|
169
|
+
setSelectedDestinationFeature(selectedFeature)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const handleCreateApolloAnnotation = async () => {
|
|
173
|
+
if (parentFeatureChecked) {
|
|
174
|
+
const change = new AddFeatureChange({
|
|
175
|
+
changedIds: [annotationFeature._id],
|
|
176
|
+
typeName: 'AddFeatureChange',
|
|
177
|
+
assembly: assembly.name,
|
|
178
|
+
addedFeature: annotationFeature,
|
|
179
|
+
})
|
|
180
|
+
await apolloSessionModel.apolloDataStore.changeManager.submit(change)
|
|
181
|
+
session.notify('Annotation added successfully', 'success')
|
|
182
|
+
handleClose()
|
|
183
|
+
} else {
|
|
184
|
+
if (!annotationFeature.children) {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
if (!selectedDestinationFeature) {
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
for (const childId of checkedChildrens) {
|
|
192
|
+
const child = annotationFeature.children[childId]
|
|
193
|
+
const change = new AddFeatureChange({
|
|
194
|
+
parentFeatureId: selectedDestinationFeature._id,
|
|
195
|
+
changedIds: [selectedDestinationFeature._id],
|
|
196
|
+
typeName: 'AddFeatureChange',
|
|
197
|
+
assembly: assembly.name,
|
|
198
|
+
addedFeature: child,
|
|
199
|
+
})
|
|
200
|
+
await apolloSessionModel.apolloDataStore.changeManager.submit(change)
|
|
201
|
+
session.notify('Annotation added successfully', 'success')
|
|
202
|
+
handleClose()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<Dialog
|
|
209
|
+
open
|
|
210
|
+
title="Create Apollo Annotation"
|
|
211
|
+
handleClose={handleClose}
|
|
212
|
+
fullWidth={true}
|
|
213
|
+
maxWidth="sm"
|
|
214
|
+
>
|
|
215
|
+
<DialogTitle fontSize={15}>
|
|
216
|
+
Select the feature to be copied to apollo track
|
|
217
|
+
</DialogTitle>
|
|
218
|
+
<DialogContent>
|
|
219
|
+
<Box sx={{ ml: 3 }}>
|
|
220
|
+
{isGeneOrTranscript(annotationFeature, apolloSessionModel) && (
|
|
221
|
+
<FormControlLabel
|
|
222
|
+
control={
|
|
223
|
+
<Checkbox
|
|
224
|
+
size="small"
|
|
225
|
+
checked={parentFeatureChecked}
|
|
226
|
+
onChange={handleParentFeatureCheck}
|
|
227
|
+
/>
|
|
228
|
+
}
|
|
229
|
+
label={`${annotationFeature.type}:${annotationFeature.min}..${annotationFeature.max}`}
|
|
230
|
+
/>
|
|
231
|
+
)}
|
|
232
|
+
{annotationFeature.children && (
|
|
233
|
+
<Box sx={{ display: 'flex', flexDirection: 'column', ml: 3 }}>
|
|
234
|
+
{Object.values(annotationFeature.children)
|
|
235
|
+
.filter((child) => isTranscript(child, apolloSessionModel))
|
|
236
|
+
.map((child) => (
|
|
237
|
+
<FormControlLabel
|
|
238
|
+
key={child._id}
|
|
239
|
+
control={
|
|
240
|
+
<Checkbox
|
|
241
|
+
size="small"
|
|
242
|
+
checked={checkedChildrens.includes(child._id)}
|
|
243
|
+
onChange={(e) => {
|
|
244
|
+
handleChildFeatureCheck(e, child)
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
}
|
|
248
|
+
label={`${child.type}:${child.min}..${child.max}`}
|
|
249
|
+
/>
|
|
250
|
+
))}
|
|
251
|
+
</Box>
|
|
252
|
+
)}
|
|
253
|
+
</Box>
|
|
254
|
+
{!parentFeatureChecked &&
|
|
255
|
+
checkedChildrens.length > 0 &&
|
|
256
|
+
destinationFeatures.length > 0 && (
|
|
257
|
+
<Box sx={{ ml: 3 }}>
|
|
258
|
+
<Typography variant="caption" fontSize={12}>
|
|
259
|
+
Select the destination feature to copy the selected features
|
|
260
|
+
</Typography>
|
|
261
|
+
|
|
262
|
+
<Box sx={{ mt: 1 }}>
|
|
263
|
+
<Select
|
|
264
|
+
labelId="label"
|
|
265
|
+
style={{ width: '100%' }}
|
|
266
|
+
value={selectedDestinationFeature?._id ?? ''}
|
|
267
|
+
onChange={handleDestinationFeatureChange}
|
|
268
|
+
>
|
|
269
|
+
{destinationFeatures.map((f) => (
|
|
270
|
+
<MenuItem key={f._id} value={f._id}>
|
|
271
|
+
{`${f.type}:${f.min}..${f.max}`}
|
|
272
|
+
</MenuItem>
|
|
273
|
+
))}
|
|
274
|
+
</Select>
|
|
275
|
+
</Box>
|
|
276
|
+
</Box>
|
|
277
|
+
)}
|
|
278
|
+
</DialogContent>
|
|
279
|
+
<DialogActions>
|
|
280
|
+
<Button
|
|
281
|
+
variant="contained"
|
|
282
|
+
type="submit"
|
|
283
|
+
disabled={
|
|
284
|
+
checkedChildrens.length === 0 ||
|
|
285
|
+
(!parentFeatureChecked &&
|
|
286
|
+
checkedChildrens.length > 0 &&
|
|
287
|
+
!selectedDestinationFeature)
|
|
288
|
+
}
|
|
289
|
+
onClick={handleCreateApolloAnnotation}
|
|
290
|
+
>
|
|
291
|
+
Create
|
|
292
|
+
</Button>
|
|
293
|
+
<Button variant="outlined" type="submit" onClick={handleClose}>
|
|
294
|
+
Cancel
|
|
295
|
+
</Button>
|
|
296
|
+
</DialogActions>
|
|
297
|
+
{errorMessage ? (
|
|
298
|
+
<DialogContent>
|
|
299
|
+
<DialogContentText color="error">{errorMessage}</DialogContentText>
|
|
300
|
+
</DialogContent>
|
|
301
|
+
) : null}
|
|
302
|
+
</Dialog>
|
|
303
|
+
)
|
|
304
|
+
}
|
|
@@ -111,7 +111,11 @@ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
|
|
|
111
111
|
const { exportID } = (await response.json()) as { exportID: string }
|
|
112
112
|
|
|
113
113
|
const exportURL = new URL('export', internetAccount.baseURL)
|
|
114
|
-
const
|
|
114
|
+
const params: Record<string, string> = {
|
|
115
|
+
exportID,
|
|
116
|
+
includeFASTA: 'true',
|
|
117
|
+
}
|
|
118
|
+
const exportSearchParams = new URLSearchParams(params)
|
|
115
119
|
exportURL.search = exportSearchParams.toString()
|
|
116
120
|
const exportUri = exportURL.toString()
|
|
117
121
|
|