@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 0.3.7

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 (56) hide show
  1. package/dist/index.esm.js +2679 -850
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +2676 -847
  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 +5194 -1258
  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 +4 -4
  12. package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
  13. package/src/ChangeManager.ts +10 -6
  14. package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
  15. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +12 -20
  16. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +929 -175
  17. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
  18. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -1
  19. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +48 -60
  20. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +244 -51
  21. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +46 -1
  22. package/src/LinearApolloDisplay/glyphs/Glyph.ts +9 -1
  23. package/src/LinearApolloDisplay/stateModel/base.ts +29 -0
  24. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +51 -35
  25. package/src/LinearApolloDisplay/stateModel/rendering.ts +2 -1
  26. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
  27. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
  28. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +243 -124
  29. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -1
  30. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +19 -3
  31. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +53 -34
  32. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +4 -2
  33. package/src/OntologyManager/index.ts +4 -1
  34. package/src/TabularEditor/HybridGrid/Feature.tsx +4 -0
  35. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
  36. package/src/components/AddAssemblyAliases.tsx +114 -0
  37. package/src/components/AddChildFeature.tsx +3 -6
  38. package/src/components/AddFeature.tsx +14 -15
  39. package/src/components/CopyFeature.tsx +2 -4
  40. package/src/components/CreateApolloAnnotation.tsx +334 -151
  41. package/src/components/DeleteFeature.tsx +358 -11
  42. package/src/components/DownloadGFF3.tsx +20 -1
  43. package/src/components/FilterTranscripts.tsx +86 -0
  44. package/src/components/MergeExons.tsx +193 -0
  45. package/src/components/MergeTranscripts.tsx +185 -0
  46. package/src/components/SplitExon.tsx +134 -0
  47. package/src/components/index.ts +3 -0
  48. package/src/config.ts +5 -0
  49. package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
  50. package/src/extensions/annotationFromPileup.ts +99 -89
  51. package/src/session/session.ts +26 -13
  52. package/src/util/annotationFeatureUtils.ts +65 -0
  53. package/src/util/copyToClipboard.ts +21 -0
  54. package/src/util/glyphUtils.ts +49 -0
  55. package/src/util/index.ts +2 -0
  56. package/src/util/mouseEventsUtils.ts +113 -0
@@ -83,7 +83,7 @@ export function CopyFeature({
83
83
  sourceAssemblyId,
84
84
  sourceFeature,
85
85
  }: CopyFeatureProps) {
86
- const { assemblyManager, notify } = session as unknown as AbstractSessionModel
86
+ const { assemblyManager } = session as unknown as AbstractSessionModel
87
87
  const assemblies = assemblyManager.assemblyList
88
88
 
89
89
  const [selectedAssemblyId, setSelectedAssemblyId] = useState<
@@ -203,9 +203,7 @@ export function CopyFeature({
203
203
  copyFeature: true,
204
204
  allIds: featureIds,
205
205
  })
206
- await changeManager.submit(change)
207
-
208
- notify('Feature copied successfully', 'success')
206
+ void changeManager.submit(change)
209
207
  handleClose()
210
208
  event.preventDefault()
211
209
  }
@@ -3,7 +3,11 @@
3
3
  /* eslint-disable @typescript-eslint/unbound-method */
4
4
 
5
5
  import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
6
- import { AddFeatureChange } from '@apollo-annotation/shared'
6
+ import {
7
+ AddFeatureChange,
8
+ LocationEndChange,
9
+ LocationStartChange,
10
+ } from '@apollo-annotation/shared'
7
11
  import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
8
12
  import { type AbstractSessionModel } from '@jbrowse/core/util'
