@apollo-annotation/jbrowse-plugin-apollo 0.3.4 → 0.3.6

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 (131) hide show
  1. package/dist/index.esm.js +5466 -4490
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +5283 -4318
  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 +6806 -4088
  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 +4 -4
  12. package/src/ApolloInternetAccount/addMenuItems.ts +5 -2
  13. package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +1 -0
  14. package/src/ApolloInternetAccount/components/LoginButtons.tsx +1 -1
  15. package/src/ApolloInternetAccount/components/LoginIcons.tsx +1 -1
  16. package/src/ApolloInternetAccount/configSchema.ts +1 -1
  17. package/src/ApolloInternetAccount/model.ts +11 -10
  18. package/src/ApolloJobModel.ts +1 -1
  19. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +8 -6
  20. package/src/ApolloRefNameAliasAdapter/index.ts +2 -2
  21. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +17 -4
  22. package/src/ApolloSequenceAdapter/index.ts +1 -1
  23. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +8 -7
  24. package/src/ApolloTextSearchAdapter/index.ts +1 -1
  25. package/src/BackendDrivers/BackendDriver.ts +7 -7
  26. package/src/BackendDrivers/CollaborationServerDriver.ts +14 -10
  27. package/src/BackendDrivers/DesktopFileDriver.ts +11 -10
  28. package/src/BackendDrivers/InMemoryFileDriver.ts +10 -6
  29. package/src/ChangeManager.ts +5 -5
  30. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +92 -20
  31. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +170 -27
  32. package/src/FeatureDetailsWidget/AttributeKey.tsx +50 -0
  33. package/src/FeatureDetailsWidget/AttributeKeySelector.tsx +104 -0
  34. package/src/FeatureDetailsWidget/Attributes.tsx +213 -320
  35. package/src/FeatureDetailsWidget/BasicInformation.tsx +8 -9
  36. package/src/FeatureDetailsWidget/DefaultAttributeEditor.tsx +104 -0
  37. package/src/FeatureDetailsWidget/DefaultAttributeViewer.tsx +22 -0
  38. package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +10 -8
  39. package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -1
  40. package/src/FeatureDetailsWidget/Sequence.tsx +18 -35
  41. package/src/FeatureDetailsWidget/StringTextField.tsx +1 -1
  42. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +140 -95
  43. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +600 -0
  44. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +54 -0
  45. package/src/FeatureDetailsWidget/model.ts +8 -3
  46. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +19 -12
  47. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +19 -41
  48. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +44 -22
  49. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +6 -5
  50. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -7
  51. package/src/LinearApolloDisplay/stateModel/base.ts +52 -10
  52. package/src/LinearApolloDisplay/stateModel/index.ts +4 -3
  53. package/src/LinearApolloDisplay/stateModel/layouts.ts +8 -34
  54. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +13 -12
  55. package/src/LinearApolloDisplay/stateModel/rendering.ts +63 -31
  56. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +221 -0
  57. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +40 -0
  58. package/src/LinearApolloSixFrameDisplay/components/index.ts +2 -0
  59. package/src/LinearApolloSixFrameDisplay/configSchema.ts +7 -0
  60. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +821 -0
  61. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +63 -0
  62. package/src/LinearApolloSixFrameDisplay/glyphs/index.ts +1 -0
  63. package/src/LinearApolloSixFrameDisplay/index.ts +2 -0
  64. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +261 -0
  65. package/src/LinearApolloSixFrameDisplay/stateModel/index.ts +27 -0
  66. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +236 -0
  67. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +349 -0
  68. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +199 -0
  69. package/src/LinearApolloSixFrameDisplay/types.ts +1 -0
  70. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +10 -1
  71. package/src/OntologyManager/OntologyStore/fulltext.test.ts +1 -1
  72. package/src/OntologyManager/OntologyStore/fulltext.ts +8 -3
  73. package/src/OntologyManager/OntologyStore/index.test.ts +4 -1
  74. package/src/OntologyManager/OntologyStore/index.ts +19 -14
  75. package/src/OntologyManager/OntologyStore/indexeddb-schema.ts +6 -5
  76. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +11 -5
  77. package/src/OntologyManager/index.ts +10 -6
  78. package/src/OntologyManager/util.ts +3 -2
  79. package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
  80. package/src/TabularEditor/HybridGrid/Feature.tsx +9 -8
  81. package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +1 -1
  82. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +3 -2
  83. package/src/TabularEditor/HybridGrid/NumberCell.tsx +8 -1
  84. package/src/TabularEditor/HybridGrid/ToolBar.tsx +15 -13
  85. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +9 -33
  86. package/src/TabularEditor/TabularEditorPane.tsx +1 -1
  87. package/src/TabularEditor/model.ts +2 -2
  88. package/src/TabularEditor/types.ts +5 -2
  89. package/src/components/AddAssembly.tsx +611 -291
  90. package/src/components/AddChildFeature.tsx +6 -5
  91. package/src/components/AddFeature.tsx +211 -38
  92. package/src/components/AddRefSeqAliases.tsx +14 -12
  93. package/src/components/CopyFeature.tsx +8 -7
  94. package/src/components/CreateApolloAnnotation.tsx +154 -46
  95. package/src/components/DeleteAssembly.tsx +9 -8
  96. package/src/components/DeleteFeature.tsx +5 -4
  97. package/src/components/Dialog.tsx +1 -1
  98. package/src/components/DownloadGFF3.tsx +11 -10
  99. package/src/components/FilterFeatures.tsx +6 -4
  100. package/src/components/ImportFeatures.tsx +7 -6
  101. package/src/components/LogOut.tsx +5 -4
  102. package/src/components/ManageChecks.tsx +9 -8
  103. package/src/components/ManageUsers.tsx +11 -10
  104. package/src/components/OntologyTermAutocomplete.tsx +5 -5
  105. package/src/components/OntologyTermMultiSelect.tsx +9 -6
  106. package/src/components/OpenLocalFile.tsx +4 -3
  107. package/src/components/ViewChangeLog.tsx +7 -6
  108. package/src/components/ViewCheckResults.tsx +8 -7
  109. package/src/components/index.ts +0 -1
  110. package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -0
  111. package/src/extensions/annotationFromJBrowseFeature.ts +14 -12
  112. package/src/extensions/annotationFromPileup.ts +6 -6
  113. package/src/index.ts +33 -50
  114. package/src/makeDisplayComponent.tsx +93 -41
  115. package/src/session/ClientDataStore.ts +21 -17
  116. package/src/session/session.ts +20 -26
  117. package/src/types.ts +4 -4
  118. package/src/util/annotationFeatureUtils.ts +53 -0
  119. package/src/util/index.ts +4 -3
  120. package/src/util/loadAssemblyIntoClient.ts +10 -3
  121. package/src/ApolloSixFrameRenderer/ApolloSixFrameRenderer.tsx +0 -13
  122. package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +0 -707
  123. package/src/ApolloSixFrameRenderer/configSchema.ts +0 -7
  124. package/src/ApolloSixFrameRenderer/index.ts +0 -3
  125. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +0 -200
  126. package/src/SixFrameFeatureDisplay/components/TrackLines.tsx +0 -19
  127. package/src/SixFrameFeatureDisplay/components/index.ts +0 -1
  128. package/src/SixFrameFeatureDisplay/configSchema.ts +0 -21
  129. package/src/SixFrameFeatureDisplay/index.ts +0 -2
  130. package/src/SixFrameFeatureDisplay/stateModel.ts +0 -439
  131. package/src/components/ModifyFeatureAttribute.tsx +0 -460
