@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,11 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
- /* eslint-disable @typescript-eslint/no-misused-promises */
3
- import { AnnotationFeature } from '@apollo-annotation/mst'
4
- import { DeleteFeatureChange } from '@apollo-annotation/shared'
5
- import { AbstractSessionModel } from '@jbrowse/core/util'
2
+
3
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
4
+ import {
5
+ DeleteFeatureChange,
6
+ LocationEndChange,
7
+ LocationStartChange,
8
+ } from '@apollo-annotation/shared'
6
9
  import {
7
10
  Button,
8
11
  DialogActions,
@@ -12,10 +15,19 @@ import {
12
15
  import { getSnapshot } from 'mobx-state-tree'
13
16
  import React, { useState } from 'react'
14
17
 
15
- import { ChangeManager } from '../ChangeManager'
16
- import { ApolloSessionModel } from '../session'
18
+ import { type ChangeManager } from '../ChangeManager'
19
+ import { type ApolloSessionModel } from '../session'
20
+
17
21
  import { Dialog } from './Dialog'
18
22
 
23
+ interface LocationChange {
24
+ typeName: 'LocationStartChange' | 'LocationEndChange'
25
+ changedId: string
26
+ featureId: string
27
+ oldLocation: number
28
+ newLocation: number
29
+ }
30
+
19
31
  interface DeleteFeatureProps {
20
32
  session: ApolloSessionModel
21
33
  handleClose(): void
@@ -26,6 +38,60 @@ interface DeleteFeatureProps {
26
38
  setSelectedFeature(feature?: AnnotationFeature): void
27
39
  }
28
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
+
29
95
  export function DeleteFeature({
30
96
  changeManager,
31
97
  handleClose,
@@ -35,8 +101,198 @@ export function DeleteFeature({
35
101
  sourceAssemblyId,
36
102
  sourceFeature,
37
103
  }: DeleteFeatureProps) {
38
- const { notify } = session as unknown as AbstractSessionModel
39
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
+ }
40
296
 
41
297
  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
42
298
  event.preventDefault()
@@ -45,16 +301,104 @@ export function DeleteFeature({
45
301
  setSelectedFeature()
46
302
  }
47
303
 
48
- // Delete features
49
- const change = new DeleteFeatureChange({
304
+ const locationChanges: LocationChange[] = []
305
+ // const deleteChanges: DeleteFeatureChange = []
306
+
307
+ const deleteChanges = new DeleteFeatureChange({
50
308
  changedIds: [sourceFeature._id],
51
309
  typeName: 'DeleteFeatureChange',
52
310
  assembly: sourceAssemblyId,
53
- deletedFeature: getSnapshot(sourceFeature),
54
- parentFeatureId: sourceFeature.parent?._id,
311
+ changes: [
312
+ {
313
+ deletedFeature: getSnapshot(sourceFeature),
314
+ parentFeatureId: sourceFeature.parent?._id,
315
+ },
316
+ ],
55
317
  })
56
- await changeManager.submit(change)
57
- notify('Feature deleted successfully', 'success')
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
+
58
402
  handleClose()
59
403
  event.preventDefault()
60
404
  }
@@ -67,7 +411,11 @@ export function DeleteFeature({
67
411
  maxWidth={false}
68
412
  data-testid="delete-feature"
69
413
  >
70
- <form onSubmit={onSubmit}>
414
+ <form
415
+ onSubmit={(event) => {
416
+ void onSubmit(event)
417
+ }}
418
+ >
71
419
  <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
72
420
  <DialogContentText>
73
421
  Are you sure you want to delete the selected feature?
@@ -1,7 +1,7 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
2
  import { Dialog as JBDialog } from '@jbrowse/core/ui'
3
3
  import CloseIcon from '@mui/icons-material/Close'
4
- import { DialogProps, DialogTitle, IconButton } from '@mui/material'
4
+ import { type DialogProps, DialogTitle, IconButton } from '@mui/material'
5
5
  import { observer } from 'mobx-react'
6
6
  import React from 'react'
7
7
  import { makeStyles } from 'tss-react/mui'
@@ -3,32 +3,36 @@
3
3
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
4
4
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
5
5
  /* eslint-disable @typescript-eslint/no-misused-promises */
6
- import { ApolloAssembly } from '@apollo-annotation/mst'
7
- import gff, { GFF3Item } from '@gmod/gff'
8
- import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
6
+ import { type ApolloAssembly } from '@apollo-annotation/mst'
7
+ import { annotationFeatureToGFF3 } from '@apollo-annotation/shared'
8
+ import gff, { type GFF3Item } from '@gmod/gff'
9
+ import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
9
10
  import { getConf } from '@jbrowse/core/configuration'
10
11
  import {
11
12
  Button,
13
+ Checkbox,
12
14
  DialogActions,
13
15
  DialogContent,
14
16
  DialogContentText,
17
+ FormControlLabel,
18
+ FormGroup,
15
19
  MenuItem,
16
20
  Select,
17
- SelectChangeEvent,
21
+ type SelectChangeEvent,
18
22
  } from '@mui/material'
19
23
  import { saveAs } from 'file-saver'
20
- import { IMSTMap, getSnapshot } from 'mobx-state-tree'
24
+ import { type IMSTMap, getSnapshot } from 'mobx-state-tree'
21
25
  import React, { useState } from 'react'
22
26
 
23
27
  import {
24
- ApolloInternetAccount,
25
- CollaborationServerDriver,
26
- InMemoryFileDriver,
28
+ type ApolloInternetAccount,
29
+ type CollaborationServerDriver,
30
+ type InMemoryFileDriver,
27
31
  } from '../BackendDrivers'
28
- import { ApolloSessionModel } from '../session'
32
+ import { type ApolloSessionModel } from '../session'
29
33
  import { createFetchErrorMessage } from '../util'
34
+
30
35
  import { Dialog } from './Dialog'
31
- import { annotationFeatureToGFF3 } from '@apollo-annotation/shared'
32
36
 
33
37
  interface DownloadGFF3Props {
34
38
  session: ApolloSessionModel
@@ -36,6 +40,7 @@ interface DownloadGFF3Props {
36
40
  }
37
41
 
38
42
  export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
43
+ const [includeFASTA, setincludeFASTA] = useState(false)
39
44
  const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
40
45
  const [errorMessage, setErrorMessage] = useState('')
41
46
 
@@ -113,7 +118,7 @@ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
113
118
  const exportURL = new URL('export', internetAccount.baseURL)
114
119
  const params: Record<string, string> = {
115
120
  exportID,
116
- includeFASTA: 'true',
121
+ includeFASTA: includeFASTA ? 'true' : 'false',
117
122
  }
118
123
  const exportSearchParams = new URLSearchParams(params)
119
124
  exportURL.search = exportSearchParams.toString()
@@ -198,6 +203,21 @@ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
198
203
  <DialogContentText>
199
204
  Select assembly to export to GFF3
200
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>
201
221
  </DialogContent>
202
222
  <DialogActions>
203
223
  <Button
@@ -1,6 +1,3 @@
1
- import React, { useState } from 'react'
2
- import { ApolloSessionModel } from '../session'
3
- import { Dialog } from './Dialog'
4
1
  import {
5
2
  Box,
6
3
  Button,
@@ -10,9 +7,14 @@ import {
10
7
  Grid2,
11
8
  TextField,
12
9
  } from '@mui/material'
10
+ import { observer } from 'mobx-react'
11
+ import React, { useState } from 'react'
12
+
13
13
  import { isOntologyClass } from '../OntologyManager'
14
+ import { type ApolloSessionModel } from '../session'
15
+
16
+ import { Dialog } from './Dialog'
14
17
  import { OntologyTermAutocomplete } from './OntologyTermAutocomplete'
15
- import { observer } from 'mobx-react'
16
18
 
17
19
  interface FilterFeaturesProps {
18
20
  onUpdate: (types: string[]) => void
@@ -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
+ Grid2,
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
+ <Grid2 container spacing={2}>
63
+ <Grid2 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
+ inputProps={{ 'aria-label': 'controlled' }}
75
+ />
76
+ }
77
+ label={item}
78
+ />
79
+ ))}
80
+ </FormGroup>
81
+ </Grid2>
82
+ </Grid2>
83
+ </DialogContent>
84
+ </Dialog>
85
+ )
86
+ })
@@ -6,7 +6,7 @@
6
6
  /* eslint-disable @typescript-eslint/no-unsafe-assignment */
7
7
  /* eslint-disable @typescript-eslint/no-misused-promises */
8
8
  import { AddFeaturesFromFileChange } from '@apollo-annotation/shared'
9
- import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
9
+ import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
10
10
  import { getConf } from '@jbrowse/core/configuration'
11
11
  import {
12
12
  Button,
@@ -15,7 +15,7 @@ import {
15
15
  DialogContentText,
16
16
  MenuItem,
17
17
  Select,
18
- SelectChangeEvent,
18
+ type SelectChangeEvent,
19
19
  } from '@mui/material'
20
20
  import Checkbox from '@mui/material/Checkbox'
21
21
  import FormControlLabel from '@mui/material/FormControlLabel'
@@ -23,12 +23,13 @@ import LinearProgress from '@mui/material/LinearProgress'
23
23
  import React, { useEffect, useState } from 'react'
24
24
 
25
25
  import {
26
- ApolloInternetAccount,
27
- CollaborationServerDriver,
26
+ type ApolloInternetAccount,
27
+ type CollaborationServerDriver,
28
28
  } from '../BackendDrivers'
29
- import { ChangeManager } from '../ChangeManager'
30
- import { ApolloSessionModel } from '../session'
29
+ import { type ChangeManager } from '../ChangeManager'
30
+ import { type ApolloSessionModel } from '../session'
31
31
  import { createFetchErrorMessage } from '../util'
32
+
32
33
  import { Dialog } from './Dialog'
33
34
 
34
35
  interface ImportFeaturesProps {
@@ -6,14 +6,15 @@ import {
6
6
  DialogContentText,
7
7
  MenuItem,
8
8
  Select,
9
- SelectChangeEvent,
9
+ type SelectChangeEvent,
10
10
  } from '@mui/material'
11
11
  import { getRoot } from 'mobx-state-tree'
12
12
  import React, { useState } from 'react'
13
13
 
14
- import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
15
- import { ApolloSessionModel } from '../session'
16
- import { ApolloRootModel } from '../types'
14
+ import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
15
+ import { type ApolloSessionModel } from '../session'
16
+ import { type ApolloRootModel } from '../types'
17
+
17
18
  import { Dialog } from './Dialog'
18
19
 
19
20
  interface DeleteAssemblyProps {