@apollo-annotation/jbrowse-plugin-apollo 0.1.21 → 0.2.0
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 +431 -570
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +439 -578
- 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 +11064 -1091
- 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 -5
- package/src/ApolloInternetAccount/components/AuthTypeSelector.tsx +4 -2
- package/src/ApolloInternetAccount/configSchema.ts +1 -1
- package/src/ApolloInternetAccount/model.ts +5 -10
- package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +1 -1
- package/src/ApolloSixFrameRenderer/components/ApolloRendering.tsx +4 -5
- package/src/BackendDrivers/DesktopFileDriver.ts +3 -2
- package/src/FeatureDetailsWidget/Attributes.tsx +1 -6
- package/src/FeatureDetailsWidget/NumberTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/StringTextField.tsx +1 -0
- package/src/FeatureDetailsWidget/TranscriptBasic.tsx +131 -382
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +209 -284
- package/src/FeatureDetailsWidget/model.ts +4 -4
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +1 -0
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +25 -3
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +95 -32
- package/src/LinearApolloDisplay/stateModel/base.ts +5 -3
- package/src/LinearApolloDisplay/stateModel/index.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +1 -1
- package/src/LinearApolloDisplay/stateModel/rendering.ts +1 -1
- package/src/OntologyManager/OntologyStore/fulltext.ts +5 -2
- package/src/OntologyManager/OntologyStore/index.ts +25 -22
- package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +8 -3
- package/src/OntologyManager/index.ts +31 -8
- package/src/SixFrameFeatureDisplay/stateModel.ts +1 -1
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +1 -0
- package/src/TabularEditor/HybridGrid/NumberCell.tsx +1 -0
- package/src/TabularEditor/model.ts +1 -1
- package/src/components/AddChildFeature.tsx +1 -0
- package/src/components/AddFeature.tsx +1 -1
- package/src/components/AddRefSeqAliases.tsx +1 -0
- package/src/components/CopyFeature.tsx +1 -0
- package/src/components/DeleteAssembly.tsx +1 -0
- package/src/components/DeleteFeature.tsx +1 -0
- package/src/components/LogOut.tsx +2 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/ManageUsers.tsx +2 -1
- package/src/components/OntologyTermAutocomplete.tsx +7 -9
- package/src/components/OntologyTermMultiSelect.tsx +2 -1
- package/src/components/OpenLocalFile.tsx +3 -1
- package/src/components/ViewChangeLog.tsx +1 -0
- package/src/components/ViewCheckResults.tsx +1 -0
- package/src/config.ts +5 -0
- package/src/extensions/annotationFromPileup.ts +1 -1
- package/src/makeDisplayComponent.tsx +28 -7
- package/src/session/ClientDataStore.ts +1 -1
- package/src/session/session.ts +2 -1
|
@@ -1,131 +1,149 @@
|
|
|
1
|
-
import { AnnotationFeature
|
|
1
|
+
import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
2
2
|
import { splitStringIntoChunks } from '@apollo-annotation/shared'
|
|
3
3
|
import { revcom } from '@jbrowse/core/util'
|
|
4
4
|
import {
|
|
5
5
|
Button,
|
|
6
6
|
MenuItem,
|
|
7
|
+
Paper,
|
|
7
8
|
Select,
|
|
8
9
|
SelectChangeEvent,
|
|
9
10
|
Typography,
|
|
11
|
+
useTheme,
|
|
10
12
|
} from '@mui/material'
|
|
11
13
|
import { observer } from 'mobx-react'
|
|
12
|
-
import React, { useState } from 'react'
|
|
14
|
+
import React, { useRef, useState } from 'react'
|
|
13
15
|
|
|
14
16
|
import { ApolloSessionModel } from '../session'
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
endSeq: string
|
|
18
|
+
const SEQUENCE_WRAP_LENGTH = 60
|
|
19
|
+
|
|
20
|
+
type SegmentType = 'upOrDownstream' | 'UTR' | 'CDS' | 'intron' | 'protein'
|
|
21
|
+
type SegmentListType = 'CDS' | 'cDNA' | 'genomic'
|
|
22
|
+
|
|
23
|
+
interface SequenceSegment {
|
|
24
|
+
type: SegmentType
|
|
25
|
+
sequenceLines: string[]
|
|
26
|
+
locs: { min: number; max: number }[]
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
function getSequenceSegments(
|
|
30
|
+
segmentType: SegmentListType,
|
|
29
31
|
feature: AnnotationFeature,
|
|
30
|
-
|
|
31
|
-
)
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
32
|
+
getSequence: (min: number, max: number) => string,
|
|
33
|
+
) {
|
|
34
|
+
const segments: SequenceSegment[] = []
|
|
35
|
+
const { cdsLocations, strand, transcriptParts } = feature
|
|
36
|
+
switch (segmentType) {
|
|
37
|
+
case 'genomic':
|
|
38
|
+
case 'cDNA': {
|
|
39
|
+
const [firstLocation] = transcriptParts
|
|
40
|
+
for (const loc of firstLocation) {
|
|
41
|
+
if (segmentType === 'cDNA' && loc.type === 'intron') {
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
let sequence = getSequence(loc.min, loc.max)
|
|
45
|
+
if (strand === -1) {
|
|
46
|
+
sequence = revcom(sequence)
|
|
47
|
+
}
|
|
48
|
+
const type: SegmentType =
|
|
49
|
+
loc.type === 'fivePrimeUTR' || loc.type === 'threePrimeUTR'
|
|
50
|
+
? 'UTR'
|
|
51
|
+
: loc.type
|
|
52
|
+
const previousSegment = segments.at(-1)
|
|
53
|
+
if (!previousSegment) {
|
|
54
|
+
const sequenceLines = splitStringIntoChunks(
|
|
55
|
+
sequence,
|
|
56
|
+
SEQUENCE_WRAP_LENGTH,
|
|
57
|
+
)
|
|
58
|
+
segments.push({
|
|
59
|
+
type,
|
|
60
|
+
sequenceLines,
|
|
61
|
+
locs: [{ min: loc.min, max: loc.max }],
|
|
62
|
+
})
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
if (previousSegment.type === type) {
|
|
66
|
+
const [previousSegmentFirstLine, ...previousSegmentFollowingLines] =
|
|
67
|
+
previousSegment.sequenceLines
|
|
68
|
+
const newSequence = previousSegmentFollowingLines.join('') + sequence
|
|
69
|
+
previousSegment.sequenceLines = [
|
|
70
|
+
previousSegmentFirstLine,
|
|
71
|
+
...splitStringIntoChunks(newSequence, SEQUENCE_WRAP_LENGTH),
|
|
72
|
+
]
|
|
73
|
+
previousSegment.locs.push({ min: loc.min, max: loc.max })
|
|
74
|
+
} else {
|
|
75
|
+
const count = segments.reduce(
|
|
76
|
+
(accumulator, currentSegment) =>
|
|
77
|
+
accumulator +
|
|
78
|
+
currentSegment.sequenceLines.reduce(
|
|
79
|
+
(subAccumulator, currentLine) =>
|
|
80
|
+
subAccumulator + currentLine.length,
|
|
81
|
+
0,
|
|
82
|
+
),
|
|
83
|
+
0,
|
|
84
|
+
)
|
|
85
|
+
const previousLineLength = count % SEQUENCE_WRAP_LENGTH
|
|
86
|
+
const newSegmentFirstLineLength =
|
|
87
|
+
SEQUENCE_WRAP_LENGTH - previousLineLength
|
|
88
|
+
const newSegmentFirstLine = sequence.slice(
|
|
89
|
+
0,
|
|
90
|
+
newSegmentFirstLineLength,
|
|
91
|
+
)
|
|
92
|
+
const newSegmentRemainderLines = splitStringIntoChunks(
|
|
93
|
+
sequence.slice(newSegmentFirstLineLength),
|
|
94
|
+
SEQUENCE_WRAP_LENGTH,
|
|
95
|
+
)
|
|
96
|
+
segments.push({
|
|
97
|
+
type,
|
|
98
|
+
sequenceLines: [newSegmentFirstLine, ...newSegmentRemainderLines],
|
|
99
|
+
locs: [{ min: loc.min, max: loc.max }],
|
|
100
|
+
})
|
|
101
|
+
}
|
|
66
102
|
}
|
|
67
|
-
|
|
103
|
+
return segments
|
|
68
104
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
case 'CDS': {
|
|
106
|
+
let wholeSequence = ''
|
|
107
|
+
const [firstLocation] = cdsLocations
|
|
108
|
+
const locs: { min: number; max: number }[] = []
|
|
109
|
+
for (const loc of firstLocation) {
|
|
110
|
+
let sequence = getSequence(loc.min, loc.max)
|
|
111
|
+
if (strand === -1) {
|
|
112
|
+
sequence = revcom(sequence)
|
|
113
|
+
}
|
|
114
|
+
wholeSequence += sequence
|
|
115
|
+
locs.push({ min: loc.min, max: loc.max })
|
|
72
116
|
}
|
|
117
|
+
const sequenceLines = splitStringIntoChunks(
|
|
118
|
+
wholeSequence,
|
|
119
|
+
SEQUENCE_WRAP_LENGTH,
|
|
120
|
+
)
|
|
121
|
+
segments.push({ type: 'CDS', sequenceLines, locs })
|
|
122
|
+
return segments
|
|
73
123
|
}
|
|
74
124
|
}
|
|
75
|
-
|
|
76
|
-
CDSresult.sort((a, b) => {
|
|
77
|
-
return Number(a.min) - Number(b.min)
|
|
78
|
-
})
|
|
79
|
-
if (CDSresult.length > 0) {
|
|
80
|
-
CDSresult[0].startSeq = ''
|
|
81
|
-
|
|
82
|
-
// eslint-disable-next-line unicorn/prefer-at
|
|
83
|
-
CDSresult[CDSresult.length - 1].endSeq = ''
|
|
125
|
+
}
|
|
84
126
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
127
|
+
function getSegmentColor(type: SegmentType) {
|
|
128
|
+
switch (type) {
|
|
129
|
+
case 'upOrDownstream': {
|
|
130
|
+
return 'rgb(255,255,255)'
|
|
131
|
+
}
|
|
132
|
+
case 'UTR': {
|
|
133
|
+
return 'rgb(194,106,119)'
|
|
134
|
+
}
|
|
135
|
+
case 'CDS': {
|
|
136
|
+
return 'rgb(93,168,153)'
|
|
137
|
+
}
|
|
138
|
+
case 'intron': {
|
|
139
|
+
return 'rgb(187,187,187)'
|
|
140
|
+
}
|
|
141
|
+
case 'protein': {
|
|
142
|
+
return 'rgb(148,203,236)'
|
|
98
143
|
}
|
|
99
144
|
}
|
|
100
|
-
return CDSresult
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
interface Props {
|
|
104
|
-
textSegments: { text: string; color: string }[]
|
|
105
145
|
}
|
|
106
146
|
|
|
107
|
-
function formatSequence(
|
|
108
|
-
seq: string,
|
|
109
|
-
refName: string,
|
|
110
|
-
start: number,
|
|
111
|
-
end: number,
|
|
112
|
-
wrap?: number,
|
|
113
|
-
) {
|
|
114
|
-
const header = `>${refName}:${start + 1}–${end}\n`
|
|
115
|
-
const body =
|
|
116
|
-
wrap === undefined ? seq : splitStringIntoChunks(seq, wrap).join('\n')
|
|
117
|
-
return `${header}${body}`
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export const intronColor = 'rgb(120,120,120)' // Slightly brighter gray
|
|
121
|
-
export const utrColor = 'rgb(20,200,200)' // Slightly brighter cyan
|
|
122
|
-
export const proteinColor = 'rgb(220,70,220)' // Slightly brighter magenta
|
|
123
|
-
export const cdsColor = 'rgb(240,200,20)' // Slightly brighter yellow
|
|
124
|
-
export const updownstreamColor = 'rgb(255,130,130)' // Slightly brighter red
|
|
125
|
-
export const genomeColor = 'rgb(20,230,20)' // Slightly brighter green
|
|
126
|
-
|
|
127
|
-
let textSegments = [{ text: '', color: '' }]
|
|
128
|
-
|
|
129
147
|
export const TranscriptSequence = observer(function TranscriptSequence({
|
|
130
148
|
assembly,
|
|
131
149
|
feature,
|
|
@@ -140,7 +158,9 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
140
158
|
const currentAssembly = session.apolloDataStore.assemblies.get(assembly)
|
|
141
159
|
const refData = currentAssembly?.getByRefName(refName)
|
|
142
160
|
const [showSequence, setShowSequence] = useState(false)
|
|
143
|
-
const [selectedOption, setSelectedOption] = useState('
|
|
161
|
+
const [selectedOption, setSelectedOption] = useState<SegmentListType>('CDS')
|
|
162
|
+
const theme = useTheme()
|
|
163
|
+
const seqRef = useRef<HTMLDivElement>(null)
|
|
144
164
|
|
|
145
165
|
if (!(currentAssembly && refData)) {
|
|
146
166
|
return null
|
|
@@ -149,215 +169,120 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
149
169
|
if (!refSeq) {
|
|
150
170
|
return null
|
|
151
171
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
let sequence = ''
|
|
155
|
-
if (showSequence) {
|
|
156
|
-
getSequenceAsString(min, max)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function getSequenceAsString(start: number, end: number): string {
|
|
160
|
-
sequence = refSeq?.getSequence(start, end) ?? ''
|
|
161
|
-
if (sequence === '') {
|
|
162
|
-
void session.apolloDataStore.loadRefSeq([
|
|
163
|
-
{ assemblyName: assembly, refName, start, end },
|
|
164
|
-
])
|
|
165
|
-
} else {
|
|
166
|
-
sequence = formatSequence(sequence, refName, start, end)
|
|
167
|
-
}
|
|
168
|
-
getSequenceAsTextSegment(selectedOption) // For color coded sequence
|
|
169
|
-
return sequence
|
|
172
|
+
if (feature.type !== 'mRNA') {
|
|
173
|
+
return null
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
const handleSeqButtonClick = () => {
|
|
173
177
|
setShowSequence(!showSequence)
|
|
174
178
|
}
|
|
175
179
|
|
|
176
|
-
function getSequenceAsTextSegment(option: string) {
|
|
177
|
-
let seqData = ''
|
|
178
|
-
textSegments = []
|
|
179
|
-
if (!refData) {
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
switch (option) {
|
|
183
|
-
case 'CDS': {
|
|
184
|
-
textSegments.push({ text: `>${refName} : CDS\n`, color: 'black' })
|
|
185
|
-
for (const item of transcriptItems) {
|
|
186
|
-
if (item.type === 'CDS') {
|
|
187
|
-
const refSeq: string = refData.getSequence(
|
|
188
|
-
Number(item.min + 1),
|
|
189
|
-
Number(item.max),
|
|
190
|
-
)
|
|
191
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq
|
|
192
|
-
textSegments.push({ text: seqData, color: cdsColor })
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
break
|
|
196
|
-
}
|
|
197
|
-
case 'cDNA': {
|
|
198
|
-
textSegments.push({ text: `>${refName} : cDNA\n`, color: 'black' })
|
|
199
|
-
for (const item of transcriptItems) {
|
|
200
|
-
if (
|
|
201
|
-
item.type === 'CDS' ||
|
|
202
|
-
item.type === 'three_prime_UTR' ||
|
|
203
|
-
item.type === 'five_prime_UTR'
|
|
204
|
-
) {
|
|
205
|
-
const refSeq: string = refData.getSequence(
|
|
206
|
-
Number(item.min + 1),
|
|
207
|
-
Number(item.max),
|
|
208
|
-
)
|
|
209
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq
|
|
210
|
-
if (item.type === 'CDS') {
|
|
211
|
-
textSegments.push({ text: seqData, color: cdsColor })
|
|
212
|
-
} else {
|
|
213
|
-
textSegments.push({ text: seqData, color: utrColor })
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
break
|
|
218
|
-
}
|
|
219
|
-
case 'Full': {
|
|
220
|
-
textSegments.push({
|
|
221
|
-
text: `>${refName} : Full genomic\n`,
|
|
222
|
-
color: 'black',
|
|
223
|
-
})
|
|
224
|
-
let lastEnd = 0
|
|
225
|
-
let count = 0
|
|
226
|
-
for (const item of transcriptItems) {
|
|
227
|
-
count++
|
|
228
|
-
if (
|
|
229
|
-
lastEnd != 0 &&
|
|
230
|
-
lastEnd != Number(item.min) &&
|
|
231
|
-
count != transcriptItems.length
|
|
232
|
-
) {
|
|
233
|
-
// Intron etc. between CDS/UTRs. No need to check this on very last item
|
|
234
|
-
const refSeq: string = refData.getSequence(
|
|
235
|
-
lastEnd + 1,
|
|
236
|
-
Number(item.min) - 1,
|
|
237
|
-
)
|
|
238
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq
|
|
239
|
-
textSegments.push({ text: seqData, color: 'black' })
|
|
240
|
-
}
|
|
241
|
-
if (
|
|
242
|
-
item.type === 'CDS' ||
|
|
243
|
-
item.type === 'three_prime_UTR' ||
|
|
244
|
-
item.type === 'five_prime_UTR'
|
|
245
|
-
) {
|
|
246
|
-
const refSeq: string = refData.getSequence(
|
|
247
|
-
Number(item.min + 1),
|
|
248
|
-
Number(item.max),
|
|
249
|
-
)
|
|
250
|
-
seqData += item.strand === -1 && refSeq ? revcom(refSeq) : refSeq
|
|
251
|
-
switch (item.type) {
|
|
252
|
-
case 'CDS': {
|
|
253
|
-
textSegments.push({ text: seqData, color: cdsColor })
|
|
254
|
-
break
|
|
255
|
-
}
|
|
256
|
-
case 'three_prime_UTR': {
|
|
257
|
-
textSegments.push({ text: seqData, color: utrColor })
|
|
258
|
-
break
|
|
259
|
-
}
|
|
260
|
-
case 'five_prime_UTR': {
|
|
261
|
-
textSegments.push({ text: seqData, color: utrColor })
|
|
262
|
-
break
|
|
263
|
-
}
|
|
264
|
-
default: {
|
|
265
|
-
textSegments.push({ text: seqData, color: 'black' })
|
|
266
|
-
break
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
lastEnd = Number(item.max)
|
|
271
|
-
}
|
|
272
|
-
break
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
180
|
function handleChangeSeqOption(e: SelectChangeEvent) {
|
|
278
181
|
const option = e.target.value
|
|
279
|
-
setSelectedOption(option)
|
|
280
|
-
getSequenceAsTextSegment(option)
|
|
182
|
+
setSelectedOption(option as SegmentListType)
|
|
281
183
|
}
|
|
282
184
|
|
|
283
185
|
// Function to copy text to clipboard
|
|
284
186
|
const copyToClipboard = () => {
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
navigator.clipboard
|
|
289
|
-
.writeText(textToCopy)
|
|
290
|
-
.then(() => {
|
|
291
|
-
// console.log('Text copied to clipboard!')
|
|
292
|
-
})
|
|
293
|
-
.catch((error: unknown) => {
|
|
294
|
-
console.error('Failed to copy text to clipboard', error)
|
|
295
|
-
})
|
|
187
|
+
const seqDiv = seqRef.current
|
|
188
|
+
if (!seqDiv) {
|
|
189
|
+
return
|
|
296
190
|
}
|
|
191
|
+
const textBlob = new Blob([seqDiv.outerText], { type: 'text/plain' })
|
|
192
|
+
const htmlBlob = new Blob([seqDiv.outerHTML], { type: 'text/html' })
|
|
193
|
+
const clipboardItem = new ClipboardItem({
|
|
194
|
+
[textBlob.type]: textBlob,
|
|
195
|
+
[htmlBlob.type]: htmlBlob,
|
|
196
|
+
})
|
|
197
|
+
void navigator.clipboard.write([clipboardItem])
|
|
297
198
|
}
|
|
298
199
|
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
)
|
|
200
|
+
const sequenceSegments = showSequence
|
|
201
|
+
? getSequenceSegments(selectedOption, feature, (min: number, max: number) =>
|
|
202
|
+
refData.getSequence(min, max),
|
|
203
|
+
)
|
|
204
|
+
: []
|
|
205
|
+
const locationIntervals: { min: number; max: number }[] = []
|
|
206
|
+
if (showSequence) {
|
|
207
|
+
const allLocs = sequenceSegments.flatMap((segment) => segment.locs)
|
|
208
|
+
let [previous] = allLocs
|
|
209
|
+
for (let i = 1; i < allLocs.length; i++) {
|
|
210
|
+
if (previous.min === allLocs[i].max || previous.max === allLocs[i].min) {
|
|
211
|
+
previous = {
|
|
212
|
+
min: Math.min(previous.min, allLocs[i].min),
|
|
213
|
+
max: Math.max(previous.max, allLocs[i].max),
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
locationIntervals.push(previous)
|
|
217
|
+
previous = allLocs[i]
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
locationIntervals.push(previous)
|
|
309
221
|
}
|
|
310
222
|
|
|
311
223
|
return (
|
|
312
224
|
<>
|
|
313
|
-
<Typography
|
|
314
|
-
style={{ display: 'inline', marginLeft: '15px' }}
|
|
315
|
-
variant="h5"
|
|
316
|
-
>
|
|
317
|
-
Sequence
|
|
318
|
-
</Typography>
|
|
225
|
+
<Typography variant="h5">Sequence</Typography>
|
|
319
226
|
<div>
|
|
320
|
-
<Button
|
|
321
|
-
variant="contained"
|
|
322
|
-
style={{ marginLeft: '15px' }}
|
|
323
|
-
onClick={handleSeqButtonClick}
|
|
324
|
-
>
|
|
227
|
+
<Button variant="contained" onClick={handleSeqButtonClick}>
|
|
325
228
|
{showSequence ? 'Hide sequence' : 'Show sequence'}
|
|
326
229
|
</Button>
|
|
327
230
|
</div>
|
|
328
|
-
|
|
329
|
-
|
|
231
|
+
{showSequence && (
|
|
232
|
+
<>
|
|
330
233
|
<Select
|
|
234
|
+
defaultValue="CDS"
|
|
331
235
|
value={selectedOption}
|
|
332
236
|
onChange={handleChangeSeqOption}
|
|
333
|
-
style={{ width: '150px', marginLeft: '15px', height: '25px' }}
|
|
334
237
|
>
|
|
335
|
-
<MenuItem value=
|
|
336
|
-
<MenuItem value=
|
|
337
|
-
<MenuItem value=
|
|
338
|
-
<MenuItem value={'Full'}>Full genomics</MenuItem>
|
|
238
|
+
<MenuItem value="CDS">CDS</MenuItem>
|
|
239
|
+
<MenuItem value="cDNA">cDNA</MenuItem>
|
|
240
|
+
<MenuItem value="genomic">Genomic</MenuItem>
|
|
339
241
|
</Select>
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
242
|
+
<Paper
|
|
243
|
+
style={{
|
|
244
|
+
fontFamily: 'monospace',
|
|
245
|
+
padding: theme.spacing(),
|
|
246
|
+
overflowX: 'auto',
|
|
247
|
+
}}
|
|
248
|
+
ref={seqRef}
|
|
249
|
+
>
|
|
250
|
+
>{refSeq.name}:
|
|
251
|
+
{locationIntervals
|
|
252
|
+
.map((interval) =>
|
|
253
|
+
feature.strand === 1
|
|
254
|
+
? `${interval.min + 1}-${interval.max}`
|
|
255
|
+
: `${interval.max}-${interval.min + 1}`,
|
|
256
|
+
)
|
|
257
|
+
.join(';')}
|
|
258
|
+
({feature.strand === 1 ? '+' : '-'})
|
|
259
|
+
<br />
|
|
260
|
+
{sequenceSegments.map((segment, index) => (
|
|
261
|
+
<span
|
|
262
|
+
key={`${segment.type}-${index}`}
|
|
263
|
+
style={{
|
|
264
|
+
background: getSegmentColor(segment.type),
|
|
265
|
+
color: theme.palette.getContrastText(
|
|
266
|
+
getSegmentColor(segment.type),
|
|
267
|
+
),
|
|
268
|
+
}}
|
|
269
|
+
>
|
|
270
|
+
{segment.sequenceLines.map((sequenceLine, idx) => (
|
|
271
|
+
<React.Fragment key={`${sequenceLine.slice(0, 5)}-${idx}`}>
|
|
272
|
+
{sequenceLine}
|
|
273
|
+
{idx === segment.sequenceLines.length - 1 &&
|
|
274
|
+
sequenceLine.length !== SEQUENCE_WRAP_LENGTH ? null : (
|
|
275
|
+
<br />
|
|
276
|
+
)}
|
|
277
|
+
</React.Fragment>
|
|
278
|
+
))}
|
|
279
|
+
</span>
|
|
280
|
+
))}
|
|
281
|
+
</Paper>
|
|
282
|
+
<Button variant="contained" onClick={copyToClipboard}>
|
|
283
|
+
Copy sequence
|
|
284
|
+
</Button>
|
|
285
|
+
</>
|
|
361
286
|
)}
|
|
362
287
|
</>
|
|
363
288
|
)
|
|
@@ -65,10 +65,10 @@ export const ApolloFeatureDetailsWidgetModel = types
|
|
|
65
65
|
|
|
66
66
|
// eslint disables because of
|
|
67
67
|
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
68
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
69
69
|
export interface ApolloFeatureDetailsWidget
|
|
70
70
|
extends Instance<typeof ApolloFeatureDetailsWidgetModel> {}
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
72
72
|
export interface ApolloFeatureDetailsWidgetSnapshot
|
|
73
73
|
extends SnapshotIn<typeof ApolloFeatureDetailsWidgetModel> {}
|
|
74
74
|
|
|
@@ -126,9 +126,9 @@ export const ApolloTranscriptDetailsModel = types
|
|
|
126
126
|
|
|
127
127
|
// eslint disables because of
|
|
128
128
|
// https://mobx-state-tree.js.org/tips/typescript#using-a-mst-type-at-design-time
|
|
129
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-
|
|
129
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
130
130
|
export interface ApolloTranscriptDetailsWidget
|
|
131
131
|
extends Instance<typeof ApolloTranscriptDetailsModel> {}
|
|
132
|
-
// eslint-disable-next-line @typescript-eslint/no-empty-
|
|
132
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
133
133
|
export interface ApolloTranscriptDetailsWidgetSnapshot
|
|
134
134
|
extends SnapshotIn<typeof ApolloTranscriptDetailsModel> {}
|
|
@@ -2,7 +2,11 @@ import { AnnotationFeature } from '@apollo-annotation/mst'
|
|
|
2
2
|
import { Theme, alpha } from '@mui/material'
|
|
3
3
|
import { MenuItem } from '@jbrowse/core/ui'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
AbstractSessionModel,
|
|
7
|
+
isSessionModelWithWidgets,
|
|
8
|
+
SessionWithWidgets,
|
|
9
|
+
} from '@jbrowse/core/util'
|
|
6
10
|
|
|
7
11
|
import {
|
|
8
12
|
AddChildFeature,
|
|
@@ -175,7 +179,7 @@ function drawTooltip(
|
|
|
175
179
|
if (!position) {
|
|
176
180
|
return
|
|
177
181
|
}
|
|
178
|
-
const { layoutIndex, layoutRow } = position
|
|
182
|
+
const { featureRow, layoutIndex, layoutRow } = position
|
|
179
183
|
const { bpPerPx, displayedRegions, offsetPx } = lgv
|
|
180
184
|
const displayedRegion = displayedRegions[layoutIndex]
|
|
181
185
|
const { refName, reversed } = displayedRegion
|
|
@@ -191,7 +195,7 @@ function drawTooltip(
|
|
|
191
195
|
coord: reversed ? max : min,
|
|
192
196
|
regionNumber: layoutIndex,
|
|
193
197
|
})?.offsetPx ?? 0) - offsetPx
|
|
194
|
-
const top = layoutRow * apolloRowHeight
|
|
198
|
+
const top = (layoutRow + featureRow) * apolloRowHeight
|
|
195
199
|
const widthPx = length / bpPerPx
|
|
196
200
|
|
|
197
201
|
const featureType = `Type: ${feature.type}`
|
|
@@ -385,6 +389,24 @@ function getContextMenuItems(
|
|
|
385
389
|
},
|
|
386
390
|
},
|
|
387
391
|
)
|
|
392
|
+
if (sourceFeature.type === 'mRNA' && isSessionModelWithWidgets(session)) {
|
|
393
|
+
menuItems.push({
|
|
394
|
+
label: 'Edit transcript details',
|
|
395
|
+
onClick: () => {
|
|
396
|
+
const apolloTranscriptWidget = session.addWidget(
|
|
397
|
+
'ApolloTranscriptDetails',
|
|
398
|
+
'apolloTranscriptDetails',
|
|
399
|
+
{
|
|
400
|
+
feature: sourceFeature,
|
|
401
|
+
assembly: currentAssemblyId,
|
|
402
|
+
changeManager,
|
|
403
|
+
refName: region.refName,
|
|
404
|
+
},
|
|
405
|
+
)
|
|
406
|
+
session.showWidget(apolloTranscriptWidget)
|
|
407
|
+
},
|
|
408
|
+
})
|
|
409
|
+
}
|
|
388
410
|
return menuItems
|
|
389
411
|
}
|
|
390
412
|
|