@@ -1,90 +1,51 @@
1
- /* eslint-disable @typescript-eslint/unbound-method */
2
- /* eslint-disable @typescript-eslint/no-unnecessary-condition */
3
- /* eslint-disable @typescript-eslint/no-misused-promises */
4
- import { AnnotationFeature } from '@apollo-annotation/mst'
1
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
5
2
  import { FeatureAttributeChange } from '@apollo-annotation/shared'
6
- import { AbstractSessionModel } from '@jbrowse/core/util'
3
+ import { type AbstractSessionModel, getEnv } from '@jbrowse/core/util'
7
4
  import DeleteIcon from '@mui/icons-material/Delete'
5
+ import EditIcon from '@mui/icons-material/Edit'
6
+ import MoreHorizIcon from '@mui/icons-material/MoreHoriz'
8
7
  import {
9
8
  Button,
10
- DialogActions,
11
- FormControl,
12
- FormControlLabel,
13
- FormLabel,
14
- Grid2,
15
9
  IconButton,
10
+ List,
11
+ ListItem,
12
+ ListItemIcon,
13
+ ListItemText,
14
+ Menu,
15
+ MenuItem,
16
16
  Paper,
17
- Radio,
18
- RadioGroup,
19
- TextField,
20
17
  Typography,
21
18
  } from '@mui/material'
