@apollo-annotation/jbrowse-plugin-apollo 0.3.5 → 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 (136) hide show
  1. package/dist/index.esm.js +6964 -4598
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +6610 -4261
  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 +11563 -7493
  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 +23 -2
  13. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
  14. package/src/ApolloInternetAccount/components/LoginButtons.tsx +1 -1
  15. package/src/ApolloInternetAccount/components/LoginIcons.tsx +1 -1
  16. package/src/ApolloInternetAccount/configSchema.ts +1 -1
  17. package/src/ApolloInternetAccount/model.ts +11 -10
  18. package/src/ApolloJobModel.ts +1 -1
  19. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +8 -6
  20. package/src/ApolloRefNameAliasAdapter/index.ts +2 -2
  21. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +4 -4
  22. package/src/ApolloSequenceAdapter/index.ts +1 -1
  23. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +8 -7
  24. package/src/ApolloTextSearchAdapter/index.ts +1 -1
  25. package/src/BackendDrivers/BackendDriver.ts +7 -7
  26. package/src/BackendDrivers/CollaborationServerDriver.ts +14 -10
  27. package/src/BackendDrivers/DesktopFileDriver.ts +11 -10
  28. package/src/BackendDrivers/InMemoryFileDriver.ts +10 -6
  29. package/src/ChangeManager.ts +15 -11
  30. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +8 -7
  31. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +35 -14
  32. package/src/FeatureDetailsWidget/AttributeKey.tsx +50 -0
  33. package/src/FeatureDetailsWidget/AttributeKeySelector.tsx +104 -0
  34. package/src/FeatureDetailsWidget/Attributes.tsx +215 -367
  35. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -5
  36. package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +104 -0
  37. package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +22 -0
  38. package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +4 -4
  39. package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -1
  40. package/src/FeatureDetailsWidget/Sequence.tsx +2 -2
  41. package/src/FeatureDetailsWidget/StringTextField.tsx +1 -1
  42. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +15 -23
  43. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +950 -196
  44. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +8 -4
  45. package/src/FeatureDetailsWidget/model.ts +8 -3
  46. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +7 -7
  47. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +59 -72
  48. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +253 -60
  49. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +52 -6
  50. package/src/LinearApolloDisplay/glyphs/Glyph.ts +16 -8
  51. package/src/LinearApolloDisplay/stateModel/base.ts +81 -10
  52. package/src/LinearApolloDisplay/stateModel/index.ts +4 -3
  53. package/src/LinearApolloDisplay/stateModel/layouts.ts +8 -39
  54. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +63 -46
  55. package/src/LinearApolloDisplay/stateModel/rendering.ts +60 -31
  56. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +226 -0
  57. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +32 -0
  58. package/src/LinearApolloSixFrameDisplay/components/index.ts +2 -0
  59. package/src/LinearApolloSixFrameDisplay/configSchema.ts +7 -0
  60. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +940 -0
  61. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +63 -0
  62. package/src/LinearApolloSixFrameDisplay/glyphs/index.ts +1 -0
  63. package/src/LinearApolloSixFrameDisplay/index.ts +2 -0
  64. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +302 -0
  65. package/src/LinearApolloSixFrameDisplay/stateModel/index.ts +27 -0
  66. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +252 -0
  67. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +368 -0
  68. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +201 -0
  69. package/src/LinearApolloSixFrameDisplay/types.ts +1 -0
  70. package/src/OntologyManager/OntologyStore/fulltext.test.ts +1 -1
  71. package/src/OntologyManager/OntologyStore/fulltext.ts +8 -3
  72. package/src/OntologyManager/OntologyStore/index.test.ts +3 -1
  73. package/src/OntologyManager/OntologyStore/index.ts +19 -14
  74. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +6 -5
  75. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +11 -5
  76. package/src/OntologyManager/index.ts +12 -7
  77. package/src/OntologyManager/util.ts +3 -2
  78. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
  79. package/src/TabularEditor/HybridGrid/Feature.tsx +13 -7
  80. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +1 -1
  81. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +3 -2
  82. package/src/TabularEditor/HybridGrid/ToolBar.tsx +1 -1
  83. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +114 -22
  84. package/src/TabularEditor/TabularEditorPane.tsx +1 -1
  85. package/src/TabularEditor/model.ts +2 -2
  86. package/src/TabularEditor/types.ts +5 -2
  87. package/src/components/AddAssembly.tsx +182 -179
  88. package/src/components/AddAssemblyAliases.tsx +114 -0
  89. package/src/components/AddChildFeature.tsx +8 -10
  90. package/src/components/AddFeature.tsx +216 -44
  91. package/src/components/AddRefSeqAliases.tsx +14 -12
  92. package/src/components/CopyFeature.tsx +10 -11
  93. package/src/components/CreateApolloAnnotation.tsx +342 -158
  94. package/src/components/DeleteAssembly.tsx +9 -8
  95. package/src/components/DeleteFeature.tsx +362 -14
  96. package/src/components/Dialog.tsx +1 -1
  97. package/src/components/DownloadGFF3.tsx +31 -11
  98. package/src/components/FilterFeatures.tsx +6 -4
  99. package/src/components/FilterTranscripts.tsx +86 -0
  100. package/src/components/ImportFeatures.tsx +7 -6
  101. package/src/components/LogOut.tsx +5 -4
  102. package/src/components/ManageChecks.tsx +9 -8
  103. package/src/components/ManageUsers.tsx +11 -10
  104. package/src/components/MergeExons.tsx +193 -0
  105. package/src/components/MergeTranscripts.tsx +185 -0
  106. package/src/components/OntologyTermAutocomplete.tsx +5 -5
  107. package/src/components/OntologyTermMultiSelect.tsx +6 -6
  108. package/src/components/OpenLocalFile.tsx +4 -3
  109. package/src/components/SplitExon.tsx +134 -0
  110. package/src/components/ViewChangeLog.tsx +7 -6
  111. package/src/components/ViewCheckResults.tsx +8 -7
  112. package/src/components/index.ts +3 -0
  113. package/src/config.ts +5 -0
  114. package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -0
  115. package/src/extensions/annotationFromJBrowseFeature.ts +13 -10
  116. package/src/extensions/annotationFromPileup.ts +104 -94
  117. package/src/index.ts +33 -50
  118. package/src/makeDisplayComponent.tsx +90 -37
  119. package/src/session/ClientDataStore.ts +21 -17
  120. package/src/session/session.ts +46 -39
  121. package/src/types.ts +4 -4
  122. package/src/util/annotationFeatureUtils.ts +66 -1
  123. package/src/util/copyToClipboard.ts +21 -0
  124. package/src/util/glyphUtils.ts +49 -0
  125. package/src/util/index.ts +5 -3
  126. package/src/util/loadAssemblyIntoClient.ts +10 -3
  127. package/src/util/mouseEventsUtils.ts +113 -0
  128. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +0 -13
  129. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +0 -707
  130. package/src/ApolloSixFrameRenderer/configSchema.ts +0 -7
  131. package/src/ApolloSixFrameRenderer/index.ts +0 -3
  132. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +0 -19
  133. package/src/SixFrameFeatureDisplay/components/index.ts +0 -1
  134. package/src/SixFrameFeatureDisplay/configSchema.ts +0 -21
  135. package/src/SixFrameFeatureDisplay/index.ts +0 -2
  136. package/src/SixFrameFeatureDisplay/stateModel.ts +0 -443
