@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.9

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 (86) hide show
  1. package/dist/index.esm.js +11212 -10483
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
  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 +7726 -9014
  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 +18 -18
  12. package/src/ApolloInternetAccount/model.ts +123 -70
  13. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
  14. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
  15. package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
  16. package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
  17. package/src/ChangeManager.ts +36 -14
  18. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  19. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
  20. package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
  21. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  22. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
  23. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
  24. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
  25. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  26. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
  27. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  28. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  29. package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
  30. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  31. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  32. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  33. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  34. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  36. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  37. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
  38. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
  39. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  40. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  41. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  42. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
  43. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
  44. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
  45. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  46. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  47. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  48. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  49. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  50. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  51. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  52. package/src/components/AddAssembly.tsx +34 -38
  53. package/src/components/AddAssemblyAliases.tsx +1 -1
  54. package/src/components/AddChildFeature.tsx +5 -2
  55. package/src/components/AddFeature.tsx +30 -21
  56. package/src/components/AddRefSeqAliases.tsx +64 -50
  57. package/src/components/CopyFeature.tsx +4 -2
  58. package/src/components/CreateApolloAnnotation.tsx +22 -9
  59. package/src/components/DeleteAssembly.tsx +3 -10
  60. package/src/components/DownloadGFF3.tsx +2 -2
  61. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  62. package/src/components/FilterFeatures.tsx +7 -7
  63. package/src/components/FilterTranscripts.tsx +6 -6
  64. package/src/components/ImportFeatures.tsx +1 -1
  65. package/src/components/ManageChecks.tsx +3 -10
  66. package/src/components/ManageUsers.tsx +23 -22
  67. package/src/components/MergeTranscripts.tsx +12 -15
  68. package/src/components/OntologyTermAutocomplete.tsx +1 -8
  69. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  70. package/src/components/OpenLocalFile.tsx +11 -7
  71. package/src/components/ViewChangeLog.tsx +25 -50
  72. package/src/components/ViewCheckResults.tsx +2 -8
  73. package/src/components/index.ts +1 -0
  74. package/src/config.ts +6 -0
  75. package/src/index.ts +53 -115
  76. package/src/makeDisplayComponent.tsx +9 -14
  77. package/src/menus/index.ts +1 -0
  78. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  79. package/src/menus/topLevelMenuAdmin.ts +154 -0
  80. package/src/session/ClientDataStore.ts +32 -14
  81. package/src/session/session.ts +159 -121
  82. package/src/util/annotationFeatureUtils.ts +15 -21
  83. package/src/util/displayUtils.ts +149 -0
  84. package/src/util/glyphUtils.ts +329 -0
  85. package/src/util/loadAssemblyIntoClient.ts +3 -2
  86. package/src/util/mouseEventsUtils.ts +32 -0
@@ -10,6 +10,7 @@ import { makeStyles } from 'tss-react/mui'
10
10
  import { isOntologyClass } from '../../OntologyManager'
11
11
  import type OntologyStore from '../../OntologyManager/OntologyStore'
12
12
  import { OntologyTermAutocomplete } from '../../components/OntologyTermAutocomplete'
13
+ import { navToFeatureCenter } from '../../util'
13
14
  import { type DisplayStateModel } from '../types'
14
15
 
15
16
  import {
@@ -84,12 +85,13 @@ function makeContextMenuItems(
84
85
  )
85
86
  }
86
87
 
