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

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 (44) hide show
  1. package/dist/index.esm.js +1513 -1074
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +1510 -1065
  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 +4681 -2097
  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/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +13 -0
  13. package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +88 -17
  14. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +144 -22
  15. package/src/FeatureDetailsWidget/Attributes.tsx +93 -43
  16. package/src/FeatureDetailsWidget/BasicInformation.tsx +2 -4
  17. package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +6 -4
  18. package/src/FeatureDetailsWidget/Sequence.tsx +16 -33
  19. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +137 -92
  20. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +600 -0
  21. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +54 -0
  22. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +13 -6
  23. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +6 -27
  24. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +35 -13
  25. package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -2
  26. package/src/LinearApolloDisplay/stateModel/rendering.ts +4 -0
  27. package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +10 -1
  28. package/src/OntologyManager/OntologyStore/index.test.ts +1 -0
  29. package/src/OntologyManager/index.ts +2 -0
  30. package/src/SixFrameFeatureDisplay/stateModel.ts +5 -1
  31. package/src/TabularEditor/HybridGrid/Feature.tsx +0 -1
  32. package/src/TabularEditor/HybridGrid/NumberCell.tsx +8 -1
  33. package/src/TabularEditor/HybridGrid/ToolBar.tsx +14 -12
  34. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +3 -27
  35. package/src/components/AddAssembly.tsx +608 -291
  36. package/src/components/CreateApolloAnnotation.tsx +144 -37
  37. package/src/components/OntologyTermMultiSelect.tsx +3 -0
  38. package/src/components/index.ts +0 -1
  39. package/src/extensions/annotationFromJBrowseFeature.ts +3 -2
  40. package/src/makeDisplayComponent.tsx +3 -4
  41. package/src/util/annotationFeatureUtils.ts +53 -0
  42. package/src/util/index.ts +1 -0
  43. package/src/FeatureDetailsWidget/TranscriptBasic.tsx +0 -200
  44. package/src/components/ModifyFeatureAttribute.tsx +0 -460
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apollo-annotation/jbrowse-plugin-apollo",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Apollo plugin for JBrowse 2",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -48,9 +48,9 @@
48
48
  }
49
49
  },
