@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.
- package/dist/index.esm.js +4603 -2045
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
- 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 +9387 -4016
- 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 +15 -15
- package/src/ApolloInternetAccount/model.ts +48 -13
- package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
- package/src/ChangeManager.ts +42 -18
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
- package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
- package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
- package/src/OntologyManager/index.ts +4 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +114 -0
- package/src/components/AddChildFeature.tsx +7 -7
- package/src/components/AddFeature.tsx +20 -15
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +4 -4
- package/src/components/CreateApolloAnnotation.tsx +335 -151
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/DeleteFeature.tsx +358 -11
- package/src/components/DownloadGFF3.tsx +20 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +86 -0
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeExons.tsx +193 -0
- package/src/components/MergeTranscripts.tsx +182 -0
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/SplitExon.tsx +134 -0
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +4 -0
- package/src/config.ts +11 -0
- package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
- package/src/extensions/annotationFromPileup.ts +99 -89
- package/src/index.ts +42 -105
- package/src/makeDisplayComponent.tsx +0 -1
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +163 -104
- package/src/util/annotationFeatureUtils.ts +59 -0
- package/src/util/copyToClipboard.ts +21 -0
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +201 -0
- package/src/util/index.ts +2 -0
- package/src/util/mouseEventsUtils.ts +145 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
DeleteFeatureChange,
|
|
6
|
+
LocationEndChange,
|
|
7
|
+
LocationStartChange,
|
|
8
|
+
} from '@apollo-annotation/shared'
|
|
6
9
|
import {
|
|
7
10
|
Button,
|
|
8
11
|
DialogActions,
|
|
@@ -17,6 +20,14 @@ import { type ApolloSessionModel } from '../session'
|
|
|
17
20
|
|
|
18
21
|
import { Dialog } from './Dialog'
|
|
19
22
|
|
|
23
|
+
interface LocationChange {
|
|
24
|
+
typeName: 'LocationStartChange' | 'LocationEndChange'
|
|
25
|
+
changedId: string
|
|
26
|
+
featureId: string
|
|
27
|
+
oldLocation: number
|
|
28
|
+
newLocation: number
|
|
29
|
+
}
|
|
30
|
+
|
|
20
31
|
interface DeleteFeatureProps {
|
|
21
32
|
session: ApolloSessionModel
|
|
22
33
|
handleClose(): void
|
|
@@ -27,6 +38,60 @@ interface DeleteFeatureProps {
|
|
|
27
38
|
setSelectedFeature(feature?: AnnotationFeature): void
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
function lumpLocationChanges(
|
|
42
|
+
changes: LocationChange[],
|
|
43
|
+
assembly: string,
|
|
44
|
+
): LocationStartChange | LocationEndChange | undefined {
|
|
45
|
+
if (changes.length === 0) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
const locationStartChange = new LocationStartChange({
|
|
49
|
+
typeName: 'LocationStartChange',
|
|
50
|
+
changedIds: [],
|
|
51
|
+
changes: [],
|
|
52
|
+
assembly,
|
|
53
|
+
})
|
|
54
|
+
const locationEndChange = new LocationEndChange({
|
|
55
|
+
typeName: 'LocationEndChange',
|
|
56
|
+
changedIds: [],
|
|
57
|
+
changes: [],
|
|
58
|
+
assembly,
|
|
59
|
+
})
|
|
60
|
+
for (const change of changes) {
|
|
61
|
+
if (change.typeName === 'LocationStartChange') {
|
|
62
|
+
locationStartChange.changedIds.push(change.changedId)
|
|
63
|
+
const cc = {
|
|
64
|
+
featureId: change.featureId,
|
|
65
|
+
oldStart: change.oldLocation,
|
|
66
|
+
newStart: change.newLocation,
|
|
67
|
+
}
|
|
68
|
+
locationStartChange.changes.push(cc)
|
|
69
|
+
}
|
|
70
|
+
if (change.typeName === 'LocationEndChange') {
|
|
71
|
+
locationEndChange.changedIds.push(change.changedId)
|
|
72
|
+
const cc = {
|
|
73
|
+
featureId: change.featureId,
|
|
74
|
+
oldEnd: change.oldLocation,
|
|
75
|
+
newEnd: change.newLocation,
|
|
76
|
+
}
|
|
77
|
+
locationEndChange.changes.push(cc)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
locationStartChange.changedIds.length > 0 &&
|
|
82
|
+
locationEndChange.changedIds.length === 0
|
|
83
|
+
) {
|
|
84
|
+
return locationStartChange
|
|
85
|
+
}
|
|
86
|
+
if (
|
|
87
|
+
locationEndChange.changedIds.length > 0 &&
|
|
88
|
+
locationStartChange.changedIds.length === 0
|
|
89
|
+
) {
|
|
90
|
+
return locationEndChange
|
|
91
|
+
}
|
|
92
|
+
throw new Error('Unexpected list of changes')
|
|
93
|
+
}
|
|
94
|
+
|
|
30
95
|
export function DeleteFeature({
|
|
31
96
|
changeManager,
|
|
32
97
|
handleClose,
|
|
@@ -36,8 +101,198 @@ export function DeleteFeature({
|
|
|
36
101
|
sourceAssemblyId,
|
|
37
102
|
sourceFeature,
|
|
38
103
|
}: DeleteFeatureProps) {
|
|
39
|
-
const { notify } = session as unknown as AbstractSessionModel
|
|
40
104
|
const [errorMessage, setErrorMessage] = useState('')
|
|
105
|
+
const { ontologyManager } = session.apolloDataStore
|
|
106
|
+
const { featureTypeOntology } = ontologyManager
|
|
107
|
+
|
|
108
|
+
function trimCDS(
|
|
109
|
+
sourceFeature: AnnotationFeature,
|
|
110
|
+
): DeleteFeatureChange | LocationChange | undefined {
|
|
111
|
+
if (!featureTypeOntology) {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
if (!featureTypeOntology.isTypeOf(sourceFeature.type, 'exon')) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
if (
|
|
118
|
+
!sourceFeature.parent?.cdsLocations ||
|
|
119
|
+
sourceFeature.parent.cdsLocations.length === 0 ||
|
|
120
|
+
sourceFeature.parent.cdsLocations[0].length === 0
|
|
121
|
+
) {
|
|
122
|
+
// No CDS - parent of this exon is a non-coding transcript
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
if (!sourceFeature.parent.children) {
|
|
126
|
+
throw new Error('Unable to find parent of CDS')
|
|
127
|
+
}
|
|
128
|
+
if (sourceFeature.parent.cdsLocations.length != 1) {
|
|
129
|
+
throw new Error('Unable to handle a transcript with multiple CDSs')
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const _cdsLocations = sourceFeature.parent.cdsLocations.at(0) ?? []
|
|
133
|
+
const cdsLocations = _cdsLocations.sort(({ min: a }, { min: b }) => a - b)
|
|
134
|
+
let cdsFeature
|
|
135
|
+
for (const child of sourceFeature.parent.children.values()) {
|
|
136
|
+
if (child.type === cdsLocations[0].type) {
|
|
137
|
+
cdsFeature = child
|
|
138
|
+
break
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!cdsFeature) {
|
|
142
|
+
throw new Error('Unable to find CDS')
|
|
143
|
+
}
|
|
144
|
+
const cdsStart = cdsLocations[0].min
|
|
145
|
+
// eslint-disable-next-line unicorn/prefer-at
|
|
146
|
+
const cdsEnd = cdsLocations[cdsLocations.length - 1].max
|
|
147
|
+
if (
|
|
148
|
+
(sourceFeature.min > cdsStart && sourceFeature.max < cdsEnd) ||
|
|
149
|
+
sourceFeature.max < cdsStart ||
|
|
150
|
+
sourceFeature.min > cdsEnd
|
|
151
|
+
) {
|
|
152
|
+
// No adjustment if the exon being deleted is fully contained in the CDS
|
|
153
|
+
// or completely outside of the CDS
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
if (sourceFeature.min <= cdsStart && sourceFeature.max >= cdsEnd) {
|
|
157
|
+
// CDS is fully contained in the exon, delete CDS
|
|
158
|
+
return new DeleteFeatureChange({
|
|
159
|
+
changedIds: [cdsFeature._id],
|
|
160
|
+
typeName: 'DeleteFeatureChange',
|
|
161
|
+
assembly: sourceAssemblyId,
|
|
162
|
+
changes: [
|
|
163
|
+
{
|
|
164
|
+
deletedFeature: getSnapshot(cdsFeature),
|
|
165
|
+
parentFeatureId: cdsFeature.parent?._id,
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
if (sourceFeature.min <= cdsStart && sourceFeature.max > cdsStart) {
|
|
171
|
+
// Exon overlaps the start of the CDS so we need to move the CDS start
|
|
172
|
+
let newCdsStart
|
|
173
|
+
for (const cdsLocation of cdsLocations) {
|
|
174
|
+
if (cdsLocation.min > sourceFeature.max) {
|
|
175
|
+
newCdsStart = cdsLocation.min
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (!newCdsStart) {
|
|
180
|
+
throw new Error('Error setting new CDS start')
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
typeName: 'LocationStartChange',
|
|
184
|
+
changedId: cdsFeature._id,
|
|
185
|
+
featureId: cdsFeature._id,
|
|
186
|
+
oldLocation: cdsFeature.min,
|
|
187
|
+
newLocation: newCdsStart,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (sourceFeature.min < cdsEnd && sourceFeature.max >= cdsEnd) {
|
|
191
|
+
// Exon overlaps the end of the CDS so we need to move the CDS end
|
|
192
|
+
let newCdsEnd
|
|
193
|
+
for (const cdsLocation of cdsLocations.reverse()) {
|
|
194
|
+
if (cdsLocation.max < sourceFeature.min) {
|
|
195
|
+
newCdsEnd = cdsLocation.max
|
|
196
|
+
break
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!newCdsEnd) {
|
|
200
|
+
throw new Error('Error setting new CDS end')
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
typeName: 'LocationEndChange',
|
|
204
|
+
changedId: cdsFeature._id,
|
|
205
|
+
featureId: cdsFeature._id,
|
|
206
|
+
oldLocation: cdsFeature.max,
|
|
207
|
+
newLocation: newCdsEnd,
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
throw new Error('Unexpected relationship between exon and CDS')
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function trimParent(
|
|
214
|
+
featureToDelete: AnnotationFeature,
|
|
215
|
+
): LocationChange | undefined {
|
|
216
|
+
if (
|
|
217
|
+
!featureToDelete.parent?.children ||
|
|
218
|
+
featureToDelete.parent.children.size === 1
|
|
219
|
+
) {
|
|
220
|
+
// Do not resize if this parent has only one child (i.e. the feature being deleted)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
const childrenByStart = []
|
|
224
|
+
for (const x of featureToDelete.parent.children.values()) {
|
|
225
|
+
if (!featureTypeOntology?.isTypeOf(x.type, 'CDS')) {
|
|
226
|
+
// CDS has been already handled so don't use it to resize parent
|
|
227
|
+
childrenByStart.push(x)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
childrenByStart.sort((a, b) => a.min - b.min)
|
|
231
|
+
|
|
232
|
+
const childrenByEnd = []
|
|
233
|
+
for (const x of featureToDelete.parent.children.values()) {
|
|
234
|
+
if (!featureTypeOntology?.isTypeOf(x.type, 'CDS')) {
|
|
235
|
+
// CDS has been already handled so don't use it to resize parent
|
|
236
|
+
childrenByEnd.push(x)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
childrenByEnd.sort((a, b) => b.max - a.max)
|
|
240
|
+
|
|
241
|
+
if (featureToDelete.min === childrenByStart[0].min) {
|
|
242
|
+
// The feature to delete has the lowest start coordinate of all children
|
|
243
|
+
// Find the next lowest coordinate and reset parent to this new start
|
|
244
|
+
let newParentFeatureStart
|
|
245
|
+
for (const child of childrenByStart) {
|
|
246
|
+
if (
|
|
247
|
+
child._id !== featureToDelete._id &&
|
|
248
|
+
child.min >= featureToDelete.min
|
|
249
|
+
) {
|
|
250
|
+
newParentFeatureStart = child.min
|
|
251
|
+
break
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (
|
|
255
|
+
newParentFeatureStart &&
|
|
256
|
+
newParentFeatureStart != featureToDelete.parent.min
|
|
257
|
+
) {
|
|
258
|
+
return {
|
|
259
|
+
typeName: 'LocationStartChange',
|
|
260
|
+
changedId: featureToDelete.parent._id,
|
|
261
|
+
featureId: featureToDelete.parent._id,
|
|
262
|
+
oldLocation: featureToDelete.parent.min,
|
|
263
|
+
newLocation: newParentFeatureStart,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (featureToDelete.max === childrenByEnd[0].max) {
|
|
269
|
+
// The feature to delete has the highest end coordinate of all children
|
|
270
|
+
// Find the next highest coordinate and reset parent to this new end
|
|
271
|
+
let newParentFeatureEnd
|
|
272
|
+
for (const child of childrenByEnd) {
|
|
273
|
+
if (
|
|
274
|
+
child._id != featureToDelete._id &&
|
|
275
|
+
child.max <= featureToDelete.max
|
|
276
|
+
) {
|
|
277
|
+
newParentFeatureEnd = child.max
|
|
278
|
+
break
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (
|
|
282
|
+
newParentFeatureEnd &&
|
|
283
|
+
newParentFeatureEnd != featureToDelete.parent.max
|
|
284
|
+
) {
|
|
285
|
+
return {
|
|
286
|
+
typeName: 'LocationEndChange',
|
|
287
|
+
changedId: featureToDelete.parent._id,
|
|
288
|
+
featureId: featureToDelete.parent._id,
|
|
289
|
+
oldLocation: featureToDelete.parent.max,
|
|
290
|
+
newLocation: newParentFeatureEnd,
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return
|
|
295
|
+
}
|
|
41
296
|
|
|
42
297
|
async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
|
|
43
298
|
event.preventDefault()
|
|
@@ -46,16 +301,104 @@ export function DeleteFeature({
|
|
|
46
301
|
setSelectedFeature()
|
|
47
302
|
}
|
|
48
303
|
|
|
49
|
-
|
|
50
|
-
const
|
|
304
|
+
const locationChanges: LocationChange[] = []
|
|
305
|
+
// const deleteChanges: DeleteFeatureChange = []
|
|
306
|
+
|
|
307
|
+
const deleteChanges = new DeleteFeatureChange({
|
|
51
308
|
changedIds: [sourceFeature._id],
|
|
52
309
|
typeName: 'DeleteFeatureChange',
|
|
53
310
|
assembly: sourceAssemblyId,
|
|
54
|
-
|
|
55
|
-
|
|
311
|
+
changes: [
|
|
312
|
+
{
|
|
313
|
+
deletedFeature: getSnapshot(sourceFeature),
|
|
314
|
+
parentFeatureId: sourceFeature.parent?._id,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
56
317
|
})
|
|
57
|
-
|
|
58
|
-
|
|
318
|
+
|
|
319
|
+
if (
|
|
320
|
+
featureTypeOntology &&
|
|
321
|
+
(featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') ||
|
|
322
|
+
featureTypeOntology.isTypeOf(
|
|
323
|
+
sourceFeature.type,
|
|
324
|
+
'pseudogenic_transcript',
|
|
325
|
+
))
|
|
326
|
+
) {
|
|
327
|
+
const geneChange = trimParent(sourceFeature)
|
|
328
|
+
if (geneChange) {
|
|
329
|
+
locationChanges.push(geneChange)
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (
|
|
334
|
+
featureTypeOntology &&
|
|
335
|
+
featureTypeOntology.isTypeOf(sourceFeature.type, 'exon')
|
|
336
|
+
) {
|
|
337
|
+
const cdsChange = trimCDS(sourceFeature)
|
|
338
|
+
if (cdsChange) {
|
|
339
|
+
if (cdsChange.typeName === 'DeleteFeatureChange') {
|
|
340
|
+
deleteChanges.changedIds.push(...cdsChange.changedIds)
|
|
341
|
+
deleteChanges.changes.push(...cdsChange.changes)
|
|
342
|
+
} else {
|
|
343
|
+
locationChanges.push(cdsChange)
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const txChange = trimParent(sourceFeature)
|
|
348
|
+
if (txChange) {
|
|
349
|
+
locationChanges.push(txChange)
|
|
350
|
+
// Parent transcript has changed. See if we need to resize the parent gene
|
|
351
|
+
const gene = sourceFeature.parent?.parent
|
|
352
|
+
if (gene?.children) {
|
|
353
|
+
if (txChange.typeName === 'LocationStartChange') {
|
|
354
|
+
let newGeneStart = txChange.newLocation
|
|
355
|
+
for (const [, tx] of gene.children) {
|
|
356
|
+
if (tx._id != txChange.featureId && tx.min < newGeneStart) {
|
|
357
|
+
// Reset to longest child (tx)
|
|
358
|
+
newGeneStart = tx.min
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (newGeneStart != gene.min) {
|
|
362
|
+
locationChanges.push({
|
|
363
|
+
typeName: txChange.typeName,
|
|
364
|
+
changedId: gene._id,
|
|
365
|
+
featureId: gene._id,
|
|
366
|
+
oldLocation: gene.min,
|
|
367
|
+
newLocation: newGeneStart,
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
let newGeneEnd = txChange.newLocation
|
|
372
|
+
for (const [, tx] of gene.children) {
|
|
373
|
+
if (tx._id != txChange.featureId && tx.max > newGeneEnd) {
|
|
374
|
+
// Reset to longest child (tx)
|
|
375
|
+
newGeneEnd = tx.max
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (newGeneEnd != gene.max) {
|
|
379
|
+
locationChanges.push({
|
|
380
|
+
typeName: txChange.typeName,
|
|
381
|
+
changedId: gene._id,
|
|
382
|
+
featureId: gene._id,
|
|
383
|
+
oldLocation: gene.max,
|
|
384
|
+
newLocation: newGeneEnd,
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const lumpedLocChanges = lumpLocationChanges(
|
|
393
|
+
locationChanges,
|
|
394
|
+
sourceAssemblyId,
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
await changeManager.submit(deleteChanges)
|
|
398
|
+
if (lumpedLocChanges) {
|
|
399
|
+
await changeManager.submit(lumpedLocChanges)
|
|
400
|
+
}
|
|
401
|
+
|
|
59
402
|
handleClose()
|
|
60
403
|
event.preventDefault()
|
|
61
404
|
}
|
|
@@ -68,7 +411,11 @@ export function DeleteFeature({
|
|
|
68
411
|
maxWidth={false}
|
|
69
412
|
data-testid="delete-feature"
|
|
70
413
|
>
|
|
71
|
-
<form
|
|
414
|
+
<form
|
|
415
|
+
onSubmit={(event) => {
|
|
416
|
+
void onSubmit(event)
|
|
417
|
+
}}
|
|
418
|
+
>
|
|
72
419
|
<DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
|
|
73
420
|
<DialogContentText>
|
|
74
421
|
Are you sure you want to delete the selected feature?
|
|
@@ -10,9 +10,12 @@ import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
|
|
|
10
10
|
import { getConf } from '@jbrowse/core/configuration'
|
|
11
11
|
import {
|
|
12
12
|
Button,
|
|
13
|
+
Checkbox,
|
|
13
14
|
DialogActions,
|
|
14
15
|
DialogContent,
|
|
15
16
|
DialogContentText,
|
|
17
|
+
FormControlLabel,
|
|
18
|
+
FormGroup,
|
|
16
19
|
MenuItem,
|
|
17
20
|
Select,
|
|
18
21
|
type SelectChangeEvent,
|
|
@@ -37,6 +40,7 @@ interface DownloadGFF3Props {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
|
|
43
|
+
const [includeFASTA, setincludeFASTA] = useState(false)
|
|
40
44
|
const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
|
|
41
45
|
const [errorMessage, setErrorMessage] = useState('')
|
|
42
46
|
|
|
@@ -114,7 +118,7 @@ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
|
|
|
114
118
|
const exportURL = new URL('export', internetAccount.baseURL)
|
|
115
119
|
const params: Record<string, string> = {
|
|
116
120
|
exportID,
|
|
117
|
-
includeFASTA: 'true',
|
|
121
|
+
includeFASTA: includeFASTA ? 'true' : 'false',
|
|
118
122
|
}
|
|
119
123
|
const exportSearchParams = new URLSearchParams(params)
|
|
120
124
|
exportURL.search = exportSearchParams.toString()
|
|
@@ -199,6 +203,21 @@ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
|
|
|
199
203
|
<DialogContentText>
|
|
200
204
|
Select assembly to export to GFF3
|
|
201
205
|
</DialogContentText>
|
|
206
|
+
|
|
207
|
+
<FormGroup>
|
|
208
|
+
<FormControlLabel
|
|
209
|
+
data-testid="include-fasta-checkbox"
|
|
210
|
+
control={
|
|
211
|
+
<Checkbox
|
|
212
|
+
checked={includeFASTA}
|
|
213
|
+
onChange={() => {
|
|
214
|
+
setincludeFASTA(!includeFASTA)
|
|
215
|
+
}}
|
|
216
|
+
/>
|
|
217
|
+
}
|
|
218
|
+
label="Include fasta sequence in GFF output"
|
|
219
|
+
/>
|
|
220
|
+
</FormGroup>
|
|
202
221
|
</DialogContent>
|
|
203
222
|
<DialogActions>
|
|
204
223
|
<Button
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Dialog } from '@jbrowse/core/ui'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogContent,
|
|
6
|
+
TextField,
|
|
7
|
+
Typography,
|
|
8
|
+
} from '@mui/material'
|
|
9
|
+
import { observer } from 'mobx-react'
|
|
10
|
+
import React, { useState } from 'react'
|
|
11
|
+
|
|
12
|
+
const EditZoomThresholdDialog = observer(function ({
|
|
13
|
+
model,
|
|
14
|
+
handleClose,
|
|
15
|
+
}: {
|
|
16
|
+
model: {
|
|
17
|
+
zoomThresholdSetting: number
|
|
18
|
+
setZoomThresholdSetting: (a: { zoomThreshold: number }) => void
|
|
19
|
+
}
|
|
20
|
+
handleClose: () => void
|
|
21
|
+
}) {
|
|
22
|
+
const [zoomThreshold, setZoomThreshold] = useState(
|
|
23
|
+
`${model.zoomThresholdSetting}`,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Dialog open onClose={handleClose} title="Edit zoom threshold setting">
|
|
28
|
+
<DialogContent>
|
|
29
|
+
<Typography>
|
|
30
|
+
The zoom level in base pairs (bp) per pixel at which features are
|
|
31
|
+
rendered in this Annotations track. Increasing the value will allow
|
|
32
|
+
features to render when zooming out, but might impact performance.
|
|
33
|
+
</Typography>
|
|
34
|
+
<TextField
|
|
35
|
+
label="Threshold value (bpPerPx)"
|
|
36
|
+
value={zoomThreshold}
|
|
37
|
+
onChange={(event) => {
|
|
38
|
+
setZoomThreshold(event.target.value)
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
<DialogActions>
|
|
43
|
+
<Button
|
|
44
|
+
variant="contained"
|
|
45
|
+
onClick={() => {
|
|
46
|
+
model.setZoomThresholdSetting({
|
|
47
|
+
zoomThreshold: +zoomThreshold,
|
|
48
|
+
})
|
|
49
|
+
handleClose()
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
Submit
|
|
53
|
+
</Button>
|
|
54
|
+
<Button
|
|
55
|
+
variant="contained"
|
|
56
|
+
color="secondary"
|
|
57
|
+
onClick={() => {
|
|
58
|
+
handleClose()
|
|
59
|
+
}}
|
|
60
|
+
>
|
|
61
|
+
Cancel
|
|
62
|
+
</Button>
|
|
63
|
+
</DialogActions>
|
|
64
|
+
</DialogContent>
|
|
65
|
+
</Dialog>
|
|
66
|
+
)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
export default EditZoomThresholdDialog
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
Chip,
|
|
5
5
|
DialogContent,
|
|
6
6
|
DialogContentText,
|
|
7
|
-
|
|
7
|
+
Grid,
|
|
8
8
|
TextField,
|
|
9
9
|
} from '@mui/material'
|
|
10
10
|
import { observer } from 'mobx-react'
|
|
@@ -62,8 +62,8 @@ export const FilterFeatures = observer(function FilterFeatures({
|
|
|
62
62
|
<DialogContentText>
|
|
63
63
|
Select the feature types you want to display in the apollo track
|
|
64
64
|
</DialogContentText>
|
|
65
|
-
<
|
|
66
|
-
<
|
|
65
|
+
<Grid container spacing={2}>
|
|
66
|
+
<Grid size={8}>
|
|
67
67
|
<OntologyTermAutocomplete
|
|
68
68
|
session={session}
|
|
69
69
|
ontologyName="Sequence Ontology"
|
|
@@ -84,8 +84,8 @@ export const FilterFeatures = observer(function FilterFeatures({
|
|
|
84
84
|
}
|
|
85
85
|
}}
|
|
86
86
|
/>
|
|
87
|
-
</
|
|
88
|
-
<
|
|
87
|
+
</Grid>
|
|
88
|
+
<Grid size={4}>
|
|
89
89
|
<Button
|
|
90
90
|
variant="contained"
|
|
91
91
|
onClick={handleAddFeatureType}
|
|
@@ -95,8 +95,8 @@ export const FilterFeatures = observer(function FilterFeatures({
|
|
|
95
95
|
>
|
|
96
96
|
Add
|
|
97
97
|
</Button>
|
|
98
|
-
</
|
|
99
|
-
</
|
|
98
|
+
</Grid>
|
|
99
|
+
</Grid>
|
|
100
100
|
{selectedFeatureTypes.length > 0 && (
|
|
101
101
|
<div>
|
|
102
102
|
<hr />
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
|
+
import {
|
|
3
|
+
Checkbox,
|
|
4
|
+
DialogContent,
|
|
5
|
+
DialogContentText,
|
|
6
|
+
FormControlLabel,
|
|
7
|
+
FormGroup,
|
|
8
|
+
Grid,
|
|
9
|
+
} from '@mui/material'
|
|
10
|
+
import { observer } from 'mobx-react'
|
|
11
|
+
import React, { useState } from 'react'
|
|
12
|
+
|
|
13
|
+
import { Dialog } from './Dialog'
|
|
14
|
+
|
|
15
|
+
interface FilterTranscriptsProps {
|
|
16
|
+
onUpdate: (forms: string[]) => void
|
|
17
|
+
sourceFeature: AnnotationFeature
|
|
18
|
+
filteredTranscripts: string[]
|
|
19
|
+
handleClose: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const FilterTranscripts = observer(function FilterTranscripts({
|
|
23
|
+
sourceFeature,
|
|
24
|
+
filteredTranscripts,
|
|
25
|
+
handleClose,
|
|
26
|
+
onUpdate,
|
|
27
|
+
}: FilterTranscriptsProps) {
|
|
28
|
+
const allTranscripts: string[] = []
|
|
29
|
+
if (sourceFeature.children) {
|
|
30
|
+
for (const [, child] of sourceFeature.children) {
|
|
31
|
+
const childID: string | undefined = child.attributes
|
|
32
|
+
.get('gff_id')
|
|
33
|
+
?.toString()
|
|
34
|
+
if (childID) {
|
|
35
|
+
allTranscripts.push(childID)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const [excludedTranscripts, setExcludedTranscripts] =
|
|
40
|
+
useState<string[]>(filteredTranscripts)
|
|
41
|
+
const handleChange = (value: string) => {
|
|
42
|
+
const newForms = excludedTranscripts.includes(value)
|
|
43
|
+
? excludedTranscripts.filter((form) => form !== value)
|
|
44
|
+
: [...excludedTranscripts, value]
|
|
45
|
+
onUpdate(newForms)
|
|
46
|
+
setExcludedTranscripts(newForms)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Dialog
|
|
51
|
+
open
|
|
52
|
+
maxWidth={false}
|
|
53
|
+
data-testid="filter-transcripts-dialog"
|
|
54
|
+
title="Filter transcripts by ID"
|
|
55
|
+
handleClose={handleClose}
|
|
56
|
+
>
|
|
57
|
+
<DialogContent>
|
|
58
|
+
<DialogContentText>
|
|
59
|
+
Select the alternate transcripts you want to display in the apollo
|
|
60
|
+
track
|
|
61
|
+
</DialogContentText>
|
|
62
|
+
<Grid container spacing={2}>
|
|
63
|
+
<Grid size={8}>
|
|
64
|
+
<FormGroup>
|
|
65
|
+
{allTranscripts.map((item) => (
|
|
66
|
+
// eslint-disable-next-line react/jsx-key
|
|
67
|
+
<FormControlLabel
|
|
68
|
+
control={
|
|
69
|
+
<Checkbox
|
|
70
|
+
checked={!excludedTranscripts.includes(item)}
|
|
71
|
+
onChange={() => {
|
|
72
|
+
handleChange(item)
|
|
73
|
+
}}
|
|
74
|
+
slotProps={{ input: { 'aria-label': 'controlled' } }}
|
|
75
|
+
/>
|
|
76
|
+
}
|
|
77
|
+
label={item}
|
|
78
|
+
/>
|
|
79
|
+
))}
|
|
80
|
+
</FormGroup>
|
|
81
|
+
</Grid>
|
|
82
|
+
</Grid>
|
|
83
|
+
</DialogContent>
|
|
84
|
+
</Dialog>
|
|
85
|
+
)
|
|
86
|
+
})
|
|
@@ -238,7 +238,7 @@ export function ManageChecks({ handleClose, session }: ManageChecksProps) {
|
|
|
238
238
|
>
|
|
239
239
|
{assemblies.map((option) => (
|
|
240
240
|
<MenuItem key={option.name} value={option.name}>
|
|
241
|
-
{option.displayName
|
|
241
|
+
{option.displayName}
|
|
242
242
|
</MenuItem>
|
|
243
243
|
))}
|
|
244
244
|
</Select>
|