87
- function getTopLevelFeature(feature: AnnotationFeature): AnnotationFeature {
88
- let cur = feature
89
- while (cur.parent) {
90
- cur = cur.parent
91
- }
92
- return cur
88
+ function navigateHere(
89
+ displayState: DisplayStateModel,
90
+ feature: AnnotationFeature,
91
+ ) {
92
+ displayState.lgv.navTo(
93
+ navToFeatureCenter(feature, 0.1, displayState.lgv.totalBp),
94
+ )
93
95
  }
94
96
 
95
97
  export const Feature = observer(function Feature({
@@ -111,8 +113,8 @@ export const Feature = observer(function Feature({
111
113
  }) {
112
114
  const { classes } = useStyles()
113
115
  const {
114
- apolloHover,
115
116
  changeManager,
117
+ hoveredFeature,
116
118
  selectedFeature,
117
119
  session,
118
120
  tabularEditor: tabularEditorState,
@@ -134,12 +136,7 @@ export const Feature = observer(function Feature({
134
136
  <>
135
137
  <tr
136
138
  onMouseEnter={(_e) => {
137
- displayState.setApolloHover({
138
- feature,
139
- topLevelFeature: getTopLevelFeature(feature),
140
- // @ts-expect-error TODO fix in future when moving hover logic to session.
141
- glyph: displayState.getGlyph(getTopLevelFeature(feature)),
142
- })
139
+ displayState.setHoveredFeature({ feature, bp: min })
143
140
  }}
144
141
  className={
145
142
  classes.feature +
@@ -153,6 +150,10 @@ export const Feature = observer(function Feature({
153
150
  e.stopPropagation()
154
151
  displayState.setSelectedFeature(feature)
155
152
  }}
153
+ onDoubleClick={() => {
154
+ displayState.setSelectedFeature(feature)
155
+ navigateHere(displayState, feature)
156
+ }}
156
157
  onContextMenu={(e) => {
157
158
  e.preventDefault()
158
159
  setContextMenu({
@@ -258,7 +259,8 @@ export const Feature = observer(function Feature({
258
259
  return text.includes(filterText)
259
260
  })
260
261
  .map(([featureId, childFeature]) => {
261
- const childHovered = apolloHover?.feature._id === childFeature._id
262
+ const childHovered =
263
+ hoveredFeature?.feature._id === childFeature._id
262
264
  const childSelected = selectedFeature?._id === childFeature._id
263
265
  return (
264
266
  <Feature
@@ -37,7 +37,7 @@ const HybridGrid = observer(function HybridGrid({
37
37
  }: {
38
38
  model: DisplayStateModel
39
39
  }) {
40
- const { apolloHover, seenFeatures, selectedFeature, tabularEditor } = model
40
+ const { hoveredFeature, seenFeatures, selectedFeature, tabularEditor } = model
41
41
  const theme = useTheme()
42
42
  const { classes } = useStyles()
43
43
  const scrollContainerRef = useRef<HTMLDivElement>(null)
@@ -96,7 +96,7 @@ const HybridGrid = observer(function HybridGrid({
96
96
  })
97
97
  .map(([featureId, feature]) => {
98
98
  const isSelected = selectedFeature?._id === featureId
99
- const isHovered = apolloHover?.feature._id === featureId
99
+ const isHovered = hoveredFeature?.feature._id === featureId
100
100
  return (
101
101
  <Feature
102
102
  key={featureId}
@@ -121,9 +121,11 @@ const HybridGrid = observer(function HybridGrid({
121
121
  onClose={() => {
122
122
  setContextMenu(null)
123
123
  }}
124
- TransitionProps={{
125
- onExit: () => {
126
- setContextMenu(null)
124
+ slotProps={{
125
+ transition: {
126
+ onExit: () => {
127
+ setContextMenu(null)
128
+ },
127
129
  },
128
130
  }}
129
131
  style={{ zIndex: theme.zIndex.tooltip }}
@@ -39,7 +39,7 @@ import {
39
39
  } from '@mui/material'
40
40
  import ObjectID from 'bson-objectid'
41
41
  import { getRoot } from 'mobx-state-tree'
42
- import React, { useEffect, useState } from 'react'
42
+ import React, { useState } from 'react'
43
43
  import { makeStyles } from 'tss-react/mui'
44
44
 
45
45
  import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
@@ -80,7 +80,7 @@ const useStyles = makeStyles()((theme) => ({
80
80
  borderTop: '1px solid rgba(0, 0, 0, .125)',
81
81
  },
82
82
  radioIcon: {
83
- color: theme?.palette?.tertiary?.contrastText,
83
+ color: theme.palette.tertiary.contrastText,
84
84
  },
85
85
  dialog: {
86
86
  // minHeight: 500,
@@ -149,25 +149,8 @@ export function AddAssembly({
149
149
  const [fastaGziIndexUrl, setFastaGziIndexUrl] = useState<string>('')
150
150
 
151
151
  const [loading, setLoading] = useState(false)
152
- const [isGzip, setIsGzip] = useState<boolean>(false)
153
-
154
- useEffect(() => {
155
- setFastaIndexUrl(fastaUrl ? `${fastaUrl}.fai` : '')
156
- }, [fastaUrl])
157
-
158
- useEffect(() => {
159
- setFastaGziIndexUrl(fastaUrl ? `${fastaUrl}.gzi` : '')
160
- }, [fastaUrl])
161
-
162
- useEffect(() => {
163
- if (sequenceIsEditable || fileType === FileType.GFF3) {
164
- setIsGzip(
165
- fastaFile?.name.toLocaleLowerCase().endsWith('.gz') ? true : false,
166
- )
167
- } else {
168
- setIsGzip(true)
169
- }
170
- }, [fastaFile, sequenceIsEditable, fileType])
152
+ const [fastaGzipChecked, setFastaGzipChecked] = useState<boolean>(false)
153
+ const [gff3GzipChecked, setGff3GzipChecked] = useState<boolean>(false)
171
154
 
172
155
  function checkAssemblyName(assembly: string) {
173
156
  const { assemblies } = session as unknown as AbstractSessionModel
@@ -194,10 +177,13 @@ export function AddAssembly({
194
177
  const uri = url.href
195
178
  const formData = new FormData()
196
179
  let filename = file.name
180
+ const isGzip =
181
+ fileType === FileType.BGZIP_FASTA ||
182
+ (fileType === FileType.FASTA &&
183
+ (!sequenceIsEditable || fastaGzipChecked)) ||
184
+ (fileType === FileType.GFF3 && gff3GzipChecked)
197
185
 
198
- if (fileType === FileType.FAI || fileType === FileType.GZI) {
199
- filename = `${filename}.txt`
200
- } else if (isGzip && !file.name.toLocaleLowerCase().endsWith('.gz')) {
186
+ if (isGzip && !file.name.toLocaleLowerCase().endsWith('.gz')) {
201
187
  filename = `${filename}.gz`
202
188
  } else if (!isGzip && file.name.toLocaleLowerCase().endsWith('.gz')) {
203
189
  filename = `${filename}.txt`
@@ -370,11 +356,6 @@ export function AddAssembly({
370
356
  if (newExpanded) {
371
357
  setExpanded(panel)
372
358
  }
373
- if (panel === 'panelGffInput') {
374
- setIsGzip(false)
375
- } else {
376
- setIsGzip(true)
377
- }
378
359
  }
379
360
 
380
361
  return (
@@ -497,12 +478,12 @@ export function AddAssembly({
497
478
  data-testid="fasta-is-gzip-checkbox"
498
479
  control={
499
480
  <Checkbox
500
- checked={isGzip}
481
+ checked={!sequenceIsEditable || fastaGzipChecked}
501
482
  onChange={() => {
502
483
  if (sequenceIsEditable) {
503
- setIsGzip(!isGzip)
484
+ setFastaGzipChecked(!fastaGzipChecked)
504
485
  } else {
505
- setIsGzip(true)
486
+ setFastaGzipChecked(true)
506
487
  }
507
488
  }}
508
489
  disabled={!sequenceIsEditable}
@@ -533,7 +514,13 @@ export function AddAssembly({
533
514
  onChange={(
534
515
  e: React.ChangeEvent<HTMLInputElement>,
535
516
  ) => {
536
- setFastaFile(e.target.files?.item(0) ?? null)
517
+ const file = e.target.files?.item(0)
518
+ if (file) {
519
+ setFastaFile(file)
520
+ if (file.name.endsWith('.gz')) {
521
+ setFastaGzipChecked(true)
522
+ }
523
+ }
537
524
  }}
538
525
  disabled={submitted && !errorMessage}
539
526
  />
@@ -606,7 +593,10 @@ export function AddAssembly({
606
593
  onChange={(
607
594
  e: React.ChangeEvent<HTMLInputElement>,
608
595
  ) => {
609
- setFastaUrl(e.target.value)
596
+ const { value } = e.target
597
+ setFastaUrl(value)
598
+ setFastaIndexUrl(value ? `${value}.fai` : '')
599
+ setFastaGziIndexUrl(value ? `${value}.gzi` : '')
610
600
  }}
611
601
  disabled={submitted && !errorMessage}
612
602
  slotProps={{
@@ -727,8 +717,14 @@ export function AddAssembly({
727
717
  type="file"
728
718
  disabled={submitted && !errorMessage}
729
719
  onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
730
- setFastaFile(e.target.files?.item(0) ?? null)
731
- setFileType(FileType.GFF3)
720
+ const file = e.target.files?.item(0)
721
+ if (file) {
722
+ setFastaFile(file)
723
+ setFileType(FileType.GFF3)
724
+ if (file.name.endsWith('.gz')) {
725
+ setGff3GzipChecked(true)
726
+ }
727
+ }
732
728
  }}
733
729
  />
734
730
  <FormGroup style={{ display: 'grid' }}>
@@ -748,9 +744,9 @@ export function AddAssembly({
748
744
  data-testid="gff3-is-gzip-checkbox"
749
745
  control={
750
746
  <Checkbox
751
- checked={isGzip}
747
+ checked={gff3GzipChecked}
752
748
  onChange={() => {
753
- setIsGzip(!isGzip)
749
+ setGff3GzipChecked(!gff3GzipChecked)
754
750
  }}
755
751
  disabled={submitted && !errorMessage}
756
752
  />
@@ -57,7 +57,7 @@ export function AddAssemblyAliases({
57
57
  const rows: AssemblyAlias[] = assemblies.map((assembly) => {
58
58
  return {
59
59
  id: assembly.name,
60
- name: assembly.displayName ?? assembly.name,
60
+ name: assembly.displayName,
61
61
  aliases: assembly.aliases.join(', '),
62
62
  } as AssemblyAlias
63
63
  })
@@ -64,12 +64,13 @@ export function AddChildFeature({
64
64
  function onSubmit(event: React.FormEvent<HTMLFormElement>) {
65
65
  event.preventDefault()
66
66
  setErrorMessage('')
67
+ const _id = new ObjectID().toHexString()
67
68
  const change = new AddFeatureChange({
68
69
  changedIds: [sourceFeature._id],
69
70
  typeName: 'AddFeatureChange',
70
71
  assembly: sourceAssemblyId,
71
72
  addedFeature: {
72
- _id: new ObjectID().toHexString(),
73
+ _id,
73
74
  refSeq: sourceFeature.refSeq,
74
75
  min: Number(start) - 1,
75
76
  max: Number(end),
@@ -77,7 +78,9 @@ export function AddChildFeature({
77
78
  },
78
79
  parentFeatureId: sourceFeature._id,
79
80
  })
80
- void changeManager.submit(change)
81
+ void changeManager.submit(change).then(() => {
82
+ session.apolloSetSelectedFeature(_id)
83
+ })
81
84
  handleClose()
82
85
  event.preventDefault()
83
86
  }
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable @typescript-eslint/unbound-method */
2
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
3
2
 
4
3
  import { type AnnotationFeatureSnapshot } from '@apollo-annotation/mst'
5
4
  import { AddFeatureChange } from '@apollo-annotation/shared'
@@ -26,6 +25,7 @@ import {
26
25
  import ObjectID from 'bson-objectid'
27
26
  import React, { useState } from 'react'
28
27
 
28
+ import { CollaborationServerDriver } from '../BackendDrivers'
29
29
  import { type ChangeManager } from '../ChangeManager'
30
30
  import { isOntologyClass } from '../OntologyManager'
31
31
  import { type ApolloSessionModel } from '../session'
@@ -96,30 +96,32 @@ export function AddFeature({
96
96
  const [end, setEnd] = useState(String(region.end))
97
97
  const [start, setStart] = useState(String(region.start + 1))
98
98
  const [type, setType] = useState<NewFeature>(NewFeature.GENE_AND_SUBFEATURES)
99
- const [customType, setCustomType] = useState<string>()
99
+ const [customType, setCustomType] = useState<string>('')
100
100
  const [strand, setStrand] = useState<1 | -1 | undefined>()
101
101
  const [errorMessage, setErrorMessage] = useState('')
102
102
 
103
- function onSubmit(event: React.FormEvent<HTMLFormElement>) {
103
+ async function onSubmit(event: React.FormEvent<HTMLFormElement>) {
104
104
  event.preventDefault()
105
105
  setErrorMessage('')
106
106
 
107
- let refSeqId
108
- for (const [, asm] of session.apolloDataStore.assemblies ?? new Map()) {
109
- if (asm._id === region.assemblyName) {
110
- for (const [, refseq] of asm.refSeqs ?? new Map()) {
111
- if (refseq.name === region.refName) {
112
- refSeqId = refseq._id
113
- }
114
- }
115
- }
107
+ const backendDriver = session.apolloDataStore.getBackendDriver(
108
+ region.assemblyName,
109
+ )
110
+ if (!backendDriver) {
111
+ setErrorMessage('No backend driver found')
112
+ return
116
113
  }
117
-
118
- if (!refSeqId) {
119
- setErrorMessage(
120
- 'Invalid refseq id. Make sure you have the Apollo annotation track open',
114
+ let refSeqId = region.refName
115
+ if (backendDriver instanceof CollaborationServerDriver) {
116
+ const backendRefSeqId = await backendDriver.getRefSeqId(
117
+ region.assemblyName,
118
+ region.refName,
121
119
  )
122
- return
120
+ if (!backendRefSeqId) {
121
+ setErrorMessage(`Could not find refSeq for "${region.refName}"`)
122
+ return
123
+ }
124
+ refSeqId = backendRefSeqId
123
125
  }
124
126
 
125
127
  if (type === NewFeature.GENE_AND_SUBFEATURES) {
@@ -147,7 +149,9 @@ export function AddFeature({
147
149
  children,
148
150
  },
149
151
  })
150
- void changeManager.submit(change)
152
+ void changeManager.submit(change).then(() => {
153
+ session.apolloSetSelectedFeature(id)
154
+ })
151
155
  handleClose()
152
156
  return
153
157
  }
@@ -164,7 +168,9 @@ export function AddFeature({
164
168
  assembly: region.assemblyName,
165
169
  addedFeature: mRNA,
166
170
  })
167
- void changeManager.submit(change)
171
+ void changeManager.submit(change).then(() => {
172
+ session.apolloSetSelectedFeature(mRNA._id)
173
+ })
168
174
  handleClose()
169
175
  return
170
176
  }
@@ -187,7 +193,9 @@ export function AddFeature({
187
193
  strand,
188
194
  },
189
195
  })
190
- void changeManager.submit(change)
196
+ void changeManager.submit(change).then(() => {
197
+ session.apolloSetSelectedFeature(id)
198
+ })
191
199
  handleClose()
192
200
  return
193
201
  }
@@ -242,6 +250,7 @@ export function AddFeature({
242
250
  maxWidth={false}
243
251
  data-testid="add-feature-dialog"
244
252
  >
253
+ {/* eslint-disable-next-line @typescript-eslint/no-misused-promises */}
245
254
  <form onSubmit={onSubmit} data-testid="submit-form">
246
255
  <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
247
256
  <TextField
@@ -338,7 +347,7 @@ export function AddFeature({
338
347
  session={session}
339
348
  ontologyName="Sequence Ontology"
340
349
  style={{ width: 170 }}
341
- value=""
350
+ value={customType}
342
351
  filterTerms={isOntologyClass}
343
352
  renderInput={(params) => (
344
353
  <TextField
@@ -10,18 +10,25 @@ import {
10
10
  DialogContent,
11
11
  DialogContentText,
12
12
  FormControl,
13
- Grid2,
13
+ Grid,
14
14
  InputLabel,
15
15
  MenuItem,
16
16
  Select,
17
17
  type SelectChangeEvent,
18
18
  } from '@mui/material'
19
- import { DataGrid, type GridColDef, type GridRowModel } from '@mui/x-data-grid'
19
+ import {
20
+ DataGrid,
21
+ type GridColDef,
22
+ type GridRowModel,
23
+ type GridRowSelectionModel,
24
+ } from '@mui/x-data-grid'
25
+ import { observer } from 'mobx-react'
20
26
  import React, { useEffect, useRef, useState } from 'react'
21
27
 
22
28
  import {
23
29
  type ApolloInternetAccount,
24
30
  type CollaborationServerDriver,
31
+ type RefNameAliases,
25
32
  } from '../BackendDrivers'
26
33
  import { type ChangeManager } from '../ChangeManager'
27
34
  import { type ApolloSessionModel } from '../session'
@@ -30,7 +37,7 @@ import { Dialog } from './Dialog'
30
37
 
31
38
  const columns: GridColDef[] = [
32
39
  { field: 'refName', headerName: 'Ref Name' },
33
- { field: 'aliases', headerName: 'Aliases', editable: true },
40
+ { field: 'aliases', headerName: 'Aliases', editable: true, flex: 1 },
34
41
  ]
35
42
 
36
43
  interface AddChildFeatureProps {
@@ -44,7 +51,7 @@ const isGeneratedObjectId = (key: string): boolean => {
44
51
  return pattern.test(key)
45
52
  }
46
53
 
47
- export function AddRefSeqAliases({
54
+ export const AddRefSeqAliases = observer(function AddRefSeqAliases({
48
55
  changeManager,
49
56
  handleClose,
50
57
  session,
@@ -75,44 +82,50 @@ export function AddRefSeqAliases({
75
82
  const assemblies = collaborationServerDriver.getAssemblies()
76
83
 
77
84
  useEffect(() => {
78
- let retry = 0
79
- const maxRetries = 2
80
- const initializeRefNameAliasMap = () => {
81
- if (!selectedAssembly) {
82
- return
83
- }
84
- const initialMap = new Map<string, string[]>()
85
- if (retry < maxRetries && !selectedAssembly.refNames) {
86
- retry++
87
- setTimeout(initializeRefNameAliasMap, 50)
88
- }
89
- if (!selectedAssembly.refNames) {
90
- return
91
- }
92
- const refNameAliasess = selectedAssembly.refNameAliases
93
- for (const key in refNameAliasess) {
94
- const value = refNameAliasess[key]
95
- if (!value || isGeneratedObjectId(key)) {
96
- continue
97
- }
98
- if (initialMap.has(value)) {
99
- const aliases = initialMap.get(value) ?? []
100
- initialMap.set(value, [...aliases, key])
101
- } else {
102
- initialMap.set(value, [key])
103
- }
104
- }
105
- setRefNameAliasMap(initialMap)
85
+ if (assemblies.length > 0) {
86
+ setSelectedAssembly(assemblies[0])
87
+ collaborationServerDriver
88
+ .getRefNameAliases(assemblies[0].name)
89
+ .then((refNameAliases) => {
90
+ initializeRefNameAliasMap(refNameAliases)
91
+ })
92
+ .catch(() => {
93
+ setRefNameAliasMap(new Map())
94
+ setErrorMessage('Error fetching refName aliases for assembly')
95
+ })
106
96
  }
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ }, [])
107
99
 
108
- initializeRefNameAliasMap()
109
- }, [selectedAssembly])
100
+ const initializeRefNameAliasMap = (refNameAliasesList: RefNameAliases[]) => {
101
+ const initialMap = new Map<string, string[]>()
102
+ for (const refNameAliases of refNameAliasesList) {
103
+ const key = refNameAliases.refName
104
+ if (isGeneratedObjectId(key)) {
105
+ continue
106
+ }
107
+ initialMap.set(key, refNameAliases.aliases)
108
+ }
109
+ setRefNameAliasMap(initialMap)
110
+ }
110
111
 
111
112
  const handleChangeAssembly = (e: SelectChangeEvent) => {
112
113
  const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
113
114
  setSelectedAssembly(newAssembly)
115
+ if (!newAssembly?.name) {
116
+ return
117
+ }
118
+ collaborationServerDriver
119
+ .getRefNameAliases(newAssembly.name)
120
+ .then((refNameAliases) => {
121
+ initializeRefNameAliasMap(refNameAliases)
122
+ setErrorMessage('')
123
+ })
124
+ .catch(() => {
125
+ setRefNameAliasMap(new Map())
126
+ setErrorMessage('Error fetching refName aliases for assembly')
127
+ })
114
128
  setEnableSubmit(false)
115
- setErrorMessage('')
116
129
  if (fileRef.current) {
117
130
  fileRef.current.value = ''
118
131
  }
@@ -145,11 +158,12 @@ export function AddRefSeqAliases({
145
158
  })
146
159
  }
147
160
 
148
- const rowSelectionChange = (ids: number[]) => {
149
- if (ids.length > 0) {
161
+ const rowSelectionChange = (gridRowSelectionModel: GridRowSelectionModel) => {
162
+ const { ids } = gridRowSelectionModel
163
+ if (ids.size > 0) {
150
164
  setEnableSubmit(true)
151
- const selectedRows = ids.flatMap((id) =>
152
- getTableRows().filter((row) => row.id === id),
165
+ const selectedRows = [...ids.values()].flatMap((id) =>
166
+ getTableRows().filter((row) => String(row.id) === String(id)),
153
167
  )
154
168
  setSelectedRows(selectedRows)
155
169
  } else {
@@ -212,8 +226,8 @@ export function AddRefSeqAliases({
212
226
  fullWidth
213
227
  >
214
228
  <DialogContent style={{ display: 'flex', flexDirection: 'column' }}>
215
- <Grid2 container spacing={2}>
216
- <Grid2>
229
+ <Grid container spacing={2}>
230
+ <Grid>
217
231
  <FormControl disabled={enableSubmit && !errorMessage} fullWidth>
218
232
  <InputLabel id="demo-simple-select-label">Assembly</InputLabel>
219
233
  <Select
@@ -222,16 +236,17 @@ export function AddRefSeqAliases({
222
236
  label="Assembly"
223
237
  value={selectedAssembly?.name ?? ''}
224
238
  onChange={handleChangeAssembly}
239
+ style={{ minWidth: 150 }}
225
240
  >
226
241
  {assemblies.map((option) => (
227
242
  <MenuItem key={option.name} value={option.name}>
228
- {option.displayName ?? option.name}
243
+ {option.displayName}
229
244
  </MenuItem>
230
245
  ))}
231
246
  </Select>
232
247
  </FormControl>
233
- </Grid2>
234
- <Grid2>
248
+ </Grid>
249
+ <Grid>
235
250
  <InputLabel>Load RefName alias</InputLabel>
236
251
  <input
237
252
  type="file"
@@ -239,8 +254,8 @@ export function AddRefSeqAliases({
239
254
  ref={fileRef}
240
255
  disabled={(enableSubmit && !errorMessage) || !selectedAssembly}
241
256
  />
242
- </Grid2>
243
- </Grid2>
257
+ </Grid>
258
+ </Grid>
244
259
  {selectedAssembly && refNameAliasMap.size > 0 ? (
245
260
  <div style={{ height: 200, width: '100%', marginTop: 20 }}>
246
261
  <InputLabel>
@@ -255,11 +270,10 @@ export function AddRefSeqAliases({
255
270
  },
256
271
  }}
257
272
  pageSizeOptions={[5, 10]}
258
- onRowSelectionModelChange={(ids) => {
259
- rowSelectionChange(ids as number[])
260
- }}
273
+ onRowSelectionModelChange={rowSelectionChange}
261
274
  processRowUpdate={processRowUpdate}
262
275
  checkboxSelection
276
+ disableRowSelectionExcludeModel
263
277
  ></DataGrid>
264
278
  </div>
265
279
  ) : null}
@@ -284,4 +298,4 @@ export function AddRefSeqAliases({
284
298
  ) : null}
285
299
  </Dialog>
286
300
  )
287
- }
301
+ })
@@ -99,8 +99,8 @@ export function CopyFeature({
99
99
  }
100
100
 
101
101
  useEffect(() => {
102
- setSelectedRefSeqId('')
103
102
  async function getRefNames() {
103
+ setSelectedRefSeqId('')
104
104
  if (!selectedAssemblyId) {
105
105
  setErrorMessage('No assemblies to copy to')
106
106
  return
@@ -203,7 +203,9 @@ export function CopyFeature({
203
203
  copyFeature: true,
204
204
  allIds: featureIds,
205
205
  })
206
- void changeManager.submit(change)
206
+ void changeManager.submit(change).then(() => {
207
+ session.apolloSetSelectedFeature(newFeatureLine._id)
208
+ })
207
209
  handleClose()
208
210
  event.preventDefault()
209
211
  }