50
50
  "dependencies": {
51
- "@apollo-annotation/common": "^0.3.4",
52
- "@apollo-annotation/mst": "^0.3.4",
53
- "@apollo-annotation/shared": "^0.3.4",
51
+ "@apollo-annotation/common": "^0.3.5",
52
+ "@apollo-annotation/mst": "^0.3.5",
53
+ "@apollo-annotation/shared": "^0.3.5",
54
54
  "@emotion/react": "^11.10.6",
55
55
  "@emotion/styled": "^11.10.6",
56
56
  "@gmod/gff": "1.2.0",
@@ -127,6 +127,19 @@ export class ApolloSequenceAdapter extends BaseSequenceAdapter {
127
127
  const backendDriver = dataStore.getBackendDriver(
128
128
  assemblyId,
129
129
  ) as BackendDriver
130
+ const regions = await backendDriver.getRegions(
131
+ regionWithAssemblyName.assemblyName,
132
+ )
133
+ const region = regions.find(
134
+ (region) => region.refName === regionWithAssemblyName.refName,
135
+ )
136
+ if (!region) {
137
+ observer.error('Cannot get region')
138
+ return
139
+ }
140
+ if (regionWithAssemblyName.end > region.end) {
141
+ regionWithAssemblyName.end = region.end
142
+ }
130
143
  const { seq } = await backendDriver.getSequence(regionWithAssemblyName)
131
144
  observer.next(
132
145
  new SimpleFeature({
@@ -1,7 +1,14 @@
1
1
  import { getSession } from '@jbrowse/core/util'
2
2
  import { observer } from 'mobx-react'
3
- import React from 'react'
3
+ import React, { useEffect, useState } from 'react'
4
4
  import { makeStyles } from 'tss-react/mui'
5
+ import {
6
+ Accordion,
7
+ AccordionDetails,
8
+ AccordionSummary,
9
+ Typography,
10
+ } from '@mui/material'
11
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
5
12
 
6
13
  import { ApolloSessionModel } from '../session'
7
14
  import { Attributes } from './Attributes'
@@ -24,6 +31,12 @@ export const ApolloFeatureDetailsWidget = observer(
24
31
  const currentAssembly = session.apolloDataStore.assemblies.get(assembly)
25
32
  const { classes } = useStyles()
26
33
 
34
+ const [panelState, setPanelState] = useState<string[]>(['attributes'])
35
+
36
+ useEffect(() => {
37
+ setPanelState(['attributes'])
38
+ }, [feature])
39
+
27
40
  if (!(feature && currentAssembly)) {
28
41
  return null
29
42
  }
@@ -39,6 +52,14 @@ export const ApolloFeatureDetailsWidget = observer(
39
52
  ])
40
53
  }
41
54
 
55
+ function handlePanelChange(expanded: boolean, panel: string) {
56
+ if (expanded) {
57
+ setPanelState([...panelState, panel])
58
+ } else {
59
+ setPanelState(panelState.filter((p) => p !== panel))
60
+ }
61
+ }
62
+
42
63
  return (
43
64
  <div className={classes.root}>
44
65
  <BasicInformation
@@ -46,22 +67,72 @@ export const ApolloFeatureDetailsWidget = observer(
46
67
  session={session}
47
68
  assembly={currentAssembly._id}
48
69
  />
49
- <hr />
50
- <Attributes
51
- feature={feature}
52
- session={session}
53
- assembly={currentAssembly._id}
54
- editable={true}
55
- />
56
- <hr />
57
- <Sequence
58
- feature={feature}
59
- session={session}
60
- assembly={currentAssembly._id}
61
- refName={refName}
62
- />
63
- <hr />
64
- <FeatureDetailsNavigation model={model} feature={feature} />
70
+ <Accordion
71
+ style={{ marginTop: 10 }}
72
+ expanded={panelState.includes('attributes')}
73
+ onChange={(e, expanded) => {
74
+ handlePanelChange(expanded, 'attributes')
75
+ }}
76
+ >
77
+ <AccordionSummary
78
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
79
+ aria-controls="panel1-content"
80
+ id="panel1-header"
81
+ >
82
+ <Typography component="span">Attributes</Typography>
83
+ </AccordionSummary>
84
+ <AccordionDetails>
85
+ <Attributes
86
+ feature={feature}
87
+ session={session}
88
+ assembly={currentAssembly._id}
89
+ editable={true}
90
+ />
91
+ </AccordionDetails>
92
+ </Accordion>
93
+ <Accordion
94
+ style={{ marginTop: 10 }}
95
+ expanded={panelState.includes('sequence')}
96
+ onChange={(e, expanded) => {
97
+ handlePanelChange(expanded, 'sequence')
98
+ }}
99
+ >
100
+ <AccordionSummary
101
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
102
+ aria-controls="panel2-content"
103
+ id="panel2-header"
104
+ >
105
+ <Typography component="span">Sequence</Typography>
106
+ </AccordionSummary>
107
+ <AccordionDetails>
108
+ {panelState.includes('sequence') && (
109
+ <Sequence
110
+ feature={feature}
111
+ session={session}
112
+ assembly={currentAssembly._id}
113
+ refName={refName}
114
+ />
115
+ )}
116
+ </AccordionDetails>
117
+ </Accordion>
118
+ <Accordion
119
+ style={{ marginTop: 10 }}
120
+ expanded={panelState.includes('related_features')}
121
+ onChange={(e, expanded) => {
122
+ handlePanelChange(expanded, 'related_features')
123
+ }}
124
+ >
125
+ <AccordionSummary
126
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
127
+ aria-controls="panel3-content"
128
+ id="panel3-header"
129
+ >
130
+ <Typography component="span">Related features</Typography>
131
+ </AccordionSummary>
132
+ <AccordionDetails>
133
+ <FeatureDetailsNavigation model={model} feature={feature} />
134
+ </AccordionDetails>
135
+ </Accordion>
65
136
  </div>
66
137
  )
67
138
  },
@@ -1,16 +1,29 @@
1
1
  import { AbstractSessionModel, getSession } from '@jbrowse/core/util'
2
2
  import { observer } from 'mobx-react'
3
3
  import { getRoot } from 'mobx-state-tree'
4
- import React from 'react'
4
+ import React, { useEffect, useState } from 'react'
5
5
  import { makeStyles } from 'tss-react/mui'
6
+ import {
7
+ Accordion,
8
+ AccordionDetails,
9
+ AccordionSummary,
10
+ Tooltip,
11
+ Typography,
12
+ } from '@mui/material'
13
+
14
+ import styled from '@emotion/styled'
15
+
16
+ import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
17
+ import InfoIcon from '@mui/icons-material/Info'
6
18
 
7
19
  import { ApolloInternetAccountModel } from '../ApolloInternetAccount/model'
8
20
  import { ApolloSessionModel } from '../session'
9
21
  import { ApolloRootModel } from '../types'
10
22
  import { Attributes } from './Attributes'
11
- import { TranscriptBasicInformation } from './TranscriptBasic'
12
23
  import { TranscriptSequence } from './TranscriptSequence'
13
24
  import { ApolloTranscriptDetailsWidget as ApolloTranscriptDetailsWidgetState } from './model'
25
+ import { TranscriptWidgetSummary } from './TranscriptWidgetSummary'
26
+ import { TranscriptWidgetEditLocation } from './TranscriptWidgetEditLocation'
14
27
 
15
28
  const useStyles = makeStyles()((theme) => ({
16
29
  root: {
@@ -18,13 +31,31 @@ const useStyles = makeStyles()((theme) => ({
18
31
  },
19
32
  }))
20
33
 
34
+ export const StyledAccordionSummary = styled(AccordionSummary)(() => ({
35
+ minHeight: 30,
36
+ maxHeight: 30,
37
+ '&.Mui-expanded': {
38
+ minHeight: 30,
39
+ maxHeight: 30,
40
+ },
41
+ }))
42
+
21
43
  export const ApolloTranscriptDetailsWidget = observer(
22
44
  function ApolloTranscriptDetails(props: {
23
45
  model: ApolloTranscriptDetailsWidgetState
24
46
  }) {
25
47
  const { classes } = useStyles()
48
+ const DEFAULT_PANELS = ['summary', 'location', 'attrs']
49
+ const [panelState, setPanelState] = useState<string[]>(DEFAULT_PANELS)
50
+
26
51
  const { model } = props
27
52
  const { assembly, feature, refName } = model
53
+
54
+ useEffect(() => {
55
+ setPanelState(DEFAULT_PANELS)
56
+ // eslint-disable-next-line react-hooks/exhaustive-deps
57
+ }, [feature])
58
+
28
59
  const session = getSession(model) as unknown as AbstractSessionModel
29
60
  const apolloSession = getSession(model) as unknown as ApolloSessionModel
30
61
  const currentAssembly =
@@ -53,28 +84,119 @@ export const ApolloTranscriptDetailsWidget = observer(
53
84
  ])
54
85
  }
55
86
 
87
+ function handlePanelChange(expanded: boolean, panel: string) {
88
+ if (expanded) {
89
+ setPanelState([...panelState, panel])
90
+ } else {
91
+ setPanelState(panelState.filter((p) => p !== panel))
92
+ }
93
+ }
94
+
56
95
  return (
57
96
  <div className={classes.root}>
58
- <TranscriptBasicInformation
59
- feature={feature}
60
- session={apolloSession}
61
- assembly={currentAssembly._id || ''}
62
- refName={refName}
63
- />
64
- <hr />
65
- <Attributes
66
- feature={feature}
67
- session={apolloSession}
68
- assembly={currentAssembly._id || ''}
69
- editable={editable}
70
- />
71
- <hr />
72
- <TranscriptSequence
73
- feature={feature}
74
- session={apolloSession}
75
- assembly={currentAssembly._id || ''}
76
- refName={refName}
77
- />
97
+ <Accordion
98
+ expanded={panelState.includes('summary')}
99
+ onChange={(e, expanded) => {
100
+ handlePanelChange(expanded, 'summary')
101
+ }}
102
+ >
103
+ <StyledAccordionSummary
104
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
105
+ aria-controls="panel1-content"
106
+ id="panel1-header"
107
+ >
108
+ <Typography component="span" fontWeight={'bold'}>
109
+ Summary
110
+ </Typography>
111
+ </StyledAccordionSummary>
112
+ <AccordionDetails>
113
+ <TranscriptWidgetSummary feature={feature} refName={refName} />
114
+ </AccordionDetails>
115
+ </Accordion>
116
+ <Accordion
117
+ style={{ marginTop: 5 }}
118
+ expanded={panelState.includes('location')}
119
+ onChange={(e, expanded) => {
120
+ handlePanelChange(expanded, 'location')
121
+ }}
122
+ >
123
+ <StyledAccordionSummary
124
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
125
+ aria-controls="panel2-content"
126
+ id="panel2-header"
127
+ >
128
+ <Typography component="span" fontWeight={'bold'}>
129
+ Location
130
+ </Typography>
131
+ </StyledAccordionSummary>
132
+ <AccordionDetails>
133
+ <TranscriptWidgetEditLocation
134
+ feature={feature}
135
+ refName={refName}
136
+ session={apolloSession}
137
+ assembly={currentAssembly._id || ''}
138
+ />
139
+ </AccordionDetails>
140
+ </Accordion>
141
+ <Accordion
142
+ style={{ marginTop: 5 }}
143
+ expanded={panelState.includes('attrs')}
144
+ onChange={(e, expanded) => {
145
+ handlePanelChange(expanded, 'attrs')
146
+ }}
147
+ >
148
+ <StyledAccordionSummary
149
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
150
+ aria-controls="panel3-content"
151
+ id="panel3-header"
152
+ >
153
+ <div style={{ display: 'flex', alignItems: 'center' }}>
154
+ <Typography component="span" fontWeight={'bold'}>
155
+ Attributes{' '}
156
+ </Typography>
157
+ <Tooltip title="Separate multiple values for the attribute with commas">
158
+ <InfoIcon
159
+ style={{ color: 'white', fontSize: 15, marginLeft: 10 }}
160
+ />
161
+ </Tooltip>
162
+ </div>
163
+ </StyledAccordionSummary>
164
+ <AccordionDetails>
165
+ <Attributes
166
+ feature={feature}
167
+ session={apolloSession}
168
+ assembly={currentAssembly._id || ''}
169
+ editable={editable}
170
+ />
171
+ </AccordionDetails>
172
+ </Accordion>
173
+ <Accordion
174
+ style={{ marginTop: 5 }}
175
+ expanded={panelState.includes('sequence')}
176
+ onChange={(e, expanded) => {
177
+ handlePanelChange(expanded, 'sequence')
178
+ }}
179
+ >
180
+ <StyledAccordionSummary
181
+ expandIcon={<ExpandMoreIcon style={{ color: 'white' }} />}
182
+ aria-controls="panel4-content"
183
+ id="panel4-header"
184
+ >
185
+ <Typography component="span" fontWeight={'bold'}>
186
+ Sequence
187
+ </Typography>
188
+ </StyledAccordionSummary>
189
+ <AccordionDetails>
190
+ {panelState.includes('sequence') && (
191
+ <TranscriptSequence
192
+ feature={feature}
193
+ session={apolloSession}
194
+ assembly={currentAssembly._id || ''}
195
+ refName={refName}
196
+ />
197
+ )}
198
+ </AccordionDetails>
199
+ </Accordion>
78
200
  </div>
79
201
  )
80
202
  },
@@ -1,6 +1,4 @@
1
- /* eslint-disable @typescript-eslint/unbound-method */
2
1
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
3
- /* eslint-disable @typescript-eslint/no-misused-promises */
4
2
  import { AnnotationFeature } from '@apollo-annotation/mst'
5
3
  import { FeatureAttributeChange } from '@apollo-annotation/shared'
6
4
  import { AbstractSessionModel } from '@jbrowse/core/util'
@@ -21,10 +19,9 @@ import {
21
19
  } from '@mui/material'
22
20
  import { observer } from 'mobx-react'
23
21
  import { getSnapshot } from 'mobx-state-tree'
24
- import React, { useState } from 'react'
22
+ import React, { useEffect, useState } from 'react'
25
23
  import { makeStyles } from 'tss-react/mui'
26
24
 
27
- import { AttributeValueEditorProps } from '../components'
28
25
  import { OntologyTermMultiSelect } from '../components/OntologyTermMultiSelect'
29
26
  import { ApolloSessionModel } from '../session'
30
27
  import { StringTextField } from './StringTextField'
@@ -32,15 +29,33 @@ import { StringTextField } from './StringTextField'
32
29
  const reservedKeys = new Map([
33
30
  [
34
31
  'Gene Ontology',
35
- (props: AttributeValueEditorProps) => {
36
- return <OntologyTermMultiSelect {...props} ontologyName="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
+ )
37
44
  },
38
45
  ],
39
46
  [
40
47
  'Sequence Ontology',
41
- (props: AttributeValueEditorProps) => {
48
+ (props: {
49
+ session: ApolloSessionModel
50
+ value: string[]
51
+ onChange: (newValue: string[]) => void
52
+ }) => {
42
53
  return (
43
- <OntologyTermMultiSelect {...props} ontologyName="Sequence Ontology" />
54
+ <OntologyTermMultiSelect
55
+ {...props}
56
+ ontologyName="Sequence Ontology"
57
+ label={'Sequence Ontology'}
58
+ />
44
59
  )
45
60
  },
46
61
  ],
@@ -70,8 +85,12 @@ const useStyles = makeStyles()((theme) => ({
70
85
  },
71
86
  }))
72
87
 
73
- function CustomAttributeValueEditor(props: AttributeValueEditorProps) {
74
- const { onChange, value } = props
88
+ function CustomAttributeValueEditor(props: {
89
+ value: unknown
90
+ onChange: (newValue: string[]) => void
91
+ label: string
92
+ }) {
93
+ const { onChange, value, label } = props
75
94
  return (
76
95
  <StringTextField
77
96
  value={value}
@@ -80,11 +99,28 @@ function CustomAttributeValueEditor(props: AttributeValueEditorProps) {
80
99
  }}
81
100
  variant="outlined"
82
101
  fullWidth
83
- helperText="Separate multiple values for the attribute with commas"
102
+ label={label}
103
+ style={{ width: '100%' }}
84
104
  />
85
105
  )
86
106
  }
87
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
+
88
124
  export const Attributes = observer(function Attributes({
89
125
  assembly,
90
126
  editable,
@@ -100,33 +136,24 @@ export const Attributes = observer(function Attributes({
100
136
  const [showAddNewForm, setShowAddNewForm] = useState(false)
101
137
  const { classes } = useStyles()
102
138
  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
- }),
139
+ const [attributes, setAttributes] = useState(() =>
140
+ transformAttributes(feature),
115
141
  )
142
+
143
+ useEffect(() => {
144
+ setAttributes(transformAttributes(feature))
145
+ }, [feature])
146
+
116
147
  const { notify } = session as unknown as AbstractSessionModel
117
148
 
118
149
  const { changeManager } = session.apolloDataStore
119
150
 
120
- async function onChangeCommitted(newKey: string, newValue?: string[]) {
151
+ async function onChangeCommitted(attributes: Record<string, string[]>) {
121
152
  setErrorMessage('')
122
153
 
123
154
  const attrs: Record<string, string[]> = {}
124
155
  if (attributes) {
125
- const modifiedAttrs = Object.entries({
126
- ...attributes,
127
- [newKey]: newValue,
128
- })
129
- for (const [key, val] of modifiedAttrs) {
156
+ for (const [key, val] of Object.entries(attributes)) {
130
157
  if (!val) {
131
158
  continue
132
159
  }
@@ -196,6 +223,7 @@ export const Attributes = observer(function Attributes({
196
223
  await changeManager.submit(change)
197
224
  notify('Feature attributes modified successfully', 'success')
198
225
  }
226
+
199
227
  function handleAddNewAttributeChange() {
200
228
  setErrorMessage('')
201
229
  if (newAttributeKey.trim().length === 0) {
@@ -224,7 +252,29 @@ export const Attributes = observer(function Attributes({
224
252
  )
225
253
  return
226
254
  }
227
- void onChangeCommitted(newAttributeKey, [])
255
+
256
+ setAttributes({
257
+ ...attributes,
258
+ [newAttributeKey]: [],
259
+ })
260
+
261
+ setShowAddNewForm(false)
262
+ setNewAttributeKey('')
263
+ }
264
+
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
+ }
272
+
273
+ function updateAttribute(key: string, newValue: string[]) {
274
+ const newAttributes = { ...attributes }
275
+ newAttributes[key] = newValue
276
+ setAttributes(newAttributes)
277
+ void onChangeCommitted(newAttributes)
228
278
  }
229
279
 
230
280
  function handleRadioButtonChange(
@@ -241,8 +291,7 @@ export const Attributes = observer(function Attributes({
241
291
  }
242
292
 
243
293
  return (
244
- <>
245
- <Typography variant="h5">Attributes</Typography>
294
+ <div data-testid="attributes_test">
246
295
  <Grid2 container direction="column" spacing={1}>
247
296
  {Object.entries(attributes).map(([key, value]) => {
248
297
  if (key === '') {
@@ -251,25 +300,26 @@ export const Attributes = observer(function Attributes({
251
300
  const EditorComponent =
252
301
  reservedKeys.get(key) ?? CustomAttributeValueEditor
253
302
  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}>
303
+ <Grid2 container key={key}>
304
+ <Grid2 size={11}>
261
305
  <EditorComponent
262
306
  session={session}
263
307
  value={value}
264
- onChange={(newValue) => onChangeCommitted(key, newValue)}
308
+ onChange={(newValue: string[]) => {
309
+ updateAttribute(key, newValue)
310
+ }}
311
+ label={key}
265
312
  />
266
313
  </Grid2>
267
- <Grid2>
314
+ <Grid2 size={1}>
268
315
  <IconButton
269
316
  aria-label="delete"
270
317
  size="medium"
271
318
  disabled={!editable}
272
- onClick={() => onChangeCommitted(key)}
319
+ onClick={() => {
320
+ deleteAttribute(key)
321
+ }}
322
+ style={{ marginTop: '10px' }}
273
323
  >
274
324
  <DeleteIcon fontSize="medium" key={key} />
275
325
  </IconButton>
@@ -375,6 +425,6 @@ export const Attributes = observer(function Attributes({
375
425
  {errorMessage ? (
376
426
  <Typography color="error">{errorMessage}</Typography>
377
427
  ) : null}
378
- </>
428
+ </div>
379
429
  )
380
430
  })
@@ -1,4 +1,3 @@
1
- /* eslint-disable @typescript-eslint/use-unknown-in-catch-callback-variable */
2
1
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
2
  import { AnnotationFeature } from '@apollo-annotation/mst'
4
3
  import {
@@ -111,8 +110,7 @@ export const BasicInformation = observer(function BasicInformation({
111
110
  }
112
111
 
113
112
  return (
114
- <>
115
- <Typography variant="h5">Basic information</Typography>
113
+ <div data-testid="basic_information">
116
114
  <NumberTextField
117
115
  margin="dense"
118
116
  id="start"
@@ -183,6 +181,6 @@ export const BasicInformation = observer(function BasicInformation({
183
181
  {errorMessage ? (
184
182
  <Typography color="error">{errorMessage}</Typography>
185
183
  ) : null}
186
- </>
184
+ </div>
187
185
  )
188
186
  })
@@ -5,6 +5,7 @@ import { observer } from 'mobx-react'
5
5
 
6
6
  import { AnnotationFeature } from '@apollo-annotation/mst'
7
7
  import { ApolloFeatureDetailsWidget as ApolloFeatureDetails } from './model'
8
+ import { getFeatureNameOrId } from '../util'
8
9
 
9
10
  export const FeatureDetailsNavigation = observer(
10
11
  function FeatureDetailsNavigation(props: {
@@ -25,8 +26,7 @@ export const FeatureDetailsNavigation = observer(
25
26
  }
26
27
 
27
28
  return (
28
- <div>
29
- <Typography variant="h5">Go to related feature</Typography>
29
+ <div style={{ marginTop: 10 }}>
30
30
  {parent && (
31
31
  <div>
32
32
  <Typography variant="h6">Parent:</Typography>
@@ -36,7 +36,8 @@ export const FeatureDetailsNavigation = observer(
36
36
  model.setFeature(parent)
37
37
  }}
38
38
  >
39
- {parent.type} ({parent.min}..{parent.max})
39
+ {parent.type}
40
+ {getFeatureNameOrId(parent)} ({parent.min}..{parent.max})
40
41
  </Button>
41
42
  </div>
42
43
  )}
@@ -53,7 +54,8 @@ export const FeatureDetailsNavigation = observer(
53
54
  model.setFeature(child)
54
55
  }}
55
56
  >
56
- {child.type} ({child.min}..{child.max})
57
+ {child.type}
58
+ {getFeatureNameOrId(child)} ({child.min}..{child.max})
57
59
  </Button>
58
60
  </div>
59
61
  ))}