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

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 (84) hide show
  1. package/dist/index.esm.js +4603 -2045
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
  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 +9387 -4016
  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 +15 -15
  12. package/src/ApolloInternetAccount/model.ts +48 -13
  13. package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
  14. package/src/ChangeManager.ts +42 -18
  15. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  16. package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
  17. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
  18. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
  19. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
  20. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
  21. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
  22. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
  23. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
  24. package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
  25. package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
  26. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  27. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
  28. package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
  29. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  30. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  31. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  32. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  33. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  34. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
  36. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
  37. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
  38. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
  39. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  40. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
  41. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
  42. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
  43. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
  44. package/src/OntologyManager/index.ts +4 -1
  45. package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
  46. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  47. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
  48. package/src/components/AddAssembly.tsx +1 -1
  49. package/src/components/AddAssemblyAliases.tsx +114 -0
  50. package/src/components/AddChildFeature.tsx +7 -7
  51. package/src/components/AddFeature.tsx +20 -15
  52. package/src/components/AddRefSeqAliases.tsx +9 -9
  53. package/src/components/CopyFeature.tsx +4 -4
  54. package/src/components/CreateApolloAnnotation.tsx +335 -151
  55. package/src/components/DeleteAssembly.tsx +1 -1
  56. package/src/components/DeleteFeature.tsx +358 -11
  57. package/src/components/DownloadGFF3.tsx +20 -1
  58. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  59. package/src/components/FilterFeatures.tsx +7 -7
  60. package/src/components/FilterTranscripts.tsx +86 -0
  61. package/src/components/ImportFeatures.tsx +1 -1
  62. package/src/components/ManageChecks.tsx +1 -1
  63. package/src/components/MergeExons.tsx +193 -0
  64. package/src/components/MergeTranscripts.tsx +182 -0
  65. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  66. package/src/components/OpenLocalFile.tsx +11 -7
  67. package/src/components/SplitExon.tsx +134 -0
  68. package/src/components/ViewCheckResults.tsx +1 -1
  69. package/src/components/index.ts +4 -0
  70. package/src/config.ts +11 -0
  71. package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
  72. package/src/extensions/annotationFromPileup.ts +99 -89
  73. package/src/index.ts +42 -105
  74. package/src/makeDisplayComponent.tsx +0 -1
  75. package/src/menus/index.ts +1 -0
  76. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
  77. package/src/menus/topLevelMenuAdmin.ts +154 -0
  78. package/src/session/session.ts +163 -104
  79. package/src/util/annotationFeatureUtils.ts +59 -0
  80. package/src/util/copyToClipboard.ts +21 -0
  81. package/src/util/displayUtils.ts +149 -0
  82. package/src/util/glyphUtils.ts +201 -0
  83. package/src/util/index.ts +2 -0
  84. package/src/util/mouseEventsUtils.ts +145 -0
@@ -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,237 @@ 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
+ apolloSessionModel.apolloSetSelectedFeature(newGeneId)
424
+ }
425
+
426
+ const extendSelectedDestinationFeatureLocation = async (
427
+ newMin: number,
428
+ newMax: number,
429
+ ) => {
430
+ if (!selectedDestinationFeature) {
431
+ return
432
+ }
433
+ const changes = []
434
+ if (newMin !== selectedDestinationFeature.min) {
435
+ changes.push(
436
+ new LocationStartChange({
437
+ typeName: 'LocationStartChange',
302
438
  changedIds: [selectedDestinationFeature._id],
303
- typeName: 'AddFeatureChange',
439
+ featureId: selectedDestinationFeature._id,
304
440
  assembly: assembly.name,
305
- addedFeature: child,
306
- })
307
- await apolloSessionModel.apolloDataStore.changeManager.submit(change)
308
- }
309
- session.notify('Annotation added successfully', 'success')
310
- handleClose()
441
+ oldStart: selectedDestinationFeature.min,
442
+ newStart: newMin,
443
+ }),
444
+ )
445
+ }
446
+ if (newMax !== selectedDestinationFeature.max) {
447
+ changes.push(
448
+ new LocationEndChange({
449
+ typeName: 'LocationEndChange',
450
+ changedIds: [selectedDestinationFeature._id],
451
+ featureId: selectedDestinationFeature._id,
452
+ assembly: assembly.name,
453
+ oldEnd: selectedDestinationFeature.max,
454
+ newEnd: newMax,
455
+ }),
456
+ )
457
+ }
458
+ for (const change of changes) {
459
+ await submitChange(change)
311
460
  }
