@apollo-annotation/jbrowse-plugin-apollo 1.0.0 → 1.1.0

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 (172) hide show
  1. package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts +8 -2
  2. package/dist/ApolloInternetAccount/components/AuthTypeSelector.d.ts.map +1 -1
  3. package/dist/ApolloInternetAccount/components/LoginButtons.d.ts +8 -3
  4. package/dist/ApolloInternetAccount/components/LoginButtons.d.ts.map +1 -1
  5. package/dist/ApolloInternetAccount/configSchema.d.ts +44 -0
  6. package/dist/ApolloInternetAccount/configSchema.d.ts.map +1 -0
  7. package/dist/ApolloInternetAccount/model.d.ts +174 -0
  8. package/dist/ApolloInternetAccount/model.d.ts.map +1 -0
  9. package/dist/ApolloJobModel.d.ts.map +1 -1
  10. package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts +8 -0
  11. package/dist/ApolloRefNameAliasAdapter/configSchema.d.ts.map +1 -0
  12. package/dist/ApolloSequenceAdapter/configSchema.d.ts +8 -0
  13. package/dist/ApolloSequenceAdapter/configSchema.d.ts.map +1 -0
  14. package/dist/ApolloTextSearchAdapter/configSchema.d.ts +20 -0
  15. package/dist/ApolloTextSearchAdapter/configSchema.d.ts.map +1 -0
  16. package/dist/BackendDrivers/CollaborationServerDriver.d.ts +1 -1
  17. package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
  18. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -1
  19. package/dist/FeatureDetailsWidget/ApolloFeatureDetailsWidget.d.ts.map +1 -1
  20. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
  21. package/dist/FeatureDetailsWidget/Attributes.d.ts.map +1 -1
  22. package/dist/FeatureDetailsWidget/DefaultAttributeEditor.d.ts.map +1 -1
  23. package/dist/FeatureDetailsWidget/DefaultAttributeViewer.d.ts.map +1 -1
  24. package/dist/FeatureDetailsWidget/NumberTextField.d.ts.map +1 -1
  25. package/dist/FeatureDetailsWidget/StringTextField.d.ts.map +1 -1
  26. package/dist/FeatureDetailsWidget/TranscriptSequence.d.ts.map +1 -1
  27. package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
  28. package/dist/FeatureDetailsWidget/Translation.d.ts +14 -0
  29. package/dist/FeatureDetailsWidget/Translation.d.ts.map +1 -0
  30. package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
  31. package/dist/LinearApolloDisplay/configSchema.d.ts +2 -0
  32. package/dist/LinearApolloDisplay/configSchema.d.ts.map +1 -0
  33. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -1
  34. package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  35. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -1
  36. package/dist/LinearApolloDisplay/stateModel/base.d.ts +1 -18
  37. package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
  38. package/dist/LinearApolloDisplay/stateModel/index.d.ts +1 -17
  39. package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
  40. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +2 -19
  41. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
  42. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +2 -34
  43. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  44. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +2 -19
  45. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
  46. package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts +2 -0
  47. package/dist/LinearApolloReferenceSequenceDisplay/configSchema.d.ts.map +1 -0
  48. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +1 -1
  49. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
  50. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +1 -1
  51. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +1 -1
  52. package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
  53. package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts +2 -0
  54. package/dist/LinearApolloSixFrameDisplay/configSchema.d.ts.map +1 -0
  55. package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  56. package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts +1 -1
  57. package/dist/LinearApolloSixFrameDisplay/stateModel/base.d.ts.map +1 -1
  58. package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts +1 -1
  59. package/dist/LinearApolloSixFrameDisplay/stateModel/index.d.ts.map +1 -1
  60. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts +2 -3
  61. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
  62. package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts +2 -2
  63. package/dist/LinearApolloSixFrameDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  64. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts +2 -3
  65. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
  66. package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
  67. package/dist/OntologyManager/index.d.ts +734 -0
  68. package/dist/OntologyManager/index.d.ts.map +1 -0
  69. package/dist/TabularEditor/HybridGrid/NumberCell.d.ts.map +1 -1
  70. package/dist/components/AddCodingSequence.d.ts +14 -0
  71. package/dist/components/AddCodingSequence.d.ts.map +1 -0
  72. package/dist/components/AddRefSeqAliases.d.ts.map +1 -1
  73. package/dist/components/CopyFeature.d.ts.map +1 -1
  74. package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
  75. package/dist/components/DeleteFeature.d.ts.map +1 -1
  76. package/dist/components/DownloadGFF3.d.ts.map +1 -1
  77. package/dist/components/DuplicateFeatureDialog.d.ts +12 -0
  78. package/dist/components/DuplicateFeatureDialog.d.ts.map +1 -0
  79. package/dist/components/ImportAnnotations.d.ts +9 -0
  80. package/dist/components/ImportAnnotations.d.ts.map +1 -0
  81. package/dist/components/ManageUsers.d.ts.map +1 -1
  82. package/dist/components/OntologyTermMultiSelect.d.ts.map +1 -1
  83. package/dist/components/ViewChangeLog.d.ts.map +1 -1
  84. package/dist/components/index.d.ts +2 -0
  85. package/dist/components/index.d.ts.map +1 -1
  86. package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.esm.js +830 -434
  89. package/dist/index.esm.js.map +1 -1
  90. package/dist/jbrowse-plugin-apollo.cjs.development.js +825 -429
  91. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  92. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  93. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  94. package/dist/jbrowse-plugin-apollo.umd.development.js +863 -468
  95. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  96. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  97. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  98. package/dist/session/ClientDataStore.d.ts +2194 -0
  99. package/dist/session/ClientDataStore.d.ts.map +1 -0
  100. package/dist/session/handleApolloFeaturesUrlParam.d.ts +8 -0
  101. package/dist/session/handleApolloFeaturesUrlParam.d.ts.map +1 -0
  102. package/dist/session/session.d.ts +6788 -0
  103. package/dist/session/session.d.ts.map +1 -0
  104. package/dist/util/displayUtils.d.ts +36 -0
  105. package/dist/util/displayUtils.d.ts.map +1 -0
  106. package/dist/util/sequenceUtils.d.ts +2 -0
  107. package/dist/util/sequenceUtils.d.ts.map +1 -0
  108. package/package.json +14 -14
  109. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +73 -38
  110. package/src/ApolloInternetAccount/components/LoginButtons.tsx +31 -10
  111. package/src/ApolloInternetAccount/model.ts +8 -5
  112. package/src/ApolloJobModel.ts +3 -8
  113. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
  114. package/src/BackendDrivers/CollaborationServerDriver.ts +7 -11
  115. package/src/BackendDrivers/LocalDriver/LocalDriver.ts +13 -4
  116. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +1 -0
  117. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +4 -2
  118. package/src/FeatureDetailsWidget/Attributes.tsx +1 -0
  119. package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +1 -0
  120. package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +1 -0
  121. package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
  122. package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
  123. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +2 -1
  124. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +17 -354
  125. package/src/FeatureDetailsWidget/Translation.tsx +358 -0
  126. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +4 -1
  127. package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +2 -5
  128. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +4 -6
  129. package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +39 -6
  130. package/src/LinearApolloDisplay/stateModel/base.ts +47 -23
  131. package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -5
  132. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +4 -1
  133. package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -5
  134. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +10 -10
  135. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +5 -3
  136. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +1 -1
  137. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +2 -3
  138. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +12 -20
  139. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +10 -6
  140. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +38 -45
  141. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +1 -4
  142. package/src/OntologyManager/OntologyStore/fulltext.ts +0 -2
  143. package/src/OntologyManager/OntologyStore/index.ts +1 -1
  144. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
  145. package/src/TabularEditor/HybridGrid/NumberCell.tsx +2 -1
  146. package/src/components/AddAssemblyAliases.tsx +1 -1
  147. package/src/components/AddCodingSequence.tsx +255 -0
  148. package/src/components/AddFeature.tsx +1 -1
  149. package/src/components/AddRefSeqAliases.tsx +3 -2
  150. package/src/components/CopyFeature.tsx +9 -7
  151. package/src/components/CreateApolloAnnotation.tsx +28 -23
  152. package/src/components/DeleteAssembly.tsx +2 -2
  153. package/src/components/DeleteFeature.tsx +1 -4
  154. package/src/components/DownloadGFF3.tsx +4 -3
  155. package/src/components/DuplicateFeatureDialog.tsx +154 -0
  156. package/src/components/DuplicateTranscript.tsx +1 -1
  157. package/src/components/FilterTranscripts.tsx +1 -1
  158. package/src/components/ImportAnnotations.tsx +108 -0
  159. package/src/components/ManageUsers.tsx +1 -2
  160. package/src/components/OntologyTermAutocomplete.tsx +1 -3
  161. package/src/components/OntologyTermMultiSelect.tsx +3 -1
  162. package/src/components/ViewChangeLog.tsx +2 -1
  163. package/src/components/index.ts +2 -0
  164. package/src/extensions/annotationFromPileup.ts +34 -26
  165. package/src/index.ts +9 -8
  166. package/src/session/ClientDataStore.ts +16 -21
  167. package/src/session/handleApolloFeaturesUrlParam.ts +122 -0
  168. package/src/session/session.ts +49 -9
  169. package/src/util/annotationFeatureUtils.ts +1 -1
  170. package/src/util/displayUtils.ts +1 -1
  171. package/src/util/glyphUtils.ts +3 -3
  172. package/src/util/sequenceUtils.ts +29 -0
