@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.
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
@@ -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
- for (const node of graph.nodes ?? []) {
118
- if (isOntologyDBNode(node)) {
119
- await nodeStore.add({
120
- ...node,
121
- fullTextWords: serializeWords(
122
- getWords(node, fullTextIndexPaths, this.prefixes),
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
- for (const edge of graph.edges ?? []) {
131
- if (isOntologyDBEdge(edge)) {
132
- await edgeStore.add(edge)
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: this.options,
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 (childFeature.type === 'mRNA') {
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 (grandChildFeature.type === 'CDS') {
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={
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-call */
2
1
  import { Menu, MenuItem } from '@jbrowse/core/ui'
3
2
  import { useTheme } from '@mui/material'
4
3
  import { observer } from 'mobx-react'
@@ -137,7 +137,14 @@ export function featureContextMenuItems(
137
137
  },
138
138
  },
139
139
  )
140
- if (feature.type === 'mRNA' && isSessionModelWithWidgets(session)) {
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
- Grid,
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
- <Grid container spacing={2}>
214
- <Grid item xs={4}>
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
- </Grid>
232
- <Grid item xs={1}></Grid>
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
- </Grid>
242
- </Grid>
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 exportSearchParams = new URLSearchParams({ exportID })
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