@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,8 @@
1
1
  /* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
2
2
  /* eslint-disable @typescript-eslint/unbound-method */
3
3
  /* eslint-disable @typescript-eslint/no-misused-promises */
4
- import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
5
- import { AbstractSessionModel } from '@jbrowse/core/util'
4
+ import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
5
+ import { type AbstractSessionModel } from '@jbrowse/core/util'
6
6
  import {
7
7
  Button,
8
8
  Checkbox,
@@ -12,7 +12,7 @@ import {
12
12
  MenuItem,
13
13
  Paper,
14
14
  Select,
15
- SelectChangeEvent,
15
+ type SelectChangeEvent,
16
16
  Table,
17
17
  TableBody,
18
18
  TableCell,
@@ -23,14 +23,15 @@ import {
23
23
  import { getRoot } from 'mobx-state-tree'
24
24
  import React, { useEffect, useState } from 'react'
25
25
 
26
- import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
26
+ import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
27
27
  import {
28
- ApolloInternetAccount,
29
- CollaborationServerDriver,
28
+ type ApolloInternetAccount,
29
+ type CollaborationServerDriver,
30
30
  } from '../BackendDrivers'
31
- import { ApolloSessionModel } from '../session'
32
- import { ApolloRootModel } from '../types'
31
+ import { type ApolloSessionModel } from '../session'
32
+ import { type ApolloRootModel } from '../types'
33
33
  import { createFetchErrorMessage } from '../util'
34
+
34
35
  import { Dialog } from './Dialog'
35
36
 
36
37
  interface ManageChecksProps {
@@ -6,7 +6,7 @@
6
6
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
7
7
  /* eslint-disable @typescript-eslint/no-unsafe-return */
8
8
  import { DeleteUserChange, UserChange } from '@apollo-annotation/shared'
9
- import { AbstractRootModel } from '@jbrowse/core/util'
9
+ import { type AbstractRootModel } from '@jbrowse/core/util'
10
10
  import DeleteIcon from '@mui/icons-material/Delete'
11
11
  import {
12
12
  Button,
@@ -15,25 +15,26 @@ import {
15
15
  DialogContentText,
16
16
  MenuItem,
17
17
  Select,
18
- SelectChangeEvent,
18
+ type SelectChangeEvent,
19
19
  } from '@mui/material'
20
20
  import {
21
21
  DataGrid,
22
22
  GridActionsCellItem,
23
- GridCellParams,
24
- GridColDef,
25
- GridRowId,
26
- GridRowModel,
27
- GridRowParams,
23
+ type GridCellParams,
24
+ type GridColDef,
25
+ type GridRowId,
26
+ type GridRowModel,
27
+ type GridRowParams,
28
28
  GridToolbar,
29
29
  } from '@mui/x-data-grid'
30
30
  import { getRoot } from 'mobx-state-tree'
31
31
  import React, { useCallback, useEffect, useState } from 'react'
32
32
 
33
- import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
34
- import { ChangeManager } from '../ChangeManager'
35
- import { ApolloSessionModel } from '../session'
33
+ import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
34
+ import { type ChangeManager } from '../ChangeManager'
35
+ import { type ApolloSessionModel } from '../session'
36
36
  import { createFetchErrorMessage } from '../util'
37
+
37
38
  import { Dialog } from './Dialog'
38
39
 
39
40
  interface UserResponse {
@@ -0,0 +1,193 @@
1
+ /* eslint-disable @typescript-eslint/unbound-method */
2
+
3
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
4
+ import { MergeExonsChange } from '@apollo-annotation/shared'
5
+ import {
6
+ Box,
7
+ Button,
8
+ DialogActions,
9
+ DialogContent,
10
+ DialogContentText,
11
+ FormControl,
12
+ FormControlLabel,
13
+ Radio,
14
+ RadioGroup,
15
+ type SelectChangeEvent,
16
+ } from '@mui/material'
17
+ import { getSnapshot } from 'mobx-state-tree'
18
+ import React, { useState } from 'react'
19
+
20
+ import { type ChangeManager } from '../ChangeManager'
21
+ import { type ApolloSessionModel } from '../session'
22
+
23
+ import { Dialog } from './Dialog'
24
+
25
+ interface MergeExonsProps {
26
+ session: ApolloSessionModel
27
+ handleClose(): void
28
+ sourceFeature: AnnotationFeature
29
+ sourceAssemblyId: string
30
+ changeManager: ChangeManager
31
+ selectedFeature?: AnnotationFeature
32
+ setSelectedFeature(feature?: AnnotationFeature): void
33
+ }
34
+
35
+ function getNeighboringExons(
36
+ referenceExon: AnnotationFeature,
37
+ ): Record<string, AnnotationFeature> {
38
+ const neighboringExons: Record<string, AnnotationFeature> = {}
39
+ const tx = referenceExon.parent
40
+ if (!tx) {
41
+ throw new Error('Unable to find parent of reference exon')
42
+ }
43
+ let exons: AnnotationFeature[] = []
44
+ if (tx.children) {
45
+ for (const [, feature] of tx.children) {
46
+ if (feature.type === 'exon') {
47
+ exons.push(feature)
48
+ }
49
+ }
50
+ }
51
+ exons = exons.sort((a, b) => {
52
+ if (a.min === b.min) {
53
+ return a.max - b.max
54
+ }
55
+ return a.min - b.min
56
+ })
57
+ if (tx.strand && tx.strand === -1) {
58
+ exons = exons.reverse()
59
+ }
60
+ let i = 0
61
+ for (const x of exons) {
62
+ if (x._id === referenceExon._id) {
63
+ if (exons.length > i + 1) {
64
+ neighboringExons.three_prime = exons[i + 1]
65
+ }
66
+ if (i > 0) {
67
+ neighboringExons.five_prime = exons[i - 1]
68
+ }
69
+ break
70
+ }
71
+ i++
72
+ }
73
+ return neighboringExons
74
+ }
75
+
76
+ function makeRadioButtonName(
77
+ key: string,
78
+ neighboringExons: Record<string, AnnotationFeature>,
79
+ ): string {
80
+ const neighboringExon = neighboringExons[key]
81
+ let name
82
+ if (key === 'three_prime') {
83
+ name = `3'end (coords: ${neighboringExon.min + 1}-${neighboringExon.max})`
84
+ } else if (key === 'five_prime') {
85
+ name = `5'end (coords: ${neighboringExon.min + 1}-${neighboringExon.max})`
86
+ } else {
87
+ throw new Error(`Unexpected direction: "${key}"`)
88
+ }
89
+ return name
90
+ }
91
+
92
+ export function MergeExons({
93
+ changeManager,
94
+ handleClose,
95
+ selectedFeature,
96
+ setSelectedFeature,
97
+ sourceAssemblyId,
98
+ sourceFeature,
99
+ }: MergeExonsProps) {
100
+ const [errorMessage, setErrorMessage] = useState('')
101
+ const [selectedExon, setSelectedExon] = useState<AnnotationFeature>()
102
+
103
+ function onSubmit(event: React.FormEvent<HTMLFormElement>) {
104
+ event.preventDefault()
105
+ setErrorMessage('')
106
+ const { parent } = sourceFeature
107
+ if (!(selectedExon && parent)) {
108
+ return
109
+ }
110
+ if (selectedFeature?._id === sourceFeature._id) {
111
+ setSelectedFeature()
112
+ }
113
+ const change = new MergeExonsChange({
114
+ changedIds: [sourceFeature._id],
115
+ typeName: 'MergeExonsChange',
116
+ assembly: sourceAssemblyId,
117
+ firstExon: getSnapshot(sourceFeature),
118
+ secondExon: getSnapshot(selectedExon),
119
+ parentFeatureId: parent._id,
120
+ })
121
+ void changeManager.submit(change)
122
+ handleClose()
123
+ event.preventDefault()
124
+ }
125
+
126
+ const handleTypeChange = (e: SelectChangeEvent) => {
127
+ setErrorMessage('')
128
+ const { value } = e.target
129
+ setSelectedExon(neighboringExons[value])
130
+ }
131
+
132
+ const neighboringExons = getNeighboringExons(sourceFeature)
133
+
134
+ return (
135
+ <Dialog
136
+ open
137
+ title="Merge exons"
138
+ handleClose={handleClose}
139
+ maxWidth={false}
140
+ data-testid="merge-exons"
141
+ >
142
+ <form onSubmit={onSubmit}>
143
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
144
+ {Object.keys(neighboringExons).length === 0
145
+ ? 'There are no neighbouring exons to merge with'
146
+ : 'Merge with exon on:'}
147
+ <FormControl style={{ marginTop: 5 }}>
148
+ <RadioGroup
149
+ aria-labelledby="demo-radio-buttons-group-label"
150
+ name="radio-buttons-group"
151
+ value={selectedExon}
152
+ onChange={handleTypeChange}
153
+ >
154
+ {Object.keys(neighboringExons).map((key) => (
155
+ <FormControlLabel
156
+ value={key}
157
+ key={key}
158
+ control={<Radio />}
159
+ label={
160
+ <Box display="flex" alignItems="center">
161
+ {makeRadioButtonName(key, neighboringExons)}
162
+ </Box>
163
+ }
164
+ />
165
+ ))}
166
+ </RadioGroup>
167
+ </FormControl>
168
+ </DialogContent>
169
+
170
+ <DialogActions>
171
+ <Button
172
+ variant="contained"
173
+ type="submit"
174
+ disabled={
175
+ Object.keys(neighboringExons).length === 0 ||
176
+ selectedExon === undefined
177
+ }
178
+ >
179
+ Submit
180
+ </Button>
181
+ <Button variant="outlined" type="submit" onClick={handleClose}>
182
+ Cancel
183
+ </Button>
184
+ </DialogActions>
185
+ </form>
186
+ {errorMessage ? (
187
+ <DialogContent>
188
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
189
+ </DialogContent>
190
+ ) : null}
191
+ </Dialog>
192
+ )
193
+ }
@@ -0,0 +1,185 @@
1
+ /* eslint-disable @typescript-eslint/unbound-method */
2
+ /* eslint-disable @typescript-eslint/no-misused-promises */
3
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
4
+ import { MergeTranscriptsChange } from '@apollo-annotation/shared'
5
+ import { type AbstractSessionModel } from '@jbrowse/core/util'
6
+ import {
7
+ Box,
8
+ Button,
9
+ DialogActions,
10
+ DialogContent,
11
+ DialogContentText,
12
+ FormControl,
13
+ FormControlLabel,
14
+ Radio,
15
+ RadioGroup,
16
+ type SelectChangeEvent,
17
+ } from '@mui/material'
18
+ import { getSnapshot } from 'mobx-state-tree'
19
+ import React, { useState } from 'react'
20
+
21
+ import { type ChangeManager } from '../ChangeManager'
22
+ import { type ApolloSessionModel } from '../session'
23
+
24
+ import { Dialog } from './Dialog'
25
+
26
+ interface MergeTranscriptsProps {
27
+ session: ApolloSessionModel
28
+ handleClose(): void
29
+ sourceFeature: AnnotationFeature
30
+ sourceAssemblyId: string
31
+ changeManager: ChangeManager
32
+ selectedFeature?: AnnotationFeature
33
+ setSelectedFeature(feature?: AnnotationFeature): void
34
+ }
35
+
36
+ function getTranscripts(
37
+ referenceTranscript: AnnotationFeature,
38
+ session: ApolloSessionModel,
39
+ ): Record<string, AnnotationFeature> {
40
+ const gene = referenceTranscript.parent
41
+ if (!gene) {
42
+ throw new Error('Unable to find parent of reference transcript')
43
+ }
44
+
45
+ const { featureTypeOntology } = session.apolloDataStore.ontologyManager
46
+ if (!featureTypeOntology) {
47
+ throw new Error('featureTypeOntology is undefined')
48
+ }
49
+
50
+ const transcripts: Record<string, AnnotationFeature> = {}
51
+ if (gene.children) {
52
+ for (const [, feature] of gene.children) {
53
+ if (
54
+ featureTypeOntology.isTypeOf(feature.type, 'transcript') &&
55
+ feature._id !== referenceTranscript._id
56
+ ) {
57
+ transcripts[feature._id] = feature
58
+ }
59
+ }
60
+ }
61
+ return transcripts
62
+ }
63
+
64
+ function makeRadioButtonName(transcript: AnnotationFeature): string {
65
+ let id
66
+ if (transcript.attributes.get('gff_name')) {
67
+ id = transcript.attributes.get('gff_name')?.join(',')
68
+ } else if (transcript.attributes.get('gff_id')) {
69
+ id = transcript.attributes.get('gff_id')?.join(',')
70
+ } else {
71
+ id = transcript._id
72
+ }
73
+ return `${id} [${transcript.min + 1}-${transcript.max}]`
74
+ }
75
+
76
+ export function MergeTranscripts({
77
+ changeManager,
78
+ handleClose,
79
+ selectedFeature,
80
+ session,
81
+ setSelectedFeature,
82
+ sourceAssemblyId,
83
+ sourceFeature,
84
+ }: MergeTranscriptsProps) {
85
+ const { notify } = session as unknown as AbstractSessionModel
86
+ const [errorMessage, setErrorMessage] = useState('')
87
+ const [selectedTranscript, setSelectedTranscript] =
88
+ useState<AnnotationFeature>()
89
+
90
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
91
+ event.preventDefault()
92
+ setErrorMessage('')
93
+ if (!selectedTranscript) {
94
+ return
95
+ }
96
+ if (selectedFeature?._id === sourceFeature._id) {
97
+ setSelectedFeature()
98
+ }
99
+
100
+ if (!sourceFeature.parent) {
101
+ throw new Error('Cannot find parent')
102
+ }
103
+
104
+ const change = new MergeTranscriptsChange({
105
+ changedIds: [sourceFeature._id],
106
+ typeName: 'MergeTranscriptsChange',
107
+ assembly: sourceAssemblyId,
108
+ firstTranscript: getSnapshot(sourceFeature),
109
+ secondTranscript: getSnapshot(selectedTranscript),
110
+ parentFeatureId: sourceFeature.parent._id,
111
+ })
112
+ await changeManager.submit(change)
113
+ notify('Transcripts successfully merged', 'success')
114
+ handleClose()
115
+ event.preventDefault()
116
+ }
117
+
118
+ const handleTypeChange = (e: SelectChangeEvent) => {
119
+ setErrorMessage('')
120
+ const { value } = e.target
121
+ setSelectedTranscript(transcripts[value])
122
+ }
123
+
124
+ const transcripts = getTranscripts(sourceFeature, session)
125
+
126
+ return (
127
+ <Dialog
128
+ open
129
+ title="Merge transcripts"
130
+ handleClose={handleClose}
131
+ maxWidth={false}
132
+ data-testid="merge-transcripts"
133
+ >
134
+ <form onSubmit={onSubmit}>
135
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
136
+ {Object.keys(transcripts).length === 0
137
+ ? 'There are no transcripts to merge with'
138
+ : 'Merge with transcript:'}
139
+ <FormControl style={{ marginTop: 5 }}>
140
+ <RadioGroup
141
+ aria-labelledby="demo-radio-buttons-group-label"
142
+ name="radio-buttons-group"
143
+ value={selectedTranscript}
144
+ onChange={handleTypeChange}
145
+ >
146
+ {Object.keys(transcripts).map((key) => (
147
+ <FormControlLabel
148
+ value={key}
149
+ key={key}
150
+ control={<Radio />}
151
+ label={
152
+ <Box display="flex" alignItems="center">
153
+ {makeRadioButtonName(transcripts[key])}
154
+ </Box>
155
+ }
156
+ />
157
+ ))}
158
+ </RadioGroup>
159
+ </FormControl>
160
+ </DialogContent>
161
+
162
+ <DialogActions>
163
+ <Button
164
+ variant="contained"
165
+ type="submit"
166
+ disabled={
167
+ Object.keys(transcripts).length === 0 ||
168
+ selectedTranscript === undefined
169
+ }
170
+ >
171
+ Submit
172
+ </Button>
173
+ <Button variant="outlined" type="submit" onClick={handleClose}>
174
+ Cancel
175
+ </Button>
176
+ </DialogActions>
177
+ </form>
178
+ {errorMessage ? (
179
+ <DialogContent>
180
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
181
+ </DialogContent>
182
+ ) : null}
183
+ </Dialog>
184
+ )
185
+ }
@@ -1,17 +1,17 @@
1
1
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
2
 
3
- import { AbstractSessionModel } from '@jbrowse/core/util'
3
+ import { type AbstractSessionModel } from '@jbrowse/core/util'
4
4
  import { isAbortException } from '@jbrowse/core/util/aborting'
5
5
  import {
6
6
  Autocomplete,
7
- AutocompleteRenderInputParams,
7
+ type AutocompleteRenderInputParams,
8
8
  TextField,
9
9
  } from '@mui/material'
10
10
  import React, { useCallback, useEffect, useState } from 'react'
11
11
 
12
- import { OntologyTerm, isDeprecated } from '../OntologyManager'
13
- import OntologyStore from '../OntologyManager/OntologyStore'
14
- import { ApolloSessionModel } from '../session'
12
+ import { type OntologyTerm, isDeprecated } from '../OntologyManager'
13
+ import type OntologyStore from '../OntologyManager/OntologyStore'
14
+ import { type ApolloSessionModel } from '../session'
15
15
 
16
16
  interface OntologyTermAutocompleteProps {
17
17
  session: ApolloSessionModel
@@ -4,7 +4,7 @@
4
4
  import { isAbortException } from '@jbrowse/core/util/aborting'
5
5
  import {
6
6
  Autocomplete,
7
- AutocompleteRenderGetTagProps,
7
+ type AutocompleteRenderGetTagProps,
8
8
  Chip,
9
9
  Grid2,
10
10
  TextField,
@@ -18,14 +18,14 @@ import { getParent } from 'mobx-state-tree'
18
18
  import * as React from 'react'
19
19
 
20
20
  import {
21
- OntologyManager,
22
- OntologyRecord,
23
- OntologyTerm,
21
+ type OntologyManager,
22
+ type OntologyRecord,
23
+ type OntologyTerm,
24
24
  isOntologyClass,
25
25
  } from '../OntologyManager'
26
- import { Match } from '../OntologyManager/OntologyStore/fulltext'
26
+ import { type Match } from '../OntologyManager/OntologyStore/fulltext'
27
27
  import { isDeprecated } from '../OntologyManager/OntologyStore/indexeddb-schema'
28
- import { ApolloSessionModel } from '../session'
28
+ import { type ApolloSessionModel } from '../session'
29
29
 
30
30
  interface TermValue {
31
31
  term: OntologyTerm
@@ -2,7 +2,7 @@
2
2
  /* eslint-disable @typescript-eslint/unbound-method */
3
3
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
4
  /* eslint-disable @typescript-eslint/no-misused-promises */
5
- import { AbstractSessionModel, isElectron } from '@jbrowse/core/util'
5
+ import { type AbstractSessionModel, isElectron } from '@jbrowse/core/util'
6
6
  import {
7
7
  Button,
8
8
  DialogActions,
@@ -16,9 +16,10 @@ import {
16
16
  import { nanoid } from 'nanoid'
17
17
  import React, { useState } from 'react'
18
18
 
19
- import { InMemoryFileDriver } from '../BackendDrivers'
20
- import { ApolloSessionModel } from '../session'
19
+ import { type InMemoryFileDriver } from '../BackendDrivers'
20
+ import { type ApolloSessionModel } from '../session'
21
21
  import { loadAssemblyIntoClient } from '../util'
22
+
22
23
  import { Dialog } from './Dialog'
23
24
 
24
25
  interface OpenLocalFileProps {
@@ -0,0 +1,134 @@
1
+ /* eslint-disable @typescript-eslint/unbound-method */
2
+
3
+ import {
4
+ type AnnotationFeature,
5
+ type AnnotationFeatureSnapshot,
6
+ } from '@apollo-annotation/mst'
7
+ import { SplitExonChange } from '@apollo-annotation/shared'
8
+ import {
9
+ Button,
10
+ DialogActions,
11
+ DialogContent,
12
+ DialogContentText,
13
+ } from '@mui/material'
14
+ import ObjectID from 'bson-objectid'
15
+ import { getSnapshot } from 'mobx-state-tree'
16
+ import React, { useState } from 'react'
17
+
18
+ import { type ChangeManager } from '../ChangeManager'
19
+ import { type ApolloSessionModel } from '../session'
20
+
21
+ import { Dialog } from './Dialog'
22
+
23
+ interface SplitExonProps {
24
+ session: ApolloSessionModel
25
+ handleClose(): void
26
+ sourceFeature: AnnotationFeature
27
+ sourceAssemblyId: string
28
+ changeManager: ChangeManager
29
+ selectedFeature?: AnnotationFeature
30
+ setSelectedFeature(feature?: AnnotationFeature): void
31
+ }
32
+
33
+ interface splittableExon {
34
+ isSplittable: boolean
35
+ comment: string
36
+ }
37
+
38
+ function exonIsSplittable(
39
+ exonToBeSplit: AnnotationFeatureSnapshot,
40
+ ): splittableExon {
41
+ if (exonToBeSplit.max - exonToBeSplit.min < 2) {
42
+ return {
43
+ isSplittable: false,
44
+ comment: 'This exon is too short to be split',
45
+ }
46
+ }
47
+ return { isSplittable: true, comment: '' }
48
+ }
49
+
50
+ function makeDialogText(splitExon: AnnotationFeatureSnapshot): string {
51
+ const splittable = exonIsSplittable(splitExon)
52
+ if (splittable.isSplittable) {
53
+ return 'Are you sure you want to split the selected exon?'
54
+ }
55
+ return splittable.comment
56
+ }
57
+
58
+ export function SplitExon({
59
+ changeManager,
60
+ handleClose,
61
+ selectedFeature,
62
+ setSelectedFeature,
63
+ sourceAssemblyId,
64
+ sourceFeature,
65
+ }: SplitExonProps) {
66
+ const [errorMessage, setErrorMessage] = useState('')
67
+
68
+ const exonToBeSplit = getSnapshot(sourceFeature)
69
+
70
+ function onSubmit(event: React.FormEvent<HTMLFormElement>) {
71
+ event.preventDefault()
72
+ setErrorMessage('')
73
+ if (selectedFeature?._id === sourceFeature._id) {
74
+ setSelectedFeature()
75
+ }
76
+
77
+ const midpoint =
78
+ exonToBeSplit.min + (exonToBeSplit.max - exonToBeSplit.min) / 2
79
+ const upstreamCut = Math.floor(midpoint)
80
+ const downstreamCut = Math.ceil(midpoint)
81
+
82
+ if (!sourceFeature.parent?._id) {
83
+ throw new Error('Splitting an exon without parent is not possible yet')
84
+ }
85
+
86
+ const change = new SplitExonChange({
87
+ changedIds: [sourceFeature._id],
88
+ typeName: 'SplitExonChange',
89
+ assembly: sourceAssemblyId,
90
+ exonToBeSplit,
91
+ parentFeatureId: sourceFeature.parent._id,
92
+ upstreamCut,
93
+ downstreamCut,
94
+ leftExonId: new ObjectID().toHexString(),
95
+ rightExonId: new ObjectID().toHexString(),
96
+ })
97
+ void changeManager.submit(change)
98
+ handleClose()
99
+ event.preventDefault()
100
+ }
101
+
102
+ return (
103
+ <Dialog
104
+ open
105
+ title="Split exon"
106
+ handleClose={handleClose}
107
+ maxWidth={false}
108
+ data-testid="split-exon"
109
+ >
110
+ <form onSubmit={onSubmit}>
111
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
112
+ <DialogContentText>{makeDialogText(exonToBeSplit)}</DialogContentText>
113
+ </DialogContent>
114
+ <DialogActions>
115
+ <Button
116
+ variant="contained"
117
+ type="submit"
118
+ disabled={!exonIsSplittable(exonToBeSplit).isSplittable}
119
+ >
120
+ Yes
121
+ </Button>
122
+ <Button variant="outlined" type="submit" onClick={handleClose}>
123
+ Cancel
124
+ </Button>
125
+ </DialogActions>
126
+ </form>
127
+ {errorMessage ? (
128
+ <DialogContent>
129
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
130
+ </DialogContent>
131
+ ) : null}
132
+ </Dialog>
133
+ )
134
+ }