@apollo-annotation/jbrowse-plugin-apollo 0.3.4 → 0.3.5
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 +1513 -1074
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +1510 -1065
- 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 +4681 -2097
- 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/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +13 -0
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +88 -17
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +144 -22
- package/src/FeatureDetailsWidget/Attributes.tsx +93 -43
- package/src/FeatureDetailsWidget/BasicInformation.tsx +2 -4
- package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +6 -4
- package/src/FeatureDetailsWidget/Sequence.tsx +16 -33
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +137 -92
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +600 -0
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +54 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +13 -6
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +6 -27
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +35 -13
- package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -2
- package/src/LinearApolloDisplay/stateModel/rendering.ts +4 -0
- package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +10 -1
- package/src/OntologyManager/OntologyStore/index.test.ts +1 -0
- package/src/OntologyManager/index.ts +2 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +5 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +0 -1
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +8 -1
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +14 -12
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +3 -27
- package/src/components/AddAssembly.tsx +608 -291
- package/src/components/CreateApolloAnnotation.tsx +144 -37
- package/src/components/OntologyTermMultiSelect.tsx +3 -0
- package/src/components/index.ts +0 -1
- package/src/extensions/annotationFromJBrowseFeature.ts +3 -2
- package/src/makeDisplayComponent.tsx +3 -4
- package/src/util/annotationFeatureUtils.ts +53 -0
- package/src/util/index.ts +1 -0
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +0 -200
- package/src/components/ModifyFeatureAttribute.tsx +0 -460
|
@@ -45,8 +45,27 @@ const isGeneOrTranscript = (
|
|
|
45
45
|
}
|
|
46
46
|
return (
|
|
47
47
|
featureTypeOntology.isTypeOf(annotationFeature.type, 'gene') ||
|
|
48
|
-
featureTypeOntology.isTypeOf(annotationFeature.type, '
|
|
49
|
-
featureTypeOntology.isTypeOf(annotationFeature.type, '
|
|
48
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript') ||
|
|
49
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'pseudogene') ||
|
|
50
|
+
featureTypeOntology.isTypeOf(
|
|
51
|
+
annotationFeature.type,
|
|
52
|
+
'pseudogenic_transcript',
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const isGene = (
|
|
58
|
+
annotationFeature: AnnotationFeatureSnapshot,
|
|
59
|
+
apolloSessionModel: ApolloSessionModel,
|
|
60
|
+
) => {
|
|
61
|
+
const { featureTypeOntology } =
|
|
62
|
+
apolloSessionModel.apolloDataStore.ontologyManager
|
|
63
|
+
if (!featureTypeOntology) {
|
|
64
|
+
throw new Error('featureTypeOntology is undefined')
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'gene') ||
|
|
68
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'pseudogene')
|
|
50
69
|
)
|
|
51
70
|
}
|
|
52
71
|
|
|
@@ -60,11 +79,51 @@ const isTranscript = (
|
|
|
60
79
|
throw new Error('featureTypeOntology is undefined')
|
|
61
80
|
}
|
|
62
81
|
return (
|
|
63
|
-
featureTypeOntology.isTypeOf(annotationFeature.type, '
|
|
64
|
-
featureTypeOntology.isTypeOf(
|
|
82
|
+
featureTypeOntology.isTypeOf(annotationFeature.type, 'transcript') ||
|
|
83
|
+
featureTypeOntology.isTypeOf(
|
|
84
|
+
annotationFeature.type,
|
|
85
|
+
'pseudogenic_transcript',
|
|
86
|
+
)
|
|
65
87
|
)
|
|
66
88
|
}
|
|
67
89
|
|
|
90
|
+
const getFeatureId = (feature: AnnotationFeatureSnapshot) => {
|
|
91
|
+
const { attributes } = feature
|
|
92
|
+
const id = attributes?.id
|
|
93
|
+
if (id) {
|
|
94
|
+
return id[0]
|
|
95
|
+
}
|
|
96
|
+
return feature.type
|
|
97
|
+
}
|
|
98
|
+
|
|
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
|
+
}
|
|
114
|
+
|
|
115
|
+
if (featureTypeOntology.isTypeOf(feature.type, 'transcript')) {
|
|
116
|
+
attrName = 'transcript_name'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const { attributes } = feature
|
|
120
|
+
const name = attributes?.[attrName]
|
|
121
|
+
if (name) {
|
|
122
|
+
return name[0]
|
|
123
|
+
}
|
|
124
|
+
return getFeatureId(feature)
|
|
125
|
+
}
|
|
126
|
+
|
|
68
127
|
export function CreateApolloAnnotation({
|
|
69
128
|
annotationFeature,
|
|
70
129
|
assembly,
|
|
@@ -104,6 +163,9 @@ export function CreateApolloAnnotation({
|
|
|
104
163
|
const filteredFeatures: AnnotationFeatureSnapshot[] = []
|
|
105
164
|
|
|
106
165
|
for (const [, f] of features) {
|
|
166
|
+
if (f.type === 'chromosome') {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
107
169
|
const featureSnapshot = getSnapshot(f)
|
|
108
170
|
if (min >= featureSnapshot.min && max <= featureSnapshot.max) {
|
|
109
171
|
filteredFeatures.push(featureSnapshot)
|
|
@@ -115,33 +177,34 @@ export function CreateApolloAnnotation({
|
|
|
115
177
|
|
|
116
178
|
useEffect(() => {
|
|
117
179
|
setErrorMessage('')
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
|
|
180
|
+
let mins: number[] = []
|
|
181
|
+
let maxes: number[] = []
|
|
123
182
|
if (annotationFeature.children) {
|
|
124
183
|
const checkedAnnotationFeatureChildren = Object.values(
|
|
125
184
|
annotationFeature.children,
|
|
126
185
|
)
|
|
127
186
|
.filter((child) => isTranscript(child, apolloSessionModel))
|
|
128
187
|
.filter((child) => checkedChildrens.includes(child._id))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const max = Math.max(...maxes)
|
|
133
|
-
const filteredFeatures = getFeatures(min, max)
|
|
134
|
-
setDestinationFeatures(filteredFeatures)
|
|
188
|
+
mins = checkedAnnotationFeatureChildren.map((f) => f.min)
|
|
189
|
+
maxes = checkedAnnotationFeatureChildren.map((f) => f.max)
|
|
190
|
+
}
|
|
135
191
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
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]
|
|
143
200
|
}
|
|
144
|
-
|
|
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])
|
|
145
208
|
|
|
146
209
|
const handleParentFeatureCheck = (
|
|
147
210
|
event: React.ChangeEvent<HTMLInputElement>,
|
|
@@ -171,12 +234,55 @@ export function CreateApolloAnnotation({
|
|
|
171
234
|
|
|
172
235
|
const handleCreateApolloAnnotation = async () => {
|
|
173
236
|
if (parentFeatureChecked) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
237
|
+
let change
|
|
238
|
+
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
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
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
|
+
})
|
|
276
|
+
} else {
|
|
277
|
+
setErrorMessage('There is no destination gene for this transcript')
|
|
278
|
+
return
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!change) {
|
|
283
|
+
return
|
|
284
|
+
}
|
|
285
|
+
|
|
180
286
|
await apolloSessionModel.apolloDataStore.changeManager.submit(change)
|
|
181
287
|
session.notify('Annotation added successfully', 'success')
|
|
182
288
|
handleClose()
|
|
@@ -198,9 +304,9 @@ export function CreateApolloAnnotation({
|
|
|
198
304
|
addedFeature: child,
|
|
199
305
|
})
|
|
200
306
|
await apolloSessionModel.apolloDataStore.changeManager.submit(change)
|
|
201
|
-
session.notify('Annotation added successfully', 'success')
|
|
202
|
-
handleClose()
|
|
203
307
|
}
|
|
308
|
+
session.notify('Annotation added successfully', 'success')
|
|
309
|
+
handleClose()
|
|
204
310
|
}
|
|
205
311
|
}
|
|
206
312
|
|
|
@@ -226,7 +332,7 @@ export function CreateApolloAnnotation({
|
|
|
226
332
|
onChange={handleParentFeatureCheck}
|
|
227
333
|
/>
|
|
228
334
|
}
|
|
229
|
-
label={`${annotationFeature
|
|
335
|
+
label={`${getFeatureNameOrId(annotationFeature, apolloSessionModel)} (${annotationFeature.min + 1}..${annotationFeature.max})`}
|
|
230
336
|
/>
|
|
231
337
|
)}
|
|
232
338
|
{annotationFeature.children && (
|
|
@@ -245,15 +351,16 @@ export function CreateApolloAnnotation({
|
|
|
245
351
|
}}
|
|
246
352
|
/>
|
|
247
353
|
}
|
|
248
|
-
label={`${child
|
|
354
|
+
label={`${getFeatureNameOrId(child, apolloSessionModel)} (${child.min + 1}..${child.max})`}
|
|
249
355
|
/>
|
|
250
356
|
))}
|
|
251
357
|
</Box>
|
|
252
358
|
)}
|
|
253
359
|
</Box>
|
|
254
|
-
{
|
|
255
|
-
checkedChildrens.length > 0
|
|
256
|
-
|
|
360
|
+
{destinationFeatures.length > 0 &&
|
|
361
|
+
((!parentFeatureChecked && checkedChildrens.length > 0) ||
|
|
362
|
+
(parentFeatureChecked &&
|
|
363
|
+
isTranscript(annotationFeature, apolloSessionModel))) && (
|
|
257
364
|
<Box sx={{ ml: 3 }}>
|
|
258
365
|
<Typography variant="caption" fontSize={12}>
|
|
259
366
|
Select the destination feature to copy the selected features
|
|
@@ -268,7 +375,7 @@ export function CreateApolloAnnotation({
|
|
|
268
375
|
>
|
|
269
376
|
{destinationFeatures.map((f) => (
|
|
270
377
|
<MenuItem key={f._id} value={f._id}>
|
|
271
|
-
{`${f
|
|
378
|
+
{`${getFeatureNameOrId(f, apolloSessionModel)} (${f.min}..${f.max})`}
|
|
272
379
|
</MenuItem>
|
|
273
380
|
))}
|
|
274
381
|
</Select>
|
|
@@ -114,6 +114,7 @@ export function OntologyTermMultiSelect({
|
|
|
114
114
|
ontologyVersion,
|
|
115
115
|
session,
|
|
116
116
|
value: initialValue,
|
|
117
|
+
label,
|
|
117
118
|
}: {
|
|
118
119
|
session: ApolloSessionModel
|
|
119
120
|
value: string[]
|
|
@@ -122,6 +123,7 @@ export function OntologyTermMultiSelect({
|
|
|
122
123
|
/** if true, include deprecated/obsolete terms */
|
|
123
124
|
includeDeprecated?: boolean
|
|
124
125
|
onChange(newValue: string[]): void
|
|
126
|
+
label?: string
|
|
125
127
|
}) {
|
|
126
128
|
const { ontologyManager } = session.apolloDataStore
|
|
127
129
|
const ontology = ontologyManager.findOntology(ontologyName, ontologyVersion)
|
|
@@ -256,6 +258,7 @@ export function OntologyTermMultiSelect({
|
|
|
256
258
|
{...params}
|
|
257
259
|
{...extraTextFieldParams}
|
|
258
260
|
variant="outlined"
|
|
261
|
+
label={label}
|
|
259
262
|
fullWidth
|
|
260
263
|
/>
|
|
261
264
|
)}
|
package/src/components/index.ts
CHANGED
|
@@ -9,7 +9,6 @@ export * from './ImportFeatures'
|
|
|
9
9
|
export * from './LogOut'
|
|
10
10
|
export * from './ManageChecks'
|
|
11
11
|
export * from './ManageUsers'
|
|
12
|
-
export * from './ModifyFeatureAttribute'
|
|
13
12
|
export * from './OpenLocalFile'
|
|
14
13
|
export * from './ViewChangeLog'
|
|
15
14
|
export * from './AddRefSeqAliases'
|
|
@@ -23,6 +23,7 @@ function simpleFeatureToGFF3Feature(
|
|
|
23
23
|
feature: Feature,
|
|
24
24
|
refSeqId: string,
|
|
25
25
|
): GFF3Feature {
|
|
26
|
+
// eslint-disable-next-line unicorn/prefer-structured-clone
|
|
26
27
|
const xfeature = JSON.parse(JSON.stringify(feature))
|
|
27
28
|
const children = xfeature.subfeatures
|
|
28
29
|
const gff3Feature = [
|
|
@@ -130,11 +131,11 @@ export function annotationFromJBrowseFeature(
|
|
|
130
131
|
}))
|
|
131
132
|
.views((self) => {
|
|
132
133
|
const superContextMenuItems = self.contextMenuItems
|
|
133
|
-
const session = getSession(self)
|
|
134
|
-
const assembly = self.getAssembly()
|
|
135
134
|
|
|
136
135
|
return {
|
|
137
136
|
contextMenuItems() {
|
|
137
|
+
const session = getSession(self)
|
|
138
|
+
const assembly = self.getAssembly()
|
|
138
139
|
const feature = self.contextMenuFeature
|
|
139
140
|
if (!feature) {
|
|
140
141
|
return superContextMenuItems()
|
|
@@ -171,7 +171,6 @@ export const DisplayComponent = observer(function DisplayComponent({
|
|
|
171
171
|
const { classes } = useStyles()
|
|
172
172
|
|
|
173
173
|
const {
|
|
174
|
-
detailsHeight,
|
|
175
174
|
graphical,
|
|
176
175
|
height: overallHeight,
|
|
177
176
|
isShown,
|
|
@@ -187,7 +186,7 @@ export const DisplayComponent = observer(function DisplayComponent({
|
|
|
187
186
|
}, [model, selectedFeature])
|
|
188
187
|
|
|
189
188
|
const onDetailsResize = (delta: number) => {
|
|
190
|
-
model.setDetailsHeight(detailsHeight - delta)
|
|
189
|
+
model.setDetailsHeight(model.detailsHeight - delta)
|
|
191
190
|
}
|
|
192
191
|
|
|
193
192
|
if (!ontologyStore) {
|
|
@@ -199,9 +198,9 @@ export const DisplayComponent = observer(function DisplayComponent({
|
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
if (graphical && table) {
|
|
202
|
-
const tabularHeight = tabularEditor.isShown ? detailsHeight : 0
|
|
201
|
+
const tabularHeight = tabularEditor.isShown ? model.detailsHeight : 0
|
|
203
202
|
const featureAreaHeight = isShown
|
|
204
|
-
? overallHeight - detailsHeight - accordionControlHeight * 2
|
|
203
|
+
? overallHeight - model.detailsHeight - accordionControlHeight * 2
|
|
205
204
|
: 0
|
|
206
205
|
return (
|
|
207
206
|
<div style={{ height: overallHeight }}>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
|
|
3
|
+
export function getFeatureName(feature: AnnotationFeature) {
|
|
4
|
+
const { attributes } = feature
|
|
5
|
+
const name = attributes.get('gff_name')
|
|
6
|
+
if (name) {
|
|
7
|
+
return name[0]
|
|
8
|
+
}
|
|
9
|
+
return ''
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function getFeatureId(feature: AnnotationFeature) {
|
|
13
|
+
const { attributes } = feature
|
|
14
|
+
const id = attributes.get('gff_id')
|
|
15
|
+
const transcript_id = attributes.get('transcript_id')
|
|
16
|
+
const exon_id = attributes.get('exon_id')
|
|
17
|
+
const protein_id = attributes.get('protein_id')
|
|
18
|
+
if (id) {
|
|
19
|
+
return id[0]
|
|
20
|
+
}
|
|
21
|
+
if (transcript_id) {
|
|
22
|
+
return transcript_id[0]
|
|
23
|
+
}
|
|
24
|
+
if (exon_id) {
|
|
25
|
+
return exon_id[0]
|
|
26
|
+
}
|
|
27
|
+
if (protein_id) {
|
|
28
|
+
return protein_id[0]
|
|
29
|
+
}
|
|
30
|
+
return ''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function getFeatureNameOrId(feature: AnnotationFeature) {
|
|
34
|
+
const name = getFeatureName(feature)
|
|
35
|
+
const id = getFeatureId(feature)
|
|
36
|
+
if (name) {
|
|
37
|
+
return `: ${name}`
|
|
38
|
+
}
|
|
39
|
+
if (id) {
|
|
40
|
+
return `: ${id}`
|
|
41
|
+
}
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getStrand(strand: number | undefined) {
|
|
46
|
+
if (strand === 1) {
|
|
47
|
+
return 'Forward'
|
|
48
|
+
}
|
|
49
|
+
if (strand === -1) {
|
|
50
|
+
return 'Reverse'
|
|
51
|
+
}
|
|
52
|
+
return ''
|
|
53
|
+
}
|
package/src/util/index.ts
CHANGED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
-
import {
|
|
3
|
-
LocationEndChange,
|
|
4
|
-
LocationStartChange,
|
|
5
|
-
} from '@apollo-annotation/shared'
|
|
6
|
-
import { AbstractSessionModel, getFrame, revcom } from '@jbrowse/core/util'
|
|
7
|
-
import {
|
|
8
|
-
Paper,
|
|
9
|
-
Typography,
|
|
10
|
-
Table,
|
|
11
|
-
TableBody,
|
|
12
|
-
TableCell,
|
|
13
|
-
TableContainer,
|
|
14
|
-
TableRow,
|
|
15
|
-
useTheme,
|
|
16
|
-
} from '@mui/material'
|
|
17
|
-
import { observer } from 'mobx-react'
|
|
18
|
-
import React from 'react'
|
|
19
|
-
|
|
20
|
-
import { ApolloSessionModel } from '../session'
|
|
21
|
-
import { NumberTextField } from './NumberTextField'
|
|
22
|
-
|
|
23
|
-
export const TranscriptBasicInformation = observer(
|
|
24
|
-
function TranscriptBasicInformation({
|
|
25
|
-
assembly,
|
|
26
|
-
feature,
|
|
27
|
-
refName,
|
|
28
|
-
session,
|
|
29
|
-
}: {
|
|
30
|
-
feature: AnnotationFeature
|
|
31
|
-
session: ApolloSessionModel
|
|
32
|
-
assembly: string
|
|
33
|
-
refName: string
|
|
34
|
-
}) {
|
|
35
|
-
const { notify } = session as unknown as AbstractSessionModel
|
|
36
|
-
const currentAssembly = session.apolloDataStore.assemblies.get(assembly)
|
|
37
|
-
const refData = currentAssembly?.getByRefName(refName)
|
|
38
|
-
const { changeManager } = session.apolloDataStore
|
|
39
|
-
const theme = useTheme()
|
|
40
|
-
|
|
41
|
-
function handleLocationChange(
|
|
42
|
-
oldLocation: number,
|
|
43
|
-
newLocation: number,
|
|
44
|
-
feature: AnnotationFeature,
|
|
45
|
-
isMin: boolean,
|
|
46
|
-
) {
|
|
47
|
-
if (!feature.children) {
|
|
48
|
-
throw new Error('Transcript should have child features')
|
|
49
|
-
}
|
|
50
|
-
for (const [, child] of feature.children) {
|
|
51
|
-
if (isMin && oldLocation - 1 === child.min) {
|
|
52
|
-
const change = new LocationStartChange({
|
|
53
|
-
typeName: 'LocationStartChange',
|
|
54
|
-
changedIds: [child._id],
|
|
55
|
-
featureId: feature._id,
|
|
56
|
-
oldStart: oldLocation - 1,
|
|
57
|
-
newStart: newLocation - 1,
|
|
58
|
-
assembly,
|
|
59
|
-
})
|
|
60
|
-
changeManager.submit(change).catch(() => {
|
|
61
|
-
notify('Error updating feature start position', 'error')
|
|
62
|
-
})
|
|
63
|
-
return
|
|
64
|
-
}
|
|
65
|
-
if (!isMin && newLocation === child.max) {
|
|
66
|
-
const change = new LocationEndChange({
|
|
67
|
-
typeName: 'LocationEndChange',
|
|
68
|
-
changedIds: [child._id],
|
|
69
|
-
featureId: feature._id,
|
|
70
|
-
oldEnd: child.max,
|
|
71
|
-
newEnd: newLocation,
|
|
72
|
-
assembly,
|
|
73
|
-
})
|
|
74
|
-
changeManager.submit(change).catch(() => {
|
|
75
|
-
notify('Error updating feature start position', 'error')
|
|
76
|
-
})
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!refData) {
|
|
83
|
-
return null
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
let strand, transcriptParts
|
|
87
|
-
try {
|
|
88
|
-
;({ strand, transcriptParts } = feature)
|
|
89
|
-
} catch {
|
|
90
|
-
return null
|
|
91
|
-
}
|
|
92
|
-
const [firstLocation] = transcriptParts
|
|
93
|
-
|
|
94
|
-
const locationData = firstLocation
|
|
95
|
-
.map((loc, idx) => {
|
|
96
|
-
const { max, min, type } = loc
|
|
97
|
-
let label: string = type
|
|
98
|
-
if (label === 'threePrimeUTR') {
|
|
99
|
-
label = '3` UTR'
|
|
100
|
-
} else if (label === 'fivePrimeUTR') {
|
|
101
|
-
label = '5` UTR'
|
|
102
|
-
}
|
|
103
|
-
let fivePrimeSpliceSite
|
|
104
|
-
let threePrimeSpliceSite
|
|
105
|
-
let frameColor
|
|
106
|
-
if (type === 'CDS') {
|
|
107
|
-
const { phase } = loc
|
|
108
|
-
const frame = getFrame(min, max, strand ?? 1, phase)
|
|
109
|
-
frameColor = theme.palette.framesCDS.at(frame)?.main
|
|
110
|
-
const previousLoc = firstLocation.at(idx - 1)
|
|
111
|
-
const nextLoc = firstLocation.at(idx + 1)
|
|
112
|
-
if (strand === 1) {
|
|
113
|
-
if (previousLoc?.type === 'intron') {
|
|
114
|
-
fivePrimeSpliceSite = refData.getSequence(min - 2, min)
|
|
115
|
-
}
|
|
116
|
-
if (nextLoc?.type === 'intron') {
|
|
117
|
-
threePrimeSpliceSite = refData.getSequence(max, max + 2)
|
|
118
|
-
}
|
|
119
|
-
} else {
|
|
120
|
-
if (previousLoc?.type === 'intron') {
|
|
121
|
-
fivePrimeSpliceSite = revcom(refData.getSequence(max, max + 2))
|
|
122
|
-
}
|
|
123
|
-
if (nextLoc?.type === 'intron') {
|
|
124
|
-
threePrimeSpliceSite = revcom(refData.getSequence(min - 2, min))
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
return {
|
|
129
|
-
min,
|
|
130
|
-
max,
|
|
131
|
-
label,
|
|
132
|
-
fivePrimeSpliceSite,
|
|
133
|
-
threePrimeSpliceSite,
|
|
134
|
-
frameColor,
|
|
135
|
-
}
|
|
136
|
-
})
|
|
137
|
-
.filter((loc) => loc.label !== 'intron')
|
|
138
|
-
|
|
139
|
-
return (
|
|
140
|
-
<>
|
|
141
|
-
<Typography variant="h5">Structure</Typography>
|
|
142
|
-
<Typography variant="h6">
|
|
143
|
-
{strand === 1 ? 'Forward' : 'Reverse'} strand
|
|
144
|
-
</Typography>
|
|
145
|
-
<TableContainer component={Paper}>
|
|
146
|
-
<Table size="small">
|
|
147
|
-
<TableBody>
|
|
148
|
-
{locationData.map((loc) => (
|
|
149
|
-
<TableRow key={`${loc.label}:${loc.min}-${loc.max}`}>
|
|
150
|
-
<TableCell
|
|
151
|
-
component="th"
|
|
152
|
-
scope="row"
|
|
153
|
-
style={{ background: loc.frameColor }}
|
|
154
|
-
>
|
|
155
|
-
{loc.label}
|
|
156
|
-
</TableCell>
|
|
157
|
-
<TableCell>{loc.fivePrimeSpliceSite ?? ''}</TableCell>
|
|
158
|
-
<TableCell padding="none">
|
|
159
|
-
<NumberTextField
|
|
160
|
-
margin="dense"
|
|
161
|
-
variant="outlined"
|
|
162
|
-
value={strand === 1 ? loc.min + 1 : loc.max}
|
|
163
|
-
onChangeCommitted={(newLocation: number) => {
|
|
164
|
-
handleLocationChange(
|
|
165
|
-
strand === 1 ? loc.min + 1 : loc.max,
|
|
166
|
-
newLocation,
|
|
167
|
-
feature,
|
|
168
|
-
strand === 1,
|
|
169
|
-
)
|
|
170
|
-
}}
|
|
171
|
-
/>
|
|
172
|
-
{/* {strand === 1 ? loc.min : loc.max} */}
|
|
173
|
-
</TableCell>
|
|
174
|
-
<TableCell padding="none">
|
|
175
|
-
<NumberTextField
|
|
176
|
-
margin="dense"
|
|
177
|
-
// disabled={item.type !== 'CDS'}
|
|
178
|
-
variant="outlined"
|
|
179
|
-
value={strand === 1 ? loc.max : loc.min + 1}
|
|
180
|
-
onChangeCommitted={(newLocation: number) => {
|
|
181
|
-
handleLocationChange(
|
|
182
|
-
strand === 1 ? loc.max : loc.min + 1,
|
|
183
|
-
newLocation,
|
|
184
|
-
feature,
|
|
185
|
-
strand !== 1,
|
|
186
|
-
)
|
|
187
|
-
}}
|
|
188
|
-
/>
|
|
189
|
-
{/* {strand === 1 ? loc.max : loc.min} */}
|
|
190
|
-
</TableCell>
|
|
191
|
-
<TableCell>{loc.threePrimeSpliceSite ?? ''}</TableCell>
|
|
192
|
-
</TableRow>
|
|
193
|
-
))}
|
|
194
|
-
</TableBody>
|
|
195
|
-
</Table>
|
|
196
|
-
</TableContainer>
|
|
197
|
-
</>
|
|
198
|
-
)
|
|
199
|
-
},
|
|
200
|
-
)
|