@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,119 @@
1
+ import { MenuItem } from '@jbrowse/core/ui'
2
+ import { AbstractSessionModel } from '@jbrowse/core/util'
3
+ import { AnnotationFeatureI } from 'apollo-mst'
4
+
5
+ import { ChangeManager } from '../../ChangeManager'
6
+ import {
7
+ AddChildFeature,
8
+ CopyFeature,
9
+ DeleteFeature,
10
+ ModifyFeatureAttribute,
11
+ } from '../../components'
12
+ import { ApolloSessionModel } from '../../session'
13
+ import { getApolloInternetAccount } from '../../util'
14
+
15
+ export function featureContextMenuItems(
16
+ feature: AnnotationFeatureI | undefined,
17
+ region: { assemblyName: string; refName: string; start: number; end: number },
18
+ getAssemblyId: (assemblyName: string) => string,
19
+ selectedFeature: AnnotationFeatureI | undefined,
20
+ setSelectedFeature: (f: AnnotationFeatureI | undefined) => void,
21
+ session: ApolloSessionModel,
22
+ changeManager: ChangeManager,
23
+ ) {
24
+ const internetAccount = getApolloInternetAccount(session)
25
+ const role = internetAccount ? internetAccount.role : 'admin'
26
+ const admin = role === 'admin'
27
+ const readOnly = !(role && ['admin', 'user'].includes(role))
28
+ const menuItems: MenuItem[] = []
29
+ if (feature) {
30
+ const sourceAssemblyId = getAssemblyId(region.assemblyName)
31
+ const currentAssemblyId = getAssemblyId(region.assemblyName)
32
+ menuItems.push(
33
+ {
34
+ label: 'Add child feature',
35
+ disabled: readOnly,
36
+ onClick: () => {
37
+ ;(session as unknown as AbstractSessionModel).queueDialog(
38
+ (doneCallback) => [
39
+ AddChildFeature,
40
+ {
41
+ session,
42
+ handleClose: () => {
43
+ doneCallback()
44
+ },
45
+ changeManager,
46
+ sourceFeature: feature,
47
+ sourceAssemblyId,
48
+ internetAccount,
49
+ },
50
+ ],
51
+ )
52
+ },
53
+ },
54
+ {
55
+ label: 'Copy features and annotations',
56
+ disabled: readOnly,
57
+ onClick: () => {
58
+ ;(session as unknown as AbstractSessionModel).queueDialog(
59
+ (doneCallback) => [
60
+ CopyFeature,
61
+ {
62
+ session,
63
+ handleClose: () => {
64
+ doneCallback()
65
+ },
66
+ changeManager,
67
+ sourceFeature: feature,
68
+ sourceAssemblyId: currentAssemblyId,
69
+ },
70
+ ],
71
+ )
72
+ },
73
+ },
74
+ {
75
+ label: 'Delete feature',
76
+ disabled: !admin,
77
+ onClick: () => {
78
+ ;(session as unknown as AbstractSessionModel).queueDialog(
79
+ (doneCallback) => [
80
+ DeleteFeature,
81
+ {
82
+ session,
83
+ handleClose: () => {
84
+ doneCallback()
85
+ },
86
+ changeManager,
87
+ sourceFeature: feature,
88
+ sourceAssemblyId: currentAssemblyId,
89
+ selectedFeature,
90
+ setSelectedFeature,
91
+ },
92
+ ],
93
+ )
94
+ },
95
+ },
96
+ {
97
+ label: 'Edit attributes',
98
+ disabled: readOnly,
99
+ onClick: () => {
100
+ ;(session as unknown as AbstractSessionModel).queueDialog(
101
+ (doneCallback) => [
102
+ ModifyFeatureAttribute,
103
+ {
104
+ session,
105
+ handleClose: () => {
106
+ doneCallback()
107
+ },
108
+ changeManager,
109
+ sourceFeature: feature,
110
+ sourceAssemblyId: currentAssemblyId,
111
+ },
112
+ ],
113
+ )
114
+ },
115
+ },
116
+ )
117
+ }
118
+ return menuItems
119
+ }
@@ -0,0 +1 @@
1
+ export { default } from './HybridGrid'
@@ -0,0 +1,34 @@
1
+ import { observer } from 'mobx-react'
2
+ import React from 'react'
3
+
4
+ import HybridGrid from './HybridGrid'
5
+ import { ToolBar } from './HybridGrid/ToolBar'
6
+ import { DisplayStateModel } from './types'
7
+
8
+ function stopPropagation(e: React.MouseEvent) {
9
+ e.stopPropagation()
10
+ }
11
+
12
+ export const TabularEditorPane = observer(function TabularEditorPane({
13
+ model: displayState,
14
+ }: {
15
+ model: DisplayStateModel
16
+ }) {
17
+ const model = displayState.tabularEditor
18
+ if (!model.isShown) {
19
+ return null
20
+ }
21
+ return (
22
+ // TODO: a11y
23
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
24
+ <div
25
+ onMouseDown={stopPropagation}
26
+ onClick={stopPropagation}
27
+ style={{ width: '100%', height: '100%', position: 'relative' }}
28
+ >
29
+ <ToolBar model={displayState} />
30
+ <HybridGrid model={displayState} />
31
+ {/* <DataGrid model={model} /> */}
32
+ </div>
33
+ )
34
+ })
@@ -0,0 +1,3 @@
1
+ export { TabularEditorPane } from './TabularEditorPane'
2
+
3
+ export { TabularEditorStateModelType } from './model'
@@ -0,0 +1,44 @@
1
+ import { Instance, getParent, types } from 'mobx-state-tree'
2
+
3
+ import { DisplayStateModel } from './types'
4
+
5
+ export const TabularEditorStateModelType = types
6
+ .model('TabularEditor', {
7
+ isShown: true,
8
+ featureCollapsed: types.map(types.boolean),
9
+ filterText: '',
10
+ })
11
+ .actions((self) => ({
12
+ setFeatureCollapsed(id: string, state: boolean) {
13
+ self.featureCollapsed.set(id, state)
14
+ },
15
+ setFilterText(text: string) {
16
+ self.filterText = text
17
+ },
18
+ clearFilterText() {
19
+ self.filterText = ''
20
+ },
21
+ collapseAllFeatures() {
22
+ // iterate over all seen features and set them to collapsed
23
+ const display = getParent<DisplayStateModel>(self)
24
+ for (const [featureId] of display.seenFeatures.entries()) {
25
+ self.featureCollapsed.set(featureId, true)
26
+ }
27
+ },
28
+ togglePane() {
29
+ self.isShown = !self.isShown
30
+ },
31
+ hidePane() {
32
+ self.isShown = false
33
+ },
34
+ showPane() {
35
+ self.isShown = true
36
+ },
37
+ // onPatch(patch: any) {
38
+ // console.log(patch)
39
+ // },
40
+ }))
41
+
42
+ export type TabularEditorStateModel = Instance<
43
+ typeof TabularEditorStateModelType
44
+ >
@@ -0,0 +1,3 @@
1
+ import { LinearApolloDisplay } from '../LinearApolloDisplay/stateModel'
2
+
3
+ export type DisplayStateModel = LinearApolloDisplay
@@ -0,0 +1,464 @@
1
+ import { readConfObject } from '@jbrowse/core/configuration'
2
+ import { AbstractSessionModel } from '@jbrowse/core/util'
3
+ import LinkIcon from '@mui/icons-material/Link'
4
+ import {
5
+ Box,
6
+ Button,
7
+ Checkbox,
8
+ DialogActions,
9
+ DialogContent,
10
+ DialogContentText,
11
+ FormControl,
12
+ FormControlLabel,
13
+ FormGroup,
14
+ FormLabel,
15
+ MenuItem,
16
+ Radio,
17
+ RadioGroup,
18
+ Select,
19
+ SelectChangeEvent,
20
+ TextField,
21
+ Typography,
22
+ } from '@mui/material'
23
+ import InputAdornment from '@mui/material/InputAdornment'
24
+ import LinearProgress from '@mui/material/LinearProgress'
25
+ import {
26
+ AddAssemblyAndFeaturesFromFileChange,
27
+ AddAssemblyFromExternalChange,
28
+ AddAssemblyFromFileChange,
29
+ } from 'apollo-shared'
30
+ import ObjectID from 'bson-objectid'
31
+ import { getRoot } from 'mobx-state-tree'
32
+ import React, { useState } from 'react'
33
+
34
+ import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
35
+ import { ChangeManager } from '../ChangeManager'
36
+ import { ApolloSessionModel } from '../session'
37
+ import { ApolloRootModel } from '../types'
38
+ import { createFetchErrorMessage } from '../util'
39
+ import { Dialog } from './Dialog'
40
+
41
+ interface AddAssemblyProps {
42
+ session: ApolloSessionModel
43
+ handleClose(): void
44
+ changeManager: ChangeManager
45
+ }
46
+
47
+ enum FileType {
48
+ GFF3 = 'text/x-gff3',
49
+ FASTA = 'text/x-fasta',
50
+ EXTERNAL = 'text/x-external',
51
+ }
52
+
53
+ export function AddAssembly({
54
+ changeManager,
55
+ handleClose,
56
+ session,
57
+ }: AddAssemblyProps) {
58
+ const { internetAccounts } = getRoot<ApolloRootModel>(session)
59
+ const { notify } = session as unknown as AbstractSessionModel
60
+ const apolloInternetAccounts = internetAccounts.filter(
61
+ (ia) => ia.type === 'ApolloInternetAccount',
62
+ ) as ApolloInternetAccountModel[]
63
+ if (apolloInternetAccounts.length === 0) {
64
+ throw new Error('No Apollo internet account found')
65
+ }
66
+ const [assemblyName, setAssemblyName] = useState('')
67
+ const [errorMessage, setErrorMessage] = useState('')
68
+ const [validAsm, setValidAsm] = useState(false)
69
+ const [file, setFile] = useState<File | null>(null)
70
+ const [fileType, setFileType] = useState(FileType.GFF3)
71
+ const [importFeatures, setImportFeatures] = useState(true)
72
+ const [submitted, setSubmitted] = useState(false)
73
+ const [selectedInternetAccount, setSelectedInternetAccount] = useState(
74
+ apolloInternetAccounts[0],
75
+ )
76
+ const [fastaFile, setFastaFile] = useState('')
77
+ const [fastaIndexFile, setFastaIndexFile] = useState('')
78
+ const [fastaGziIndexFile, setFastaGziIndexFile] = useState('')
79
+ const [loading, setLoading] = useState(false)
80
+
81
+ function handleChangeInternetAccount(e: SelectChangeEvent<string>) {
82
+ setSubmitted(false)
83
+ const newlySelectedInternetAccount = apolloInternetAccounts.find(
84
+ (ia) => ia.internetAccountId === e.target.value,
85
+ )
86
+ if (!newlySelectedInternetAccount) {
87
+ throw new Error(
88
+ `Could not find internetAccount with ID "${e.target.value}"`,
89
+ )
90
+ }
91
+ setSelectedInternetAccount(newlySelectedInternetAccount)
92
+ }
93
+
94
+ function handleChangeFile(e: React.ChangeEvent<HTMLInputElement>) {
95
+ if (!e.target.files) {
96
+ return
97
+ }
98
+ const selectedFile = e.target.files.item(0)
99
+ setFile(selectedFile)
100
+ if (
101
+ selectedFile?.name.toLowerCase().endsWith('.fasta') ??
102
+ selectedFile?.name.toLowerCase().endsWith('.fna') ??
103
+ selectedFile?.name.toLowerCase().endsWith('.fa')
104
+ ) {
105
+ setFileType(FileType.FASTA)
106
+ } else if (
107
+ selectedFile?.name.toLowerCase().endsWith('.gff3') ??
108
+ selectedFile?.name.toLowerCase().endsWith('.gff')
109
+ ) {
110
+ setFileType(FileType.GFF3)
111
+ }
112
+ }
113
+
114
+ function handleChangeFileType(e: React.ChangeEvent<HTMLInputElement>) {
115
+ setFileType(e.target.value as FileType)
116
+ setImportFeatures(e.target.value === FileType.GFF3)
117
+ setFastaFile('')
118
+ setFastaIndexFile('')
119
+ setFile(null)
120
+ }
121
+
122
+ function checkAssemblyName(assembly: string) {
123
+ const { assemblies } = session as unknown as AbstractSessionModel
124
+ const checkAsm = assemblies.find(
125
+ (asm) => readConfObject(asm, 'displayName') === assembly,
126
+ )
127
+ if (checkAsm) {
128
+ setValidAsm(false)
129
+ setErrorMessage(`Assembly ${assembly} already exists.`)
130
+ } else {
131
+ setValidAsm(true)
132
+ setErrorMessage('')
133
+ }
134
+ }
135
+
136
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
137
+ event.preventDefault()
138
+ setErrorMessage('')
139
+ setSubmitted(true)
140
+ setLoading(true)
141
+
142
+ notify(`Assembly "${assemblyName}" is being added`, 'info')
143
+ handleClose()
144
+ event.preventDefault()
145
+
146
+ const { jobsManager } = session
147
+ const controller = new AbortController()
148
+
149
+ const job = {
150
+ name: `UploadAssemblyFile for ${assemblyName}`,
151
+ statusMessage: 'Pre-validating',
152
+ progressPct: 0,
153
+ cancelCallback: () => {
154
+ controller.abort()
155
+ jobsManager.abortJob(job.name)
156
+ },
157
+ }
158
+
159
+ jobsManager.runJob(job)
160
+
161
+ let fileId = ''
162
+ const { baseURL, getFetcher, internetAccountId } = selectedInternetAccount
163
+ if (fileType !== FileType.EXTERNAL && file) {
164
+ // First upload file
165
+ const url = new URL('/files', baseURL).href
166
+ const formData = new FormData()
167
+ formData.append('file', file)
168
+ formData.append('fileName', file.name)
169
+ formData.append('type', fileType)
170
+ const apolloFetchFile = getFetcher({
171
+ locationType: 'UriLocation',
172
+ uri: url,
173
+ })
174
+ if (apolloFetchFile) {
175
+ jobsManager.update(job.name, 'Uploading file, this may take awhile')
176
+ const { signal } = controller
177
+ const response = await apolloFetchFile(url, {
178
+ method: 'POST',
179
+ body: formData,
180
+ signal,
181
+ })
182
+ if (!response.ok) {
183
+ const newErrorMessage = await createFetchErrorMessage(
184
+ response,
185
+ 'Error when inserting new assembly (while uploading file)',
186
+ )
187
+ jobsManager.abortJob(job.name, newErrorMessage)
188
+ setErrorMessage(newErrorMessage)
189
+ return
190
+ }
191
+ const result = await response.json()
192
+ fileId = result._id
193
+ }
194
+ }
195
+
196
+ let change:
197
+ | AddAssemblyFromExternalChange
198
+ | AddAssemblyAndFeaturesFromFileChange
199
+ | AddAssemblyFromFileChange
200
+ if (fileType === FileType.EXTERNAL) {
201
+ change = new AddAssemblyFromExternalChange({
202
+ typeName: 'AddAssemblyFromExternalChange',
203
+
204
+ assembly: new ObjectID().toHexString(),
205
+ assemblyName,
206
+ externalLocation: {
207
+ fa: fastaFile,
208
+ fai: fastaIndexFile,
209
+ ...(fastaGziIndexFile ? { gzi: fastaGziIndexFile } : {}),
210
+ },
211
+ })
212
+ } else {
213
+ const fileUploadChangeBase = {
214
+ assembly: new ObjectID().toHexString(),
215
+ assemblyName,
216
+ fileId,
217
+ }
218
+ change =
219
+ fileType === FileType.GFF3 && importFeatures
220
+ ? new AddAssemblyAndFeaturesFromFileChange({
221
+ typeName: 'AddAssemblyAndFeaturesFromFileChange',
222
+ ...fileUploadChangeBase,
223
+ })
224
+ : new AddAssemblyFromFileChange({
225
+ typeName: 'AddAssemblyFromFileChange',
226
+ ...fileUploadChangeBase,
227
+ })
228
+ }
229
+
230
+ jobsManager.done(job)
231
+
232
+ await changeManager.submit(change, {
233
+ internetAccountId,
234
+ updateJobsManager: true,
235
+ })
236
+
237
+ setSubmitted(false)
238
+ setLoading(false)
239
+ }
240
+
241
+ let validFastaFile = false
242
+ try {
243
+ const url = new URL(fastaFile)
244
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
245
+ validFastaFile = true
246
+ }
247
+ } catch {
248
+ // pass
249
+ }
250
+ let validFastaIndexFile = false
251
+ try {
252
+ const url = new URL(fastaIndexFile)
253
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
254
+ validFastaIndexFile = true
255
+ }
256
+ } catch {
257
+ // pass
258
+ }
259
+ let validFastaGziIndexFile = false
260
+ try {
261
+ const url = new URL(fastaGziIndexFile)
262
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
263
+ validFastaGziIndexFile = true
264
+ }
265
+ } catch {
266
+ // pass
267
+ }
268
+
269
+ return (
270
+ <Dialog
271
+ open
272
+ maxWidth={false}
273
+ data-testid="add-assembly-dialog"
274
+ title="Add new assembly"
275
+ handleClose={handleClose}
276
+ >
277
+ {loading ? <LinearProgress /> : null}
278
+ <form onSubmit={onSubmit}>
279
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
280
+ {apolloInternetAccounts.length > 1 ? (
281
+ <>
282
+ <DialogContentText>Select account</DialogContentText>
283
+ <Select
284
+ value={selectedInternetAccount.internetAccountId}
285
+ onChange={handleChangeInternetAccount}
286
+ disabled={submitted && !errorMessage}
287
+ >
288
+ {internetAccounts.map((option) => (
289
+ <MenuItem key={option.id} value={option.internetAccountId}>
290
+ {option.name}
291
+ </MenuItem>
292
+ ))}
293
+ </Select>
294
+ </>
295
+ ) : null}
296
+ <TextField
297
+ margin="dense"
298
+ id="name"
299
+ label="Assembly name"
300
+ type="TextField"
301
+ fullWidth
302
+ variant="outlined"
303
+ onChange={(e) => {
304
+ setSubmitted(false)
305
+ setAssemblyName(e.target.value)
306
+ checkAssemblyName(e.target.value)
307
+ }}
308
+ disabled={submitted && !errorMessage}
309
+ />
310
+ <FormControl style={{ marginTop: 20 }}>
311
+ <FormLabel>Select GFF3, FASTA or EXTERNAL option</FormLabel>
312
+ <RadioGroup
313
+ aria-labelledby="demo-radio-buttons-group-label"
314
+ defaultValue={FileType.GFF3}
315
+ name="radio-buttons-group"
316
+ onChange={handleChangeFileType}
317
+ value={fileType}
318
+ >
319
+ <FormControlLabel
320
+ value={FileType.GFF3}
321
+ control={<Radio />}
322
+ label="GFF3"
323
+ disabled={submitted && !errorMessage}
324
+ />
325
+ <FormControlLabel
326
+ value={FileType.FASTA}
327
+ control={<Radio />}
328
+ label="FASTA"
329
+ disabled={submitted && !errorMessage}
330
+ />
331
+ <FormControlLabel
332
+ value={FileType.EXTERNAL}
333
+ control={<Radio />}
334
+ label="External"
335
+ disabled={submitted && !errorMessage}
336
+ />
337
+ </RadioGroup>
338
+ </FormControl>
339
+ {fileType === FileType.EXTERNAL ? (
340
+ <Box style={{ marginTop: 20 }}>
341
+ <Typography variant="caption">
342
+ Enter FASTA and FASTA index(es) URL
343
+ </Typography>
344
+ <TextField
345
+ margin="dense"
346
+ helperText="Can be bgz-compressed"
347
+ id="fasta"
348
+ label="FASTA"
349
+ type="TextField"
350
+ fullWidth
351
+ variant="outlined"
352
+ error={!validFastaFile}
353
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
354
+ setFastaFile(e.target.value)
355
+ }
356
+ disabled={submitted && !errorMessage}
357
+ InputProps={{
358
+ startAdornment: (
359
+ <InputAdornment position="start">
360
+ <LinkIcon />
361
+ </InputAdornment>
362
+ ),
363
+ }}
364
+ />
365
+ <TextField
366
+ margin="dense"
367
+ id="fasta-index"
368
+ label="FASTA Index"
369
+ helperText=".fai or .gz.fai"
370
+ type="TextField"
371
+ fullWidth
372
+ variant="outlined"
373
+ error={!validFastaIndexFile}
374
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
375
+ setFastaIndexFile(e.target.value)
376
+ }
377
+ disabled={submitted && !errorMessage}
378
+ InputProps={{
379
+ startAdornment: (
380
+ <InputAdornment position="start">
381
+ <LinkIcon />
382
+ </InputAdornment>
383
+ ),
384
+ }}
385
+ />
386
+ <TextField
387
+ margin="dense"
388
+ id="fasta-gzi-index"
389
+ label="FASTA GZI Index"
390
+ helperText="Only for bgz-compressed FASTA, .gz.gzi"
391
+ type="TextField"
392
+ fullWidth
393
+ variant="outlined"
394
+ error={Boolean(fastaGziIndexFile) && !validFastaGziIndexFile}
395
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
396
+ setFastaGziIndexFile(e.target.value)
397
+ }
398
+ disabled={submitted && !errorMessage}
399
+ InputProps={{
400
+ startAdornment: (
401
+ <InputAdornment position="start">
402
+ <LinkIcon />
403
+ </InputAdornment>
404
+ ),
405
+ }}
406
+ />
407
+ </Box>
408
+ ) : (
409
+ <Box style={{ marginTop: 20 }}>
410
+ <input
411
+ type="file"
412
+ onChange={handleChangeFile}
413
+ disabled={submitted && !errorMessage}
414
+ />
415
+ <FormGroup>
416
+ <FormControlLabel
417
+ control={
418
+ <Checkbox
419
+ checked={fileType === FileType.GFF3 && importFeatures}
420
+ onChange={() => setImportFeatures(!importFeatures)}
421
+ disabled={
422
+ fileType !== FileType.GFF3 ||
423
+ (submitted && !errorMessage)
424
+ }
425
+ />
426
+ }
427
+ label="Also load features from GFF3 file"
428
+ />
429
+ </FormGroup>
430
+ </Box>
431
+ )}
432
+ </DialogContent>
433
+ <DialogActions>
434
+ <Button
435
+ disabled={
436
+ !validAsm ||
437
+ !(
438
+ (assemblyName && file) ??
439
+ (assemblyName &&
440
+ fastaFile &&
441
+ fastaIndexFile &&
442
+ validFastaFile &&
443
+ validFastaIndexFile)
444
+ ) ||
445
+ submitted
446
+ }
447
+ variant="contained"
448
+ type="submit"
449
+ >
450
+ {submitted ? 'Submitting...' : 'Submit'}
451
+ </Button>
452
+ <Button variant="outlined" type="submit" onClick={handleClose}>
453
+ Cancel
454
+ </Button>
455
+ </DialogActions>
456
+ </form>
457
+ {errorMessage ? (
458
+ <DialogContent>
459
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
460
+ </DialogContent>
461
+ ) : null}
462
+ </Dialog>
463
+ )
464
+ }