9
13
  import {
@@ -15,11 +19,13 @@ import {
15
19
  DialogContentText,
16
20
  DialogTitle,
17
21
  FormControlLabel,
22
+ FormGroup,
18
23
  MenuItem,
19
24
  Select,
20
25
  type SelectChangeEvent,
21
26
  Typography,
22
27
  } from '@mui/material'
28
+ import ObjectID from 'bson-objectid'
23
29
  import { getSnapshot } from 'mobx-state-tree'
24
30
  import React, { useEffect, useMemo, useState } from 'react'
25
31
 
@@ -33,6 +39,10 @@ interface CreateApolloAnnotationProps {
33
39
  annotationFeature: AnnotationFeatureSnapshot
34
40
  assembly: Assembly
35
41
  refSeqId: string
42
+ region: {
43
+ start: number
44
+ end: number
45
+ }
36
46
  }
37
47
 
38
48
  const isGeneOrTranscript = (
@@ -88,41 +98,59 @@ const isTranscript = (
88
98
  )
89
99
  }
90
100
 
91
- const getFeatureId = (feature: AnnotationFeatureSnapshot) => {
101
+ export function getFeatureName(feature: AnnotationFeatureSnapshot) {
92
102
  const { attributes } = feature
93
- const id = attributes?.id
94
- if (id) {
95
- return id[0]
103
+ const keys = ['name', 'gff_name', 'transcript_name', 'gene_name']
104
+ for (const key of keys) {
105
+ const value = attributes?.[key]
106
+ if (value?.[0]) {
107
+ return value[0]
108
+ }
96
109
  }
97
- return feature.type
110
+ return ''
98
111
  }
99
112
 
100
- const getFeatureNameOrId = (
101
- feature: AnnotationFeatureSnapshot,
102
- apolloSessionModel: ApolloSessionModel,
103
- ) => {
104
- const { featureTypeOntology } =
105
- apolloSessionModel.apolloDataStore.ontologyManager
106
- if (!featureTypeOntology) {
107
- return getFeatureId(feature)
108
- }
109
-
110
- let attrName = ''
111
-
112
- if (featureTypeOntology.isTypeOf(feature.type, 'gene')) {
113
- attrName = 'gene_name'
113
+ export function getGeneNameOrId(feature: AnnotationFeatureSnapshot) {
114
+ const { attributes } = feature
115
+ const keys = ['gene_name', 'gene_id', 'gene_stable_id']
116
+ for (const key of keys) {
117
+ const value = attributes?.[key]
118
+ if (value?.[0]) {
119
+ return value[0]
120
+ }
114
121
  }
122
+ return ''
123
+ }
115
124
 
116
- if (featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
117
- attrName = 'transcript_name'
125
+ export function getFeatureId(feature: AnnotationFeatureSnapshot) {
126
+ const { attributes } = feature
127
+ const keys = [
128
+ 'id',
129
+ 'gff_id',
130
+ 'transcript_id',
131
+ 'gene_id',
132
+ 'gene_stable_id',
133
+ 'stable_id',
134
+ ]
135
+ for (const key of keys) {
136
+ const value = attributes?.[key]
137
+ if (value?.[0]) {
138
+ return value[0]
139
+ }
118
140
  }
141
+ return ''
142
+ }
119
143
 
120
- const { attributes } = feature
121
- const name = attributes?.[attrName]
144
+ const getFeatureNameOrId = (feature: AnnotationFeatureSnapshot) => {
145
+ const name = getFeatureName(feature)
146
+ const id = getFeatureId(feature)
122
147
  if (name) {
123
- return name[0]
148
+ return `${feature.type} - ${name}`
124
149
  }
125
- return getFeatureId(feature)
150
+ if (id) {
151
+ return `${feature.type} - ${id}`
152
+ }
153
+ return feature.type
126
154
  }
127
155
 
128
156
  export function CreateApolloAnnotation({
@@ -131,44 +159,46 @@ export function CreateApolloAnnotation({
131
159
  handleClose,
132
160
  refSeqId,
133
161
  session,
162
+ region,
134
163
  }: CreateApolloAnnotationProps) {
135
164
  const apolloSessionModel = session as unknown as ApolloSessionModel
165
+ const { featureTypeOntology } =
166
+ apolloSessionModel.apolloDataStore.ontologyManager
136
167
  const childIds = useMemo(
137
168
  () => Object.keys(annotationFeature.children ?? {}),
138
169
  [annotationFeature],
139
170
  )
140
171
 
141
- const features = useMemo(() => {
142
- for (const [, asm] of apolloSessionModel.apolloDataStore.assemblies) {
143
- if (asm._id === assembly.name) {
144
- for (const [, refSeq] of asm.refSeqs) {
145
- if (refSeq._id === refSeqId) {
146
- return refSeq.features
147
- }
148
- }
149
- }
150
- }
151
- return []
152
- }, [])
153
-
154
172
  const [parentFeatureChecked, setParentFeatureChecked] = useState(true)
155
173
  const [checkedChildrens, setCheckedChildrens] = useState<string[]>(childIds)
156
174
  const [errorMessage, setErrorMessage] = useState('')
157
175
  const [destinationFeatures, setDestinationFeatures] = useState<
158
176
  AnnotationFeatureSnapshot[]
159
177
  >([])
178
+ const [createNewGene, setCreateNewGene] = useState(false)
160
179
  const [selectedDestinationFeature, setSelectedDestinationFeature] =
161
180
  useState<AnnotationFeatureSnapshot>()
162
181
 
163
- const getFeatures = (min: number, max: number) => {
182
+ const apolloAssembly = apolloSessionModel.apolloDataStore.assemblies.get(
183
+ assembly.name,
184
+ )
185
+ const refSeq = apolloAssembly?.refSeqs.get(refSeqId)
186
+ const features = refSeq?.getFeatures(region.start, region.end)
187
+
188
+ const getDestinationFeatures = () => {
164
189
  const filteredFeatures: AnnotationFeatureSnapshot[] = []
165
190
 
166
- for (const [, f] of features) {
167
- if (f.type === 'chromosome') {
191
+ for (const f of features ?? []) {
192
+ if (f.min > region.end || f.max < region.start) {
168
193
  continue
169
194
  }
170
- const featureSnapshot = getSnapshot(f)
171
- if (min >= featureSnapshot.min && max <= featureSnapshot.max) {
195
+
196
+ // Destination feature should be of type gene amd should be on the same strand as the source feature
197
+ if (
198
+ featureTypeOntology?.isTypeOf(f.type, 'gene') &&
199
+ f.strand === annotationFeature.strand
200
+ ) {
201
+ const featureSnapshot = getSnapshot(f)
172
202
  filteredFeatures.push(featureSnapshot)
173
203
  }
174
204
  }
@@ -178,34 +208,10 @@ export function CreateApolloAnnotation({
178
208
 
179
209
  useEffect(() => {
180
210
  setErrorMessage('')
181
- let mins: number[] = []
182
- let maxes: number[] = []
183
- if (annotationFeature.children) {
184
- const checkedAnnotationFeatureChildren = Object.values(
185
- annotationFeature.children,
186
- )
187
- .filter((child) => isTranscript(child, apolloSessionModel))
188
- .filter((child) => checkedChildrens.includes(child._id))
189
- mins = checkedAnnotationFeatureChildren.map((f) => f.min)
190
- maxes = checkedAnnotationFeatureChildren.map((f) => f.max)
191
- }
192
-
193
- const { featureTypeOntology } =
194
- apolloSessionModel.apolloDataStore.ontologyManager
195
- if (
196
- featureTypeOntology &&
197
- featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript')
198
- ) {
199
- mins = [annotationFeature.min, ...mins]
200
- maxes = [annotationFeature.max, ...maxes]
201
- }
202
-
203
- const min = Math.min(...mins)
204
- const max = Math.max(...maxes)
205
- const filteredFeatures = getFeatures(min, max)
206
- setDestinationFeatures(filteredFeatures)
207
- setSelectedDestinationFeature(filteredFeatures[0])
208
- }, [checkedChildrens, parentFeatureChecked])
211
+ const features = getDestinationFeatures()
212
+ setDestinationFeatures(features)
213
+ setSelectedDestinationFeature(features[0])
214
+ }, [checkedChildrens, parentFeatureChecked, region])
209
215
 
210
216
  const handleParentFeatureCheck = (
211
217
  event: React.ChangeEvent<HTMLInputElement>,
@@ -235,82 +241,236 @@ export function CreateApolloAnnotation({
235
241
 
236
242
  const handleCreateApolloAnnotation = async () => {
237
243
  if (parentFeatureChecked) {
238
- let change
244
+ // IF SOURCE FEATURE IS GENE
239
245
  if (isGene(annotationFeature, apolloSessionModel)) {
240
- if (
241
- annotationFeature.children &&
242
- checkedChildrens.length !==
243
- Object.values(annotationFeature.children).length
244
- ) {
245
- const childrens: Record<string, AnnotationFeatureSnapshot> = {}
246
- for (const childId of checkedChildrens) {
247
- childrens[childId] = annotationFeature.children[childId]
248
- }
249
- change = new AddFeatureChange({
250
- changedIds: [annotationFeature._id],
251
- typeName: 'AddFeatureChange',
252
- assembly: assembly.name,
253
- addedFeature: {
254
- ...annotationFeature,
255
- children: childrens,
256
- },
257
- })
258
- } else {
259
- change = new AddFeatureChange({
260
- changedIds: [annotationFeature._id],
261
- typeName: 'AddFeatureChange',
262
- assembly: assembly.name,
263
- addedFeature: annotationFeature,
264
- })
265
- }
246
+ await copyGeneFeature()
247
+ session.notify(
248
+ 'Successfully copied selected gene and transcript(s)',
249
+ 'success',
250
+ )
266
251
  }
267
-
268
252
  if (isTranscript(annotationFeature, apolloSessionModel)) {
269
- if (selectedDestinationFeature) {
270
- change = new AddFeatureChange({
271
- parentFeatureId: selectedDestinationFeature._id,
272
- changedIds: [selectedDestinationFeature._id],
273
- typeName: 'AddFeatureChange',
274
- assembly: assembly.name,
275
- addedFeature: annotationFeature,
276
- })
253
+ // IF THE SOURCE IS TRANSCRIPT AND THE DESTINATION IS SELECTED AND CREATE NEW GENE IS NOT CHECKED
254
+ if (selectedDestinationFeature && !createNewGene) {
255
+ const transcripts: Record<string, AnnotationFeatureSnapshot> = {}
256
+ transcripts[annotationFeature._id] = annotationFeature
257
+
258
+ // If source trancript doesn't overlap with destination gene
259
+ // If not overlapping, then extend the destination gene to include the transcript
260
+ if (
261
+ selectedDestinationFeature.max < annotationFeature.max ||
262
+ selectedDestinationFeature.min > annotationFeature.min
263
+ ) {
264
+ const newMin = Math.min(
265
+ selectedDestinationFeature.min,
266
+ annotationFeature.min,
267
+ )
268
+ const newMax = Math.max(
269
+ selectedDestinationFeature.max,
270
+ annotationFeature.max,
271
+ )
272
+ await extendSelectedDestinationFeatureLocation(newMin, newMax)
273
+ await copyTranscriptsToDestinationGene(transcripts)
274
+ } else {
275
+ await copyTranscriptsToDestinationGene(transcripts)
276
+ }
277
+ session.notify(
278
+ 'Successfully copied selected transcripts to destination gene',
279
+ 'success',
280
+ )
277
281
  } else {
278
- setErrorMessage('There is no destination gene for this transcript')
279
- return
282
+ // IF THERE IS NO DESTINATION GENE SELECTED AND CREATE NEW GENE IS CHECKED
283
+ const childrens: Record<string, AnnotationFeatureSnapshot> = {}
284
+ childrens[annotationFeature._id] = annotationFeature
285
+ await createNewGeneFeatureWithTranscripts(childrens)
286
+ session.notify(
287
+ 'Successfully created a new gene with selected transcripts',
288
+ 'success',
289
+ )
280
290
  }
281
291
  }
282
-
283
- if (!change) {
284
- return
285
- }
286
-
287
- await apolloSessionModel.apolloDataStore.changeManager.submit(change)
288
- session.notify('Annotation added successfully', 'success')
289
- handleClose()
290
292
  } else {
293
+ // IF PARENT (GENE) FEATURE IS NOT CHECKED AND WE ARE COPYING CHILDREN (TRANSCRIPTS)
291
294
  if (!annotationFeature.children) {
292
295
  return
293
296
  }
294
- if (!selectedDestinationFeature) {
295
- return
297
+
298
+ // IF DESTINATION IS SELECTED AND CREATE NEW GENE IS NOT CHECKED
299
+ if (selectedDestinationFeature && !createNewGene) {
300
+ const childrens: Record<string, AnnotationFeatureSnapshot> = {}
301
+ for (const childId of checkedChildrens) {
302
+ childrens[childId] = annotationFeature.children[childId]
303
+ }
304
+ const min = Math.min(
305
+ ...Object.values(childrens).map((child) => child.min),
306
+ )
307
+ const max = Math.max(
308
+ ...Object.values(childrens).map((child) => child.max),
309
+ )
310
+
311
+ // If source trancript doesn't overlap with destination gene
312
+ // If not overlapping, then extend the destination gene to include the transcript
313
+ if (
314
+ selectedDestinationFeature.min > min ||
315
+ selectedDestinationFeature.max < max
316
+ ) {
317
+ const newMin = Math.min(selectedDestinationFeature.min, min)
318
+ const newMax = Math.max(selectedDestinationFeature.max, max)
319
+ await extendSelectedDestinationFeatureLocation(newMin, newMax)
320
+ await copyTranscriptsToDestinationGene(childrens)
321
+ } else {
322
+ await copyTranscriptsToDestinationGene(childrens)
323
+ }
324
+ session.notify(
325
+ 'Successfully copied transcript to destination gene',
326
+ 'success',
327
+ )
328
+ } else {
329
+ // IF THERE IS NO DESTINATION GENE SELECTED AND CREATE NEW GENE IS CHECKED
330
+ const childrens: Record<string, AnnotationFeatureSnapshot> = {}
331
+ for (const childId of checkedChildrens) {
332
+ childrens[childId] = annotationFeature.children[childId]
333
+ }
334
+ await createNewGeneFeatureWithTranscripts(childrens)
335
+ session.notify(
336
+ 'Successfully created a new gene with selected transcript',
337
+ 'success',
338
+ )
296
339
  }
340
+ }
341
+ handleClose()
342
+ }
297
343
 
344
+ // Copies gene feature along with its selected children
345
+ const copyGeneFeature = async () => {
346
+ let change
347
+ if (
348
+ annotationFeature.children &&
349
+ checkedChildrens.length !==
350
+ Object.values(annotationFeature.children).length
351
+ ) {
352
+ // IF SOME CHILDREN ARE CHECKED
353
+ const childrens: Record<string, AnnotationFeatureSnapshot> = {}
298
354
  for (const childId of checkedChildrens) {
299
- const child = annotationFeature.children[childId]
300
- const change = new AddFeatureChange({
301
- parentFeatureId: selectedDestinationFeature._id,
355
+ childrens[childId] = annotationFeature.children[childId]
356
+ }
357
+ change = new AddFeatureChange({
358
+ changedIds: [annotationFeature._id],
359
+ typeName: 'AddFeatureChange',
360
+ assembly: assembly.name,
361
+ addedFeature: {
362
+ ...annotationFeature,
363
+ children: childrens,
364
+ },
365
+ })
366
+ } else {
367
+ // IF PARENT AND ALL CHILDREN ARE CHECKED
368
+ change = new AddFeatureChange({
369
+ changedIds: [annotationFeature._id],
370
+ typeName: 'AddFeatureChange',
371
+ assembly: assembly.name,
372
+ addedFeature: annotationFeature,
373
+ })
374
+ }
375
+
376
+ await submitChange(change)
377
+ }
378
+
379
+ const copyTranscriptsToDestinationGene = async (
380
+ transcripts: Record<string, AnnotationFeatureSnapshot>,
381
+ ) => {
382
+ if (!selectedDestinationFeature) {
383
+ return
384
+ }
385
+ for (const transcriptId of Object.keys(transcripts)) {
386
+ const transcript = transcripts[transcriptId]
387
+ const change = new AddFeatureChange({
388
+ parentFeatureId: selectedDestinationFeature._id,
389
+ changedIds: [selectedDestinationFeature._id],
390
+ typeName: 'AddFeatureChange',
391
+ assembly: assembly.name,
392
+ addedFeature: transcript,
393
+ })
394
+ await submitChange(change)
395
+ }
396
+ }
397
+
398
+ const createNewGeneFeatureWithTranscripts = async (
399
+ childrens: Record<string, AnnotationFeatureSnapshot>,
400
+ ) => {
401
+ const newGeneId = new ObjectID().toHexString()
402
+ const min = Math.min(...Object.values(childrens).map((child) => child.min))
403
+ const max = Math.max(...Object.values(childrens).map((child) => child.max))
404
+ const change = new AddFeatureChange({
405
+ changedIds: [newGeneId],
406
+ typeName: 'AddFeatureChange',
407
+ assembly: assembly.name,
408
+ addedFeature: {
409
+ _id: newGeneId,
410
+ refSeq: refSeqId,
411
+ min,
412
+ max,
413
+ strand: annotationFeature.strand,
414
+ type: 'gene',
415
+ children: childrens,
416
+ attributes: {
417
+ name: [getGeneNameOrId(annotationFeature)],
418
+ gene_name: [getGeneNameOrId(annotationFeature)],
419
+ },
420
+ },
421
+ })
422
+ await submitChange(change)
423
+ }
424
+
425
+ const extendSelectedDestinationFeatureLocation = async (
426
+ newMin: number,
427
+ newMax: number,
428
+ ) => {
429
+ if (!selectedDestinationFeature) {
430
+ return
431
+ }
432
+ const changes = []
433
+ if (newMin !== selectedDestinationFeature.min) {
434
+ changes.push(
435
+ new LocationStartChange({
436
+ typeName: 'LocationStartChange',
302
437
  changedIds: [selectedDestinationFeature._id],
303
- typeName: 'AddFeatureChange',
438
+ featureId: selectedDestinationFeature._id,
304
439
  assembly: assembly.name,
305
- addedFeature: child,
306
- })
307
- await apolloSessionModel.apolloDataStore.changeManager.submit(change)
308
- }
309
- session.notify('Annotation added successfully', 'success')
310
- handleClose()
440
+ oldStart: selectedDestinationFeature.min,
441
+ newStart: newMin,
442
+ }),
443
+ )
444
+ }
445
+ if (newMax !== selectedDestinationFeature.max) {
446
+ changes.push(
447
+ new LocationEndChange({
448
+ typeName: 'LocationEndChange',
449
+ changedIds: [selectedDestinationFeature._id],
450
+ featureId: selectedDestinationFeature._id,
451
+ assembly: assembly.name,
452
+ oldEnd: selectedDestinationFeature.max,
453
+ newEnd: newMax,
454
+ }),
455
+ )
456
+ }
457
+ for (const change of changes) {
458
+ await submitChange(change)
311
459
  }
312
460
  }
313
461
 
462
+ const submitChange = async (
463
+ change: AddFeatureChange | LocationStartChange | LocationEndChange,
464
+ ) => {
465
+ await apolloSessionModel.apolloDataStore.changeManager.submit(change)
466
+ }
467
+
468
+ const handleCreateNewGeneChange = (
469
+ e: React.ChangeEvent<HTMLInputElement>,
470
+ ) => {
471
+ setCreateNewGene(e.target.checked)
472
+ }
473
+
314
474
  return (
315
475
  <Dialog
316
476
  open
@@ -333,7 +493,7 @@ export function CreateApolloAnnotation({
333
493
  onChange={handleParentFeatureCheck}
334
494
  />
335
495
  }
336
- label={`${getFeatureNameOrId(annotationFeature, apolloSessionModel)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
496
+ label={`${getFeatureNameOrId(annotationFeature)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
337
497
  />
338
498
  )}
339
499
  {annotationFeature.children && (
@@ -352,7 +512,7 @@ export function CreateApolloAnnotation({
352
512
  }}
353
513
  />
354
514
  }
355
- label={`${getFeatureNameOrId(child, apolloSessionModel)} (${child.min + 1}..${child.max})`}
515
+ label={`${getFeatureNameOrId(child)} (${child.min + 1}..${child.max})`}
356
516
  />
357
517
  ))}
358
518
  </Box>
@@ -362,26 +522,49 @@ export function CreateApolloAnnotation({
362
522
  ((!parentFeatureChecked && checkedChildrens.length > 0) ||
363
523
  (parentFeatureChecked &&
364
524
  isTranscript(annotationFeature, apolloSessionModel))) && (
365
- <Box sx={{ ml: 3 }}>
366
- <Typography variant="caption" fontSize={12}>
367
- Select the destination feature to copy the selected features
368
- </Typography>
369
-
370
- <Box sx={{ mt: 1 }}>
371
- <Select
372
- labelId="label"
373
- style={{ width: '100%' }}
374
- value={selectedDestinationFeature?._id ?? ''}
375
- onChange={handleDestinationFeatureChange}
376
- >
377
- {destinationFeatures.map((f) => (
378
- <MenuItem key={f._id} value={f._id}>
379
- {`${getFeatureNameOrId(f, apolloSessionModel)} (${f.min}..${f.max})`}
380
- </MenuItem>
381
- ))}
382
- </Select>
525
+ <div
526
+ style={{
527
+ border: '1px solid #ccc',
528
+ marginTop: 20,
529
+ padding: 10,
530
+ borderRadius: 5,
531
+ }}
532
+ >
533
+ <Box sx={{ ml: 3 }}>
534
+ <Typography variant="caption" fontSize={12}>
535
+ Select the destination feature to copy the selected features
536
+ </Typography>
537
+
538
+ <Box sx={{ mt: 1 }}>
539
+ <Select
540
+ labelId="label"
541
+ style={{ width: '100%' }}
542
+ value={selectedDestinationFeature?._id ?? ''}
543
+ onChange={handleDestinationFeatureChange}
544
+ disabled={createNewGene}
545
+ >
546
+ {destinationFeatures.map((f) => (
547
+ <MenuItem key={f._id} value={f._id}>
548
+ {`${getFeatureNameOrId(f)} (${f.min + 1}..${f.max})`}
549
+ </MenuItem>
550
+ ))}
551
+ </Select>
552
+ </Box>
383
553
  </Box>
384
- </Box>
554
+ <Box sx={{ ml: 3 }}>
555
+ <FormGroup>
556
+ <FormControlLabel
557
+ control={
558
+ <Checkbox
559
+ checked={createNewGene}
560
+ onChange={handleCreateNewGeneChange}
561
+ />
562
+ }
563
+ label="Create new gene"
564
+ />
565
+ </FormGroup>
566
+ </Box>
567
+ </div>
385
568
  )}
386
569
  </DialogContent>
387
570
  <DialogActions>