@apollo-annotation/jbrowse-plugin-apollo 0.1.0 → 0.1.1
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 +3096 -2525
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +3095 -2524
- 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 +2974 -2103
- 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 +7 -7
- package/src/ApolloInternetAccount/model.ts +9 -9
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +5 -2
- package/src/BackendDrivers/BackendDriver.ts +6 -3
- package/src/BackendDrivers/CollaborationServerDriver.ts +12 -6
- package/src/BackendDrivers/DesktopFileDriver.ts +13 -15
- package/src/BackendDrivers/InMemoryFileDriver.ts +9 -3
- package/src/ChangeManager.ts +6 -3
- package/src/FeatureDetailsWidget/ApolloFeatureDetailsWidget.tsx +86 -0
- package/src/FeatureDetailsWidget/Attributes.tsx +374 -0
- package/src/FeatureDetailsWidget/BasicInformation.tsx +178 -0
- package/src/FeatureDetailsWidget/NumberTextField.tsx +75 -0
- package/src/FeatureDetailsWidget/RelatedFeature.tsx +87 -0
- package/src/FeatureDetailsWidget/Sequence.tsx +88 -0
- package/src/FeatureDetailsWidget/StringTextField.tsx +60 -0
- package/src/FeatureDetailsWidget/index.ts +2 -0
- package/src/FeatureDetailsWidget/model.ts +67 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +5 -2
- package/src/LinearApolloDisplay/glyphs/CanonicalGeneGlyph.ts +12 -4
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +1 -1
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +21 -2
- package/src/LinearApolloDisplay/glyphs/ImplicitExonGeneGlyph.ts +18 -5
- package/src/LinearApolloDisplay/stateModel/base.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/getGlyph.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/glyphs.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/layouts.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/rendering.ts +35 -3
- package/src/OntologyManager/util.ts +33 -0
- package/src/SixFrameFeatureDisplay/stateModel.ts +1 -1
- package/src/TabularEditor/HybridGrid/ChangeHandling.ts +2 -2
- package/src/TabularEditor/HybridGrid/Feature.tsx +3 -3
- package/src/TabularEditor/HybridGrid/FeatureAttributes.tsx +1 -1
- package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +1 -1
- package/src/components/AddAssembly.tsx +5 -5
- package/src/components/AddChildFeature.tsx +13 -29
- package/src/components/AddFeature.tsx +1 -1
- package/src/components/CopyFeature.tsx +5 -2
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/DeleteFeature.tsx +2 -2
- package/src/components/DownloadGFF3.tsx +2 -2
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageUsers.tsx +1 -1
- package/src/components/ModifyFeatureAttribute.tsx +2 -2
- package/src/components/ViewChangeLog.tsx +7 -5
- package/src/extensions/annotationFromPileup.ts +2 -2
- package/src/index.ts +26 -8
- package/src/makeDisplayComponent.tsx +1 -2
- package/src/session/ClientDataStore.ts +6 -6
- package/src/session/session.ts +6 -3
- package/src/util/loadAssemblyIntoClient.ts +6 -3
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { AnnotationFeatureI } from '@apollo-annotation/apollo-mst'
|
|
2
|
+
import {
|
|
3
|
+
LocationEndChange,
|
|
4
|
+
LocationStartChange,
|
|
5
|
+
StrandChange,
|
|
6
|
+
TypeChange,
|
|
7
|
+
} from '@apollo-annotation/apollo-shared'
|
|
8
|
+
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
9
|
+
import {
|
|
10
|
+
FormControl,
|
|
11
|
+
FormControlLabel,
|
|
12
|
+
FormLabel,
|
|
13
|
+
Radio,
|
|
14
|
+
RadioGroup,
|
|
15
|
+
TextField,
|
|
16
|
+
Typography,
|
|
17
|
+
} from '@mui/material'
|
|
18
|
+
import { observer } from 'mobx-react'
|
|
19
|
+
import React, { useState } from 'react'
|
|
20
|
+
|
|
21
|
+
import { OntologyTermAutocomplete } from '../components/OntologyTermAutocomplete'
|
|
22
|
+
import { isOntologyClass } from '../OntologyManager'
|
|
23
|
+
import OntologyStore from '../OntologyManager/OntologyStore'
|
|
24
|
+
import { fetchValidDescendantTerms } from '../OntologyManager/util'
|
|
25
|
+
import { ApolloSessionModel } from '../session'
|
|
26
|
+
import { NumberTextField } from './NumberTextField'
|
|
27
|
+
|
|
28
|
+
export const BasicInformation = observer(function BasicInformation({
|
|
29
|
+
assembly,
|
|
30
|
+
feature,
|
|
31
|
+
session,
|
|
32
|
+
}: {
|
|
33
|
+
feature: AnnotationFeatureI
|
|
34
|
+
session: ApolloSessionModel
|
|
35
|
+
assembly: string
|
|
36
|
+
}) {
|
|
37
|
+
const [errorMessage, setErrorMessage] = useState('')
|
|
38
|
+
const [typeWarningText, setTypeWarningText] = useState('')
|
|
39
|
+
|
|
40
|
+
const { _id, assemblyId, end, start, strand, type } = feature
|
|
41
|
+
|
|
42
|
+
const notifyError = (e: Error) =>
|
|
43
|
+
(session as unknown as AbstractSessionModel).notify(e.message, 'error')
|
|
44
|
+
|
|
45
|
+
const { changeManager } = session.apolloDataStore
|
|
46
|
+
function handleTypeChange(newType: string) {
|
|
47
|
+
setErrorMessage('')
|
|
48
|
+
const featureId = _id
|
|
49
|
+
const change = new TypeChange({
|
|
50
|
+
typeName: 'TypeChange',
|
|
51
|
+
changedIds: [featureId],
|
|
52
|
+
featureId,
|
|
53
|
+
oldType: type,
|
|
54
|
+
newType,
|
|
55
|
+
assembly: assemblyId,
|
|
56
|
+
})
|
|
57
|
+
return changeManager.submit(change)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function handleStrandChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
61
|
+
const { value } = event.target
|
|
62
|
+
const newStrand = value ? (Number(value) as 1 | -1) : undefined
|
|
63
|
+
const change = new StrandChange({
|
|
64
|
+
typeName: 'StrandChange',
|
|
65
|
+
changedIds: [_id],
|
|
66
|
+
featureId: _id,
|
|
67
|
+
oldStrand: strand,
|
|
68
|
+
newStrand,
|
|
69
|
+
assembly,
|
|
70
|
+
})
|
|
71
|
+
return changeManager.submit(change)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleStartChange(newStart: number) {
|
|
75
|
+
newStart--
|
|
76
|
+
const change = new LocationStartChange({
|
|
77
|
+
typeName: 'LocationStartChange',
|
|
78
|
+
changedIds: [_id],
|
|
79
|
+
featureId: _id,
|
|
80
|
+
oldStart: start,
|
|
81
|
+
newStart,
|
|
82
|
+
assembly,
|
|
83
|
+
})
|
|
84
|
+
return changeManager.submit(change)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function handleEndChange(newEnd: number) {
|
|
88
|
+
const change = new LocationEndChange({
|
|
89
|
+
typeName: 'LocationEndChange',
|
|
90
|
+
changedIds: [_id],
|
|
91
|
+
featureId: _id,
|
|
92
|
+
oldEnd: end,
|
|
93
|
+
newEnd,
|
|
94
|
+
assembly,
|
|
95
|
+
})
|
|
96
|
+
return changeManager.submit(change)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function fetchValidTerms(
|
|
100
|
+
parentFeature: AnnotationFeatureI | undefined,
|
|
101
|
+
ontologyStore: OntologyStore,
|
|
102
|
+
_signal: AbortSignal,
|
|
103
|
+
) {
|
|
104
|
+
const terms = await fetchValidDescendantTerms(
|
|
105
|
+
parentFeature,
|
|
106
|
+
ontologyStore,
|
|
107
|
+
_signal,
|
|
108
|
+
)
|
|
109
|
+
if (!terms) {
|
|
110
|
+
setTypeWarningText(
|
|
111
|
+
`Type "${parentFeature?.type}" does not have any children in the ontology`,
|
|
112
|
+
)
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
return terms
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (
|
|
119
|
+
<>
|
|
120
|
+
<Typography variant="h4">Basic information</Typography>
|
|
121
|
+
<NumberTextField
|
|
122
|
+
margin="dense"
|
|
123
|
+
id="start"
|
|
124
|
+
label="Start"
|
|
125
|
+
fullWidth
|
|
126
|
+
variant="outlined"
|
|
127
|
+
value={start + 1}
|
|
128
|
+
onChangeCommitted={handleStartChange}
|
|
129
|
+
/>
|
|
130
|
+
<NumberTextField
|
|
131
|
+
margin="dense"
|
|
132
|
+
id="end"
|
|
133
|
+
label="End"
|
|
134
|
+
fullWidth
|
|
135
|
+
variant="outlined"
|
|
136
|
+
value={end}
|
|
137
|
+
onChangeCommitted={handleEndChange}
|
|
138
|
+
/>
|
|
139
|
+
<OntologyTermAutocomplete
|
|
140
|
+
session={session}
|
|
141
|
+
ontologyName="Sequence Ontology"
|
|
142
|
+
value={type}
|
|
143
|
+
filterTerms={isOntologyClass}
|
|
144
|
+
fetchValidTerms={fetchValidTerms.bind(null, feature)}
|
|
145
|
+
renderInput={(params) => (
|
|
146
|
+
<TextField
|
|
147
|
+
{...params}
|
|
148
|
+
label="Type"
|
|
149
|
+
variant="outlined"
|
|
150
|
+
fullWidth
|
|
151
|
+
error={Boolean(typeWarningText)}
|
|
152
|
+
helperText={typeWarningText}
|
|
153
|
+
/>
|
|
154
|
+
)}
|
|
155
|
+
onChange={(oldValue, newValue) => {
|
|
156
|
+
if (newValue) {
|
|
157
|
+
handleTypeChange(newValue).catch(notifyError)
|
|
158
|
+
}
|
|
159
|
+
}}
|
|
160
|
+
/>
|
|
161
|
+
<FormControl>
|
|
162
|
+
<FormLabel>Strand</FormLabel>
|
|
163
|
+
<RadioGroup row value={strand ?? ''} onChange={handleStrandChange}>
|
|
164
|
+
<FormControlLabel value="1" control={<Radio />} label="Forward (+)" />
|
|
165
|
+
<FormControlLabel
|
|
166
|
+
value="-1"
|
|
167
|
+
control={<Radio />}
|
|
168
|
+
label="Reverse (-)"
|
|
169
|
+
/>
|
|
170
|
+
<FormControlLabel value="" control={<Radio />} label="None" />
|
|
171
|
+
</RadioGroup>
|
|
172
|
+
</FormControl>
|
|
173
|
+
{errorMessage ? (
|
|
174
|
+
<Typography color="error">{errorMessage}</Typography>
|
|
175
|
+
) : null}
|
|
176
|
+
</>
|
|
177
|
+
)
|
|
178
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { TextField, TextFieldProps } from '@mui/material'
|
|
2
|
+
import { observer } from 'mobx-react'
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
interface NumberTextFieldProps
|
|
6
|
+
extends Omit<
|
|
7
|
+
TextFieldProps,
|
|
8
|
+
| 'type'
|
|
9
|
+
| 'onChange'
|
|
10
|
+
| 'onKeyDown'
|
|
11
|
+
| 'onBlur'
|
|
12
|
+
| 'ref'
|
|
13
|
+
| 'error'
|
|
14
|
+
| 'helperText'
|
|
15
|
+
> {
|
|
16
|
+
onChangeCommitted(newValue: number): void
|
|
17
|
+
value: unknown
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const NumberTextField = observer(function NumberTextField({
|
|
21
|
+
onChangeCommitted,
|
|
22
|
+
value: initialValue,
|
|
23
|
+
...props
|
|
24
|
+
}: NumberTextFieldProps) {
|
|
25
|
+
const [value, setValue] = useState(String(initialValue))
|
|
26
|
+
const [blur, setBlur] = useState(false)
|
|
27
|
+
const [inputNode, setInputNode] = useState<HTMLDivElement | null>(null)
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setValue(String(initialValue))
|
|
31
|
+
}, [initialValue])
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (blur) {
|
|
35
|
+
inputNode?.blur()
|
|
36
|
+
setBlur(false)
|
|
37
|
+
}
|
|
38
|
+
}, [blur, inputNode])
|
|
39
|
+
|
|
40
|
+
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
41
|
+
setValue(event.target.value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const error = Number.isNaN(Number(value))
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<TextField
|
|
48
|
+
{...props}
|
|
49
|
+
type="text"
|
|
50
|
+
onChange={onChange}
|
|
51
|
+
value={value}
|
|
52
|
+
onKeyDown={(event) => {
|
|
53
|
+
if (event.key === 'Enter') {
|
|
54
|
+
inputNode?.blur()
|
|
55
|
+
} else if (event.key === 'Escape') {
|
|
56
|
+
setValue(String(initialValue))
|
|
57
|
+
setBlur(true)
|
|
58
|
+
}
|
|
59
|
+
}}
|
|
60
|
+
onBlur={() => {
|
|
61
|
+
const valueAsNumber = Number(value)
|
|
62
|
+
if (value !== String(initialValue)) {
|
|
63
|
+
if (Number.isNaN(valueAsNumber)) {
|
|
64
|
+
setValue(String(initialValue))
|
|
65
|
+
} else {
|
|
66
|
+
onChangeCommitted(valueAsNumber)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}}
|
|
70
|
+
inputRef={(node) => setInputNode(node)}
|
|
71
|
+
error={error}
|
|
72
|
+
helperText={error ? 'Not a valid number' : undefined}
|
|
73
|
+
/>
|
|
74
|
+
)
|
|
75
|
+
})
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnnotationFeature,
|
|
3
|
+
AnnotationFeatureI,
|
|
4
|
+
} from '@apollo-annotation/apollo-mst'
|
|
5
|
+
import { SessionWithWidgets } from '@jbrowse/core/util'
|
|
6
|
+
import { Button, Paper, Typography } from '@mui/material'
|
|
7
|
+
import { observer } from 'mobx-react'
|
|
8
|
+
import { IMSTMap } from 'mobx-state-tree'
|
|
9
|
+
import React from 'react'
|
|
10
|
+
import { makeStyles } from 'tss-react/mui'
|
|
11
|
+
|
|
12
|
+
import { ApolloSessionModel } from '../session'
|
|
13
|
+
|
|
14
|
+
const useStyles = makeStyles()((theme) => ({
|
|
15
|
+
paper: {
|
|
16
|
+
margin: theme.spacing(2),
|
|
17
|
+
padding: theme.spacing(2),
|
|
18
|
+
display: 'flex',
|
|
19
|
+
flexDirection: 'column',
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
export const RelatedFeatures = observer(function RelatedFeatures({
|
|
24
|
+
assembly,
|
|
25
|
+
feature,
|
|
26
|
+
refName,
|
|
27
|
+
session,
|
|
28
|
+
}: {
|
|
29
|
+
feature: AnnotationFeatureI
|
|
30
|
+
refName: string
|
|
31
|
+
session: ApolloSessionModel
|
|
32
|
+
assembly: string
|
|
33
|
+
}) {
|
|
34
|
+
const { classes } = useStyles()
|
|
35
|
+
const { parent } = feature
|
|
36
|
+
const { children } = feature as {
|
|
37
|
+
children?: IMSTMap<typeof AnnotationFeature>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const onButtonClick = (newFeature: AnnotationFeatureI) => {
|
|
41
|
+
if (parent) {
|
|
42
|
+
const apolloFeatureWidget = (
|
|
43
|
+
session as unknown as SessionWithWidgets
|
|
44
|
+
).addWidget('ApolloFeatureDetailsWidget', 'apolloFeatureDetailsWidget', {
|
|
45
|
+
feature: newFeature,
|
|
46
|
+
assembly,
|
|
47
|
+
refName,
|
|
48
|
+
})
|
|
49
|
+
;(session as unknown as SessionWithWidgets).showWidget?.(
|
|
50
|
+
apolloFeatureWidget,
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!(parent || (children && children.size > 0))) {
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
58
|
+
return (
|
|
59
|
+
<>
|
|
60
|
+
<Typography variant="h4">Related features</Typography>
|
|
61
|
+
{parent && (
|
|
62
|
+
<>
|
|
63
|
+
<Typography variant="h5">Parent</Typography>
|
|
64
|
+
<Paper elevation={6} className={classes.paper}>
|
|
65
|
+
{`Start: ${parent.start}, End: ${parent.end}, Type: ${parent.type}`}
|
|
66
|
+
<Button variant="contained" onClick={() => onButtonClick(parent)}>
|
|
67
|
+
Go to parent
|
|
68
|
+
</Button>
|
|
69
|
+
</Paper>
|
|
70
|
+
</>
|
|
71
|
+
)}
|
|
72
|
+
{children && children.size > 0 && (
|
|
73
|
+
<>
|
|
74
|
+
<Typography variant="h5">Children</Typography>
|
|
75
|
+
{[...children.values()].map((child) => (
|
|
76
|
+
<Paper elevation={6} className={classes.paper} key={child._id}>
|
|
77
|
+
{`Start: ${child.start}, End: ${child.end}, Type: ${child.type}`}
|
|
78
|
+
<Button variant="contained" onClick={() => onButtonClick(child)}>
|
|
79
|
+
Go to child
|
|
80
|
+
</Button>
|
|
81
|
+
</Paper>
|
|
82
|
+
))}
|
|
83
|
+
</>
|
|
84
|
+
)}
|
|
85
|
+
</>
|
|
86
|
+
)
|
|
87
|
+
})
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { AnnotationFeatureI } from '@apollo-annotation/apollo-mst'
|
|
2
|
+
import { splitStringIntoChunks } from '@apollo-annotation/apollo-shared'
|
|
3
|
+
import { Button, Typography } from '@mui/material'
|
|
4
|
+
import { observer } from 'mobx-react'
|
|
5
|
+
import React, { useState } from 'react'
|
|
6
|
+
import { makeStyles } from 'tss-react/mui'
|
|
7
|
+
|
|
8
|
+
import { ApolloSessionModel } from '../session'
|
|
9
|
+
|
|
10
|
+
function formatSequence(
|
|
11
|
+
seq: string,
|
|
12
|
+
refName: string,
|
|
13
|
+
start: number,
|
|
14
|
+
end: number,
|
|
15
|
+
wrap?: number,
|
|
16
|
+
) {
|
|
17
|
+
const header = `>${refName}:${start + 1}–${end}\n`
|
|
18
|
+
const body =
|
|
19
|
+
wrap === undefined ? seq : splitStringIntoChunks(seq, wrap).join('\n')
|
|
20
|
+
return `${header}${body}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const useStyles = makeStyles()({
|
|
24
|
+
sequence: {
|
|
25
|
+
width: '100%',
|
|
26
|
+
resize: 'vertical',
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const Sequence = observer(function Sequence({
|
|
31
|
+
assembly,
|
|
32
|
+
feature,
|
|
33
|
+
refName,
|
|
34
|
+
session,
|
|
35
|
+
}: {
|
|
36
|
+
assembly: string
|
|
37
|
+
feature: AnnotationFeatureI
|
|
38
|
+
refName: string
|
|
39
|
+
session: ApolloSessionModel
|
|
40
|
+
}) {
|
|
41
|
+
const currentAssembly = session.apolloDataStore.assemblies.get(assembly)
|
|
42
|
+
const [showSequence, setShowSequence] = useState(false)
|
|
43
|
+
const { classes } = useStyles()
|
|
44
|
+
|
|
45
|
+
const onButtonClick = () => {
|
|
46
|
+
setShowSequence(!showSequence)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!(feature && currentAssembly)) {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
const refSeq = currentAssembly.getByRefName(refName)
|
|
53
|
+
if (!refSeq) {
|
|
54
|
+
return null
|
|
55
|
+
}
|
|
56
|
+
const { end, start } = feature
|
|
57
|
+
let sequence = ''
|
|
58
|
+
if (showSequence) {
|
|
59
|
+
sequence = refSeq.getSequence(start, end)
|
|
60
|
+
if (sequence) {
|
|
61
|
+
sequence = formatSequence(sequence, refName, start, end)
|
|
62
|
+
} else {
|
|
63
|
+
void session.apolloDataStore.loadRefSeq([
|
|
64
|
+
{ assemblyName: assembly, refName, start, end },
|
|
65
|
+
])
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<>
|
|
71
|
+
<Typography variant="h4">Sequence</Typography>
|
|
72
|
+
<Button variant="contained" onClick={onButtonClick}>
|
|
73
|
+
{showSequence ? 'Hide sequence' : 'Show sequence'}
|
|
74
|
+
</Button>
|
|
75
|
+
<div>
|
|
76
|
+
{showSequence && (
|
|
77
|
+
<textarea
|
|
78
|
+
readOnly
|
|
79
|
+
rows={20}
|
|
80
|
+
className={classes.sequence}
|
|
81
|
+
value={sequence}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</>
|
|
86
|
+
)
|
|
87
|
+
})
|
|
88
|
+
export default Sequence
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TextField, TextFieldProps } from '@mui/material'
|
|
2
|
+
import { observer } from 'mobx-react'
|
|
3
|
+
import React, { useEffect, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
interface StringTextFieldProps
|
|
6
|
+
extends Omit<
|
|
7
|
+
TextFieldProps,
|
|
8
|
+
'type' | 'onChange' | 'onKeyDown' | 'onBlur' | 'ref'
|
|
9
|
+
> {
|
|
10
|
+
onChangeCommitted(newValue: string): void
|
|
11
|
+
value: unknown
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const StringTextField = observer(function StringTextField({
|
|
15
|
+
onChangeCommitted,
|
|
16
|
+
value: initialValue,
|
|
17
|
+
...props
|
|
18
|
+
}: StringTextFieldProps) {
|
|
19
|
+
const [value, setValue] = useState(String(initialValue))
|
|
20
|
+
const [blur, setBlur] = useState(false)
|
|
21
|
+
const [inputNode, setInputNode] = useState<HTMLDivElement | null>(null)
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
setValue(String(initialValue))
|
|
25
|
+
}, [initialValue])
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (blur) {
|
|
29
|
+
inputNode?.blur()
|
|
30
|
+
setBlur(false)
|
|
31
|
+
}
|
|
32
|
+
}, [blur, inputNode])
|
|
33
|
+
|
|
34
|
+
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
|
35
|
+
setValue(event.target.value)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<TextField
|
|
40
|
+
{...props}
|
|
41
|
+
type="text"
|
|
42
|
+
onChange={onChange}
|
|
43
|
+
value={value}
|
|
44
|
+
onKeyDown={(event) => {
|
|
45
|
+
if (event.key === 'Enter') {
|
|
46
|
+
inputNode?.blur()
|
|
47
|
+
} else if (event.key === 'Escape') {
|
|
48
|
+
setValue(String(initialValue))
|
|
49
|
+
setBlur(true)
|
|
50
|
+
}
|
|
51
|
+
}}
|
|
52
|
+
onBlur={() => {
|
|
53
|
+
if (value !== String(initialValue)) {
|
|
54
|
+
onChangeCommitted(value)
|
|
55
|
+
}
|
|
56
|
+
}}
|
|
57
|
+
inputRef={(node) => setInputNode(node)}
|
|
58
|
+
/>
|
|
59
|
+
)
|
|
60
|
+
})
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnnotationFeature,
|
|
3
|
+
AnnotationFeatureI,
|
|
4
|
+
} from '@apollo-annotation/apollo-mst'
|
|
5
|
+
import { getSession } from '@jbrowse/core/util'
|
|
6
|
+
import { ElementId } from '@jbrowse/core/util/types/mst'
|
|
7
|
+
import { autorun } from 'mobx'
|
|
8
|
+
import { Instance, SnapshotIn, addDisposer, types } from 'mobx-state-tree'
|
|
9
|
+
|
|
10
|
+
import { ApolloSessionModel } from '../session'
|
|
11
|
+
|
|
12
|
+
export const ApolloFeatureDetailsWidgetModel = types
|
|
13
|
+
.model('ApolloFeatureDetailsWidget', {
|
|
14
|
+
id: ElementId,
|
|
15
|
+
type: types.literal('ApolloFeatureDetailsWidget'),
|
|
16
|
+
feature: types.maybe(
|
|
17
|
+
types.reference(AnnotationFeature, {
|
|
18
|
+
onInvalidated(ev) {
|
|
19
|
+
ev.parent.setTryReload(ev.invalidId)
|
|
20
|
+
ev.removeRef()
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
assembly: types.string,
|
|
25
|
+
refName: types.string,
|
|
26
|
+
})
|
|
27
|
+
.volatile(() => ({
|
|
28
|
+
tryReload: undefined as string | undefined,
|
|
29
|
+
}))
|
|
30
|
+
.actions((self) => ({
|
|
31
|
+
setFeature(feature: AnnotationFeatureI) {
|
|
32
|
+
self.feature = feature
|
|
33
|
+
},
|
|
34
|
+
setTryReload(featureId?: string) {
|
|
35
|
+
self.tryReload = featureId
|
|
36
|
+
},
|
|
37
|
+
}))
|
|
38
|
+
.actions((self) => ({
|
|
39
|
+
afterAttach() {
|
|
40
|
+
addDisposer(
|
|
41
|
+
self,
|
|
42
|
+
autorun((reaction) => {
|
|
43
|
+
if (!self.tryReload) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
const session = getSession(self) as unknown as ApolloSessionModel
|
|
47
|
+
const { apolloDataStore } = session
|
|
48
|
+
if (!apolloDataStore) {
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
const feature = apolloDataStore.getFeature(self.tryReload)
|
|
52
|
+
if (feature) {
|
|
53
|
+
self.setFeature(feature)
|
|
54
|
+
self.setTryReload()
|
|
55
|
+
reaction.dispose()
|
|
56
|
+
}
|
|
57
|
+
}),
|
|
58
|
+
)
|
|
59
|
+
},
|
|
60
|
+
}))
|
|
61
|
+
|
|
62
|
+
export type ApolloFeatureDetailsWidget = Instance<
|
|
63
|
+
typeof ApolloFeatureDetailsWidgetModel
|
|
64
|
+
>
|
|
65
|
+
export type ApolloFeatureDetailsWidgetSnapshot = SnapshotIn<
|
|
66
|
+
typeof ApolloFeatureDetailsWidgetModel
|
|
67
|
+
>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { AnnotationFeatureI } from '@apollo-annotation/apollo-mst'
|
|
2
|
+
import {
|
|
3
|
+
LocationEndChange,
|
|
4
|
+
LocationStartChange,
|
|
5
|
+
} from '@apollo-annotation/apollo-shared'
|
|
1
6
|
import { Theme, alpha } from '@mui/material'
|
|
2
|
-
import { AnnotationFeatureI } from 'apollo-mst'
|
|
3
|
-
import { LocationEndChange, LocationStartChange } from 'apollo-shared'
|
|
4
7
|
|
|
5
8
|
import { LinearApolloDisplay } from '../stateModel'
|
|
6
9
|
import { MousePosition } from '../stateModel/mouseEvents'
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AnnotationFeatureI } from 'apollo-mst'
|
|
1
|
+
import { AnnotationFeatureI } from '@apollo-annotation/apollo-mst'
|
|
3
2
|
import {
|
|
4
3
|
DiscontinuousLocationEndChange,
|
|
5
4
|
DiscontinuousLocationStartChange,
|
|
6
5
|
LocationEndChange,
|
|
7
6
|
LocationStartChange,
|
|
8
|
-
} from 'apollo-shared'
|
|
7
|
+
} from '@apollo-annotation/apollo-shared'
|
|
8
|
+
import { alpha } from '@mui/material'
|
|
9
9
|
|
|
10
10
|
import { LinearApolloDisplay } from '../stateModel'
|
|
11
11
|
import {
|
|
12
12
|
CDSDiscontinuousLocation,
|
|
13
13
|
MousePosition,
|
|
14
14
|
} from '../stateModel/mouseEvents'
|
|
15
|
+
import { frameColors, getFrame } from '../stateModel/rendering'
|
|
15
16
|
import { CanvasMouseEvent } from '../types'
|
|
16
17
|
import { Glyph } from './Glyph'
|
|
17
18
|
|
|
@@ -248,10 +249,17 @@ export class CanonicalGeneGlyph extends Glyph {
|
|
|
248
249
|
ctx.fillRect(startPx, cdsTop, widthPx, cdsHeight)
|
|
249
250
|
if (widthPx > 2) {
|
|
250
251
|
ctx.clearRect(startPx + 1, cdsTop + 1, widthPx - 2, cdsHeight - 2)
|
|
252
|
+
const frame = getFrame(
|
|
253
|
+
cdsLocation.start,
|
|
254
|
+
cdsLocation.end,
|
|
255
|
+
cdsLocation.strand,
|
|
256
|
+
cdsLocation.phase,
|
|
257
|
+
)
|
|
258
|
+
const cdsColorCode = frameColors.at(frame) ?? 'rgb(171,71,188)'
|
|
251
259
|
ctx.fillStyle =
|
|
252
260
|
apolloSelectedFeature && cds._id === apolloSelectedFeature._id
|
|
253
261
|
? 'rgb(0,0,0)'
|
|
254
|
-
:
|
|
262
|
+
: cdsColorCode
|
|
255
263
|
ctx.fillRect(startPx + 1, cdsTop + 1, widthPx - 2, cdsHeight - 2)
|
|
256
264
|
if (forwardFill && backwardFill && strand) {
|
|
257
265
|
const reversal = reversed ? -1 : 1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { AnnotationFeatureI } from '@apollo-annotation/apollo-mst'
|
|
1
2
|
import { MenuItem } from '@jbrowse/core/ui'
|
|
2
|
-
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
3
|
+
import { AbstractSessionModel, SessionWithWidgets } from '@jbrowse/core/util'
|
|
3
4
|
import { alpha } from '@mui/material'
|
|
4
|
-
import { AnnotationFeatureI } from 'apollo-mst'
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
AddChildFeature,
|
|
@@ -375,6 +375,25 @@ export abstract class Glyph {
|
|
|
375
375
|
)
|
|
376
376
|
},
|
|
377
377
|
},
|
|
378
|
+
{
|
|
379
|
+
label: 'Edit feature details',
|
|
380
|
+
onClick: () => {
|
|
381
|
+
const apolloFeatureWidget = (
|
|
382
|
+
session as unknown as SessionWithWidgets
|
|
383
|
+
).addWidget(
|
|
384
|
+
'ApolloFeatureDetailsWidget',
|
|
385
|
+
'apolloFeatureDetailsWidget',
|
|
386
|
+
{
|
|
387
|
+
feature: sourceFeature,
|
|
388
|
+
assembly: currentAssemblyId,
|
|
389
|
+
refName: region.refName,
|
|
390
|
+
},
|
|
391
|
+
)
|
|
392
|
+
;(session as unknown as SessionWithWidgets).showWidget?.(
|
|
393
|
+
apolloFeatureWidget,
|
|
394
|
+
)
|
|
395
|
+
},
|
|
396
|
+
},
|
|
378
397
|
)
|
|
379
398
|
}
|
|
380
399
|
return menuItems
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
import { AnnotationFeatureI } from '@apollo-annotation/apollo-mst'
|
|
2
|
+
import {
|
|
3
|
+
LocationEndChange,
|
|
4
|
+
LocationStartChange,
|
|
5
|
+
} from '@apollo-annotation/apollo-shared'
|
|
1
6
|
import { alpha } from '@mui/material'
|
|
2
|
-
import { AnnotationFeatureI } from 'apollo-mst'
|
|
3
|
-
import { LocationEndChange, LocationStartChange } from 'apollo-shared'
|
|
4
7
|
|
|
5
8
|
import { LinearApolloDisplay } from '../stateModel'
|
|
6
9
|
import { MousePosition } from '../stateModel/mouseEvents'
|
|
10
|
+
import { frameColors, getFrame } from '../stateModel/rendering'
|
|
7
11
|
import { CanvasMouseEvent } from '../types'
|
|
8
12
|
import { Glyph } from './Glyph'
|
|
9
13
|
|
|
@@ -126,13 +130,22 @@ export class ImplicitExonGeneGlyph extends Glyph {
|
|
|
126
130
|
ctx.fillRect(startPx, cdsOrUTRTop, widthPx, height)
|
|
127
131
|
if (widthPx > 2) {
|
|
128
132
|
ctx.clearRect(startPx + 1, cdsOrUTRTop + 1, widthPx - 2, height - 2)
|
|
133
|
+
let colorCode = 'rgb(211,211,211)'
|
|
134
|
+
if (isCDS) {
|
|
135
|
+
const frame = getFrame(
|
|
136
|
+
cdsOrUTR.start,
|
|
137
|
+
cdsOrUTR.end,
|
|
138
|
+
cdsOrUTR.strand,
|
|
139
|
+
cdsOrUTR.phase,
|
|
140
|
+
)
|
|
141
|
+
const color = frameColors.at(frame)
|
|
142
|
+
colorCode = color ?? 'rgb(171,71,188)'
|
|
143
|
+
}
|
|
129
144
|
ctx.fillStyle =
|
|
130
145
|
apolloSelectedFeature &&
|
|
131
146
|
cdsOrUTR._id === apolloSelectedFeature._id
|
|
132
147
|
? 'rgb(0,0,0)'
|
|
133
|
-
:
|
|
134
|
-
? 'rgb(171,71,188)'
|
|
135
|
-
: 'rgb(211,211,211)'
|
|
148
|
+
: colorCode
|
|
136
149
|
ctx.fillRect(startPx + 1, cdsOrUTRTop + 1, widthPx - 2, height - 2)
|
|
137
150
|
if (forwardFill && backwardFill && strand) {
|
|
138
151
|
const reversal = reversed ? -1 : 1
|