@@ -1,8 +1,15 @@
1
1
  /* eslint-disable react-hooks/exhaustive-deps */
2
2
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
3
  /* eslint-disable @typescript-eslint/unbound-method */
4
- import React, { useEffect, useMemo, useState } from 'react'
5
4
 
5
+ import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
6
+ import {
7
+ AddFeatureChange,
8
+ LocationEndChange,
9
+ LocationStartChange,
10
+ } from '@apollo-annotation/shared'
11
+ import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
12
+ import { type AbstractSessionModel } from '@jbrowse/core/util'
6
13
  import {
7
14
  Box,
8
15
  Button,
@@ -12,19 +19,19 @@ import {
12
19
  DialogContentText,
13
20
  DialogTitle,
14
21
  FormControlLabel,
22
+ FormGroup,
15
23
  MenuItem,
16
24
  Select,
17
- SelectChangeEvent,
25
+ type SelectChangeEvent,
18
26
  Typography,
19
27
  } from '@mui/material'
28
+ import ObjectID from 'bson-objectid'
29
+ import { getSnapshot } from 'mobx-state-tree'
30
+ import React, { useEffect, useMemo, useState } from 'react'
31
+
32
+ import { type ApolloSessionModel } from '../session'
20
33
 
21
34
  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
35
 
29
36
  interface CreateApolloAnnotationProps {
30
37
  session: AbstractSessionModel
@@ -32,6 +39,10 @@ interface CreateApolloAnnotationProps {
32
39
  annotationFeature: AnnotationFeatureSnapshot
33
40
  assembly: Assembly
34
41
  refSeqId: string
42
+ region: {
43
+ start: number
44
+ end: number
45
+ }
35
46
  }
36
47
 
37
48
  const isGeneOrTranscript = (
@@ -87,41 +98,59 @@ const isTranscript = (
87
98
  )
88
99
  }
89
100
 
90
- const getFeatureId = (feature: AnnotationFeatureSnapshot) => {
101
+ export function getFeatureName(feature: AnnotationFeatureSnapshot) {
91
102
  const { attributes } = feature
92
- const id = attributes?.id
93
- if (id) {
94
- 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
+ }
95
109
  }
96
- return feature.type
110
+ return ''
97
111
  }
98
112
 
99
- const getFeatureNameOrId = (
100
- feature: AnnotationFeatureSnapshot,
101
- apolloSessionModel: ApolloSessionModel,
102
- ) => {
103
- const { featureTypeOntology } =
104
- apolloSessionModel.apolloDataStore.ontologyManager
105
- if (!featureTypeOntology) {
106
- return getFeatureId(feature)
107
- }
108
-
109
- let attrName = ''
110
-
111
- if (featureTypeOntology.isTypeOf(feature.type, 'gene')) {
112
- 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
+ }
113
121
  }
122
+ return ''
123
+ }
114
124
 
115
- if (featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
116
- 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
+ }
117
140
  }