19
+ import { entries } from 'mobx'
22
20
  import { observer } from 'mobx-react'
23
21
  import { getSnapshot } from 'mobx-state-tree'
24
22
  import React, { useState } from 'react'
25
23
  import { makeStyles } from 'tss-react/mui'
26
24
 
27
- import { AttributeValueEditorProps } from '../components'
28
- import { OntologyTermMultiSelect } from '../components/OntologyTermMultiSelect'
29
- import { ApolloSessionModel } from '../session'
30
- import { StringTextField } from './StringTextField'
25
+ import { type ApolloSessionModel } from '../session'
31
26
 
32
- const reservedKeys = new Map([
33
- [
34
- 'Gene Ontology',
35
- (props: AttributeValueEditorProps) => {
36
- return <OntologyTermMultiSelect {...props} ontologyName="Gene Ontology" />
37
- },
38
- ],
39
- [
40
- 'Sequence Ontology',
41
- (props: AttributeValueEditorProps) => {
42
- return (
43
- <OntologyTermMultiSelect {...props} ontologyName="Sequence Ontology" />
44
- )
45
- },
46
- ],
47
- ])
48
-
49
- const reservedTerms = [
50
- 'ID',
51
- 'Name',
52
- 'Alias',
53
- 'Target',
54
- 'Gap',
55
- 'Derives_from',
56
- 'Note',
57
- 'Dbxref',
58
- 'Ontology',
59
- 'Is_Circular',
60
- ]
27
+ import { AttributeKey } from './AttributeKey'
28
+ import { AttributeKeySelector } from './AttributeKeySelector'
29
+ import {
30
+ type AttributeEditorProps,
31
+ DefaultAttributeEditor,
32
+ } from './DefaultAttributeEditor'
33
+ import {
34
+ type AttributeViewerProps,
35
+ DefaultAttributeViewer,
36
+ } from './DefaultAttributeViewer'
61
37
 
