@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.
Files changed (84) hide show
  1. package/dist/index.esm.js +4603 -2045
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
  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 +9387 -4016
  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 +15 -15
  12. package/src/ApolloInternetAccount/model.ts +48 -13
  13. package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
  14. package/src/ChangeManager.ts +42 -18
  15. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  16. package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
  17. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
  18. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
  19. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
  20. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
  21. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
  22. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
  23. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
  24. package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
  25. package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
  26. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  27. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
  28. package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
  29. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  30. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  31. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  32. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  33. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  34. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
  36. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
  37. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
  38. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
  39. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  40. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
  41. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
  42. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
  43. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
  44. package/src/OntologyManager/index.ts +4 -1
  45. package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
  46. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  47. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
  48. package/src/components/AddAssembly.tsx +1 -1
  49. package/src/components/AddAssemblyAliases.tsx +114 -0
  50. package/src/components/AddChildFeature.tsx +7 -7
  51. package/src/components/AddFeature.tsx +20 -15
  52. package/src/components/AddRefSeqAliases.tsx +9 -9
  53. package/src/components/CopyFeature.tsx +4 -4
  54. package/src/components/CreateApolloAnnotation.tsx +335 -151
  55. package/src/components/DeleteAssembly.tsx +1 -1
  56. package/src/components/DeleteFeature.tsx +358 -11
  57. package/src/components/DownloadGFF3.tsx +20 -1
  58. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  59. package/src/components/FilterFeatures.tsx +7 -7
  60. package/src/components/FilterTranscripts.tsx +86 -0
  61. package/src/components/ImportFeatures.tsx +1 -1
  62. package/src/components/ManageChecks.tsx +1 -1
  63. package/src/components/MergeExons.tsx +193 -0
  64. package/src/components/MergeTranscripts.tsx +182 -0
  65. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  66. package/src/components/OpenLocalFile.tsx +11 -7
  67. package/src/components/SplitExon.tsx +134 -0
  68. package/src/components/ViewCheckResults.tsx +1 -1
  69. package/src/components/index.ts +4 -0
  70. package/src/config.ts +11 -0
  71. package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
  72. package/src/extensions/annotationFromPileup.ts +99 -89
  73. package/src/index.ts +42 -105
  74. package/src/makeDisplayComponent.tsx +0 -1
  75. package/src/menus/index.ts +1 -0
  76. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
  77. package/src/menus/topLevelMenuAdmin.ts +154 -0
  78. package/src/session/session.ts +163 -104
  79. package/src/util/annotationFeatureUtils.ts +59 -0
  80. package/src/util/copyToClipboard.ts +21 -0
  81. package/src/util/displayUtils.ts +149 -0
  82. package/src/util/glyphUtils.ts +201 -0
  83. package/src/util/index.ts +2 -0
  84. package/src/util/mouseEventsUtils.ts +145 -0
@@ -1,8 +1,11 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
- /* eslint-disable @typescript-eslint/no-misused-promises */
2
+
3
3
  import { type AnnotationFeature } from '@apollo-annotation/mst'
4
- import { DeleteFeatureChange } from '@apollo-annotation/shared'
5
- import { type AbstractSessionModel } from '@jbrowse/core/util'
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
- // Delete features
50
- const change = new DeleteFeatureChange({
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
- deletedFeature: getSnapshot(sourceFeature),
55
- parentFeatureId: sourceFeature.parent?._id,
311
+ changes: [
312
+ {
313
+ deletedFeature: getSnapshot(sourceFeature),
314
+ parentFeatureId: sourceFeature.parent?._id,
315
+ },
316
+ ],
56
317
  })
57
- await changeManager.submit(change)
58
- 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
+
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 onSubmit={onSubmit}>
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
- Grid2,
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
- <Grid2 container spacing={2}>
66
- <Grid2 size={8}>
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
- </Grid2>
88
- <Grid2 size={4}>
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
- </Grid2>
99
- </Grid2>
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
+ })
@@ -271,7 +271,7 @@ export function ImportFeatures({
271
271
  <Checkbox
272
272
  checked={deleteFeatures}
273
273
  onChange={handleDeleteFeatures}
274
- inputProps={{ 'aria-label': 'controlled' }}
274
+ slotProps={{ input: { 'aria-label': 'controlled' } }}
275
275
  color="warning"
276
276
  />
277
277
  }
@@ -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 ?? option.name}
241
+ {option.displayName}
242
242
  </MenuItem>
243
243
  ))}
244
244
  </Select>