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

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