@apollo-annotation/jbrowse-plugin-apollo 0.1.18 → 0.1.20

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 (85) hide show
  1. package/dist/index.esm.js +3189 -3575
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +3185 -3570
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +14884 -15905
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +33 -33
  12. package/src/ApolloInternetAccount/addMenuItems.ts +18 -0
  13. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
  14. package/src/ApolloInternetAccount/configSchema.ts +5 -2
  15. package/src/ApolloInternetAccount/model.ts +14 -5
  16. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +94 -0
  17. package/src/ApolloRefNameAliasAdapter/configSchema.ts +12 -0
  18. package/src/ApolloRefNameAliasAdapter/index.ts +21 -0
  19. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +1 -0
  20. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +10 -10
  21. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +35 -32
  22. package/src/BackendDrivers/BackendDriver.ts +8 -0
  23. package/src/BackendDrivers/CollaborationServerDriver.ts +49 -1
  24. package/src/BackendDrivers/DesktopFileDriver.ts +14 -1
  25. package/src/BackendDrivers/InMemoryFileDriver.ts +17 -1
  26. package/src/ChangeManager.ts +1 -1
  27. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +5 -25
  28. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +82 -0
  29. package/src/FeatureDetailsWidget/Attributes.tsx +11 -3
  30. package/src/FeatureDetailsWidget/BasicInformation.tsx +38 -30
  31. package/src/FeatureDetailsWidget/Sequence.tsx +7 -7
  32. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +446 -0
  33. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +365 -0
  34. package/src/FeatureDetailsWidget/index.ts +2 -0
  35. package/src/FeatureDetailsWidget/model.ts +77 -9
  36. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +0 -2
  37. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +453 -380
  38. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +520 -0
  39. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +138 -134
  40. package/src/LinearApolloDisplay/glyphs/Glyph.ts +38 -370
  41. package/src/LinearApolloDisplay/glyphs/index.ts +1 -2
  42. package/src/LinearApolloDisplay/stateModel/base.ts +3 -6
  43. package/src/LinearApolloDisplay/stateModel/getGlyph.ts +30 -30
  44. package/src/LinearApolloDisplay/stateModel/index.ts +5 -1
  45. package/src/LinearApolloDisplay/stateModel/layouts.ts +32 -24
  46. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +206 -217
  47. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -67
  48. package/src/OntologyManager/OntologyStore/fulltext.ts +1 -1
  49. package/src/OntologyManager/OntologyStore/index.ts +2 -1
  50. package/src/OntologyManager/index.ts +6 -2
  51. package/src/OntologyManager/util.ts +2 -2
  52. package/src/SixFrameFeatureDisplay/stateModel.ts +15 -10
  53. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +21 -46
  54. package/src/TabularEditor/HybridGrid/Feature.tsx +31 -82
  55. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +3 -2
  56. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +2 -3
  57. package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
  58. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +46 -5
  59. package/src/TabularEditor/model.ts +5 -3
  60. package/src/components/AddAssembly.tsx +15 -9
  61. package/src/components/AddChildFeature.tsx +7 -73
  62. package/src/components/AddFeature.tsx +2 -57
  63. package/src/components/AddRefSeqAliases.tsx +285 -0
  64. package/src/components/CopyFeature.tsx +16 -33
  65. package/src/components/DeleteFeature.tsx +4 -6
  66. package/src/components/ImportFeatures.tsx +6 -3
  67. package/src/components/LogOut.tsx +105 -0
  68. package/src/components/ManageChecks.tsx +1 -0
  69. package/src/components/ManageUsers.tsx +21 -1
  70. package/src/components/ModifyFeatureAttribute.tsx +2 -2
  71. package/src/components/OntologyTermAutocomplete.tsx +0 -2
  72. package/src/components/OntologyTermMultiSelect.tsx +1 -0
  73. package/src/components/OpenLocalFile.tsx +6 -5
  74. package/src/components/ViewChangeLog.tsx +1 -0
  75. package/src/components/ViewCheckResults.tsx +1 -0
  76. package/src/components/index.ts +4 -0
  77. package/src/extensions/annotationFromPileup.ts +10 -16
  78. package/src/index.ts +57 -3
  79. package/src/session/ClientDataStore.ts +49 -46
  80. package/src/session/session.ts +186 -114
  81. package/src/util/loadAssemblyIntoClient.ts +4 -210
  82. package/src/FeatureDetailsWidget/RelatedFeature.tsx +0 -97
  83. package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +0 -1204
  84. package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +0 -716
  85. package/src/LinearApolloDisplay/stateModel/glyphs.ts +0 -47