62
38
  const useStyles = makeStyles()((theme) => ({
63
- newAttributePaper: {
64
- padding: theme.spacing(2),
65
- },
66
- attributeName: {
67
- background: theme.palette.secondary.main,
68
- color: theme.palette.secondary.contrastText,
69
- padding: theme.spacing(1),
39
+ list: {
40
+ 'li:nth-of-type(odd)': {
41
+ backgroundColor: theme.palette.action.focus,
42
+ },
43
+ 'li:nth-of-type(even)': {
44
+ backgroundColor: theme.palette.action.hover,
45
+ },
70
46
  },
71
47
  }))
72
48
 
73
- function CustomAttributeValueEditor(props: AttributeValueEditorProps) {
74
- const { onChange, value } = props
75
- return (
76
- <StringTextField
77
- value={value}
78
- onChangeCommitted={(newValue) => {
79
- onChange(newValue.split(','))
80
- }}
81
- variant="outlined"
82
- fullWidth
83
- helperText="Separate multiple values for the attribute with commas"
84
- />
85
- )
86
- }
87
-
88
49
  export const Attributes = observer(function Attributes({
89
50
  assembly,
90
51
  editable,
@@ -96,285 +57,217 @@ export const Attributes = observer(function Attributes({
96
57
  assembly: string
97
58
  editable: boolean
98
59
  }) {
99
- const [errorMessage, setErrorMessage] = useState('')
100
- const [showAddNewForm, setShowAddNewForm] = useState(false)
60
+ const { pluginManager } = getEnv(session)
101
61
  const { classes } = useStyles()
102
- const [newAttributeKey, setNewAttributeKey] = useState('')
103
- const attributes = Object.fromEntries(
104
- [...feature.attributes.entries()].map(([key, value]) => {
105
- if (key.startsWith('gff_')) {
106
- const newKey = key.slice(4)
107
- const capitalizedKey = newKey.charAt(0).toUpperCase() + newKey.slice(1)
108
- return [capitalizedKey, getSnapshot(value)]
109
- }
110
- if (key === '_id') {
111
- return ['ID', getSnapshot(value)]
112
- }
113
- return [key, getSnapshot(value)]
114
- }),
115
- )
116
- const { notify } = session as unknown as AbstractSessionModel
62
+ const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null)
63
+ const [selectedKey, setSelectedKey] = useState<null | string>(null)
64
+ const [editingKey, setEditingKey] = useState<null | string>(null)
65
+ const [showAddNewForm, setShowAddNewForm] = useState(false)
66
+ const [newKey, setNewKey] = useState<string | undefined>()
117
67
 
118
- const { changeManager } = session.apolloDataStore
68
+ const open = Boolean(anchorEl)
119
69
 
120
- async function onChangeCommitted(newKey: string, newValue?: string[]) {
121
- setErrorMessage('')
70
+ const { changeManager } = session.apolloDataStore
71
+ const { notify } = session as unknown as AbstractSessionModel
122
72
 
123
- const attrs: Record<string, string[]> = {}
124
- if (attributes) {
125
- const modifiedAttrs = Object.entries({
126
- ...attributes,
127
- [newKey]: newValue,
128
- })
129
- for (const [key, val] of modifiedAttrs) {
130
- if (!val) {
131
- continue
132
- }
133
- const newKey = key.toLowerCase()
134
- if (newKey === 'parent') {
135
- continue
136
- }
137
- if ([...reservedKeys.keys()].includes(key)) {
138
- attrs[key] = val
139
- continue
140
- }
141
- switch (key) {
142
- case 'ID': {
143
- attrs._id = val
144
- break
145
- }
146
- case 'Name': {
147
- attrs.gff_name = val
148
- break
149
- }
150
- case 'Alias': {
151
- attrs.gff_alias = val
152
- break
153
- }
154
- case 'Target': {
155
- attrs.gff_target = val
156
- break
157
- }
158
- case 'Gap': {
159
- attrs.gff_gap = val
160
- break
161
- }
162
- case 'Derives_from': {
163
- attrs.gff_derives_from = val
164
- break
165
- }
166
- case 'Note': {
167
- attrs.gff_note = val
168
- break
169
- }
170
- case 'Dbxref': {
171
- attrs.gff_dbxref = val
172
- break
173
- }
174
- case 'Ontology_term': {
175
- attrs.gff_ontology_term = val
176
- break
177
- }
178
- case 'Is_circular': {
179
- attrs.gff_is_circular = val
180
- break
181
- }
182
- default: {
183
- attrs[key.toLowerCase()] = val
184
- }
185
- }
186
- }
73
+ function handleListMenuClick(
74
+ event: React.MouseEvent<HTMLButtonElement>,
75
+ key: string,
76
+ ) {
77
+ setAnchorEl(event.currentTarget)
78
+ setSelectedKey(key)
79
+ }
80
+ function handleClose() {
81
+ setAnchorEl(null)
82
+ setSelectedKey(null)
83
+ }
84
+ function handleDelete() {
85
+ if (selectedKey) {
86
+ deleteFeatureAttribute(selectedKey)
87
+ }
88
+ handleClose()
89
+ }
90
+ function handleEdit() {
91
+ if (selectedKey) {
92
+ setEditingKey(selectedKey)
187
93
  }
94
+ handleClose()
95
+ }
96
+
97
+ const { _id, attributes } = feature
188
98
 
99
+ function deleteFeatureAttribute(key: string) {
100
+ const attributesSerialized = getSnapshot(attributes)
101
+ const { [key]: deletedAttribute, ...remainingAttributes } =
102
+ attributesSerialized
189
103
  const change = new FeatureAttributeChange({
190
- changedIds: [feature._id],
104
+ changedIds: [_id],
191
105
  typeName: 'FeatureAttributeChange',
192
106
  assembly,
193
- featureId: feature._id,
194
- attributes: attrs,
107
+ featureId: _id,
108
+ attributes: remainingAttributes,
195
109
  })
196
- await changeManager.submit(change)
197
- notify('Feature attributes modified successfully', 'success')
110
+ void changeManager.submit(change)
198
111
  }
199
- function handleAddNewAttributeChange() {
200
- setErrorMessage('')
201
- if (newAttributeKey.trim().length === 0) {
202
- setErrorMessage('Attribute key is mandatory')
203
- return
204
- }
205
- if (newAttributeKey === 'Parent') {
206
- setErrorMessage(
207
- '"Parent" -key is handled internally and it cannot be modified manually',
208
- )
209
- return
210
- }
211
- if (newAttributeKey in attributes) {
212
- setErrorMessage(`Attribute "${newAttributeKey}" already exists`)
112
+
113
+ function modifyFeatureAttribute(key: string, attribute: string[]) {
114
+ const serializedAttributes = { ...getSnapshot(attributes) }
115
+ if (!(key in serializedAttributes)) {
116
+ notify(`"${key}" not found in feature attributes`, 'error')
213
117
  return
214
118
  }
215
- if (
216
- /^[A-Z]/.test(newAttributeKey) &&
217
- !reservedTerms.includes(newAttributeKey) &&
218
- ![...reservedKeys.keys()].includes(newAttributeKey)
219
- ) {
220
- setErrorMessage(
221
- `Key cannot starts with uppercase letter unless key is one of these: ${reservedTerms.join(
222
- ', ',
223
- )}`,
224
- )
119
+ const oldAttribute = serializedAttributes[key]
120
+ if (oldAttribute.toString() === attribute.toString()) {
225
121
  return
226
122
  }
227
- void onChangeCommitted(newAttributeKey, [])
123
+ serializedAttributes[key] = attribute
124
+
125
+ const change = new FeatureAttributeChange({
126
+ changedIds: [feature._id],
127
+ typeName: 'FeatureAttributeChange',
128
+ assembly,
129
+ featureId: feature._id,
130
+ attributes: serializedAttributes,
131
+ })
132
+ void changeManager.submit(change)
228
133
  }
229
134
 
230
- function handleRadioButtonChange(
231
- event: React.ChangeEvent<HTMLInputElement>,
232
- value: string,
233
- ) {
234
- if (value === 'custom') {
235
- setNewAttributeKey('')
236
- } else if (reservedKeys.has(value)) {
237
- setNewAttributeKey(value)
238
- } else {
239
- setErrorMessage('Unknown attribute type')
135
+ function addFeatureAttribute(key: string, attribute: string[]) {
136
+ const serializedAttributes = { ...getSnapshot(attributes) }
137
+ if (key in serializedAttributes) {
138
+ notify(`Feature already has attribute "${key}"`, 'error')
139
+ return
240
140
  }
141
+ serializedAttributes[key] = attribute
142
+
143
+ const change = new FeatureAttributeChange({
144
+ changedIds: [feature._id],
145
+ typeName: 'FeatureAttributeChange',
146
+ assembly,
147
+ featureId: feature._id,
148
+ attributes: serializedAttributes,
149
+ })
150
+ void changeManager.submit(change)
241
151
  }
242
152
 
153
+ const NewKeyAttributeEditor = pluginManager.evaluateExtensionPoint(
154
+ 'Apollo-AttributeEditorComponent',
155
+ DefaultAttributeEditor,
156
+ { key: newKey },
157
+ ) as React.ElementType<AttributeEditorProps>
158
+
243
159
  return (
244
160
  <>
245
- <Typography variant="h5">Attributes</Typography>
246
- <Grid2 container direction="column" spacing={1}>
247
- {Object.entries(attributes).map(([key, value]) => {
248
- if (key === '') {
249
- return null
250
- }
251
- const EditorComponent =
252
- reservedKeys.get(key) ?? CustomAttributeValueEditor
161
+ <List className={classes.list}>
162
+ {entries(attributes).map(([key, values]) => {
163
+ const AttributeEditor = pluginManager.evaluateExtensionPoint(
164
+ 'Apollo-AttributeEditorComponent',
165
+ DefaultAttributeEditor,
166
+ { key },
167
+ ) as React.ElementType<AttributeEditorProps>
168
+ const AttributeViewer = pluginManager.evaluateExtensionPoint(
169
+ 'Apollo-AttributeViewerComponent',
170
+ DefaultAttributeViewer,
171
+ { key },
172
+ ) as React.ElementType<AttributeViewerProps>
253
173
  return (
254
- <Grid2 container spacing={3} alignItems="center" key={key}>
255
- <Grid2>
256
- <Paper variant="outlined" className={classes.attributeName}>
257
- <Typography>{key}</Typography>
258
- </Paper>
259
- </Grid2>
260
- <Grid2 flexGrow={1}>
261
- <EditorComponent
262
- session={session}
263
- value={value}
264
- onChange={(newValue) => onChangeCommitted(key, newValue)}
265
- />
266
- </Grid2>
267
- <Grid2>
268
- <IconButton
269
- aria-label="delete"
270
- size="medium"
271
- disabled={!editable}
272
- onClick={() => onChangeCommitted(key)}
273
- >
274
- <DeleteIcon fontSize="medium" key={key} />
275
- </IconButton>
276
- </Grid2>
277
- </Grid2>
278
- )
279
- })}
280
- <Grid2>
281
- <Button
282
- color="primary"
283
- variant="contained"
284
- disabled={showAddNewForm || !editable}
285
- onClick={() => {
286
- setShowAddNewForm(true)
287
- }}
288
- >
289
- Add new
290
- </Button>
291
- </Grid2>
292
- {showAddNewForm ? (
293
- <Grid2>
294
- <Paper elevation={8} className={classes.newAttributePaper}>
295
- <Grid2 container direction="column">
296
- <Grid2>
297
- <FormControl>
298
- <FormLabel id="attribute-radio-button-group">
299
- Select attribute type
300
- </FormLabel>
301
- <RadioGroup
302
- aria-labelledby="demo-radio-buttons-group-label"
303
- defaultValue="custom"
304
- name="radio-buttons-group"
305
- onChange={handleRadioButtonChange}
306
- >
307
- <FormControlLabel
308
- value="custom"
309
- control={<Radio />}
310
- disableTypography
311
- label={
312
- <Grid2 container spacing={1} alignItems="center">
313
- <Grid2>
314
- <Typography>Custom</Typography>
315
- </Grid2>
316
- <Grid2>
317
- <TextField
318
- label="Custom attribute key"
319
- variant="outlined"
320
- value={
321
- reservedKeys.has(newAttributeKey)
322
- ? ''
323
- : newAttributeKey
324
- }
325
- disabled={reservedKeys.has(newAttributeKey)}
326
- onChange={(event) => {
327
- setNewAttributeKey(event.target.value)
328
- }}
329
- />
330
- </Grid2>
331
- </Grid2>
174
+ <ListItem
175
+ key={key}
176
+ secondaryAction={
177
+ editable && !editingKey ? (
178
+ <IconButton
179
+ edge="end"
180
+ onClick={(event) => {
181
+ handleListMenuClick(event, key)
182
+ }}
183
+ >
184
+ <MoreHorizIcon />
185
+ </IconButton>
186
+ ) : null
187
+ }
188
+ >
189
+ <ListItemText
190
+ disableTypography
191
+ primary={<AttributeKey attributeKey={key} />}
192
+ secondary={
193
+ editingKey === key ? (
194
+ <AttributeEditor
195
+ session={session}
196
+ attributeValues={values as string[] | undefined}
197
+ setAttribute={(newValues) => {
198
+ setEditingKey(null)
199
+ if (newValues) {
200
+ modifyFeatureAttribute(key, newValues)
332
201
  }
333
- />
334
- {[...reservedKeys.keys()].map((key) => (
335
- <FormControlLabel
336
- key={key}
337
- value={key}
338
- control={<Radio />}
339
- label={key}
340
- />
341
- ))}
342
- </RadioGroup>
343
- </FormControl>
344
- </Grid2>
345
- <Grid2>
346
- <DialogActions>
347
- <Button
348
- key="addButton"
349
- color="primary"
350
- variant="contained"
351
- onClick={handleAddNewAttributeChange}
352
- disabled={!newAttributeKey}
353
- >
354
- Add
355
- </Button>
356
- <Button
357
- key="cancelAddButton"
358
- variant="outlined"
359
- type="submit"
360
- onClick={() => {
361
- setShowAddNewForm(false)
362
- setNewAttributeKey('')
363
- setErrorMessage('')
364
202
  }}
365
- >
366
- Cancel
367
- </Button>
368
- </DialogActions>
369
- </Grid2>
370
- </Grid2>
371
- </Paper>
372
- </Grid2>
203
+ />
204
+ ) : (
205
+ <AttributeViewer values={values as string[] | undefined} />
206
+ )
207
+ }
208
+ />
209
+ </ListItem>
210
+ )
211
+ })}
212
+ {newKey ? (
213
+ <ListItem>
214
+ <ListItemText
215
+ disableTypography
216
+ primary={<AttributeKey attributeKey={newKey} />}
217
+ secondary={
218
+ <NewKeyAttributeEditor
219
+ session={session}
220
+ attributeValues={[]}
221
+ setAttribute={(newValues) => {
222
+ if (newValues) {
223
+ addFeatureAttribute(newKey, newValues)
224
+ }
225
+ setNewKey(undefined)
226
+ }}
227
+ isNew
228
+ />
229
+ }
230
+ />
231
+ </ListItem>
373
232
  ) : null}
374
- </Grid2>
375
- {errorMessage ? (
376
- <Typography color="error">{errorMessage}</Typography>
233
+ </List>
234
+ {editable ? (
235
+ <Button
236
+ color="primary"
237
+ variant="contained"
238
+ disabled={showAddNewForm || Boolean(newKey)}
239
+ onClick={() => {
240
+ setShowAddNewForm(true)
241
+ }}
242
+ >
243
+ Add new
244
+ </Button>
245
+ ) : null}
246
+ {showAddNewForm ? (
247
+ <Paper variant="outlined" style={{ marginTop: 8 }}>
248
+ <AttributeKeySelector
249
+ session={session}
250
+ setKey={(newKey) => {
251
+ setNewKey(newKey)
252
+ setShowAddNewForm(false)
253
+ }}
254
+ />
255
+ </Paper>
377
256
  ) : null}
257
+ <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
258
+ <MenuItem onClick={handleDelete}>
259
+ <ListItemIcon>
260
+ <DeleteIcon fontSize="small" />
261
+ </ListItemIcon>
262
+ <Typography variant="inherit">Delete</Typography>
263
+ </MenuItem>
264
+ <MenuItem onClick={handleEdit}>
265
+ <ListItemIcon>
266
+ <EditIcon fontSize="small" />
267
+ </ListItemIcon>
268
+ <Typography variant="inherit">Edit</Typography>
269
+ </MenuItem>
270
+ </Menu>
378
271
  </>
379
272
  )
380
273
  })
@@ -1,22 +1,22 @@
1
- /* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
2
1
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
- import { AnnotationFeature } from '@apollo-annotation/mst'
2
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
4
3
  import {
5
4
  LocationEndChange,
6
5
  LocationStartChange,
7
6
  StrandChange,
8
7
  TypeChange,
9
8
  } from '@apollo-annotation/shared'
10
- import { AbstractSessionModel } from '@jbrowse/core/util'
9
+ import { type AbstractSessionModel } from '@jbrowse/core/util'
11
10
  import { TextField, Typography } from '@mui/material'
12
11
  import { observer } from 'mobx-react'
13
12
  import React, { useState } from 'react'
14
13
 
15
- import { OntologyTermAutocomplete } from '../components/OntologyTermAutocomplete'
16
14
  import { isOntologyClass } from '../OntologyManager'
17
- import OntologyStore from '../OntologyManager/OntologyStore'
15
+ import type OntologyStore from '../OntologyManager/OntologyStore'
18
16
  import { fetchValidDescendantTerms } from '../OntologyManager/util'
19
- import { ApolloSessionModel } from '../session'
17
+ import { OntologyTermAutocomplete } from '../components/OntologyTermAutocomplete'
18
+ import { type ApolloSessionModel } from '../session'
19
+
20
20
  import { NumberTextField } from './NumberTextField'
21
21
 
22
22
  export const BasicInformation = observer(function BasicInformation({
@@ -111,8 +111,7 @@ export const BasicInformation = observer(function BasicInformation({
111
111
  }
112
112
 
113
113
  return (
114
- <>
115
- <Typography variant="h5">Basic information</Typography>
114
+ <div data-testid="basic_information">
116
115
  <NumberTextField
117
116
  margin="dense"
118
117
  id="start"
@@ -183,6 +182,6 @@ export const BasicInformation = observer(function BasicInformation({
183
182
  {errorMessage ? (
184
183
  <Typography color="error">{errorMessage}</Typography>
185
184
  ) : null}
186
- </>
185
+ </div>
187
186
  )
188
187
  })