@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.
- package/dist/index.esm.js +1513 -1074
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +1510 -1065
- package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.development.js +4681 -2097
- package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
- package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
- package/package.json +4 -4
- package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +13 -0
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +88 -17
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +144 -22
- package/src/FeatureDetailsWidget/Attributes.tsx +93 -43
- package/src/FeatureDetailsWidget/BasicInformation.tsx +2 -4
- package/src/FeatureDetailsWidget/FeatureDetailsNavigation.tsx +6 -4
- package/src/FeatureDetailsWidget/Sequence.tsx +16 -33
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +137 -92
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +600 -0
- package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +54 -0
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +13 -6
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +6 -27
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +35 -13
- package/src/LinearApolloDisplay/stateModel/layouts.ts +7 -2
- package/src/LinearApolloDisplay/stateModel/rendering.ts +4 -0
- package/src/OntologyManager/OntologyStore/fulltext-stopwords.ts +10 -1
- package/src/OntologyManager/OntologyStore/index.test.ts +1 -0
- package/src/OntologyManager/index.ts +2 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +5 -1
- package/src/TabularEditor/HybridGrid/Feature.tsx +0 -1
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +8 -1
- package/src/TabularEditor/HybridGrid/ToolBar.tsx +14 -12
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +3 -27
- package/src/components/AddAssembly.tsx +608 -291
- package/src/components/CreateApolloAnnotation.tsx +144 -37
- package/src/components/OntologyTermMultiSelect.tsx +3 -0
- package/src/components/index.ts +0 -1
- package/src/extensions/annotationFromJBrowseFeature.ts +3 -2
- package/src/makeDisplayComponent.tsx +3 -4
- package/src/util/annotationFeatureUtils.ts +53 -0
- package/src/util/index.ts +1 -0
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +0 -200
- 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.
|
|
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.
|
|
52
|
-
"@apollo-annotation/mst": "^0.3.
|
|
53
|
-
"@apollo-annotation/shared": "^0.3.
|
|
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
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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:
|
|
36
|
-
|
|
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:
|
|
48
|
+
(props: {
|
|
49
|
+
session: ApolloSessionModel
|
|
50
|
+
value: string[]
|
|
51
|
+
onChange: (newValue: string[]) => void
|
|
52
|
+
}) => {
|
|
42
53
|
return (
|
|
43
|
-
<OntologyTermMultiSelect
|
|
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:
|
|
74
|
-
|
|
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
|
-
|
|
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 =
|
|
104
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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={() =>
|
|
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}
|
|
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}
|
|
57
|
+
{child.type}
|
|
58
|
+
{getFeatureNameOrId(child)} ({child.min}..{child.max})
|
|
57
59
|
</Button>
|
|
58
60
|
</div>
|
|
59
61
|
))}
|