@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.
- package/dist/index.esm.js +2679 -850
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2676 -847
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +5194 -1258
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +4 -4
- package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
- package/src/ChangeManager.ts +10 -6
- package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +12 -20
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +929 -175
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -1
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +48 -60
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +244 -51
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +46 -1
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +9 -1
- package/src/LinearApolloDisplay/stateModel/base.ts +29 -0
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +51 -35
- package/src/LinearApolloDisplay/stateModel/rendering.ts +2 -1
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +243 -124
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -1
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +19 -3
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +53 -34
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +4 -2
- package/src/OntologyManager/index.ts +4 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +4 -0
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +3 -6
- package/src/components/AddFeature.tsx +14 -15
- package/src/components/CopyFeature.tsx +2 -4
- package/src/components/CreateApolloAnnotation.tsx +334 -151
- package/src/components/DeleteFeature.tsx +358 -11
- package/src/components/DownloadGFF3.tsx +20 -1
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +185 -0
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/index.ts +3 -0
- package/src/config.ts +5 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +99 -89
- package/src/session/session.ts +26 -13
- package/src/util/annotationFeatureUtils.ts +65 -0
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/glyphUtils.ts +49 -0
- package/src/util/index.ts +2 -0
- 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
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
101
|
+
export function getFeatureName(feature: AnnotationFeatureSnapshot) {
|
|
92
102
|
const { attributes } = feature
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
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
|
|
110
|
+
return ''
|
|
98
111
|
}
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
feature
|
|
102
|
-
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
121
|
-
const name =
|
|
144
|
+
const getFeatureNameOrId = (feature: AnnotationFeatureSnapshot) => {
|
|
145
|
+
const name = getFeatureName(feature)
|
|
146
|
+
const id = getFeatureId(feature)
|
|
122
147
|
if (name) {
|
|
123
|
-
return name
|
|
148
|
+
return `${feature.type} - ${name}`
|
|
124
149
|
}
|
|
125
|
-
|
|
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
|
|
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
|
|
167
|
-
if (f.
|
|
191
|
+
for (const f of features ?? []) {
|
|
192
|
+
if (f.min > region.end || f.max < region.start) {
|
|
168
193
|
continue
|
|
169
194
|
}
|
|
170
|
-
|
|
171
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
|
|
244
|
+
// IF SOURCE FEATURE IS GENE
|
|
239
245
|
if (isGene(annotationFeature, apolloSessionModel)) {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
279
|
-
|
|
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
|
-
|
|
295
|
-
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
438
|
+
featureId: selectedDestinationFeature._id,
|
|
304
439
|
assembly: assembly.name,
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
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
|
|
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
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
>
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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
|
-
|
|
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>
|