@@ -0,0 +1,255 @@
1
+ /* eslint-disable @typescript-eslint/unbound-method */
2
+ import type {
3
+ AnnotationFeature,
4
+ AnnotationFeatureSnapshot,
5
+ } from '@apollo-annotation/mst'
6
+ import { AddFeatureChange } from '@apollo-annotation/shared'
7
+ import type { AbstractSessionModel } from '@jbrowse/core/util'
8
+ import { revcom } from '@jbrowse/core/util'
9
+ import {
10
+ Button,
11
+ DialogActions,
12
+ DialogContent,
13
+ DialogContentText,
14
+ FormControl,
15
+ FormControlLabel,
16
+ Radio,
17
+ RadioGroup,
18
+ TextField,
19
+ } from '@mui/material'
20
+ import ObjectID from 'bson-objectid'
21
+ import React, { useState } from 'react'
22
+
23
+ import type { ChangeManager } from '../ChangeManager'
24
+ import type { ApolloSessionModel } from '../session'
25
+ import { findLongestOrf } from '../util/sequenceUtils'
26
+
27
+ import { Dialog } from './Dialog'
28
+
29
+ interface AddCodingSequenceProps {
30
+ session: ApolloSessionModel
31
+ handleClose(): void
32
+ sourceFeature: AnnotationFeature
33
+ sourceAssemblyId: string
34
+ changeManager: ChangeManager
35
+ refName: string
36
+ }
37
+
38
+ // Returns the genomic half-open boundary for stitched position `pos`.
39
+ // For strand +1: the inclusive start (min) of the nucleotide.
40
+ // For strand -1: the exclusive end (max) of the nucleotide.
41
+ // Symmetric usage:
42
+ // plus: cdsMin = helper(orf[0]), cdsMax = helper(orf[1]+3)
43
+ // minus: cdsMax = helper(orf[0]), cdsMin = helper(orf[1]+3)
44
+ function stitchedToGenomicBound(
45
+ pos: number,
46
+ exons: { min: number; max: number }[],
47
+ strand: 1 | -1,
48
+ ): number {
49
+ let cum = 0
50
+ for (const exon of exons) {
51
+ const len = exon.max - exon.min
52
+ if (pos <= cum + len) {
53
+ const off = pos - cum
54
+ return strand === 1 ? exon.min + off : exon.max - off
55
+ }
56
+ cum += len
57
+ }
58
+ const last = exons.at(-1)
59
+ if (!last) {
60
+ throw new Error('No exons found')
61
+ }
62
+ return strand === 1 ? last.max : last.min
63
+ }
64
+
65
+ export function AddCodingSequence({
66
+ changeManager,
67
+ handleClose,
68
+ refName,
69
+ session,
70
+ sourceAssemblyId,
71
+ sourceFeature,
72
+ }: AddCodingSequenceProps) {
73
+ const [method, setMethod] = useState<'longest-orf' | 'manual'>('longest-orf')
74
+ const [minInput, setMinInput] = useState('')
75
+ const [maxInput, setMaxInput] = useState('')
76
+ const [errorMessage, setErrorMessage] = useState('')
77
+
78
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
79
+ event.preventDefault()
80
+ setErrorMessage('')
81
+
82
+ let cdsMin: number
83
+ let cdsMax: number
84
+
85
+ try {
86
+ if (method === 'longest-orf') {
87
+ const exonParts = sourceFeature.transcriptExonParts.filter(
88
+ (p) => p.type === 'exon',
89
+ )
90
+ if (exonParts.length === 0) {
91
+ setErrorMessage('No exons found in this transcript')
92
+ return
93
+ }
94
+
95
+ const backendDriver =
96
+ session.apolloDataStore.getBackendDriver(sourceAssemblyId)
97
+ if (!backendDriver) {
98
+ setErrorMessage('No backend driver found for this assembly')
99
+ return
100
+ }
101
+
102
+ let stitchedSequence = ''
103
+ for (const exon of exonParts) {
104
+ const { seq } = await backendDriver.getSequence({
105
+ assemblyName: sourceAssemblyId,
106
+ refName,
107
+ start: exon.min,
108
+ end: exon.max,
109
+ })
110
+ stitchedSequence += sourceFeature.strand === -1 ? revcom(seq) : seq
111
+ }
112
+
113
+ const orf = findLongestOrf(stitchedSequence)
114
+ if (!orf) {
115
+ ;(session as unknown as AbstractSessionModel).notify(
116
+ 'No open reading frame found in this transcript',
117
+ 'error',
118
+ )
119
+ handleClose()
120
+ return
121
+ }
122
+
123
+ const strand = sourceFeature.strand ?? 1
124
+ if (strand === 1) {
125
+ cdsMin = stitchedToGenomicBound(orf[0], exonParts, 1)
126
+ cdsMax = stitchedToGenomicBound(orf[1] + 3, exonParts, 1)
127
+ } else {
128
+ cdsMax = stitchedToGenomicBound(orf[0], exonParts, -1)
129
+ cdsMin = stitchedToGenomicBound(orf[1] + 3, exonParts, -1)
130
+ }
131
+ } else {
132
+ cdsMin = Number(minInput) - 1
133
+ cdsMax = Number(maxInput)
134
+ }
135
+
136
+ const _id = new ObjectID().toHexString()
137
+ const addedFeature: AnnotationFeatureSnapshot = {
138
+ _id,
139
+ refSeq: sourceFeature.refSeq,
140
+ min: cdsMin,
141
+ max: cdsMax,
142
+ type: 'CDS',
143
+ }
144
+ if (sourceFeature.strand) {
145
+ addedFeature.strand = sourceFeature.strand
146
+ }
147
+ const change = new AddFeatureChange({
148
+ changedIds: [sourceFeature._id],
149
+ typeName: 'AddFeatureChange',
150
+ assembly: sourceAssemblyId,
151
+ addedFeature,
152
+ parentFeatureId: sourceFeature._id,
153
+ })
154
+ await changeManager.submit(change)
155
+ session.apolloSetSelectedFeature(_id)
156
+ handleClose()
157
+ } catch (error) {
158
+ setErrorMessage(
159
+ error instanceof Error ? error.message : 'An unknown error occurred',
160
+ )
161
+ }
162
+ }
163
+
164
+ const manualError =
165
+ method === 'manual' &&
166
+ Boolean(minInput) &&
167
+ Boolean(maxInput) &&
168
+ Number(maxInput) <= Number(minInput)
169
+
170
+ const submitDisabled =
171
+ method === 'manual' ? !(minInput && maxInput) || manualError : false
172
+
173
+ return (
174
+ <Dialog
175
+ open
176
+ title="Add coding sequence"
177
+ handleClose={handleClose}
178
+ maxWidth={false}
179
+ data-testid="add-coding-sequence"
180
+ >
181
+ <form
182
+ onSubmit={(event) => {
183
+ void onSubmit(event)
184
+ }}
185
+ >
186
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
187
+ <FormControl>
188
+ <RadioGroup
189
+ value={method}
190
+ onChange={(e) => {
191
+ setMethod(e.target.value as 'longest-orf' | 'manual')
192
+ }}
193
+ >
194
+ <FormControlLabel
195
+ value="longest-orf"
196
+ control={<Radio />}
197
+ label="Calculate longest open reading frame"
198
+ />
199
+ <FormControlLabel
200
+ value="manual"
201
+ control={<Radio />}
202
+ label="Manual"
203
+ />
204
+ </RadioGroup>
205
+ </FormControl>
206
+ {method === 'manual' ? (
207
+ <>
208
+ <TextField
209
+ margin="dense"
210
+ id="cds-min"
211
+ label="Min"
212
+ type="number"
213
+ fullWidth
214
+ variant="outlined"
215
+ value={minInput}
216
+ onChange={(e) => {
217
+ setMinInput(e.target.value)
218
+ }}
219
+ />
220
+ <TextField
221
+ margin="dense"
222
+ id="cds-max"
223
+ label="Max"
224
+ type="number"
225
+ fullWidth
226
+ variant="outlined"
227
+ value={maxInput}
228
+ onChange={(e) => {
229
+ setMaxInput(e.target.value)
230
+ }}
231
+ error={manualError}
232
+ helperText={
233
+ manualError ? '"Max" must be greater than "Min"' : null
234
+ }
235
+ />
236
+ </>
237
+ ) : null}
238
+ </DialogContent>
239
+ <DialogActions>
240
+ <Button variant="contained" type="submit" disabled={submitDisabled}>
241
+ Submit
242
+ </Button>
243
+ <Button variant="outlined" type="button" onClick={handleClose}>
244
+ Cancel
245
+ </Button>
246
+ </DialogActions>
247
+ </form>
248
+ {errorMessage ? (
249
+ <DialogContent>
250
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
251
+ </DialogContent>
252
+ ) : null}
253
+ </Dialog>
254
+ )
255
+ }
@@ -233,7 +233,7 @@ export function AddFeature({
233
233
  }
234
234
  }
