@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,213 @@
1
+ import gff, { GFF3Item } from '@gmod/gff'
2
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
3
+ import { getConf } from '@jbrowse/core/configuration'
4
+ import {
5
+ Button,
6
+ DialogActions,
7
+ DialogContent,
8
+ DialogContentText,
9
+ MenuItem,
10
+ Select,
11
+ SelectChangeEvent,
12
+ } from '@mui/material'
13
+ import { ApolloAssembly } from 'apollo-mst'
14
+ import { makeGFF3Feature } from 'apollo-shared'
15
+ import { saveAs } from 'file-saver'
16
+ import { IMSTMap, getSnapshot } from 'mobx-state-tree'
17
+ import React, { useState } from 'react'
18
+
19
+ import {
20
+ ApolloInternetAccount,
21
+ CollaborationServerDriver,
22
+ InMemoryFileDriver,
23
+ } from '../BackendDrivers'
24
+ import { ApolloSessionModel } from '../session'
25
+ import { createFetchErrorMessage } from '../util'
26
+ import { Dialog } from './Dialog'
27
+
28
+ interface DownloadGFF3Props {
29
+ session: ApolloSessionModel
30
+ handleClose(): void
31
+ }
32
+
33
+ export function DownloadGFF3({ handleClose, session }: DownloadGFF3Props) {
34
+ const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
35
+ const [errorMessage, setErrorMessage] = useState('')
36
+
37
+ const { collaborationServerDriver, getInternetAccount, inMemoryFileDriver } =
38
+ session.apolloDataStore as {
39
+ collaborationServerDriver: CollaborationServerDriver
40
+ inMemoryFileDriver: InMemoryFileDriver
41
+ getInternetAccount(
42
+ assemblyName?: string,
43
+ internetAccountId?: string,
44
+ ): ApolloInternetAccount
45
+ }
46
+ const assemblies = [
47
+ ...collaborationServerDriver.getAssemblies(),
48
+ ...inMemoryFileDriver.getAssemblies(),
49
+ ]
50
+
51
+ function handleChangeAssembly(e: SelectChangeEvent<string>) {
52
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
53
+ setSelectedAssembly(newAssembly)
54
+ }
55
+
56
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
57
+ event.preventDefault()
58
+ setErrorMessage('')
59
+ if (!selectedAssembly) {
60
+ setErrorMessage('Must select assembly to download')
61
+ return
62
+ }
63
+
64
+ const { internetAccountConfigId } = getConf(selectedAssembly, [
65
+ 'sequence',
66
+ 'metadata',
67
+ ]) as { internetAccountConfigId?: string }
68
+ if (internetAccountConfigId) {
69
+ await exportFromCollaborationServer(internetAccountConfigId)
70
+ } else {
71
+ exportFromMemory(session)
72
+ }
73
+ handleClose()
74
+ }
75
+
76
+ async function exportFromCollaborationServer(
77
+ internetAccountConfigId: string,
78
+ ) {
79
+ if (!selectedAssembly) {
80
+ setErrorMessage('Must select assembly to download')
81
+ return
82
+ }
83
+ const internetAccount = getInternetAccount(
84
+ selectedAssembly.configuration.name,
85
+ internetAccountConfigId,
86
+ )
87
+ const url = new URL('export/getID', internetAccount.baseURL)
88
+ const searchParams = new URLSearchParams({
89
+ assembly: selectedAssembly.name,
90
+ })
91
+ url.search = searchParams.toString()
92
+ const uri = url.toString()
93
+ const apolloFetch = internetAccount.getFetcher({
94
+ locationType: 'UriLocation',
95
+ uri,
96
+ })
97
+ const response = await apolloFetch(uri, { method: 'GET' })
98
+ if (!response.ok) {
99
+ const newErrorMessage = await createFetchErrorMessage(
100
+ response,
101
+ 'Error when exporting ID',
102
+ )
103
+ setErrorMessage(newErrorMessage)
104
+ return
105
+ }
106
+ const { exportID } = (await response.json()) as { exportID: string }
107
+
108
+ const exportURL = new URL('export', internetAccount.baseURL)
109
+ const exportSearchParams = new URLSearchParams({ exportID })
110
+ exportURL.search = exportSearchParams.toString()
111
+ const exportUri = exportURL.toString()
112
+
113
+ window.open(exportUri, '_blank')
114
+ }
115
+
116
+ function exportFromMemory(session: ApolloSessionModel) {
117
+ if (!selectedAssembly) {
118
+ setErrorMessage('Must select assembly to download')
119
+ return
120
+ }
121
+ const { assemblies } = session.apolloDataStore as {
122
+ assemblies: IMSTMap<typeof ApolloAssembly>
123
+ }
124
+ const assembly = assemblies.get(selectedAssembly.name)
125
+ const refSeqs = assembly?.refSeqs
126
+ if (!refSeqs) {
127
+ setErrorMessage(
128
+ `No refSeqs found for assembly "${selectedAssembly.name}"`,
129
+ )
130
+ return
131
+ }
132
+ const gff3Items: GFF3Item[] = [{ directive: 'gff-version', value: '3' }]
133
+ const sequenceFeatures = getConf(selectedAssembly, [
134
+ 'sequence',
135
+ 'adapter',
136
+ 'features',
137
+ ]) as { refName: string; start: number; end: number; seq: string }[]
138
+ for (const sequenceFeature of sequenceFeatures) {
139
+ const { end, refName, start } = sequenceFeature
140
+ gff3Items.push({
141
+ directive: 'sequence-region',
142
+ value: `${refName} ${start + 1} ${end}`,
143
+ })
144
+ }
145
+ for (const [, refSeq] of refSeqs) {
146
+ const features = refSeq?.features
147
+ if (!features) {
148
+ continue
149
+ }
150
+ for (const [, feature] of features) {
151
+ gff3Items.push(makeGFF3Feature(getSnapshot(feature)))
152
+ }
153
+ }
154
+ for (const sequenceFeature of sequenceFeatures) {
155
+ const { refName, seq } = sequenceFeature
156
+ gff3Items.push({ id: refName, description: '', sequence: seq })
157
+ }
158
+ const gff3 = gff.formatSync(gff3Items)
159
+ const gff3Blob = new Blob([gff3], { type: 'text/plain;charset=utf-8' })
160
+ saveAs(
161
+ gff3Blob,
162
+ `${selectedAssembly.displayName ?? selectedAssembly.name}.gff3`,
163
+ )
164
+ }
165
+
166
+ return (
167
+ <Dialog
168
+ open
169
+ title="Export GFF3"
170
+ handleClose={handleClose}
171
+ maxWidth={false}
172
+ data-testid="download-gff3"
173
+ >
174
+ <form onSubmit={onSubmit}>
175
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
176
+ <DialogContentText>Select assembly</DialogContentText>
177
+ <Select
178
+ labelId="label"
179
+ value={selectedAssembly?.name ?? ''}
180
+ onChange={handleChangeAssembly}
181
+ disabled={assemblies.length === 0}
182
+ >
183
+ {assemblies.map((option) => (
184
+ <MenuItem key={option.name} value={option.name}>
185
+ {option.displayName ?? option.name}
186
+ </MenuItem>
187
+ ))}
188
+ </Select>
189
+ <DialogContentText>
190
+ Select assembly to export to GFF3
191
+ </DialogContentText>
192
+ </DialogContent>
193
+ <DialogActions>
194
+ <Button
195
+ disabled={!selectedAssembly}
196
+ variant="contained"
197
+ type="submit"
198
+ >
199
+ Download
200
+ </Button>
201
+ <Button variant="outlined" type="submit" onClick={handleClose}>
202
+ Cancel
203
+ </Button>
204
+ </DialogActions>
205
+ </form>
206
+ {errorMessage ? (
207
+ <DialogContent>
208
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
209
+ </DialogContent>
210
+ ) : null}
211
+ </Dialog>
212
+ )
213
+ }
@@ -0,0 +1,295 @@
1
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
2
+ import { getConf } from '@jbrowse/core/configuration'
3
+ import {
4
+ Button,
5
+ DialogActions,
6
+ DialogContent,
7
+ DialogContentText,
8
+ MenuItem,
9
+ Select,
10
+ SelectChangeEvent,
11
+ } from '@mui/material'
12
+ import Checkbox from '@mui/material/Checkbox'
13
+ import FormControlLabel from '@mui/material/FormControlLabel'
14
+ import LinearProgress from '@mui/material/LinearProgress'
15
+ import { AddFeaturesFromFileChange } from 'apollo-shared'
16
+ import React, { useEffect, useState } from 'react'
17
+
18
+ import {
19
+ ApolloInternetAccount,
20
+ CollaborationServerDriver,
21
+ } from '../BackendDrivers'
22
+ import { ChangeManager } from '../ChangeManager'
23
+ import { ApolloSessionModel } from '../session'
24
+ import { createFetchErrorMessage } from '../util'
25
+ import { Dialog } from './Dialog'
26
+
27
+ interface ImportFeaturesProps {
28
+ session: ApolloSessionModel
29
+ handleClose(): void
30
+ changeManager: ChangeManager
31
+ }
32
+
33
+ export function ImportFeatures({
34
+ changeManager,
35
+ handleClose,
36
+ session,
37
+ }: ImportFeaturesProps) {
38
+ const { apolloDataStore } = session
39
+
40
+ const [file, setFile] = useState<File>()
41
+ const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
42
+ const [errorMessage, setErrorMessage] = useState('')
43
+ const [submitted, setSubmitted] = useState(false)
44
+ // default is -1, submit button should be disabled until count is set
45
+ const [featuresCount, setFeaturesCount] = useState<number | undefined>()
46
+ const [deleteFeatures, setDeleteFeatures] = useState(false)
47
+ const [loading, setLoading] = useState(false)
48
+
49
+ const { collaborationServerDriver, getInternetAccount } = apolloDataStore as {
50
+ collaborationServerDriver: CollaborationServerDriver
51
+ getInternetAccount(
52
+ assemblyName?: string,
53
+ internetAccountId?: string,
54
+ ): ApolloInternetAccount
55
+ }
56
+ const assemblies = collaborationServerDriver.getAssemblies()
57
+
58
+ function handleChangeAssembly(e: SelectChangeEvent<string>) {
59
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
60
+ setSelectedAssembly(newAssembly)
61
+ setSubmitted(false)
62
+ }
63
+
64
+ function handleDeleteFeatures(e: React.ChangeEvent<HTMLInputElement>) {
65
+ setDeleteFeatures(e.target.checked)
66
+ }
67
+
68
+ // fetch and set features count for selected assembly
69
+ useEffect(() => {
70
+ if (!selectedAssembly) {
71
+ return
72
+ }
73
+ const updateFeaturesCount = async () => {
74
+ // TODO: this code will not work for running on desktop
75
+ const { internetAccountConfigId } = getConf(selectedAssembly, [
76
+ 'sequence',
77
+ 'metadata',
78
+ ]) as { internetAccountConfigId?: string }
79
+ const apolloInternetAccount = getInternetAccount(
80
+ selectedAssembly.name,
81
+ internetAccountConfigId,
82
+ )
83
+ if (!apolloInternetAccount) {
84
+ throw new Error('No Apollo internet account found')
85
+ }
86
+
87
+ const { baseURL } = apolloInternetAccount
88
+ const uri = new URL('/features/count', baseURL)
89
+ const searchParams = new URLSearchParams({
90
+ assemblyId: selectedAssembly.name,
91
+ })
92
+ uri.search = searchParams.toString()
93
+ const fetch = apolloInternetAccount?.getFetcher({
94
+ locationType: 'UriLocation',
95
+ uri: uri.toString(),
96
+ })
97
+
98
+ setLoading(true)
99
+ const response = await fetch(uri.toString(), { method: 'GET' })
100
+
101
+ if (response.ok) {
102
+ const countObj = (await response.json()) as { count: number }
103
+ setFeaturesCount(countObj.count)
104
+ } else {
105
+ throw new Error(await createFetchErrorMessage(response))
106
+ }
107
+
108
+ setLoading(false)
109
+ }
110
+
111
+ updateFeaturesCount().catch((error) => {
112
+ console.error(error)
113
+ setErrorMessage(error.message ?? error)
114
+ })
115
+ }, [getInternetAccount, session, selectedAssembly])
116
+
117
+ function handleChangeFile(e: React.ChangeEvent<HTMLInputElement>) {
118
+ setSubmitted(false)
119
+ if (!e.target.files) {
120
+ return
121
+ }
122
+ setFile(e.target.files[0])
123
+ }
124
+
125
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
126
+ event.preventDefault()
127
+ setErrorMessage('')
128
+ setLoading(true)
129
+ setSubmitted(true)
130
+
131
+ // let fileChecksum = ''
132
+ let fileId = ''
133
+
134
+ if (!file) {
135
+ setErrorMessage('must select a file')
136
+ return
137
+ }
138
+
139
+ if (!selectedAssembly) {
140
+ setErrorMessage('Must select assembly to download')
141
+ return
142
+ }
143
+
144
+ const { internetAccountConfigId } = getConf(selectedAssembly, [
145
+ 'sequence',
146
+ 'metadata',
147
+ ]) as { internetAccountConfigId?: string }
148
+ const apolloInternetAccount = getInternetAccount(
149
+ selectedAssembly.name,
150
+ internetAccountConfigId,
151
+ )
152
+ const { baseURL } = apolloInternetAccount
153
+
154
+ // First upload file
155
+ const url = new URL('/files', baseURL).href
156
+ const formData = new FormData()
157
+ formData.append('file', file)
158
+ formData.append('fileName', file.name)
159
+ formData.append('type', 'text/x-gff3')
160
+ const apolloFetchFile = apolloInternetAccount?.getFetcher({
161
+ locationType: 'UriLocation',
162
+ uri: url,
163
+ })
164
+
165
+ handleClose()
166
+
167
+ const { jobsManager } = session
168
+ const controller = new AbortController()
169
+
170
+ const job = {
171
+ name: `Importing features for ${selectedAssembly.displayName}`,
172
+ statusMessage: 'Uploading file, this may take awhile',
173
+ progressPct: 0,
174
+ cancelCallback: () => {
175
+ controller.abort()
176
+ jobsManager.abortJob(job.name)
177
+ },
178
+ }
179
+
180
+ jobsManager.runJob(job)
181
+
182
+ if (apolloFetchFile) {
183
+ const { signal } = controller
184
+ const response = await apolloFetchFile(url, {
185
+ method: 'POST',
186
+ body: formData,
187
+ signal,
188
+ })
189
+ if (!response.ok) {
190
+ const newErrorMessage = await createFetchErrorMessage(
191
+ response,
192
+ 'Error when inserting new features (while uploading file)',
193
+ )
194
+ jobsManager.abortJob(job.name, newErrorMessage)
195
+ setErrorMessage(newErrorMessage)
196
+ return
197
+ }
198
+ const result = await response.json()
199
+ // fileChecksum = result.checksum
200
+ fileId = result._id
201
+ }
202
+
203
+ // Add features
204
+ const change = new AddFeaturesFromFileChange({
205
+ typeName: 'AddFeaturesFromFileChange',
206
+ assembly: selectedAssembly.name,
207
+ fileId,
208
+ deleteExistingFeatures: deleteFeatures,
209
+ })
210
+
211
+ jobsManager.done(job)
212
+
213
+ await changeManager.submit(change, { updateJobsManager: true })
214
+ }
215
+
216
+ return (
217
+ <Dialog
218
+ open
219
+ title="Import Features from GFF3 file"
220
+ handleClose={handleClose}
221
+ maxWidth={false}
222
+ data-testid="import-features-dialog"
223
+ >
224
+ {loading ? <LinearProgress /> : null}
225
+
226
+ <form onSubmit={onSubmit}>
227
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
228
+ <DialogContentText>Select assembly</DialogContentText>
229
+ <Select
230
+ labelId="label"
231
+ value={selectedAssembly?.name ?? ''}
232
+ onChange={handleChangeAssembly}
233
+ disabled={submitted && !errorMessage}
234
+ >
235
+ {assemblies.map((option) => (
236
+ <MenuItem key={option.name} value={option.name}>
237
+ {option.displayName ?? option.name}
238
+ </MenuItem>
239
+ ))}
240
+ </Select>
241
+ </DialogContent>
242
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
243
+ <DialogContentText>Upload GFF3 to load features</DialogContentText>
244
+ <input
245
+ type="file"
246
+ onChange={handleChangeFile}
247
+ disabled={submitted && !errorMessage}
248
+ />
249
+ </DialogContent>
250
+
251
+ {featuresCount && featuresCount > 0 ? (
252
+ <DialogContent>
253
+ <DialogContentText>
254
+ This assembly already has {featuresCount} features, would you like
255
+ to delete the existing features before importing new ones?
256
+ </DialogContentText>
257
+ <FormControlLabel
258
+ label="Yes, delete existing features"
259
+ disabled={submitted && !errorMessage}
260
+ control={
261
+ <Checkbox
262
+ checked={deleteFeatures}
263
+ onChange={handleDeleteFeatures}
264
+ inputProps={{ 'aria-label': 'controlled' }}
265
+ color="warning"
266
+ />
267
+ }
268
+ />
269
+ </DialogContent>
270
+ ) : null}
271
+
272
+ <DialogActions>
273
+ <Button
274
+ disabled={
275
+ !(selectedAssembly && file && featuresCount !== undefined) ||
276
+ submitted
277
+ }
278
+ variant="contained"
279
+ type="submit"
280
+ >
281
+ {submitted ? 'Submitting...' : 'Submit'}
282
+ </Button>
283
+ <Button variant="outlined" type="submit" onClick={handleClose}>
284
+ Close
285
+ </Button>
286
+ </DialogActions>
287
+ </form>
288
+ {errorMessage ? (
289
+ <DialogContent>
290
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
291
+ </DialogContent>
292
+ ) : null}
293
+ </Dialog>
294
+ )
295
+ }