@apollo-annotation/jbrowse-plugin-apollo 0.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 (116) hide show
  1. package/README.md +76 -0
  2. package/dist/index.esm.js +10248 -0
  3. package/dist/index.esm.js.map +1 -0
  4. package/dist/index.js +7 -0
  5. package/dist/jbrowse-plugin-apollo.cjs.development.js +10298 -0
  6. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -0
  7. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +2 -0
  8. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -0
  9. package/dist/jbrowse-plugin-apollo.umd.development.js +46957 -0
  10. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -0
  11. package/dist/jbrowse-plugin-apollo.umd.production.min.js +2 -0
  12. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -0
  13. package/package.json +130 -0
  14. package/src/ApolloInternetAccount/addMenuItems.ts +94 -0
  15. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +121 -0
  16. package/src/ApolloInternetAccount/components/LoginButtons.tsx +62 -0
  17. package/src/ApolloInternetAccount/components/LoginIcons.tsx +74 -0
  18. package/src/ApolloInternetAccount/configSchema.ts +26 -0
  19. package/src/ApolloInternetAccount/index.ts +2 -0
  20. package/src/ApolloInternetAccount/model.ts +448 -0
  21. package/src/ApolloJobModel.ts +117 -0
  22. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +186 -0
  23. package/src/ApolloSequenceAdapter/configSchema.ts +12 -0
  24. package/src/ApolloSequenceAdapter/index.ts +21 -0
  25. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +12 -0
  26. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +692 -0
  27. package/src/ApolloSixFrameRenderer/configSchema.ts +7 -0
  28. package/src/ApolloSixFrameRenderer/index.ts +3 -0
  29. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +64 -0
  30. package/src/ApolloTextSearchAdapter/configSchema.ts +24 -0
  31. package/src/ApolloTextSearchAdapter/index.ts +18 -0
  32. package/src/BackendDrivers/BackendDriver.ts +31 -0
  33. package/src/BackendDrivers/CollaborationServerDriver.ts +318 -0
  34. package/src/BackendDrivers/DesktopFileDriver.ts +170 -0
  35. package/src/BackendDrivers/InMemoryFileDriver.ts +76 -0
  36. package/src/BackendDrivers/index.ts +4 -0
  37. package/src/ChangeManager.ts +148 -0
  38. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +248 -0
  39. package/src/LinearApolloDisplay/components/index.ts +1 -0
  40. package/src/LinearApolloDisplay/configSchema.ts +16 -0
  41. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +422 -0
  42. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +1191 -0
  43. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +151 -0
  44. package/src/LinearApolloDisplay/glyphs/Glyph.ts +382 -0
  45. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +697 -0
  46. package/src/LinearApolloDisplay/glyphs/index.ts +4 -0
  47. package/src/LinearApolloDisplay/index.ts +2 -0
  48. package/src/LinearApolloDisplay/stateModel/base.ts +146 -0
  49. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +39 -0
  50. package/src/LinearApolloDisplay/stateModel/glyphs.ts +45 -0
  51. package/src/LinearApolloDisplay/stateModel/index.ts +20 -0
  52. package/src/LinearApolloDisplay/stateModel/layouts.ts +230 -0
  53. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +513 -0
  54. package/src/LinearApolloDisplay/stateModel/rendering.ts +441 -0
  55. package/src/LinearApolloDisplay/stateModel/trackHeightMixin.ts +43 -0
  56. package/src/LinearApolloDisplay/types.ts +1 -0
  57. package/src/OntologyManager/OntologyStore/__snapshots__/fulltext.test.ts.snap +208 -0
  58. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18846 -0
  59. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +137 -0
  60. package/src/OntologyManager/OntologyStore/fulltext.test.ts +94 -0
  61. package/src/OntologyManager/OntologyStore/fulltext.ts +264 -0
  62. package/src/OntologyManager/OntologyStore/index.test.ts +130 -0
  63. package/src/OntologyManager/OntologyStore/index.ts +526 -0
  64. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +89 -0
  65. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +180 -0
  66. package/src/OntologyManager/OntologyStore/obo-graph-json-schema.ts +110 -0
  67. package/src/OntologyManager/OntologyStore/prefixes.ts +35 -0
  68. package/src/OntologyManager/index.ts +173 -0
  69. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +19 -0
  70. package/src/SixFrameFeatureDisplay/components/index.ts +1 -0
  71. package/src/SixFrameFeatureDisplay/configSchema.ts +21 -0
  72. package/src/SixFrameFeatureDisplay/index.ts +2 -0
  73. package/src/SixFrameFeatureDisplay/stateModel.ts +413 -0
  74. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +88 -0
  75. package/src/TabularEditor/HybridGrid/Feature.tsx +346 -0
  76. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +34 -0
  77. package/src/TabularEditor/HybridGrid/Highlight.tsx +40 -0
  78. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +138 -0
  79. package/src/TabularEditor/HybridGrid/NumberCell.tsx +77 -0
  80. package/src/TabularEditor/HybridGrid/ToolBar.tsx +59 -0
  81. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +119 -0
  82. package/src/TabularEditor/HybridGrid/index.ts +1 -0
  83. package/src/TabularEditor/TabularEditorPane.tsx +34 -0
  84. package/src/TabularEditor/index.ts +3 -0
  85. package/src/TabularEditor/model.ts +44 -0
  86. package/src/TabularEditor/types.ts +3 -0
  87. package/src/components/AddAssembly.tsx +464 -0
  88. package/src/components/AddChildFeature.tsx +247 -0
  89. package/src/components/AddFeature.tsx +252 -0
  90. package/src/components/CopyFeature.tsx +328 -0
  91. package/src/components/DeleteAssembly.tsx +185 -0
  92. package/src/components/DeleteFeature.tsx +90 -0
  93. package/src/components/Dialog.tsx +47 -0
  94. package/src/components/DownloadGFF3.tsx +213 -0
  95. package/src/components/ImportFeatures.tsx +295 -0
  96. package/src/components/ManageChecks.tsx +280 -0
  97. package/src/components/ManageUsers.tsx +218 -0
  98. package/src/components/ModifyFeatureAttribute.tsx +457 -0
  99. package/src/components/OntologyTermAutocomplete.tsx +240 -0
  100. package/src/components/OntologyTermMultiSelect.tsx +349 -0
  101. package/src/components/OpenLocalFile.tsx +178 -0
  102. package/src/components/ViewChangeLog.tsx +208 -0
  103. package/src/components/ViewCheckResults.tsx +151 -0
  104. package/src/components/index.ts +12 -0
  105. package/src/config.ts +10 -0
  106. package/src/declare.d.ts +3 -0
  107. package/src/extensions/annotationFromPileup.ts +208 -0
  108. package/src/extensions/index.ts +1 -0
  109. package/src/index.ts +394 -0
  110. package/src/makeDisplayComponent.tsx +244 -0
  111. package/src/session/ClientDataStore.ts +282 -0
  112. package/src/session/index.ts +1 -0
  113. package/src/session/session.ts +373 -0
  114. package/src/types.ts +10 -0
  115. package/src/util/index.ts +31 -0
  116. package/src/util/loadAssemblyIntoClient.ts +291 -0