@@ -1,6 +1,10 @@
1
- import { AnnotationFeatureI } from '@apollo-annotation/mst'
1
+ import { AnnotationFeature } from '@apollo-annotation/mst'
2
2
  import { MenuItem } from '@jbrowse/core/ui'
3
- import { AbstractSessionModel } from '@jbrowse/core/util'
3
+ import {
4
+ AbstractSessionModel,
5
+ SessionWithWidgets,
6
+ isSessionModelWithWidgets,
7
+ } from '@jbrowse/core/util'
4
8
 
5
9
  import { ChangeManager } from '../../ChangeManager'
6
10
  import {
@@ -13,11 +17,11 @@ import { ApolloSessionModel } from '../../session'
13
17
  import { getApolloInternetAccount } from '../../util'
14
18
 
15
19
  export function featureContextMenuItems(
16
- feature: AnnotationFeatureI | undefined,
20
+ feature: AnnotationFeature | undefined,
17
21
  region: { assemblyName: string; refName: string; start: number; end: number },
18
22
  getAssemblyId: (assemblyName: string) => string,
19
- selectedFeature: AnnotationFeatureI | undefined,
20
- setSelectedFeature: (f: AnnotationFeatureI | undefined) => void,
23
+ selectedFeature: AnnotationFeature | undefined,
24
+ setSelectedFeature: (f: AnnotationFeature | undefined) => void,
21
25
  session: ApolloSessionModel,
22
26
  changeManager: ChangeManager,
23
27
  ) {
@@ -30,6 +34,25 @@ export function featureContextMenuItems(
30
34
  const sourceAssemblyId = getAssemblyId(region.assemblyName)
31
35
  const currentAssemblyId = getAssemblyId(region.assemblyName)
32
36
  menuItems.push(
37
+ {
38
+ label: 'Edit feature details',
39
+ onClick: () => {
40
+ const apolloFeatureWidget = (
41
+ session as unknown as SessionWithWidgets
42
+ ).addWidget(
43
+ 'ApolloFeatureDetailsWidget',
44
+ 'apolloFeatureDetailsWidget',
45
+ {
46
+ feature,
47
+ assembly: currentAssemblyId,
48
+ refName: region.refName,
49
+ },
50
+ )
51
+ ;(session as unknown as SessionWithWidgets).showWidget(
52
+ apolloFeatureWidget,
53
+ )
54
+ },
55
+ },
33
56
  {
34
57
  label: 'Add child feature',
35
58
  disabled: readOnly,
@@ -114,6 +137,24 @@ export function featureContextMenuItems(
114
137
  },
115
138
  },
116
139
  )
140
+ if (feature.type === 'mRNA' && isSessionModelWithWidgets(session)) {
141
+ menuItems.push({
142
+ label: 'Edit transcript details',
143
+ onClick: () => {
144
+ const apolloTranscriptWidget = session.addWidget(
145
+ 'ApolloTranscriptDetails',
146
+ 'apolloTranscriptDetails',
147
+ {
148
+ feature,
149
+ assembly: currentAssemblyId,
150
+ changeManager,
151
+ refName: region.refName,
152
+ },
153
+ )
154
+ session.showWidget(apolloTranscriptWidget)
155
+ },
156
+ })
157
+ }
117
158
  }
118
159
  return menuItems
119
160
  }
@@ -39,6 +39,8 @@ export const TabularEditorStateModelType = types
39
39
  // },
40
40
  }))
