@apollo-annotation/jbrowse-plugin-apollo 0.3.8 → 0.3.10

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 (54) hide show
  1. package/dist/index.esm.js +10914 -10799
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +10979 -10865
  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 +8799 -11326
  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 +7 -7
  12. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -1
  13. package/src/ApolloInternetAccount/model.ts +87 -65
  14. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
  15. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
  16. package/src/BackendDrivers/CollaborationServerDriver.ts +60 -23
  17. package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
  18. package/src/ChangeManager.ts +22 -5
  19. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
  20. package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
  21. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +68 -212
  22. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
  23. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +6 -102
  24. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +33 -232
  25. package/src/LinearApolloDisplay/glyphs/util.ts +36 -0
  26. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +174 -0
  27. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +200 -0
  28. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +62 -386
  29. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +6 -0
  30. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +122 -70
  31. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +33 -2
  32. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +101 -3
  33. package/src/components/AddAssembly.tsx +34 -38
  34. package/src/components/AddFeature.tsx +21 -18
  35. package/src/components/AddRefSeqAliases.tsx +56 -42
  36. package/src/components/CopyFeature.tsx +1 -1
  37. package/src/components/CreateApolloAnnotation.tsx +22 -10
  38. package/src/components/DeleteAssembly.tsx +2 -9
  39. package/src/components/DownloadGFF3.tsx +2 -2
  40. package/src/components/ImportFeatures.tsx +1 -1
  41. package/src/components/ManageChecks.tsx +2 -9
  42. package/src/components/ManageUsers.tsx +23 -22
  43. package/src/components/OntologyTermAutocomplete.tsx +3 -10
  44. package/src/components/OntologyTermMultiSelect.tsx +2 -2
  45. package/src/components/ViewChangeLog.tsx +25 -50
  46. package/src/components/ViewCheckResults.tsx +1 -7
  47. package/src/config.ts +3 -3
  48. package/src/index.ts +17 -16
  49. package/src/makeDisplayComponent.tsx +9 -13
  50. package/src/session/ClientDataStore.ts +33 -15
  51. package/src/session/session.ts +23 -27
  52. package/src/util/displayUtils.ts +28 -0
  53. package/src/util/glyphUtils.ts +196 -1
  54. package/src/util/loadAssemblyIntoClient.ts +3 -2
@@ -26,6 +26,10 @@ import React, { useEffect, useState } from 'react'
26
26
  import { makeStyles } from 'tss-react/mui'
27
27
 