@@ -0,0 +1,328 @@
1
+ import { readConfObject } from '@jbrowse/core/configuration'
2
+ import { AbstractSessionModel } from '@jbrowse/core/util'
3
+ import {
4
+ Button,
5
+ DialogActions,
6
+ DialogContent,
7
+ DialogContentText,
8
+ MenuItem,
9
+ Select,
10
+ SelectChangeEvent,
11
+ TextField,
12
+ } from '@mui/material'
13
+ import { AnnotationFeatureI, AnnotationFeatureSnapshot } from 'apollo-mst'
14
+ import { AddFeatureChange } from 'apollo-shared'
15
+ import ObjectID from 'bson-objectid'
16
+ import { IKeyValueMap } from 'mobx'
17
+ import { getSnapshot } from 'mobx-state-tree'
18
+ import React, { useEffect, useState } from 'react'
19
+
20
+ import { ChangeManager } from '../ChangeManager'
21
+ import { ApolloSessionModel } from '../session'
22
+ import { Dialog } from './Dialog'
23
+
24
+ interface CopyFeatureProps {
25
+ session: ApolloSessionModel
26
+ handleClose(): void
27
+ sourceFeature: AnnotationFeatureI
28
+ sourceAssemblyId: string
29
+ changeManager: ChangeManager
30
+ }
31
+
32
+ interface Collection {
33
+ _id: string
34
+ name: string
35
+ }
36
+
37
+ /**
38
+ * Recursively assign new IDs to a feature
39
+ * @param feature - Parent feature
40
+ * @param featureIds -
41
+ */
42
+ function generateNewIds(
43
+ // feature: AnnotationFeatureSnapshot,
44
+ feature: AnnotationFeatureSnapshot,
45
+ featureIds: string[],
46
+ ): AnnotationFeatureSnapshot {
47
+ const newId = new ObjectID().toHexString()
48
+ featureIds.push(newId)
49
+
50
+ const children: Record<string, AnnotationFeatureSnapshot> = {}
51
+ if (feature.children) {
52
+ for (const child of Object.values(feature.children)) {
53
+ const newChild = generateNewIds(child, featureIds)
54
+ children[newChild._id] = newChild
55
+ }
56
+ }
57
+ const referenceSeq =
58
+ typeof feature.refSeq === 'string'
59
+ ? feature.refSeq
60
+ : (feature.refSeq as unknown as ObjectID).toHexString()
61
+
62
+ return {
63
+ ...feature,
64
+ refSeq: referenceSeq,
65
+ children: feature.children && children,
66
+ _id: newId,
67
+ }
68
+ }
69
+
70
+ export function CopyFeature({
71
+ changeManager,
72
+ handleClose,
73
+ session,
74
+ sourceAssemblyId,
75
+ sourceFeature,
76
+ }: CopyFeatureProps) {
77
+ const { assemblyManager, notify } = session as unknown as AbstractSessionModel
78
+ const assemblies = assemblyManager.assemblyList
79
+
80
+ const [selectedAssemblyId, setSelectedAssemblyId] = useState<
81
+ string | undefined
82
+ >(assemblies.find((a) => a.name !== sourceAssemblyId)?.name)
83
+ const [refNames, setRefNames] = useState<Collection[]>([])
84
+ const [selectedRefSeqId, setSelectedRefSeqId] = useState('')
85
+ const [start, setStart] = useState(sourceFeature.start)
86
+ const [errorMessage, setErrorMessage] = useState('')
87
+
88
+ async function handleChangeAssembly(e: SelectChangeEvent<string>) {
89
+ setSelectedAssemblyId(e.target.value)
90
+ }
91
+
92
+ useEffect(() => {
93
+ setSelectedRefSeqId('')
94
+ async function getRefNames() {
95
+ if (!selectedAssemblyId) {
96
+ setErrorMessage('No assemblies to copy to')
97
+ return
98
+ }
99
+ const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId)
100
+ if (!assembly) {
101
+ return
102
+ }
103
+ const { refNameAliases } = assembly
104
+ if (!refNameAliases) {
105
+ return
106
+ }
107
+ const newRefNames = [...Object.entries(refNameAliases)]
108
+ .filter(([id, refName]) => id !== refName)
109
+ .map(([id, refName]) => ({ _id: id, name: refName ?? '' }))
110
+ setRefNames(newRefNames)
111
+ setSelectedRefSeqId(newRefNames[0]?._id || '')
112
+ }
113
+ getRefNames().catch((error) => setErrorMessage(String(error)))
114
+ }, [selectedAssemblyId, assemblyManager])
115
+
116
+ async function handleChangeRefSeq(e: SelectChangeEvent<string>) {
117
+ const refSeq = e.target.value as string
118
+ setSelectedRefSeqId(refSeq)
119
+ }
120
+
121
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
122
+ if (!selectedAssemblyId) {
123
+ return
124
+ }
125
+ event.preventDefault()
126
+ setErrorMessage('')
127
+ const featureLength = sourceFeature.length
128
+ const assembly = await assemblyManager.waitForAssembly(selectedAssemblyId)
129
+ if (!assembly) {
130
+ setErrorMessage(`Assembly not found: ${selectedAssemblyId}.`)
131
+ return
132
+ }
133
+ const canonicalRefName = assembly?.getCanonicalRefName(selectedRefSeqId)
134
+ const region = assembly.regions?.find((r) => r.refName === canonicalRefName)
135
+ if (!region) {
136
+ setErrorMessage(`RefSeq not found: ${selectedRefSeqId}.`)
137
+ return
138
+ }
139
+
140
+ const newEnd = start + featureLength
141
+ if (newEnd > region.end) {
142
+ setErrorMessage(
143
+ `Feature would extend beyond the bounds of the selected reference sequence. (Feature would end at ${newEnd}, but reference sequence ends at ${region.end})`,
144
+ )
145
+ return
146
+ }
147
+ if (start < region.start) {
148
+ setErrorMessage(
149
+ `Reference sequence starts at ${region.start}, feature cannot start before that.`,
150
+ )
151
+ return
152
+ }
153
+
154
+ const featureIds: string[] = []
155
+ // Let's add featureId to each child recursively
156
+ const newFeatureLine = generateNewIds(
157
+ getSnapshot(sourceFeature) as unknown as AnnotationFeatureSnapshot,
158
+ featureIds,
159
+ )
160
+ // Clear possible parentId -attribute.
161
+ const attributeMap: IKeyValueMap<string[]> = {
162
+ ...(newFeatureLine.attributes as unknown as IKeyValueMap<string[]>),
163
+ }
164
+ if ('Parent' in attributeMap) {
165
+ delete attributeMap.Parent
166
+ }
167
+
168
+ // Update gffId value if it's ObjectId
169
+ if (
170
+ newFeatureLine.gffId &&
171
+ ObjectID.isValid(newFeatureLine.gffId.toString())
172
+ ) {
173
+ newFeatureLine.gffId = newFeatureLine._id
174
+ }
175
+ newFeatureLine.refSeq = selectedRefSeqId
176
+ const locationMove = start - newFeatureLine.start
177
+ newFeatureLine.start = start
178
+ newFeatureLine.end = start + featureLength
179
+ // Updates children start, end and gffId values
180
+ const updatedChildren = updateRefSeqStartEndAndGffId(
181
+ newFeatureLine,
182
+ locationMove,
183
+ )
184
+
185
+ const change = new AddFeatureChange({
186
+ changedIds: [newFeatureLine._id],
187
+ typeName: 'AddFeatureChange',
188
+ assembly: selectedAssemblyId,
189
+ addedFeature: {
190
+ _id: newFeatureLine._id,
191
+ refSeq: newFeatureLine.refSeq,
192
+ start: newFeatureLine.start,
193
+ end: newFeatureLine.end,
194
+ type: newFeatureLine.type,
195
+ children: updatedChildren.children as unknown as Record<
196
+ string,
197
+ AnnotationFeatureSnapshot
198
+ >,
199
+ attributes: attributeMap,
200
+ discontinuousLocations: newFeatureLine.discontinuousLocations,
201
+ strand: newFeatureLine.strand,
202
+ score: newFeatureLine.score,
203
+ phase: newFeatureLine.phase,
204
+ },
205
+ copyFeature: true,
206
+ allIds: featureIds,
207
+ })
208
+ await changeManager.submit?.(change)
209
+
210
+ notify('Feature copied successfully', 'success')
211
+ handleClose()
212
+ event.preventDefault()
213
+ }
214
+
215
+ /**
216
+ * Recursively loop children and update refSeq, start, end and gffId values
217
+ * @param feature - parent feature
218
+ * @param locationMove - how much location has been moved from original
219
+ * @returns
220
+ */
221
+ function updateRefSeqStartEndAndGffId(
222
+ feature: AnnotationFeatureSnapshot,
223
+ locationMove: number,
224
+ ): AnnotationFeatureSnapshot {
225
+ const children: Record<string, AnnotationFeatureSnapshot> = {}
226
+ if (feature.children) {
227
+ for (const child of Object.values(feature.children)) {
228
+ const newChild = updateRefSeqStartEndAndGffId(child, locationMove)
229
+ // Update gffId value if it's ObjectId
230
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
231
+ if (ObjectID.isValid(newChild.gffId!.toString())) {
232
+ newChild.gffId = newChild._id
233
+ }
234
+ newChild.refSeq = selectedRefSeqId
235
+ newChild.start = newChild.start + locationMove
236
+ newChild.end = newChild.end + locationMove
237
+ children[newChild._id] = newChild
238
+ }
239
+ }
240
+ const refSeq =
241
+ typeof feature.refSeq === 'string'
242
+ ? feature.refSeq
243
+ : (feature.refSeq as unknown as ObjectID).toHexString()
244
+
245
+ const id =
246
+ typeof feature._id === 'string'
247
+ ? feature._id
248
+ : (feature._id as unknown as ObjectID).toHexString()
249
+
250
+ return {
251
+ ...feature,
252
+ refSeq,
253
+ children: feature.children && children,
254
+ _id: id,
255
+ }
256
+ }
257
+
258
+ return (
259
+ <Dialog
260
+ open
261
+ title="Copy features and annotations"
262
+ handleClose={handleClose}
263
+ maxWidth={false}
264
+ data-testid="copy-feature"
265
+ >
266
+ <form onSubmit={onSubmit}>
267
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
268
+ <DialogContentText>Target assembly</DialogContentText>
269
+ <Select
270
+ labelId="label"
271
+ value={selectedAssemblyId}
272
+ onChange={handleChangeAssembly}
273
+ >
274
+ {assemblies
275
+ .filter((option) => option.name !== sourceAssemblyId)
276
+ .map((option) => (
277
+ <MenuItem key={option.name} value={option.name}>
278
+ {readConfObject(option, 'displayName')}
279
+ </MenuItem>
280
+ ))}
281
+ </Select>
282
+ <DialogContentText>Target reference sequence</DialogContentText>
283
+ <Select
284
+ labelId="label"
285
+ value={selectedRefSeqId}
286
+ onChange={handleChangeRefSeq}
287
+ >
288
+ {refNames.map((option) => (
289
+ <MenuItem key={option._id} value={option._id}>
290
+ {option.name}
291
+ </MenuItem>
292
+ ))}
293
+ </Select>
294
+ <DialogContentText>
295
+ Start position in target reference sequence
296
+ </DialogContentText>
297
+ <TextField
298
+ margin="dense"
299
+ type="number"
300
+ fullWidth
301
+ variant="outlined"
302
+ value={start}
303
+ onChange={(e) => {
304
+ setStart(Number(e.target.value))
305
+ }}
306
+ />
307
+ </DialogContent>
308
+ <DialogActions>
309
+ <Button
310
+ disabled={!selectedAssemblyId || !selectedRefSeqId || !start}
311
+ variant="contained"
312
+ type="submit"
313
+ >
314
+ Submit
315
+ </Button>
316
+ <Button variant="outlined" type="submit" onClick={handleClose}>
317
+ Cancel
318
+ </Button>
319
+ </DialogActions>
320
+ </form>
321
+ {errorMessage ? (
322
+ <DialogContent>
323
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
324
+ </DialogContent>
325
+ ) : null}
326
+ </Dialog>
327
+ )
328
+ }
@@ -0,0 +1,185 @@
1
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
2
+ import {
3
+ Button,
4
+ Checkbox,
5
+ DialogActions,
6
+ DialogContent,
7
+ DialogContentText,
8
+ FormControlLabel,
9
+ FormGroup,
10
+ MenuItem,
11
+ Select,
12
+ SelectChangeEvent,
13
+ } from '@mui/material'
14
+ import { DeleteAssemblyChange } from 'apollo-shared'
15
+ import { getRoot } from 'mobx-state-tree'
16
+ import React, { useEffect, useState } from 'react'
17
+
18
+ import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
19
+ import {
20
+ ApolloInternetAccount,
21
+ CollaborationServerDriver,
22
+ } from '../BackendDrivers'
23
+ import { ChangeManager } from '../ChangeManager'
24
+ import { ApolloSessionModel } from '../session'
25
+ import { ApolloRootModel } from '../types'
26
+ import { Dialog } from './Dialog'
27
+
28
+ interface DeleteAssemblyProps {
29
+ session: ApolloSessionModel
30
+ handleClose(): void
31
+ changeManager: ChangeManager
32
+ }
33
+
34
+ export function DeleteAssembly({
35
+ changeManager,
36
+ handleClose,
37
+ session,
38
+ }: DeleteAssemblyProps) {
39
+ const { internetAccounts } = getRoot<ApolloRootModel>(session)
40
+ const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
41
+ const [errorMessage, setErrorMessage] = useState('')
42
+ const [confirmDelete, setconfirmDelete] = useState(false)
43
+ const [submitted, setSubmitted] = useState(false)
44
+ const apolloInternetAccounts = internetAccounts.filter(
45
+ (ia) => ia.type === 'ApolloInternetAccount',
46
+ ) as ApolloInternetAccountModel[]
47
+ if (apolloInternetAccounts.length === 0) {
48
+ throw new Error('No Apollo internet account found')
49
+ }
50
+ const [selectedInternetAccount, setSelectedInternetAccount] = useState(
51
+ apolloInternetAccounts[0],
52
+ )
53
+
54
+ const { collaborationServerDriver } = session.apolloDataStore as {
55
+ collaborationServerDriver: CollaborationServerDriver
56
+ getInternetAccount(
57
+ assemblyName?: string,
58
+ internetAccountId?: string,
59
+ ): ApolloInternetAccount
60
+ }
61
+
62
+ const assemblies = collaborationServerDriver.getAssemblies()
63
+
64
+ useEffect(() => {
65
+ if (assemblies.length > 0 && selectedAssembly === undefined) {
66
+ setSelectedAssembly(assemblies[0])
67
+ }
68
+ }, [assemblies, selectedAssembly])
69
+
70
+ function handleChangeInternetAccount(e: SelectChangeEvent<string>) {
71
+ setSubmitted(false)
72
+ const newlySelectedInternetAccount = apolloInternetAccounts.find(
73
+ (ia) => ia.internetAccountId === e.target.value,
74
+ )
75
+ if (!newlySelectedInternetAccount) {
76
+ throw new Error(
77
+ `Could not find internetAccount with ID "${e.target.value}"`,
78
+ )
79
+ }
80
+ setSelectedInternetAccount(newlySelectedInternetAccount)
81
+ }
82
+
83
+ function handleChangeAssembly(e: SelectChangeEvent<string>) {
84
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
85
+ setSelectedAssembly(newAssembly)
86
+ }
87
+
88
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
89
+ event.preventDefault()
90
+ setSubmitted(true)
91
+ setErrorMessage('')
92
+ if (!selectedAssembly) {
93
+ setErrorMessage('Must select assembly!')
94
+ return
95
+ }
96
+ const change = new DeleteAssemblyChange({
97
+ typeName: 'DeleteAssemblyChange',
98
+ assembly: selectedAssembly.name,
99
+ })
100
+ await changeManager.submit?.(change, {
101
+ internetAccountId: selectedInternetAccount.internetAccountId,
102
+ })
103
+ handleClose()
104
+ event.preventDefault()
105
+ }
106
+
107
+ return (
108
+ <Dialog
109
+ open
110
+ title="Delete Assembly"
111
+ handleClose={handleClose}
112
+ maxWidth={false}
113
+ data-testid="delete-assembly"
114
+ >
115
+ <form onSubmit={onSubmit}>
116
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
117
+ {apolloInternetAccounts.length > 1 ? (
118
+ <>
119
+ <DialogContentText>Select account</DialogContentText>
120
+ <Select
121
+ value={selectedInternetAccount.internetAccountId}
122
+ onChange={handleChangeInternetAccount}
123
+ disabled={submitted && !errorMessage}
124
+ >
125
+ {internetAccounts.map((option) => (
126
+ <MenuItem key={option.id} value={option.internetAccountId}>
127
+ {option.name}
128
+ </MenuItem>
129
+ ))}
130
+ </Select>
131
+ </>
132
+ ) : null}
133
+ <DialogContentText>Select assembly</DialogContentText>
134
+ <Select
135
+ labelId="label"
136
+ value={selectedAssembly?.name ?? ''}
137
+ onChange={handleChangeAssembly}
138
+ disabled={assemblies.length === 0}
139
+ >
140
+ {assemblies.map((option) => (
141
+ <MenuItem key={option.name} value={option.name}>
142
+ {option.displayName ?? option.name}
143
+ </MenuItem>
144
+ ))}
145
+ </Select>
146
+ <DialogContentText>
147
+ <strong style={{ color: 'red' }}>
148
+ NOTE: All assembly data will be deleted and this operation cannot
149
+ be undone!
150
+ </strong>
151
+ </DialogContentText>
152
+ <FormGroup>
153
+ <FormControlLabel
154
+ control={
155
+ <Checkbox
156
+ checked={confirmDelete}
157
+ onChange={() => setconfirmDelete(!confirmDelete)}
158
+ />
159
+ }
160
+ label="I understand that all assembly data will be deleted"
161
+ />
162
+ </FormGroup>
163
+ </DialogContent>
164
+
165
+ <DialogActions>
166
+ <Button
167
+ disabled={!selectedAssembly || !confirmDelete}
168
+ variant="contained"
169
+ type="submit"
170
+ >
171
+ Delete
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
+ }
@@ -0,0 +1,90 @@
1
+ import { AbstractSessionModel } from '@jbrowse/core/util'
2
+ import {
3
+ Button,
4
+ DialogActions,
5
+ DialogContent,
6
+ DialogContentText,
7
+ } from '@mui/material'
8
+ import { AnnotationFeatureI } from 'apollo-mst'
9
+ import { DeleteFeatureChange } from 'apollo-shared'
10
+ import { getSnapshot } from 'mobx-state-tree'
11
+ import React, { useState } from 'react'
12
+
13
+ import { ChangeManager } from '../ChangeManager'
14
+ import { ApolloSessionModel } from '../session'
15
+ import { Dialog } from './Dialog'
16
+
17
+ interface DeleteFeatureProps {
18
+ session: ApolloSessionModel
19
+ handleClose(): void
20
+ sourceFeature: AnnotationFeatureI
21
+ sourceAssemblyId: string
22
+ changeManager: ChangeManager
23
+ selectedFeature?: AnnotationFeatureI
24
+ setSelectedFeature(feature?: AnnotationFeatureI): void
25
+ }
26
+
27
+ export function DeleteFeature({
28
+ changeManager,
29
+ handleClose,
30
+ selectedFeature,
31
+ session,
32
+ setSelectedFeature,
33
+ sourceAssemblyId,
34
+ sourceFeature,
35
+ }: DeleteFeatureProps) {
36
+ const { notify } = session as unknown as AbstractSessionModel
37
+ const [errorMessage, setErrorMessage] = useState('')
38
+
39
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
40
+ event.preventDefault()
41
+ setErrorMessage('')
42
+ if (selectedFeature?._id === sourceFeature._id) {
43
+ setSelectedFeature()
44
+ }
45
+
46
+ // Delete features
47
+ const change = new DeleteFeatureChange({
48
+ changedIds: [sourceFeature._id],
49
+ typeName: 'DeleteFeatureChange',
50
+ assembly: sourceAssemblyId,
51
+ deletedFeature: getSnapshot(sourceFeature),
52
+ parentFeatureId: sourceFeature.parent?._id,
53
+ })
54
+ await changeManager.submit?.(change)
55
+ notify('Feature deleted successfully', 'success')
56
+ handleClose()
57
+ event.preventDefault()
58
+ }
59
+
60
+ return (
61
+ <Dialog
62
+ open
63
+ title="Delete feature"
64
+ handleClose={handleClose}
65
+ maxWidth={false}
66
+ data-testid="delete-feature"
67
+ >
68
+ <form onSubmit={onSubmit}>
69
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
70
+ <DialogContentText>
71
+ Are you sure you want to delete the selected feature?
72
+ </DialogContentText>
73
+ </DialogContent>
74
+ <DialogActions>
75
+ <Button variant="contained" type="submit">
76
+ Yes
77
+ </Button>
78
+ <Button variant="outlined" type="submit" onClick={handleClose}>
79
+ Cancel
80
+ </Button>
81
+ </DialogActions>
82
+ </form>
83
+ {errorMessage ? (
84
+ <DialogContent>
85
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
86
+ </DialogContent>
87
+ ) : null}
88
+ </Dialog>
89
+ )
90
+ }
@@ -0,0 +1,47 @@
1
+ import { Dialog as JBDialog } from '@jbrowse/core/ui'
2
+ import CloseIcon from '@mui/icons-material/Close'
3
+ import { DialogProps, DialogTitle, IconButton } from '@mui/material'
4
+ import { observer } from 'mobx-react'
5
+ import React from 'react'
6
+ import { makeStyles } from 'tss-react/mui'
7
+
8
+ const useStyles = makeStyles()((theme) => ({
9
+ dialogTitle: {
10
+ background: theme.palette.primary.main,
11
+ color: theme.palette.primary.contrastText,
12
+ padding: theme.spacing(2),
13
+ },
14
+ closeButton: {
15
+ position: 'absolute',
16
+ right: theme.spacing(1),
17
+ top: theme.spacing(1.5),
18
+ color: theme.palette.primary.contrastText,
19
+ },
20
+ }))
21
+
22
+ interface Props extends DialogProps {
23
+ handleClose(): void
24
+ }
25
+
26
+ export const Dialog = observer(function JBrowseDialog(props: Props) {
27
+ const { classes } = useStyles()
28
+ const { handleClose, title, ...other } = props
29
+
30
+ return (
31
+ <JBDialog
32
+ {...other}
33
+ header={
34
+ <>
35
+ <DialogTitle className={classes.dialogTitle}>{title}</DialogTitle>
36
+ <IconButton
37
+ aria-label="close"
38
+ onClick={handleClose}
39
+ className={classes.closeButton}
40
+ >
41
+ <CloseIcon />
42
+ </IconButton>
43
+ </>
44
+ }
45
+ />
46
+ )
47
+ })