312
461
  }
313
462
 
463
+ const submitChange = async (
464
+ change: AddFeatureChange | LocationStartChange | LocationEndChange,
465
+ ) => {
466
+ await apolloSessionModel.apolloDataStore.changeManager.submit(change)
467
+ }
468
+
469
+ const handleCreateNewGeneChange = (
470
+ e: React.ChangeEvent<HTMLInputElement>,
471
+ ) => {
472
+ setCreateNewGene(e.target.checked)
473
+ }
474
+
314
475
  return (
315
476
  <Dialog
316
477
  open
@@ -333,7 +494,7 @@ export function CreateApolloAnnotation({
333
494
  onChange={handleParentFeatureCheck}
334
495
  />
335
496
  }
336
- label={`${getFeatureNameOrId(annotationFeature, apolloSessionModel)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
497
+ label={`${getFeatureNameOrId(annotationFeature)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
337
498
  />
338
499
  )}
339
500
  {annotationFeature.children && (
@@ -352,7 +513,7 @@ export function CreateApolloAnnotation({
352
513
  }}
353
514
  />
354
515
  }
355
- label={`${getFeatureNameOrId(child, apolloSessionModel)} (${child.min + 1}..${child.max})`}
516
+ label={`${getFeatureNameOrId(child)} (${child.min + 1}..${child.max})`}
356
517
  />
357
518
  ))}
358
519
  </Box>
@@ -362,26 +523,49 @@ export function CreateApolloAnnotation({
362
523
  ((!parentFeatureChecked && checkedChildrens.length > 0) ||
363
524
  (parentFeatureChecked &&
364
525
  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>
526
+ <div
527
+ style={{
528
+ border: '1px solid #ccc',
529
+ marginTop: 20,
530
+ padding: 10,
531
+ borderRadius: 5,
532
+ }}
533
+ >
534
+ <Box sx={{ ml: 3 }}>
535
+ <Typography variant="caption" fontSize={12}>
536
+ Select the destination feature to copy the selected features
537
+ </Typography>
538
+
539
+ <Box sx={{ mt: 1 }}>
540
+ <Select
541
+ labelId="label"
542
+ style={{ width: '100%' }}
543
+ value={selectedDestinationFeature?._id ?? ''}
544
+ onChange={handleDestinationFeatureChange}
545
+ disabled={createNewGene}
546
+ >
547
+ {destinationFeatures.map((f) => (
548
+ <MenuItem key={f._id} value={f._id}>
549
+ {`${getFeatureNameOrId(f)} (${f.min + 1}..${f.max})`}
550
+ </MenuItem>
551
+ ))}
552
+ </Select>
553
+ </Box>
383
554
  </Box>
384
- </Box>
555
+ <Box sx={{ ml: 3 }}>
556
+ <FormGroup>
557
+ <FormControlLabel
558
+ control={
559
+ <Checkbox
560
+ checked={createNewGene}
561
+ onChange={handleCreateNewGeneChange}
562
+ />
563
+ }
564
+ label="Create new gene"
565
+ />
566
+ </FormGroup>
567
+ </Box>
568
+ </div>
385
569
  )}
386
570
  </DialogContent>
387
571
  <DialogActions>
@@ -142,7 +142,7 @@ export function DeleteAssembly({
142
142
  >
143
143
  {assemblies.map((option) => (
144
144
  <MenuItem key={option.name} value={option.name}>
145
- {option.displayName ?? option.name}
145
+ {option.displayName}
146
146
  </MenuItem>
147
147
  ))}
148
148
  </Select>