28
28
  import { type ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
29
+ import {
30
+ type ApolloInternetAccount,
31
+ type CollaborationServerDriver,
32
+ } from '../BackendDrivers'
29
33
  import { type ApolloSessionModel } from '../session'
30
34
  import { type ApolloRootModel } from '../types'
31
35
  import { createFetchErrorMessage } from '../util'
@@ -37,11 +41,6 @@ interface ViewChangeLogProps {
37
41
  handleClose(): void
38
42
  }
39
43
 
40
- interface AssemblyDocument {
41
- _id: string
42
- name: string
43
- }
44
-
45
44
  const useStyles = makeStyles()((theme) => ({
46
45
  changeTextarea: {
47
46
  fontFamily: 'monospace',
@@ -63,12 +62,18 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
63
62
  const { baseURL } = apolloInternetAccount
64
63
  const { classes } = useStyles()
65
64
  const [errorMessage, setErrorMessage] = useState<string>()
66
- const [assemblyCollection, setAssemblyCollection] = useState<
67
- AssemblyDocument[]
68
- >([])
69
- const [assemblyId, setAssemblyId] = useState<string>('')
70
65
  const [displayGridData, setDisplayGridData] = useState<GridRowsProp[]>([])
71
66
 
67
+ const { collaborationServerDriver } = session.apolloDataStore as {
68
+ collaborationServerDriver: CollaborationServerDriver
69
+ getInternetAccount(
70
+ assemblyName?: string,
71
+ internetAccountId?: string,
72
+ ): ApolloInternetAccount
73
+ }
74
+ const assemblies = collaborationServerDriver.getAssemblies()
75
+ const [selectedAssembly, setSelectedAssembly] = useState(assemblies.at(0))
76
+
72
77
  const gridColumns: GridColDef[] = [
73
78
  { field: 'sequence' },
74
79
  {
@@ -90,7 +95,6 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
90
95
  readOnly
91
96
  />
92
97
  ),
93
- valueFormatter: ({ value }) => JSON.stringify(value),
94
98
  },
95
99
  { field: 'user', headerName: 'User', width: 140 },
96
100
  {
@@ -102,47 +106,17 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
102
106
  },
103
107
  ]
104
108
 
105
- useEffect(() => {
106
- async function getAssemblies() {
107
- const uri = new URL('assemblies', baseURL).href
108
- const apolloFetch = apolloInternetAccount?.getFetcher({
109
- locationType: 'UriLocation',
110
- uri,
111
- })
112
- if (apolloFetch) {
113
- const response = await apolloFetch(uri, { method: 'GET' })
114
- if (!response.ok) {
115
- const newErrorMessage = await createFetchErrorMessage(
116
- response,
117
- 'Error when retrieving assemblies from server',
118
- )
119
- setErrorMessage(newErrorMessage)
120
- return
121
- }
122
- const data = (await response.json()) as AssemblyDocument[]
123
- setAssemblyCollection(data)
124
- }
125
- }
126
- getAssemblies().catch((error) => {
127
- setErrorMessage(String(error))
128
- })
129
- }, [apolloInternetAccount, baseURL])
130
-
131
- useEffect(() => {
132
- if (!assemblyId && assemblyCollection.length > 0) {
133
- setAssemblyId(assemblyCollection[0]._id)
134
- }
135
- }, [assemblyId, assemblyCollection])
136
-
137
109
  useEffect(() => {
138
110
  async function getGridData() {
139
- if (!assemblyId) {
111
+ if (!selectedAssembly) {
140
112
  return
141
113
  }
142
114
 
143
115
  // Get changes
144
116
  const url = new URL('changes', baseURL)
145
- const searchParams = new URLSearchParams({ assembly: assemblyId })
117
+ const searchParams = new URLSearchParams({
118
+ assembly: selectedAssembly.name,
119
+ })
146
120
  url.search = searchParams.toString()
147
121
  const uri = url.toString()
148
122
  const apolloFetch = apolloInternetAccount?.getFetcher({
@@ -168,10 +142,11 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
168
142
  getGridData().catch((error) => {
169
143
  setErrorMessage(String(error))
170
144
  })
171
- }, [assemblyId, apolloInternetAccount, baseURL])
145
+ }, [apolloInternetAccount, baseURL, selectedAssembly])
172
146
 
173
147
  function handleChangeAssembly(e: SelectChangeEvent) {
174
- setAssemblyId(e.target.value)
148
+ const newAssembly = assemblies.find((asm) => asm.name === e.target.value)
149
+ setSelectedAssembly(newAssembly)
175
150
  }
176
151
 
177
152
  return (
@@ -184,12 +159,12 @@ export function ViewChangeLog({ handleClose, session }: ViewChangeLogProps) {
184
159
  >
185
160
  <Select
186
161
  style={{ width: 200, marginLeft: 40 }}
187
- value={assemblyId}
162
+ value={selectedAssembly?.name ?? ''}
188
163
  onChange={handleChangeAssembly}
189
164
  >
190
- {assemblyCollection.map((option) => (
191
- <MenuItem key={option._id} value={option._id}>
192
- {option.name}
165
+ {assemblies.map((option) => (
166
+ <MenuItem key={option.name} value={option.name}>
167
+ {option.displayName || option.name}
193
168
  </MenuItem>
194
169
  ))}
195
170
  </Select>
@@ -4,7 +4,6 @@
4
4
  /* eslint-disable @typescript-eslint/no-unsafe-argument */
5
5
  /* eslint-disable @typescript-eslint/no-unsafe-member-access */
6
6
  /* eslint-disable @typescript-eslint/no-unsafe-return */
7
- import { type Assembly } from '@jbrowse/core/assemblyManager/assembly'
8
7
  import {
9
8
  Button,
10
9
  DialogActions,
@@ -49,7 +48,6 @@ export function ViewCheckResults({
49
48
  }
50
49
  const { baseURL } = apolloInternetAccount
51
50
  const [errorMessage, setErrorMessage] = useState<string>()
52
- const [selectedAssembly, setSelectedAssembly] = useState<Assembly>()
53
51
  const [displayGridData, setDisplayGridData] = useState<GridRowsProp[]>([])
54
52
 
55
53
  const gridColumns: GridColDef[] = [
@@ -65,11 +63,7 @@ export function ViewCheckResults({
65
63
  ]
66
64
 
67
65
  const assemblies = collaborationServerDriver.getAssemblies()
68
- useEffect(() => {
69
- if (!selectedAssembly && assemblies.length > 0) {
70
- setSelectedAssembly(assemblies[0])
71
- }
72
- }, [assemblies, selectedAssembly])
66
+ const [selectedAssembly, setSelectedAssembly] = useState(assemblies.at(0))
73
67
 
74
68
  useEffect(() => {
75
69
  async function getGridData() {
package/src/config.ts CHANGED
@@ -15,10 +15,10 @@ const ApolloPluginConfigurationSchema = ConfigurationSchema('ApolloPlugin', {
15
15
  type: 'boolean',
16
16
  defaultValue: false,
17
17
  },
18
- backgroundColorForFeature: {
19
- description: 'Color ',
18
+ geneBackgroundColor: {
19
+ description: 'Color for feature background',
20
20
  type: 'string',
21
- defaultValue: 'jexl:colorFeature(featureType)',
21
+ defaultValue: 'jexl:geneBackgroundColor(featureType)',
22
22
  contextVariable: ['featureType'],
23
23
  },
24
24
  })
package/src/index.ts CHANGED
@@ -42,7 +42,6 @@ import {
42
42
  import { installApolloRefNameAliasAdapter } from './ApolloRefNameAliasAdapter'
43
43
  import { installApolloSequenceAdapter } from './ApolloSequenceAdapter'
44
44
  import { installApolloTextSearchAdapter } from './ApolloTextSearchAdapter'
45
- import { type BackendDriver } from './BackendDrivers'
46
45
  import {
47
46
  ApolloFeatureDetailsWidget,
48
47
  ApolloFeatureDetailsWidgetModel,
@@ -309,9 +308,10 @@ export default class ApolloPlugin extends Plugin {
309
308
  if (!dataStore) {
310
309
  break
311
310
  }
312
- const backendDriver = dataStore.getBackendDriver(
313
- assemblyName,
314
- ) as BackendDriver
311
+ const backendDriver = dataStore.getBackendDriver(assemblyName)
312
+ if (!backendDriver) {
313
+ break
314
+ }
315
315
  const { seq: sequence } =
316
316
  await backendDriver.getSequence(region)
317
317
  handle.workers[0].postMessage({
@@ -331,9 +331,10 @@ export default class ApolloPlugin extends Plugin {
331
331
  if (!dataStore) {
332
332
  break
333
333
  }
334
- const backendDriver = dataStore.getBackendDriver(
335
- assembly,
336
- ) as BackendDriver
334
+ const backendDriver = dataStore.getBackendDriver(assembly)
335
+ if (!backendDriver) {
336
+ break
337
+ }
337
338
  const regions = await backendDriver.getRegions(assembly)
338
339
  handle.workers[0].postMessage({
339
340
  apollo,
@@ -352,9 +353,10 @@ export default class ApolloPlugin extends Plugin {
352
353
  if (!dataStore) {
353
354
  break
354
355
  }
355
- const backendDriver = dataStore.getBackendDriver(
356
- assembly,
357
- ) as BackendDriver
356
+ const backendDriver = dataStore.getBackendDriver(assembly)
357
+ if (!backendDriver) {
358
+ break
359
+ }
358
360
  const refNameAliases =
359
361
  await backendDriver.getRefNameAliases(assembly)
360
362
  handle.workers[0].postMessage({
@@ -378,18 +380,17 @@ export default class ApolloPlugin extends Plugin {
378
380
  configure(pluginManager: PluginManager) {
379
381
  if (isAbstractMenuManager(pluginManager.rootModel)) {
380
382
  pluginManager.jexl.addFunction(
381
- 'colorFeature',
382
- (featureType: 'pseudogenic_transcript' | 'nonCodingTranscript') => {
383
- if (featureType === 'pseudogenic_transcript') {
383
+ 'geneBackgroundColor',
384
+ (featureType: string) => {
385
+ if (featureType === 'pseudogene') {
384
386
  return alpha('rgb(148, 203, 236)', 0.6)
385
387
  }
386
- if (featureType === 'nonCodingTranscript') {
388
+ if (featureType === 'ncRNA_gene') {
387
389
  return alpha('rgb(194, 106, 119)', 0.6)
388
390
  }
389
- throw new Error('Invalid type')
391
+ return
390
392
  },
391
393
  )
392
-
393
394
  addTopLevelMenus(pluginManager.rootModel)
394
395
  }
395
396
  }
@@ -87,25 +87,21 @@ const ResizeHandle = ({
87
87
  },
88
88
  [onResize],
89
89
  )
90
- const cancelDrag: (event: MouseEvent) => void = useCallback(
91
- (event: MouseEvent) => {
92
- event.stopPropagation()
93
- event.preventDefault()
94
- globalThis.removeEventListener('mousemove', mouseMove)
95
- globalThis.removeEventListener('mouseup', cancelDrag)
96
- globalThis.removeEventListener('mouseleave', cancelDrag)
97
- },
98
- [mouseMove],
99
- )
90
+
100
91
  return (
101
92
  // TODO: a11y
102
93
  // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
103
94
  <div
104
95
  onMouseDown={(event: React.MouseEvent) => {
105
96
  event.stopPropagation()
106
- globalThis.addEventListener('mousemove', mouseMove)
107
- globalThis.addEventListener('mouseup', cancelDrag)
108
- globalThis.addEventListener('mouseleave', cancelDrag)
97
+ const controller = new AbortController()
98
+ const { signal } = controller
99
+ function abortDrag() {
100
+ controller.abort('makeDisplayComponent')
101
+ }
102
+ globalThis.addEventListener('mousemove', mouseMove, { signal })
103
+ globalThis.addEventListener('mouseup', abortDrag, { signal })
104
+ globalThis.addEventListener('mouseleave', abortDrag, { signal })
109
105
  }}
110
106
  onClick={(e) => {
111
107
  e.stopPropagation()
@@ -33,6 +33,7 @@ import {
33
33
 
34
34
  import {
35
35
  type ApolloInternetAccount,
36
+ type BackendDriver,
36
37
  CollaborationServerDriver,
37
38
  DesktopFileDriver,
38
39
  InMemoryFileDriver,
@@ -86,18 +87,38 @@ export function clientDataStoreFactory(
86
87
  }
87
88
  return self.assemblies.put(assemblySnapshot)
88
89
  },
90
+ }))
91
+ .actions((self) => ({
89
92
  addFeature(assemblyId: string, feature: AnnotationFeatureSnapshot) {
90
- const assembly = self.assemblies.get(assemblyId)
91
- if (!assembly) {
92
- throw new Error(
93
- `Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
94
- )
93
+ const session = getSession(self)
94
+ const { assemblyManager } = session
95
+ let apolloAssembly = self.assemblies.get(assemblyId)
96
+ if (!apolloAssembly) {
97
+ // maybe it's a valid assembly that we haven't loaded yet
98
+ const assembly = assemblyManager.get(assemblyId)
99
+ if (!assembly) {
100
+ throw new Error(
101
+ `Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
102
+ )
103
+ }
104
+ apolloAssembly = self.addAssembly(assemblyId)
95
105
  }
96
- const ref = assembly.refSeqs.get(feature.refSeq)
106
+ let ref = apolloAssembly.refSeqs.get(feature.refSeq)
97
107
  if (!ref) {
98
- throw new Error(
99
- `Could not find refSeq "${feature.refSeq}" to add feature "${feature._id}"`,
100
- )
108
+ // maybe it's a valid refName that we haven't loaded yet
109
+ const assembly = assemblyManager.get(assemblyId)
110
+ if (!assembly) {
111
+ throw new Error(
112
+ `Could not find assembly "${assemblyId}" to add feature "${feature._id}"`,
113
+ )
114
+ }
115
+ const canonicalRefName = assembly.getCanonicalRefName(feature.refSeq)
116
+ if (!canonicalRefName) {
117
+ throw new Error(
118
+ `Could not find refSeq "${feature.refSeq}" to add feature "${feature._id}"`,
119
+ )
120
+ }
121
+ ref = apolloAssembly.addRefSeq(feature.refSeq, canonicalRefName)
101
122
  }
102
123
  ref.features.put(feature)
103
124
  },
@@ -183,7 +204,7 @@ export function clientDataStoreFactory(
183
204
  statusMessage: `Loading ontology "${name}", version "${version}", this may take a while`,
184
205
  progressPct: 0,
185
206
  cancelCallback: () => {
186
- controller.abort()
207
+ controller.abort('ClientDataStore')
187
208
  jobsManager.abortJob(job.name)
188
209
  },
189
210
  }
@@ -212,15 +233,12 @@ export function clientDataStoreFactory(
212
233
  },
213
234
  }))
214
235
  .views((self) => ({
215
- getBackendDriver(assemblyId: string) {
216
- if (!assemblyId) {
217
- return self.collaborationServerDriver
218
- }
236
+ getBackendDriver(assemblyId: string): BackendDriver | undefined {
219
237
  const session = getSession(self)
220
238
  const { assemblyManager } = session
221
239
  const assembly = assemblyManager.get(assemblyId)
222
240
  if (!assembly) {
223
- return self.collaborationServerDriver
241
+ return
224
242
  }
225
243
  const { file, internetAccountConfigId } = getConf(assembly, [
226
244
  'sequence',
@@ -74,6 +74,7 @@ export function extendSession(
74
74
  apolloSelectedFeature: types.safeReference(AnnotationFeatureExtended),
75
75
  jobsManager: types.optional(ApolloJobModel, {}),
76
76
  isLocked: types.optional(types.boolean, false),
77
+ changeInProgress: types.optional(types.boolean, false),
77
78
  })
78
79
  .volatile(() => ({
79
80
  apolloHoveredFeature: undefined as HoveredFeature | undefined,
@@ -141,6 +142,9 @@ export function extendSession(
141
142
  toggleLocked() {
142
143
  self.isLocked = !self.isLocked
143
144
  },
145
+ setChangeInProgress(changeInProgress: boolean) {
146
+ self.changeInProgress = changeInProgress
147
+ },
144
148
  getPluginConfiguration() {
145
149
  const { jbrowse } = getRoot<ApolloRootModel>(self)
146
150
  const pluginConfiguration =
@@ -165,20 +169,16 @@ export function extendSession(
165
169
  const lgv = view as unknown as LinearGenomeViewModel
166
170
  if (lgv.initialized) {
167
171
  const { dynamicBlocks } = lgv
168
- // eslint-disable-next-line unicorn/no-array-for-each
169
- dynamicBlocks.forEach((block) => {
170
- if (block.regionNumber !== undefined) {
171
- const { assemblyName, end, refName, start } = block
172
- const assembly =
173
- self.apolloDataStore.assemblies.get(assemblyName)
174
- if (
175
- assembly &&
176
- assembly.backendDriverType === 'CollaborationServerDriver'
177
- ) {
178
- locations.push({ assemblyName, refName, start, end })
179
- }
172
+ for (const block of dynamicBlocks.contentBlocks) {
173
+ const { assemblyName, end, refName, start } = block
174
+ const assembly = self.apolloDataStore.assemblies.get(assemblyName)
175
+ if (
176
+ assembly &&
177
+ assembly.backendDriverType === 'CollaborationServerDriver'
178
+ ) {
179
+ locations.push({ assemblyName, refName, start, end })
180
180
  }
181
- })
181
+ }
182
182
  }
183
183
  }
184
184
  if (locations.length === 0) {
@@ -235,21 +235,17 @@ export function extendSession(
235
235
  const lgv = view as unknown as LinearGenomeViewModel
236
236
  if (lgv.initialized) {
237
237
  const { dynamicBlocks } = lgv
238
- // eslint-disable-next-line unicorn/no-array-for-each
239
- dynamicBlocks.forEach((block) => {
240
- if (block.regionNumber !== undefined) {
241
- const { assemblyName, end, refName, start } = block
242
- const assembly =
243
- self.apolloDataStore.assemblies.get(assemblyName)
244
- if (
245
- assembly &&
246
- assembly.backendDriverType ===
247
- 'CollaborationServerDriver'
248
- ) {
249
- locations.push({ assemblyName, refName, start, end })
250
- }
238
+ for (const block of dynamicBlocks.contentBlocks) {
239
+ const { assemblyName, end, refName, start } = block
240
+ const assembly =
241
+ self.apolloDataStore.assemblies.get(assemblyName)
242
+ if (
243
+ assembly &&
244
+ assembly.backendDriverType === 'CollaborationServerDriver'
245
+ ) {
246
+ locations.push({ assemblyName, refName, start, end })
251
247
  }
252
- })
248
+ }
253
249
  }
254
250
  }
255
251
  if (locations.length === 0) {
@@ -1,4 +1,5 @@
1
1
  import { type CheckResultIdsType } from '@apollo-annotation/mst'
2
+ import { type Theme } from '@mui/material'
2
3
  import { makeStyles } from 'tss-react/mui'
3
4
 
4
5
  export { default as EditZoomThresholdDialog } from '../components/EditZoomThresholdDialog'
@@ -147,3 +148,30 @@ export function clusterResultByMessage<
147
148
  )
148
149
  return clusters
149
150
  }
151
+
152
+ export function codonColorCode(
153
+ letter: string,
154
+ theme: Theme,
155
+ highContrast?: boolean,
156
+ ) {
157
+ if (letter === 'M') {
158
+ return theme.palette.startCodon
159
+ }
160
+ if (letter === '*') {
161
+ return highContrast ? theme.palette.text.primary : theme.palette.stopCodon
162
+ }
163
+ return
164
+ }
165
+
166
+ export function colorCode(letter: string, theme: Theme) {
167
+ const letterUpper = letter.toUpperCase()
168
+ if (
169
+ letterUpper === 'A' ||
170
+ letterUpper === 'C' ||
171
+ letterUpper === 'G' ||
172
+ letterUpper === 'T'
173
+ ) {
174
+ return theme.palette.bases[letterUpper].main.toString()
175
+ }
176
+ return 'lightgray'
177
+ }