@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/index.esm.js +2679 -850
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +2676 -847
  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 +5194 -1258
  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 +18 -0
  13. package/src/ChangeManager.ts +10 -6
  14. package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
  15. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +12 -20
  16. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +929 -175
  17. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
  18. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -1
  19. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +48 -60
  20. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +244 -51
  21. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +46 -1
  22. package/src/LinearApolloDisplay/glyphs/Glyph.ts +9 -1
  23. package/src/LinearApolloDisplay/stateModel/base.ts +29 -0
  24. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +51 -35
  25. package/src/LinearApolloDisplay/stateModel/rendering.ts +2 -1
  26. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
  27. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
  28. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +243 -124
  29. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -1
  30. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +19 -3
  31. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +53 -34
  32. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +4 -2
  33. package/src/OntologyManager/index.ts +4 -1
  34. package/src/TabularEditor/HybridGrid/Feature.tsx +4 -0
  35. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
  36. package/src/components/AddAssemblyAliases.tsx +114 -0
  37. package/src/components/AddChildFeature.tsx +3 -6
  38. package/src/components/AddFeature.tsx +14 -15
  39. package/src/components/CopyFeature.tsx +2 -4
  40. package/src/components/CreateApolloAnnotation.tsx +334 -151
  41. package/src/components/DeleteFeature.tsx +358 -11
  42. package/src/components/DownloadGFF3.tsx +20 -1
  43. package/src/components/FilterTranscripts.tsx +86 -0
  44. package/src/components/MergeExons.tsx +193 -0
  45. package/src/components/MergeTranscripts.tsx +185 -0
  46. package/src/components/SplitExon.tsx +134 -0
  47. package/src/components/index.ts +3 -0
  48. package/src/config.ts +5 -0
  49. package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
  50. package/src/extensions/annotationFromPileup.ts +99 -89
  51. package/src/session/session.ts +26 -13
  52. package/src/util/annotationFeatureUtils.ts +65 -0
  53. package/src/util/copyToClipboard.ts +21 -0
  54. package/src/util/glyphUtils.ts +49 -0
  55. package/src/util/index.ts +2 -0
  56. package/src/util/mouseEventsUtils.ts +113 -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,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
+ })