235
235
 
236
- let submitDisabled: boolean = Boolean(error) || !(start && end && type)
236
+ let submitDisabled: boolean = error || !(start && end && type)
237
237
  if (
238
238
  (type === NewFeature.CUSTOM && !customType) ||
239
239
  (!strand && type === NewFeature.GENE_AND_SUBFEATURES) ||
@@ -68,7 +68,7 @@ export const AddRefSeqAliases = observer(function AddRefSeqAliases({
68
68
  }[]
69
69
  >([])
70
70
  const [refNameAliasMap, setRefNameAliasMap] = useState<Map<string, string[]>>(
71
- new Map(),
71
+ () => new Map(),
72
72
  )
73
73
 
74
74
  const { apolloDataStore } = session
@@ -83,6 +83,7 @@ export const AddRefSeqAliases = observer(function AddRefSeqAliases({
83
83
 
84
84
  useEffect(() => {
85
85
  if (assemblies.length > 0) {
86
+ // eslint-disable-next-line @eslint-react/set-state-in-effect
86
87
  setSelectedAssembly(assemblies[0])
87
88
  collaborationServerDriver
88
89
  .getRefNameAliases(assemblies[0].name)
@@ -94,7 +95,7 @@ export const AddRefSeqAliases = observer(function AddRefSeqAliases({
94
95
  setErrorMessage('Error fetching refName aliases for assembly')
95
96
  })
96
97
  }
97
- // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ // eslint-disable-next-line @eslint-react/exhaustive-deps
98
99
  }, [])
99
100
 
100
101
  const initializeRefNameAliasMap = (refNameAliasesList: RefNameAliases[]) => {
@@ -8,6 +8,7 @@ import type {
8
8
  AnnotationFeatureSnapshot,
9
9
  } from '@apollo-annotation/mst'
10
10
  import { AddFeatureChange } from '@apollo-annotation/shared'
11
+ import type { Assembly } from '@jbrowse/core/assemblyManager/assembly'
11
12
  import { readConfObject } from '@jbrowse/core/configuration'
12
13
  import type { AbstractSessionModel } from '@jbrowse/core/util'
13
14
  import { getSnapshot } from '@jbrowse/mobx-state-tree'
@@ -105,7 +106,9 @@ export function CopyFeature({
105
106
  setErrorMessage('No assemblies to copy to')
106
107
  return
107
108
  }
108
- const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId)
109
+ const assembly = (await assemblyManager.waitForAssembly(
110
+ selectedAssemblyId,
111
+ )) as Assembly | undefined
109
112
  if (!assembly) {
110
113
  return
111
114
  }
@@ -136,7 +139,9 @@ export function CopyFeature({
136
139
  event.preventDefault()
137
140
  setErrorMessage('')
138
141
  const featureLength = sourceFeature.length
139
- const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId)
142
+ const assembly = (await assemblyManager.waitForAssembly(
143
+ selectedAssemblyId,
144
+ )) as Assembly | undefined
140
145
  if (!assembly) {
141
146
  setErrorMessage(`Assembly not found: ${selectedAssemblyId}.`)
142
147
  return
@@ -165,7 +170,7 @@ export function CopyFeature({
165
170
  const featureIds: string[] = []
166
171
  // Let's add featureId to each child recursively
167
172
  const newFeatureLine = generateNewIds(
168
- getSnapshot(sourceFeature) as unknown as AnnotationFeatureSnapshot,
173
+ getSnapshot(sourceFeature),
169
174
  featureIds,
170
175
  )
171
176
  // Clear possible parentId -attribute.
@@ -193,10 +198,7 @@ export function CopyFeature({
193
198
  min: newFeatureLine.min,
194
199
  max: newFeatureLine.max,
195
200
  type: newFeatureLine.type,
196
- children: updatedChildren.children as unknown as Record<
197
- string,
198
- AnnotationFeatureSnapshot
199
- >,
201
+ children: updatedChildren.children,
200
202
  attributes: attributeMap,
201
203
  strand: newFeatureLine.strand,
202
204
  },
@@ -1,4 +1,4 @@
1
- /* eslint-disable react-hooks/exhaustive-deps */
1
+ /* eslint-disable @eslint-react/set-state-in-effect */
2
2
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
3
  /* eslint-disable @typescript-eslint/unbound-method */
4
4
 
@@ -192,30 +192,35 @@ export function CreateApolloAnnotation({
192
192
  const refSeq = apolloAssembly?.refSeqs.get(refSeqId)
193
193
  const features = refSeq?.getFeatures(region.start, region.end)
194
194
 
195
- const getDestinationFeatures = () => {
196
- const filteredFeatures: AnnotationFeatureSnapshot[] = []
195
+ useEffect(() => {
196
+ const getDestinationFeatures = () => {
197
+ const filteredFeatures: AnnotationFeatureSnapshot[] = []
197
198
 
198
- for (const f of features ?? []) {
199
- if (f.min > region.end || f.max < region.start) {
200
- continue
201
- }
199
+ for (const f of features ?? []) {
200
+ if (f.min > region.end || f.max < region.start) {
201
+ continue
202
+ }
202
203
 
203
- // Destination feature should be of type gene
204
- if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
205
- const featureSnapshot = getSnapshot(f)
206
- filteredFeatures.push(featureSnapshot)
204
+ // Destination feature should be of type gene
205
+ if (featureTypeOntology?.isTypeOf(f.type, 'gene')) {
206
+ const featureSnapshot = getSnapshot(f)
207
+ filteredFeatures.push(featureSnapshot)
208
+ }
207
209
  }
208
- }
209
210
 
210
- return filteredFeatures
211
- }
212
-
213
- useEffect(() => {
211
+ return filteredFeatures
212
+ }
214
213
  setErrorMessage('')
215
- const features = getDestinationFeatures()
216
- setDestinationFeatures(features)
217
- setSelectedDestinationFeature(features[0])
218
- }, [checkedChildrens, parentFeatureChecked, region])
214
+ const destinationFeatures = getDestinationFeatures()
215
+ setDestinationFeatures(destinationFeatures)
216
+ setSelectedDestinationFeature(destinationFeatures[0])
217
+ }, [
218
+ checkedChildrens,
219
+ featureTypeOntology,
220
+ features,
221
+ parentFeatureChecked,
222
+ region,
223
+ ])
219
224
 
220
225
  const handleParentFeatureCheck = (
221
226
  event: React.ChangeEvent<HTMLInputElement>,
@@ -349,7 +354,7 @@ export function CreateApolloAnnotation({
349
354
  const copyGeneFeature = async () => {
350
355
  const copiedAnnotationFeature = {
351
356
  ...annotationFeature,
352
- } as AnnotationFeatureSnapshot
357
+ }
353
358
  removeSkippedAttributes(copiedAnnotationFeature, skippedAttributesOnCopy)
354
359
 
355
360
  let change
@@ -394,7 +399,7 @@ export function CreateApolloAnnotation({
394
399
  for (const transcriptId of Object.keys(transcripts)) {
395
400
  const transcript = {
396
401
  ...transcripts[transcriptId],
397
- } as AnnotationFeatureSnapshot
402
+ }
398
403
  removeSkippedAttributes(transcript, skippedAttributesOnCopy)
399
404
  transcript.strand = selectedDestinationFeature.strand
400
405
 
@@ -422,7 +427,7 @@ export function CreateApolloAnnotation({
422
427
  ) => {
423
428
  const copiedChildrens: Record<string, AnnotationFeatureSnapshot> = {}
424
429
  for (const [childId, child] of Object.entries(childrens)) {
425
- const copiedChild = { ...child } as AnnotationFeatureSnapshot
430
+ const copiedChild = { ...child }
426
431
  removeSkippedAttributes(copiedChild, skippedAttributesOnCopy)
427
432
  copiedChildrens[childId] = copiedChild
428
433
  }
@@ -40,7 +40,7 @@ export function DeleteAssembly({
40
40
  }: DeleteAssemblyProps) {
41
41
  const { internetAccounts } = getRoot<ApolloRootModel>(session)
42
42
  const [errorMessage, setErrorMessage] = useState('')
43
- const [confirmDelete, setconfirmDelete] = useState(false)
43
+ const [confirmDelete, setConfirmDelete] = useState(false)
44
44
  const [submitted, setSubmitted] = useState(false)
45
45
  const apolloInternetAccounts = internetAccounts.filter(
46
46
  (ia) => ia.type === 'ApolloInternetAccount',
@@ -151,7 +151,7 @@ export function DeleteAssembly({
151
151
  <Checkbox
152
152
  checked={confirmDelete}
153
153
  onChange={() => {
154
- setconfirmDelete(!confirmDelete)
154
+ setConfirmDelete(!confirmDelete)
155
155
  }}
156
156
  />
157
157
  }
@@ -330,10 +330,7 @@ export function DeleteFeature({
330
330
  }
331
331
  }
332
332
 
333
- if (
334
- featureTypeOntology &&
335
- featureTypeOntology.isTypeOf(sourceFeature.type, 'exon')
336
- ) {
333
+ if (featureTypeOntology?.isTypeOf(sourceFeature.type, 'exon')) {
337
334
  const cdsChange = trimCDS(sourceFeature)
338
335
  if (cdsChange) {
339
336
  if (cdsChange.typeName === 'DeleteFeatureChange') {
@@ -3,6 +3,7 @@
3
3
  /* eslint-disable @typescript-eslint/no-misused-promises */
4
4
  import { annotationFeatureToGFF3 } from '@apollo-annotation/shared'
5
5
  import { GFFFormattingTransformer } from '@gmod/gff'
6
+ import type { Assembly } from '@jbrowse/core/assemblyManager/assembly'
6
7
  import { getConf } from '@jbrowse/core/configuration'
7
8
  import type { AbstractSessionModel } from '@jbrowse/core/util'
8
9
  import {
@@ -49,7 +50,7 @@ export function DownloadGFF3({
49
50
  session,
50
51
  assembly: assemblyName,
51
52
  }: DownloadGFF3Props) {
52
- const [includeFASTA, setincludeFASTA] = useState(false)
53
+ const [includeFASTA, setIncludeFASTA] = useState(false)
53
54
  const [errorMessage, setErrorMessage] = useState('')
54
55
 
55
56
  const { getInternetAccount } = session.apolloDataStore as {
@@ -61,7 +62,7 @@ export function DownloadGFF3({
61
62
  }
62
63
 
63
64
  const { assemblyManager } = session as unknown as AbstractSessionModel
64
- const assembly = assemblyManager.get(assemblyName)
65
+ const assembly = assemblyManager.get(assemblyName) as Assembly | undefined
65
66
  if (!assembly) {
66
67
  setErrorMessage(`Assembly "${assemblyName}" not found`)
67
68
  return
@@ -140,7 +141,7 @@ export function DownloadGFF3({
140
141
  <Checkbox
141
142
  checked={includeFASTA}
142
143
  onChange={() => {
143
- setincludeFASTA(!includeFASTA)
144
+ setIncludeFASTA(!includeFASTA)
144
145
  }}
145
146
  disabled={!internetAccountConfigId}
146
147
  />
@@ -0,0 +1,154 @@
1
+ /* eslint-disable @typescript-eslint/no-misused-promises */
2
+ /* eslint-disable @typescript-eslint/unbound-method */
3
+ import type { AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
4
+ import {
5
+ AddFeatureChange,
6
+ DeleteFeatureChange,
7
+ } from '@apollo-annotation/shared'
8
+ import {
9
+ Button,
10
+ DialogActions,
11
+ DialogContent,
12
+ DialogContentText,
13
+ FormControl,
14
+ FormControlLabel,
15
+ FormLabel,
16
+ Radio,
17
+ RadioGroup,
18
+ } from '@mui/material'
19
+ import ObjectID from 'bson-objectid'
20
+ import React, { useState } from 'react'
21
+
22
+ import type { ChangeManager } from '../ChangeManager'
23
+
24
+ import { Dialog } from './Dialog'
25
+
26
+ type Choice = 'keepExisting' | 'useNew' | 'keepBoth'
27
+
28
+ interface DuplicateFeatureDialogProps {
29
+ featureSnapshot: AnnotationFeatureSnapshot
30
+ existingFeature: AnnotationFeatureSnapshot
31
+ assemblyName: string
32
+ changeManager: ChangeManager
33
+ handleClose(): void
34
+ }
35
+
36
+ function reassignIds(
37
+ feature: AnnotationFeatureSnapshot,
38
+ ): AnnotationFeatureSnapshot {
39
+ const newChildren: Record<string, AnnotationFeatureSnapshot> = {}
40
+ if (feature.children) {
41
+ for (const child of Object.values(feature.children)) {
42
+ const newChild = reassignIds(child)
43
+ newChildren[newChild._id] = newChild
44
+ }
45
+ }
46
+ return {
47
+ ...feature,
48
+ _id: new ObjectID().toHexString(),
49
+ children: feature.children ? newChildren : undefined,
50
+ }
51
+ }
52
+
53
+ export function DuplicateFeatureDialog({
54
+ assemblyName,
55
+ changeManager,
56
+ existingFeature,
57
+ featureSnapshot,
58
+ handleClose,
59
+ }: DuplicateFeatureDialogProps) {
60
+ const [choice, setChoice] = useState<Choice>('keepExisting')
61
+ const [errorMessage, setErrorMessage] = useState('')
62
+ const [submitted, setSubmitted] = useState(false)
63
+
64
+ const handleSubmit = async () => {
65
+ setErrorMessage('')
66
+ setSubmitted(true)
67
+ try {
68
+ if (choice === 'useNew') {
69
+ const deleteChange = new DeleteFeatureChange({
70
+ typeName: 'DeleteFeatureChange',
71
+ assembly: assemblyName,
72
+ changedIds: [existingFeature._id],
73
+ deletedFeature: existingFeature,
74
+ })
75
+ await changeManager.submit(deleteChange)
76
+ const addChange = new AddFeatureChange({
77
+ typeName: 'AddFeatureChange',
78
+ assembly: assemblyName,
79
+ changedIds: [featureSnapshot._id],
80
+ addedFeature: featureSnapshot,
81
+ })
82
+ await changeManager.submit(addChange)
83
+ } else if (choice === 'keepBoth') {
84
+ const newSnapshot = reassignIds(featureSnapshot)
85
+ const addChange = new AddFeatureChange({
86
+ typeName: 'AddFeatureChange',
87
+ assembly: assemblyName,
88
+ changedIds: [newSnapshot._id],
89
+ addedFeature: newSnapshot,
90
+ })
91
+ await changeManager.submit(addChange)
92
+ }
93
+ handleClose()
94
+ } catch (error) {
95
+ setErrorMessage(String(error))
96
+ setSubmitted(false)
97
+ }
98
+ }
99
+
100
+ return (
101
+ <Dialog
102
+ open
103
+ title="Duplicate Feature Detected"
104
+ handleClose={handleClose}
105
+ maxWidth="sm"
106
+ fullWidth
107
+ >
108
+ <DialogContent>
109
+ <DialogContentText>
110
+ A feature with ID &ldquo;{featureSnapshot._id}&rdquo; already exists.
111
+ How would you like to resolve this conflict?
112
+ </DialogContentText>
113
+ <FormControl component="fieldset" sx={{ mt: 2 }}>
114
+ <FormLabel component="legend">Resolution</FormLabel>
115
+ <RadioGroup
116
+ value={choice}
117
+ onChange={(e) => {
118
+ setChoice(e.target.value as Choice)
119
+ }}
120
+ >
121
+ <FormControlLabel
122
+ value="keepExisting"
123
+ control={<Radio />}
124
+ label="Keep the existing feature"
125
+ />
126
+ <FormControlLabel
127
+ value="useNew"
128
+ control={<Radio />}
129
+ label="Replace with the new feature"
130
+ />
131
+ <FormControlLabel
132
+ value="keepBoth"
133
+ control={<Radio />}
134
+ label="Keep both (assign new IDs to the incoming feature)"
135
+ />
136
+ </RadioGroup>
137
+ </FormControl>
138
+ {errorMessage ? (
139
+ <DialogContentText color="error" sx={{ mt: 1 }}>
140
+ {errorMessage}
141
+ </DialogContentText>
142
+ ) : null}
143
+ </DialogContent>
144
+ <DialogActions>
145
+ <Button variant="contained" onClick={handleSubmit} disabled={submitted}>
146
+ Confirm
147
+ </Button>
148
+ <Button onClick={handleClose} disabled={submitted}>
149
+ Cancel
150
+ </Button>
151
+ </DialogActions>
152
+ </Dialog>
153
+ )
154
+ }
@@ -68,7 +68,7 @@ export function DuplicateTranscript({
68
68
  newChildren[newChildId] = {
69
69
  ...child,
70
70
  _id: newChildId,
71
- } as AnnotationFeatureSnapshot
71
+ }
72
72
  }
73
73
  duplicateTranscript.children = newChildren
74
74
  }
@@ -63,8 +63,8 @@ export const FilterTranscripts = observer(function FilterTranscripts({
63
63
  <Grid size={8}>
64
64
  <FormGroup>
65
65
  {allTranscripts.map((item) => (
66
- // eslint-disable-next-line react/jsx-key
67
66
  <FormControlLabel
67
+ key={item}
68
68
  control={
69
69
  <Checkbox
70
70
  checked={!excludedTranscripts.includes(item)}