41
41
 
42
- export type TabularEditorStateModel = Instance<
43
- typeof TabularEditorStateModelType
44
- >
42
+ // eslint disable because of
43
+ // https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
44
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
45
+ export interface TabularEditorStateModel
46
+ extends Instance<typeof TabularEditorStateModelType> {}
@@ -103,15 +103,19 @@ export function AddAssembly({
103
103
  }
104
104
  const selectedFile = e.target.files.item(0)
105
105
  setFile(selectedFile)
106
+ if (!selectedFile) {
107
+ return
108
+ }
109
+ const fileNameLower = selectedFile.name.toLowerCase()
106
110
  if (
107
- selectedFile?.name.toLowerCase().endsWith('.fasta') ??
108
- selectedFile?.name.toLowerCase().endsWith('.fna') ??
109
- selectedFile?.name.toLowerCase().endsWith('.fa')
111
+ fileNameLower.endsWith('.fasta') ||
112
+ fileNameLower.endsWith('.fna') ||
113
+ fileNameLower.endsWith('.fa')
110
114
  ) {
111
115
  setFileType(FileType.FASTA)
112
116
  } else if (
113
- selectedFile?.name.toLowerCase().endsWith('.gff3') ??
114
- selectedFile?.name.toLowerCase().endsWith('.gff')
117
+ fileNameLower.endsWith('.gff3') ||
118
+ fileNameLower.endsWith('.gff')
115
119
  ) {
116
120
  setFileType(FileType.GFF3)
117
121
  }
@@ -168,19 +172,21 @@ export function AddAssembly({
168
172
  const { baseURL, getFetcher, internetAccountId } = selectedInternetAccount
169
173
  if (fileType !== FileType.EXTERNAL && file) {
170
174
  // First upload file
171
- const url = new URL('files', baseURL).href
175
+ const url = new URL('files', baseURL)
176
+ url.searchParams.set('type', fileType)
177
+ const uri = url.href
172
178
  const formData = new FormData()
173
179
  formData.append('file', file)
174
180
  formData.append('fileName', file.name)
175
181
  formData.append('type', fileType)
176
182
  const apolloFetchFile = getFetcher({
177
183
  locationType: 'UriLocation',
178
- uri: url,
184
+ uri,
179
185
  })
180
186
  if (apolloFetchFile) {
181
187
  jobsManager.update(job.name, 'Uploading file, this may take awhile')
182
188
  const { signal } = controller
183
- const response = await apolloFetchFile(url, {
189
+ const response = await apolloFetchFile(uri, {
184
190
  method: 'POST',
185
191
  body: formData,
186
192
  signal,
@@ -219,7 +225,7 @@ export function AddAssembly({
219
225
  const fileUploadChangeBase = {
220
226
  assembly: new ObjectID().toHexString(),
221
227
  assemblyName,
222
- fileId,
228
+ fileIds: { fa: fileId },
223
229
  }
224
230
  change =
225
231
  fileType === FileType.GFF3 && importFeatures
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/no-misused-promises */
2
- import { AnnotationFeatureI } from '@apollo-annotation/mst'
2
+ import { AnnotationFeature } from '@apollo-annotation/mst'
3
3
  import { AddFeatureChange } from '@apollo-annotation/shared'
4
4
  import { AbstractSessionModel } from '@jbrowse/core/util'
5
5
  import {
@@ -7,11 +7,6 @@ import {
7
7
  DialogActions,
8
8
  DialogContent,
9
9
  DialogContentText,
10
- FormControl,
11
- InputLabel,
12
- MenuItem,
13
- Select,
14
- SelectChangeEvent,
15
10
  TextField,
16
11
  } from '@mui/material'
17
12
  import ObjectID from 'bson-objectid'
@@ -28,17 +23,11 @@ import { OntologyTermAutocomplete } from './OntologyTermAutocomplete'
28
23
  interface AddChildFeatureProps {
29
24
  session: ApolloSessionModel
30
25
  handleClose(): void
31
- sourceFeature: AnnotationFeatureI
26
+ sourceFeature: AnnotationFeature
32
27
  sourceAssemblyId: string
33
28
  changeManager: ChangeManager
34
29
  }
35
30
 
36
- enum PhaseEnum {
37
- zero = 0,
38
- one = 1,
39
- two = 2,
40
- }
41
-
42
31
  export function AddChildFeature({
43
32
  changeManager,
44
33
  handleClose,
@@ -47,17 +36,14 @@ export function AddChildFeature({
47
36
  sourceFeature,
48
37
  }: AddChildFeatureProps) {
49
38
  const { notify } = session as unknown as AbstractSessionModel
50
- const [end, setEnd] = useState(String(sourceFeature.end))
51
- const [start, setStart] = useState(String(sourceFeature.start + 1))
39
+ const [end, setEnd] = useState(String(sourceFeature.max))
40
+ const [start, setStart] = useState(String(sourceFeature.min + 1))
52
41
  const [type, setType] = useState('')
53
- const [phase, setPhase] = useState('')
54
- const [phaseAsNumber, setPhaseAsNumber] = useState<PhaseEnum>()
55
- const [showPhase, setShowPhase] = useState<boolean>(false)
56
42
  const [errorMessage, setErrorMessage] = useState('')
57
43
  const [typeWarningText, setTypeWarningText] = useState('')
58
44
 
59
45
  async function fetchValidTerms(
60
- parentFeature: AnnotationFeatureI | undefined,
46
+ parentFeature: AnnotationFeature | undefined,
61
47
  ontologyStore: OntologyStore,
62
48
  _signal: AbortSignal,
63
49
  ) {
@@ -78,22 +64,16 @@ export function AddChildFeature({
78
64
  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
79
65
  event.preventDefault()
80
66
  setErrorMessage('')
81
- if (showPhase && phase === '') {
82
- setErrorMessage('The phase is REQUIRED for all CDS features.')
83
- return
84
- }
85
67
  const change = new AddFeatureChange({
86
68
  changedIds: [sourceFeature._id],
87
69
  typeName: 'AddFeatureChange',
88
70
  assembly: sourceAssemblyId,
89
71
  addedFeature: {
90
72
  _id: new ObjectID().toHexString(),
91
- gffId: '',
92
73
  refSeq: sourceFeature.refSeq,
93
- start: Number(start) - 1,
94
- end: Number(end),
74
+ min: Number(start) - 1,
75
+ max: Number(end),
95
76
  type,
96
- phase: phaseAsNumber,
97
77
  },
98
78
  parentFeatureId: sourceFeature._id,
99
79
  })
@@ -105,35 +85,6 @@ export function AddChildFeature({
105
85
  function handleChangeType(newType: string) {
106
86
  setErrorMessage('')
107
87
  setType(newType)
108
- if (newType.startsWith('CDS')) {
109
- setShowPhase(true)
110
- setPhase('')
111
- } else {
112
- setShowPhase(false)
113
- }
114
- }
115
- function handleChangePhase(e: SelectChangeEvent) {
116
- setErrorMessage('')
117
- setPhase(e.target.value)
118
-
119
- switch (Number(e.target.value)) {
120
- case 0: {
121
- setPhaseAsNumber(PhaseEnum.zero)
122
- break
123
- }
124
- case 1: {
125
- setPhaseAsNumber(PhaseEnum.one)
126
- break
127
- }
128
- case 2: {
129
- setPhaseAsNumber(PhaseEnum.two)
130
- break
131
- }
132
- default: {
133
- // eslint-disable-next-line unicorn/no-useless-undefined
134
- setPhaseAsNumber(undefined)
135
- }
136
- }
137
88
  }
138
89
  const error = Number(end) <= Number(start)
139
90
  return (
@@ -172,13 +123,6 @@ export function AddChildFeature({
172
123
  error={error}
173
124
  helperText={error ? '"End" must be greater than "Start"' : null}
174
125
  />
175
- {/* <Select value={type} onChange={handleChangeType} label="Type">
176
- {(possibleChildTypes ?? []).map((option) => (
177
- <MenuItem key={option} value={option}>
178
- {option}
179
- </MenuItem>
180
- ))}
181
- </Select> */}
182
126
  <OntologyTermAutocomplete
183
127
  session={session}
184
128
  ontologyName="Sequence Ontology"
@@ -202,16 +146,6 @@ export function AddChildFeature({
202
146
  }
203
147
  }}
204
148
  />
205
- {showPhase ? (
206
- <FormControl>
207
- <InputLabel>Phase</InputLabel>
208
- <Select value={phase} onChange={handleChangePhase}>
209
- <MenuItem value={0}>0</MenuItem>
210
- <MenuItem value={1}>1</MenuItem>
211
- <MenuItem value={2}>2</MenuItem>
212
- </Select>
213
- </FormControl>
214
- ) : null}
215
149
  </DialogContent>
216
150
  <DialogActions>
217
151
  <Button
@@ -30,12 +30,6 @@ interface AddFeatureProps {
30
30
  changeManager: ChangeManager
31
31
  }
32
32
 
33
- enum PhaseEnum {
34
- zero = 0,
35
- one = 1,
36
- two = 2,
37
- }
38
-
39
33
  export function AddFeature({
40
34
  changeManager,
41
35
  handleClose,
@@ -46,19 +40,12 @@ export function AddFeature({
46
40
  const [end, setEnd] = useState(String(region.end))
47
41
  const [start, setStart] = useState(String(region.start + 1))
48
42
  const [type, setType] = useState('')
49
- const [phase, setPhase] = useState('')
50
43
  const [strand, setStrand] = useState<1 | -1 | undefined>()
51
- const [phaseAsNumber, setPhaseAsNumber] = useState<PhaseEnum>()
52
- const [showPhase, setShowPhase] = useState<boolean>(false)
53
44
  const [errorMessage, setErrorMessage] = useState('')
54
45
 
55
46
  async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
56
47
  event.preventDefault()
57
48
  setErrorMessage('')
58
- if (showPhase && phase === '') {
59
- setErrorMessage('The phase is REQUIRED for all CDS features.')
60
- return
61
- }
62
49
 
63
50
  let refSeqId
64
51
  for (const [, asm] of session.apolloDataStore.assemblies ?? new Map()) {
@@ -83,12 +70,10 @@ export function AddFeature({
83
70
  assembly: region.assemblyName,
84
71
  addedFeature: {
85
72
  _id: id,
86
- gffId: '',
87
73
  refSeq: refSeqId,
88
- start: Number(start) - 1,
89
- end: Number(end),
74
+ min: Number(start) - 1,
75
+ max: Number(end),
90
76
  type,
91
- phase: phaseAsNumber,
92
77
  strand,
93
78
  },
94
79
  })
@@ -101,12 +86,6 @@ export function AddFeature({
101
86
  function handleChangeType(newType: string) {
102
87
  setErrorMessage('')
103
88
  setType(newType)
104
- if (newType.startsWith('CDS')) {
105
- setShowPhase(true)
106
- setPhase('')
107
- } else {
108
- setShowPhase(false)
109
- }
110
89
  }
111
90
 
112
91
  function handleChangeStrand(e: SelectChangeEvent) {
@@ -128,30 +107,6 @@ export function AddFeature({
128
107
  }
129
108
  }
130
109
 
131
- function handleChangePhase(e: SelectChangeEvent) {
132
- setErrorMessage('')
133
- setPhase(e.target.value)
134
-
135
- switch (Number(e.target.value)) {
136
- case 0: {
137
- setPhaseAsNumber(PhaseEnum.zero)
138
- break
139
- }
140
- case 1: {
141
- setPhaseAsNumber(PhaseEnum.one)
142
- break
143
- }
144
- case 2: {
145
- setPhaseAsNumber(PhaseEnum.two)
146
- break
147
- }
148
- default: {
149
- // eslint-disable-next-line unicorn/no-useless-undefined
150
- setPhaseAsNumber(undefined)
151
- }
152
- }
153
- }
154
-
155
110
  const error = Number(end) <= Number(start)
156
111
 
157
112
  return (
@@ -224,16 +179,6 @@ export function AddFeature({
224
179
  <MenuItem value={-1}>-</MenuItem>
225
180
  </Select>
226
181
  </FormControl>
227
- {showPhase ? (
228
- <FormControl>
229
- <InputLabel>Phase</InputLabel>
230
- <Select value={phase} onChange={handleChangePhase}>
231
- <MenuItem value={0}>0</MenuItem>
232
- <MenuItem value={1}>1</MenuItem>
233
- <MenuItem value={2}>2</MenuItem>
234
- </Select>
235
- </FormControl>
236
- ) : null}
237
182
  </DialogContent>
238
183
  <DialogActions>
239
184
  <Button
@@ -0,0 +1,285 @@
1
+ import { ChangeManager } from '../ChangeManager'
2
+ import { ApolloSessionModel } from '../session'
3
+ import { Dialog } from './Dialog'
4
+ import {
5
+ Button,
6
+ DialogActions,
7
+ DialogContent,
8
+ DialogContentText,
9
+ FormControl,
10
+ Grid,
11
+ InputLabel,
12
+ MenuItem,
13
+ Select,
14
+ SelectChangeEvent,
15
+ } from '@mui/material'
16
+ import React, { useEffect, useRef, useState } from 'react'
17
+ import {
18
+ CollaborationServerDriver,
19
+ ApolloInternetAccount,
20
+ } from '../BackendDrivers'
21
+ import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
22
+ import { DataGrid, GridColDef, GridRowModel } from '@mui/x-data-grid'
23
+ import {
24
+ AddRefSeqAliasesChange,
25
+ SerializedRefSeqAliases,
26
+ } from '@apollo-annotation/shared'
27
+
28
+ const columns: GridColDef[] = [
29
+ { field: 'refName', headerName: 'Ref Name' },
30
+ { field: 'aliases', headerName: 'Aliases', editable: true },
31
+ ]
32
+
33
+ interface AddChildFeatureProps {
34
+ session: ApolloSessionModel
35
+ handleClose(): void
36
+ changeManager: ChangeManager
37
+ }
38
+
39
+ const isGeneratedObjectId = (key: string): boolean => {
40
+ const pattern = /^[\da-f]{24}$/i
41
+ return pattern.test(key)
42
+ }
43
+
44
+ export function AddRefSeqAliases({
45
+ changeManager,
46
+ handleClose,
47
+ session,
48
+ }: AddChildFeatureProps) {
49
+ const fileRef = useRef<HTMLInputElement>(null)
50
+ const [errorMessage, setErrorMessage] = useState('')
51
+ const [enableSubmit, setEnableSubmit] = useState(false)
52
+ const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
53
+ const [selectedRows, setSelectedRows] = useState<
54
+ {
55
+ id: number
56
+ refName: string
57
+ aliases: string
58
+ }[]
59
+ >([])
60
+ const [refNameAliasMap, setRefNameAliasMap] = useState<Map<string, string[]>>(
61
+ new Map(),
62
+ )
63
+
64
+ const { apolloDataStore } = session
65
+ const { collaborationServerDriver } = apolloDataStore as {
66
+ collaborationServerDriver: CollaborationServerDriver
67
+ getInternetAccount(
68
+ assemblyName?: string,
69
+ internetAccountId?: string,
70
+ ): ApolloInternetAccount
71
+ }
72
+ const assemblies = collaborationServerDriver.getAssemblies()
73
+
74
+ useEffect(() => {
75
+ let retry = 0
76
+ const maxRetries = 2
77
+ const initializeRefNameAliasMap = () => {
78
+ if (!selectedAssembly) {
79
+ return
80
+ }
81
+ const initialMap = new Map<string, string[]>()
82
+ if (retry < maxRetries && !selectedAssembly.refNames) {
83
+ retry++
84
+ setTimeout(initializeRefNameAliasMap, 50)
85
+ }
86
+ if (!selectedAssembly.refNames) {
87
+ return
88
+ }
89
+ const refNameAliasess = selectedAssembly.refNameAliases
90
+ for (const key in refNameAliasess) {
91
+ const value = refNameAliasess[key]
92
+ if (!value || isGeneratedObjectId(key)) {
93
+ continue
94
+ }
95
+ if (initialMap.has(value)) {
96
+ const aliases = initialMap.get(value) ?? []
97
+ initialMap.set(value, [...aliases, key])
98
+ } else {
99
+ initialMap.set(value, [key])
100
+ }
101
+ }
102
+ setRefNameAliasMap(initialMap)
103
+ }
104
+
105
+ initializeRefNameAliasMap()
106
+ }, [selectedAssembly])
107
+
108
+ const handleChangeAssembly = (e: SelectChangeEvent) => {
109
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
110
+ setSelectedAssembly(newAssembly)
111
+ setEnableSubmit(false)
112
+ setErrorMessage('')
113
+ if (fileRef.current) {
114
+ fileRef.current.value = ''
115
+ }
116
+ }
117
+
118
+ const handleChangeFile = async (e: React.ChangeEvent<HTMLInputElement>) => {
119
+ if (!e.target.files) {
120
+ return
121
+ }
122
+ // eslint-disable-next-line prefer-destructuring
123
+ const file = e.target.files[0]
124
+ const fileContent = await file.text()
125
+ const lines = fileContent.split('\n')
126
+ const newMap = new Map(refNameAliasMap)
127
+ setErrorMessage('')
128
+ for (const line of lines) {
129
+ const aliases = line.split('\t')
130
+ for (const alias of aliases) {
131
+ if (newMap.has(alias)) {
132
+ newMap.set(alias, [...(newMap.get(alias) ?? []), ...aliases])
133
+ }
134
+ }
135
+ }
136
+ setRefNameAliasMap(newMap)
137
+ }
138
+
139
+ const handleChangeFileHandler = (e: React.ChangeEvent<HTMLInputElement>) => {
140
+ handleChangeFile(e).catch(() => {
141
+ setErrorMessage('Error reading file')
142
+ })
143
+ }
144
+
145
+ const rowSelectionChange = (ids: number[]) => {
146
+ if (ids.length > 0) {
147
+ setEnableSubmit(true)
148
+ const selectedRows = ids.flatMap((id) =>
149
+ getTableRows().filter((row) => row.id === id),
150
+ )
151
+ setSelectedRows(selectedRows)
152
+ } else {
153
+ setEnableSubmit(false)
154
+ setSelectedRows([])
155
+ }
156
+ }
157
+
158
+ const getTableRows = () => {
159
+ return [...refNameAliasMap].map((ele, i) => ({
160
+ id: i,
161
+ refName: ele[0],
162
+ aliases: ele[1].filter((alias) => alias !== ele[0]).join(', '),
163
+ }))
164
+ }
165
+
166
+ const processRowUpdate = (newRow: GridRowModel, _oldRow: GridRowModel) => {
167
+ const newMap = new Map(refNameAliasMap)
168
+ newMap.set(newRow.refName as string, (newRow.aliases as string).split(','))
169
+ setRefNameAliasMap(newMap)
170
+ return newRow
171
+ }
172
+
173
+ const handleSubmit = () => {
174
+ const refSeqAliases: SerializedRefSeqAliases[] = []
175
+ for (const row of selectedRows) {
176
+ const { refName } = row
177
+ const aliases: string[] = row.aliases
178
+ .split(',')
179
+ .map((alias) => alias.trim())
180
+ .filter((alias) => alias.length > 0)
181
+ refSeqAliases.push({
182
+ refName,
183
+ aliases,
184
+ })
185
+ }
186
+ setErrorMessage('')
187
+ if (!selectedAssembly) {
188
+ setErrorMessage('No assembly selected')
189
+ return
190
+ }
191
+ const change = new AddRefSeqAliasesChange({
192
+ typeName: 'AddRefSeqAliasesChange',
193
+ assembly: selectedAssembly.name,
194
+ refSeqAliases,
195
+ })
196
+ changeManager.submit(change).catch(() => {
197
+ setErrorMessage('Error submitting change')
198
+ })
199
+ handleClose()
200
+ }
201
+
202
+ return (
203
+ <Dialog
204
+ open
205
+ title="Add reference sequence aliases"
206
+ handleClose={handleClose}
207
+ maxWidth={'sm'}
208
+ data-testid="add-refseq-alias"
209
+ fullWidth
210
+ >
211
+ <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
212
+ <Grid container spacing={2}>
213
+ <Grid item xs={4}>
214
+ <FormControl disabled={enableSubmit && !errorMessage} fullWidth>
215
+ <InputLabel id="demo-simple-select-label">Assembly</InputLabel>
216
+ <Select
217
+ labelId="demo-simple-select-label"
218
+ id="demo-simple-select"
219
+ label="Assembly"
220
+ value={selectedAssembly?.name ?? ''}
221
+ onChange={handleChangeAssembly}
222
+ >
223
+ {assemblies.map((option) => (
224
+ <MenuItem key={option.name} value={option.name}>
225
+ {option.displayName ?? option.name}
226
+ </MenuItem>
227
+ ))}
228
+ </Select>
229
+ </FormControl>
230
+ </Grid>
231
+ <Grid item xs={1}></Grid>
232
+ <Grid item xs={7}>
233
+ <InputLabel>Load RefName alias</InputLabel>
234
+ <input
235
+ type="file"
236
+ onChange={handleChangeFileHandler}
237
+ ref={fileRef}
238
+ disabled={(enableSubmit && !errorMessage) || !selectedAssembly}
239
+ />
240
+ </Grid>
241
+ </Grid>
242
+ {selectedAssembly && refNameAliasMap.size > 0 ? (
243
+ <div style={{ height: 200, width: '100%', marginTop: 20 }}>
244
+ <InputLabel>
245
+ Refname aliases found for selected assembly.
246
+ </InputLabel>
247
+ <DataGrid
248
+ rows={getTableRows()}
249
+ columns={columns}
250
+ initialState={{
251
+ pagination: {
252
+ paginationModel: { page: 0, pageSize: 5 },
253
+ },
254
+ }}
255
+ pageSizeOptions={[5, 10]}
256
+ onRowSelectionModelChange={(ids) => {
257
+ rowSelectionChange(ids as number[])
258
+ }}
259
+ processRowUpdate={processRowUpdate}
260
+ checkboxSelection
261
+ ></DataGrid>
262
+ </div>
263
+ ) : null}
264
+ </DialogContent>
265
+ <DialogActions>
266
+ <Button
267
+ variant="contained"
268
+ type="submit"
269
+ disabled={!enableSubmit}
270
+ onClick={handleSubmit}
271
+ >
272
+ Submit
273
+ </Button>
274
+ <Button variant="outlined" type="submit" onClick={handleClose}>
275
+ Close
276
+ </Button>
277
+ </DialogActions>
278
+ {errorMessage ? (
279
+ <DialogContent>
280
+ <DialogContentText color="error">{errorMessage}</DialogContentText>
281
+ </DialogContent>
282
+ ) : null}
283
+ </Dialog>
284
+ )
285
+ }