141
+ return ''
142
+ }
118
143
 
119
- const { attributes } = feature
120
- const name = attributes?.[attrName]
144
+ const getFeatureNameOrId = (feature: AnnotationFeatureSnapshot) => {
145
+ const name = getFeatureName(feature)
146
+ const id = getFeatureId(feature)
121
147
  if (name) {
122
- return name[0]
148
+ return `${feature.type} - ${name}`
123
149
  }
124
- return getFeatureId(feature)
150
+ if (id) {
151
+ return `${feature.type} - ${id}`
152
+ }
153
+ return feature.type
125
154
  }
126
155
 
127
156
  export function CreateApolloAnnotation({
@@ -130,44 +159,46 @@ export function CreateApolloAnnotation({
130
159
  handleClose,
131
160
  refSeqId,
132
161
  session,
162
+ region,
133
163
  }: CreateApolloAnnotationProps) {
134
164
  const apolloSessionModel = session as unknown as ApolloSessionModel
165
+ const { featureTypeOntology } =
166
+ apolloSessionModel.apolloDataStore.ontologyManager
135
167
  const childIds = useMemo(
136
168
  () => Object.keys(annotationFeature.children ?? {}),
137
169
  [annotationFeature],
138
170
  )
139
171
 
140
- const features = useMemo(() => {
141
- for (const [, asm] of apolloSessionModel.apolloDataStore.assemblies) {
142
- if (asm._id === assembly.name) {
143
- for (const [, refSeq] of asm.refSeqs) {
144
- if (refSeq._id === refSeqId) {
145
- return refSeq.features
146
- }
147
- }
148
- }
149
- }
150
- return []
151
- }, [])
152
-
153
172
  const [parentFeatureChecked, setParentFeatureChecked] = useState(true)
154
173
  const [checkedChildrens, setCheckedChildrens] = useState<string[]>(childIds)
155
174
  const [errorMessage, setErrorMessage] = useState('')
156
175
  const [destinationFeatures, setDestinationFeatures] = useState<
157
176
  AnnotationFeatureSnapshot[]
158
177
  >([])
178
+ const [createNewGene, setCreateNewGene] = useState(false)
159
179
  const [selectedDestinationFeature, setSelectedDestinationFeature] =
160
180
  useState<AnnotationFeatureSnapshot>()
161
181
 
162
- 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 = () => {
163
189
  const filteredFeatures: AnnotationFeatureSnapshot[] = []
164
190
 
165
- for (const [, f] of features) {
166
- if (f.type === 'chromosome') {
191
+ for (const f of features ?? []) {
192
+ if (f.min > region.end || f.max < region.start) {
167
193
  continue
168
194
  }
169
- const featureSnapshot = getSnapshot(f)
170
- 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)
171
202
  filteredFeatures.push(featureSnapshot)
172
203
  }
173
204
  }
@@ -177,34 +208,10 @@ export function CreateApolloAnnotation({
177
208
 
178
209
  useEffect(() => {
179
210
  setErrorMessage('')
180
- let mins: number[] = []
181
- let maxes: number[] = []
182
- if (annotationFeature.children) {
183
- const checkedAnnotationFeatureChildren = Object.values(
184
- annotationFeature.children,
185
- )
186
- .filter((child) => isTranscript(child, apolloSessionModel))
187
- .filter((child) => checkedChildrens.includes(child._id))
188
- mins = checkedAnnotationFeatureChildren.map((f) => f.min)
189
- maxes = checkedAnnotationFeatureChildren.map((f) => f.max)
190
- }
191
-
192
- const { featureTypeOntology } =
193
- apolloSessionModel.apolloDataStore.ontologyManager
194
- if (
195
- featureTypeOntology &&
196
- featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript')
197
- ) {
198
- mins = [annotationFeature.min, ...mins]
199
- maxes = [annotationFeature.max, ...maxes]
200
- }
201
-
202
- const min = Math.min(...mins)
203
- const max = Math.max(...maxes)
204
- const filteredFeatures = getFeatures(min, max)
205
- setDestinationFeatures(filteredFeatures)
206
- setSelectedDestinationFeature(filteredFeatures[0])
207
- }, [checkedChildrens, parentFeatureChecked])
211
+ const features = getDestinationFeatures()
212
+ setDestinationFeatures(features)
213
+ setSelectedDestinationFeature(features[0])
214
+ }, [checkedChildrens, parentFeatureChecked, region])
208
215
 
209
216
  const handleParentFeatureCheck = (
210
217
  event: React.ChangeEvent<HTMLInputElement>,
@@ -234,82 +241,236 @@ export function CreateApolloAnnotation({
234
241
 
235
242
  const handleCreateApolloAnnotation = async () => {
236
243
  if (parentFeatureChecked) {
237
- let change
244
+ // IF SOURCE FEATURE IS GENE
238
245
  if (isGene(annotationFeature, apolloSessionModel)) {
239
- if (
240
- annotationFeature.children &&
241
- checkedChildrens.length !==
242
- Object.values(annotationFeature.children).length
243
- ) {
244
- const childrens: Record<string, AnnotationFeatureSnapshot> = {}
245
- for (const childId of checkedChildrens) {
246
- childrens[childId] = annotationFeature.children[childId]
247
- }
248
- change = new AddFeatureChange({
249
- changedIds: [annotationFeature._id],
250
- typeName: 'AddFeatureChange',
251
- assembly: assembly.name,
252
- addedFeature: {
253
- ...annotationFeature,
254
- children: childrens,
255
- },
256
- })
257
- } else {
258
- change = new AddFeatureChange({
259
- changedIds: [annotationFeature._id],
260
- typeName: 'AddFeatureChange',
261
- assembly: assembly.name,
262
- addedFeature: annotationFeature,
263
- })
264
- }
246
+ await copyGeneFeature()
247
+ session.notify(
248
+ 'Successfully copied selected gene and transcript(s)',
249
+ 'success',
250
+ )
265
251
  }
266
-
267
252
  if (isTranscript(annotationFeature, apolloSessionModel)) {
268
- if (selectedDestinationFeature) {
269
- change = new AddFeatureChange({
270
- parentFeatureId: selectedDestinationFeature._id,
271
- changedIds: [selectedDestinationFeature._id],
272
- typeName: 'AddFeatureChange',
273
- assembly: assembly.name,
274
- addedFeature: annotationFeature,
275
- })
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
+ )
276
281
  } else {
277
- setErrorMessage('There is no destination gene for this transcript')
278
- 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
+ )
279
290
  }
280
291
  }
281
-
282
- if (!change) {
283
- return
284
- }
285
-
286
- await apolloSessionModel.apolloDataStore.changeManager.submit(change)
287
- session.notify('Annotation added successfully', 'success')
288
- handleClose()
289
292
  } else {
293
+ // IF PARENT (GENE) FEATURE IS NOT CHECKED AND WE ARE COPYING CHILDREN (TRANSCRIPTS)
290
294
  if (!annotationFeature.children) {
291
295
  return
292
296
  }
293
- if (!selectedDestinationFeature) {
294
- 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
+ )
295
339
  }
340
+ }
341
+ handleClose()
342
+ }
296
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> = {}
297
354
  for (const childId of checkedChildrens) {
298
- const child = annotationFeature.children[childId]
299
- const change = new AddFeatureChange({
300
- 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',
301
437
  changedIds: [selectedDestinationFeature._id],
302
- typeName: 'AddFeatureChange',
438
+ featureId: selectedDestinationFeature._id,
303
439
  assembly: assembly.name,
304
- addedFeature: child,
305
- })
306
- await apolloSessionModel.apolloDataStore.changeManager.submit(change)
307
- }
308
- session.notify('Annotation added successfully', 'success')
309
- 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)
310
459
  }
311
460
  }
312
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
+
313
474
  return (
314
475
  <Dialog
315
476
  open
@@ -332,7 +493,7 @@ export function CreateApolloAnnotation({
332
493
  onChange={handleParentFeatureCheck}
333
494
  />
334
495
  }
335
- label={`${getFeatureNameOrId(annotationFeature, apolloSessionModel)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
496
+ label={`${getFeatureNameOrId(annotationFeature)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
336
497
  />
337
498
  )}
338
499
  {annotationFeature.children && (
@@ -351,7 +512,7 @@ export function CreateApolloAnnotation({
351
512
  }}
352
513
  />
353
514
  }
354
- label={`${getFeatureNameOrId(child, apolloSessionModel)} (${child.min + 1}..${child.max})`}
515
+ label={`${getFeatureNameOrId(child)} (${child.min + 1}..${child.max})`}
355
516
  />
356
517
  ))}
357
518
  </Box>
@@ -361,26 +522,49 @@ export function CreateApolloAnnotation({
361
522
  ((!parentFeatureChecked && checkedChildrens.length > 0) ||
362
523
  (parentFeatureChecked &&
363
524
  isTranscript(annotationFeature, apolloSessionModel))) && (
364
- <Box sx={{ ml: 3 }}>
365
- <Typography variant="caption" fontSize={12}>
366
- Select the destination feature to copy the selected features
367
- </Typography>
368
-
369
- <Box sx={{ mt: 1 }}>
370
- <Select
371
- labelId="label"
372
- style={{ width: '100%' }}
373
- value={selectedDestinationFeature?._id ?? ''}
374
- onChange={handleDestinationFeatureChange}
375
- >
376
- {destinationFeatures.map((f) => (
377
- <MenuItem key={f._id} value={f._id}>
378
- {`${getFeatureNameOrId(f, apolloSessionModel)} (${f.min}..${f.max})`}
379
- </MenuItem>
380
- ))}
381
- </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>
382
553
  </Box>
383
- </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>
384
568
  )}
385
569
  </DialogContent>
386
570
  <DialogActions>
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
2
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
3
  import { DeleteAssemblyChange } from '@apollo-annotation/shared'
4
- import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
4
+ import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
5
5
  import {
6
6
  Button,
7
7
  Checkbox,
@@ -12,19 +12,20 @@ import {
12
12
  FormGroup,
13
13
  MenuItem,
14
14
  Select,
15
- SelectChangeEvent,
15
+ type SelectChangeEvent,
16
16
  } from '@mui/material'
17
17
  import { getRoot } from 'mobx-state-tree'
18
18
  import React, { useEffect, useState } from 'react'
19
19
 
20
- import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
20
+ import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
21
21
  import {
22
- ApolloInternetAccount,
23
- CollaborationServerDriver,
22
+ type ApolloInternetAccount,
23
+ type CollaborationServerDriver,
24
24
  } from '../BackendDrivers'
25
- import { ChangeManager } from '../ChangeManager'
26
- import { ApolloSessionModel } from '../session'
27
- import { ApolloRootModel } from '../types'
25
+ import { type ChangeManager } from '../ChangeManager'
26
+ import { type ApolloSessionModel } from '../session'
27
+ import { type ApolloRootModel } from '../types'
28
+
28
29
  import { Dialog } from './Dialog'
29
30
 
30
31
  interface DeleteAssemblyProps {