@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.8
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 +2371 -1642
- package/dist/index.esm.js.map +1 -1
- package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
- 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 +4387 -2952
- 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 +15 -15
- package/src/ApolloInternetAccount/model.ts +48 -13
- package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
- package/src/ChangeManager.ts +33 -13
- package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
- package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
- package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
- package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
- package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
- package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
- package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
- package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
- package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
- package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
- package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
- package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
- package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
- package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
- package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
- package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
- package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
- package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +95 -38
- package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
- package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
- package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
- package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
- package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
- package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
- package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
- package/src/components/AddAssembly.tsx +1 -1
- package/src/components/AddAssemblyAliases.tsx +1 -1
- package/src/components/AddChildFeature.tsx +5 -2
- package/src/components/AddFeature.tsx +9 -3
- package/src/components/AddRefSeqAliases.tsx +9 -9
- package/src/components/CopyFeature.tsx +3 -1
- package/src/components/CreateApolloAnnotation.tsx +1 -0
- package/src/components/DeleteAssembly.tsx +1 -1
- package/src/components/EditZoomThresholdDialog.tsx +69 -0
- package/src/components/FilterFeatures.tsx +7 -7
- package/src/components/FilterTranscripts.tsx +6 -6
- package/src/components/ImportFeatures.tsx +1 -1
- package/src/components/ManageChecks.tsx +1 -1
- package/src/components/MergeTranscripts.tsx +12 -15
- package/src/components/OntologyTermMultiSelect.tsx +11 -11
- package/src/components/OpenLocalFile.tsx +11 -7
- package/src/components/ViewCheckResults.tsx +1 -1
- package/src/components/index.ts +1 -0
- package/src/config.ts +6 -0
- package/src/index.ts +42 -105
- package/src/makeDisplayComponent.tsx +0 -1
- package/src/menus/index.ts +1 -0
- package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
- package/src/menus/topLevelMenuAdmin.ts +154 -0
- package/src/session/session.ts +162 -116
- package/src/util/annotationFeatureUtils.ts +15 -21
- package/src/util/displayUtils.ts +149 -0
- package/src/util/glyphUtils.ts +152 -0
- package/src/util/mouseEventsUtils.ts +32 -0
|
@@ -28,10 +28,18 @@ type SegmentListType = 'CDS' | 'cDNA' | 'genomic' | 'protein'
|
|
|
28
28
|
|
|
29
29
|
interface SequenceSegment {
|
|
30
30
|
type: SegmentType
|
|
31
|
-
|
|
31
|
+
sequence: string
|
|
32
32
|
locs: { min: number; max: number }[]
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function getSequenceLength(segments: SequenceSegment[]): number {
|
|
36
|
+
let length = 0
|
|
37
|
+
for (const segment of segments) {
|
|
38
|
+
length += segment.sequence.length
|
|
39
|
+
}
|
|
40
|
+
return length
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
function getSequenceSegments(
|
|
36
44
|
segmentType: SegmentListType,
|
|
37
45
|
feature: AnnotationFeature,
|
|
@@ -57,51 +65,20 @@ function getSequenceSegments(
|
|
|
57
65
|
: loc.type
|
|
58
66
|
const previousSegment = segments.at(-1)
|
|
59
67
|
if (!previousSegment) {
|
|
60
|
-
const sequenceLines = splitStringIntoChunks(
|
|
61
|
-
sequence,
|
|
62
|
-
SEQUENCE_WRAP_LENGTH,
|
|
63
|
-
)
|
|
64
68
|
segments.push({
|
|
65
69
|
type,
|
|
66
|
-
|
|
70
|
+
sequence,
|
|
67
71
|
locs: [{ min: loc.min, max: loc.max }],
|
|
68
72
|
})
|
|
69
73
|
continue
|
|
70
74
|
}
|
|
71
75
|
if (previousSegment.type === type) {
|
|
72
|
-
|
|
73
|
-
previousSegment.sequenceLines
|
|
74
|
-
const newSequence = previousSegmentFollowingLines.join('') + sequence
|
|
75
|
-
previousSegment.sequenceLines = [
|
|
76
|
-
previousSegmentFirstLine,
|
|
77
|
-
...splitStringIntoChunks(newSequence, SEQUENCE_WRAP_LENGTH),
|
|
78
|
-
]
|
|
76
|
+
previousSegment.sequence += sequence
|
|
79
77
|
previousSegment.locs.push({ min: loc.min, max: loc.max })
|
|
80
78
|
} else {
|
|
81
|
-
const count = segments.reduce(
|
|
82
|
-
(accumulator, currentSegment) =>
|
|
83
|
-
accumulator +
|
|
84
|
-
currentSegment.sequenceLines.reduce(
|
|
85
|
-
(subAccumulator, currentLine) =>
|
|
86
|
-
subAccumulator + currentLine.length,
|
|
87
|
-
0,
|
|
88
|
-
),
|
|
89
|
-
0,
|
|
90
|
-
)
|
|
91
|
-
const previousLineLength = count % SEQUENCE_WRAP_LENGTH
|
|
92
|
-
const newSegmentFirstLineLength =
|
|
93
|
-
SEQUENCE_WRAP_LENGTH - previousLineLength
|
|
94
|
-
const newSegmentFirstLine = sequence.slice(
|
|
95
|
-
0,
|
|
96
|
-
newSegmentFirstLineLength,
|
|
97
|
-
)
|
|
98
|
-
const newSegmentRemainderLines = splitStringIntoChunks(
|
|
99
|
-
sequence.slice(newSegmentFirstLineLength),
|
|
100
|
-
SEQUENCE_WRAP_LENGTH,
|
|
101
|
-
)
|
|
102
79
|
segments.push({
|
|
103
80
|
type,
|
|
104
|
-
|
|
81
|
+
sequence,
|
|
105
82
|
locs: [{ min: loc.min, max: loc.max }],
|
|
106
83
|
})
|
|
107
84
|
}
|
|
@@ -113,17 +90,14 @@ function getSequenceSegments(
|
|
|
113
90
|
const [firstLocation] = cdsLocations
|
|
114
91
|
const locs: { min: number; max: number }[] = []
|
|
115
92
|
for (const loc of firstLocation) {
|
|
116
|
-
|
|
93
|
+
let locSeq = getSequence(loc.min, loc.max)
|
|
94
|
+
if (strand === -1) {
|
|
95
|
+
locSeq = revcom(locSeq)
|
|
96
|
+
}
|
|
97
|
+
wholeSequence += locSeq
|
|
117
98
|
locs.push({ min: loc.min, max: loc.max })
|
|
118
99
|
}
|
|
119
|
-
|
|
120
|
-
wholeSequence = revcom(wholeSequence)
|
|
121
|
-
}
|
|
122
|
-
const sequenceLines = splitStringIntoChunks(
|
|
123
|
-
wholeSequence,
|
|
124
|
-
SEQUENCE_WRAP_LENGTH,
|
|
125
|
-
)
|
|
126
|
-
segments.push({ type: 'CDS', sequenceLines, locs })
|
|
100
|
+
segments.push({ type: 'CDS', sequence: wholeSequence, locs })
|
|
127
101
|
return segments
|
|
128
102
|
}
|
|
129
103
|
case 'protein': {
|
|
@@ -131,20 +105,20 @@ function getSequenceSegments(
|
|
|
131
105
|
const [firstLocation] = cdsLocations
|
|
132
106
|
const locs: { min: number; max: number }[] = []
|
|
133
107
|
for (const loc of firstLocation) {
|
|
134
|
-
|
|
108
|
+
let locSeq = getSequence(loc.min, loc.max)
|
|
109
|
+
if (strand === -1) {
|
|
110
|
+
locSeq = revcom(locSeq)
|
|
111
|
+
}
|
|
112
|
+
wholeSequence += locSeq
|
|
135
113
|
locs.push({ min: loc.min, max: loc.max })
|
|
136
114
|
}
|
|
137
|
-
if (strand === -1) {
|
|
138
|
-
wholeSequence = revcom(wholeSequence)
|
|
139
|
-
}
|
|
140
115
|
let protein = ''
|
|
141
116
|
for (let i = 0; i < wholeSequence.length; i += 3) {
|
|
142
117
|
const codonSeq: string = wholeSequence.slice(i, i + 3).toUpperCase()
|
|
143
118
|
protein +=
|
|
144
119
|
defaultCodonTable[codonSeq as keyof typeof defaultCodonTable] || '&'
|
|
145
120
|
}
|
|
146
|
-
|
|
147
|
-
segments.push({ type: 'protein', sequenceLines, locs })
|
|
121
|
+
segments.push({ type: 'protein', sequence: protein, locs })
|
|
148
122
|
return segments
|
|
149
123
|
}
|
|
150
124
|
}
|
|
@@ -282,6 +256,43 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
282
256
|
void copyToClipboard(seqDiv)
|
|
283
257
|
}
|
|
284
258
|
|
|
259
|
+
function wrapSequence(
|
|
260
|
+
sequenceSegments: SequenceSegment[],
|
|
261
|
+
sequenceWrapLength: number,
|
|
262
|
+
): React.ReactNode[] {
|
|
263
|
+
const seqElements: React.ReactNode[] = []
|
|
264
|
+
let processedChars = 0
|
|
265
|
+
for (const [index, segment] of sequenceSegments.entries()) {
|
|
266
|
+
const lastLineLength = processedChars % sequenceWrapLength
|
|
267
|
+
const segmentLineBreak =
|
|
268
|
+
processedChars > 0 && lastLineLength === 0 ? '\n' : ''
|
|
269
|
+
processedChars += segment.sequence.length
|
|
270
|
+
const firstLine =
|
|
271
|
+
segmentLineBreak +
|
|
272
|
+
segment.sequence.slice(0, sequenceWrapLength - lastLineLength)
|
|
273
|
+
const remainingLines = splitStringIntoChunks(
|
|
274
|
+
segment.sequence.slice(firstLine.length),
|
|
275
|
+
sequenceWrapLength,
|
|
276
|
+
)
|
|
277
|
+
const printLines = [firstLine, ...remainingLines]
|
|
278
|
+
|
|
279
|
+
const span = (
|
|
280
|
+
<span
|
|
281
|
+
key={`${segment.type}-${index}`}
|
|
282
|
+
style={{
|
|
283
|
+
background: getSegmentColor(segment.type),
|
|
284
|
+
color: theme.palette.getContrastText(getSegmentColor(segment.type)),
|
|
285
|
+
whiteSpace: 'pre-line',
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
{printLines.join('\n')}
|
|
289
|
+
</span>
|
|
290
|
+
)
|
|
291
|
+
seqElements.push(span)
|
|
292
|
+
}
|
|
293
|
+
return seqElements
|
|
294
|
+
}
|
|
295
|
+
|
|
285
296
|
return (
|
|
286
297
|
<>
|
|
287
298
|
<Select
|
|
@@ -289,9 +300,14 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
289
300
|
value={selectedOption}
|
|
290
301
|
onChange={handleChangeSeqOption}
|
|
291
302
|
size="small"
|
|
303
|
+
data-testid="sequenceOptionSelector"
|
|
292
304
|
>
|
|
293
305
|
{sequenceOptions.map((option) => (
|
|
294
|
-
<MenuItem
|
|
306
|
+
<MenuItem
|
|
307
|
+
key={option}
|
|
308
|
+
value={option}
|
|
309
|
+
data-testid={`sequenceOption-${option}`}
|
|
310
|
+
>
|
|
295
311
|
{option}
|
|
296
312
|
</MenuItem>
|
|
297
313
|
))}
|
|
@@ -320,29 +336,10 @@ export const TranscriptSequence = observer(function TranscriptSequence({
|
|
|
320
336
|
: `${interval.max}-${interval.min + 1}`,
|
|
321
337
|
)
|
|
322
338
|
.join(';')}
|
|
323
|
-
({feature.strand === 1 ? '+' : '-'}
|
|
339
|
+
(strand={feature.strand === 1 ? '+' : '-'};length=
|
|
340
|
+
{getSequenceLength(sequenceSegments)})
|
|
324
341
|
<br />
|
|
325
|
-
{sequenceSegments
|
|
326
|
-
<span
|
|
327
|
-
key={`${segment.type}-${index}`}
|
|
328
|
-
style={{
|
|
329
|
-
background: getSegmentColor(segment.type),
|
|
330
|
-
color: theme.palette.getContrastText(
|
|
331
|
-
getSegmentColor(segment.type),
|
|
332
|
-
),
|
|
333
|
-
}}
|
|
334
|
-
>
|
|
335
|
-
{segment.sequenceLines.map((sequenceLine, idx) => (
|
|
336
|
-
<React.Fragment key={`${sequenceLine.slice(0, 5)}-${idx}`}>
|
|
337
|
-
{sequenceLine}
|
|
338
|
-
{idx === segment.sequenceLines.length - 1 &&
|
|
339
|
-
sequenceLine.length !== SEQUENCE_WRAP_LENGTH ? null : (
|
|
340
|
-
<br />
|
|
341
|
-
)}
|
|
342
|
-
</React.Fragment>
|
|
343
|
-
))}
|
|
344
|
-
</span>
|
|
345
|
-
))}
|
|
342
|
+
{wrapSequence(sequenceSegments, SEQUENCE_WRAP_LENGTH)}
|
|
346
343
|
</Paper>
|
|
347
344
|
</>
|
|
348
345
|
)
|
|
@@ -25,7 +25,7 @@ import RemoveIcon from '@mui/icons-material/Remove'
|
|
|
25
25
|
import {
|
|
26
26
|
Accordion,
|
|
27
27
|
AccordionDetails,
|
|
28
|
-
|
|
28
|
+
Grid,
|
|
29
29
|
Tooltip,
|
|
30
30
|
Typography,
|
|
31
31
|
} from '@mui/material'
|
|
@@ -821,6 +821,7 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
821
821
|
}
|
|
822
822
|
}
|
|
823
823
|
}
|
|
824
|
+
spliceSite = spliceSite.toUpperCase()
|
|
824
825
|
return [
|
|
825
826
|
{
|
|
826
827
|
spliceSite,
|
|
@@ -846,6 +847,7 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
846
847
|
}
|
|
847
848
|
}
|
|
848
849
|
}
|
|
850
|
+
spliceSite = spliceSite.toUpperCase()
|
|
849
851
|
return [
|
|
850
852
|
{
|
|
851
853
|
spliceSite,
|
|
@@ -1154,15 +1156,15 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1154
1156
|
</div>
|
|
1155
1157
|
</AccordionDetails>
|
|
1156
1158
|
</Accordion>
|
|
1157
|
-
<
|
|
1159
|
+
<Grid
|
|
1158
1160
|
container
|
|
1159
1161
|
justifyContent="center"
|
|
1160
1162
|
alignItems="center"
|
|
1161
1163
|
style={{ textAlign: 'center', marginTop: 10 }}
|
|
1162
1164
|
>
|
|
1163
|
-
<
|
|
1165
|
+
<Grid size={1} />
|
|
1164
1166
|
{strand === 1 ? (
|
|
1165
|
-
<
|
|
1167
|
+
<Grid size={4}>
|
|
1166
1168
|
<StyledTextField
|
|
1167
1169
|
margin="dense"
|
|
1168
1170
|
variant="outlined"
|
|
@@ -1177,9 +1179,9 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1177
1179
|
}}
|
|
1178
1180
|
style={{ border: '1px solid black', borderRadius: 5 }}
|
|
1179
1181
|
/>
|
|
1180
|
-
</
|
|
1182
|
+
</Grid>
|
|
1181
1183
|
) : (
|
|
1182
|
-
<
|
|
1184
|
+
<Grid size={4}>
|
|
1183
1185
|
<StyledTextField
|
|
1184
1186
|
margin="dense"
|
|
1185
1187
|
variant="outlined"
|
|
@@ -1194,13 +1196,13 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1194
1196
|
}}
|
|
1195
1197
|
style={{ border: '1px solid black', borderRadius: 5 }}
|
|
1196
1198
|
/>
|
|
1197
|
-
</
|
|
1199
|
+
</Grid>
|
|
1198
1200
|
)}
|
|
1199
|
-
<
|
|
1201
|
+
<Grid size={2}>
|
|
1200
1202
|
<Typography component={'span'}>CDS</Typography>
|
|
1201
|
-
</
|
|
1203
|
+
</Grid>
|
|
1202
1204
|
{strand === 1 ? (
|
|
1203
|
-
<
|
|
1205
|
+
<Grid size={4}>
|
|
1204
1206
|
<StyledTextField
|
|
1205
1207
|
margin="dense"
|
|
1206
1208
|
variant="outlined"
|
|
@@ -1215,9 +1217,9 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1215
1217
|
}}
|
|
1216
1218
|
style={{ border: '1px solid black', borderRadius: 5 }}
|
|
1217
1219
|
/>
|
|
1218
|
-
</
|
|
1220
|
+
</Grid>
|
|
1219
1221
|
) : (
|
|
1220
|
-
<
|
|
1222
|
+
<Grid size={4}>
|
|
1221
1223
|
<StyledTextField
|
|
1222
1224
|
margin="dense"
|
|
1223
1225
|
variant="outlined"
|
|
@@ -1232,10 +1234,10 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1232
1234
|
}}
|
|
1233
1235
|
style={{ border: '1px solid black', borderRadius: 5 }}
|
|
1234
1236
|
/>
|
|
1235
|
-
</
|
|
1237
|
+
</Grid>
|
|
1236
1238
|
)}
|
|
1237
|
-
<
|
|
1238
|
-
</
|
|
1239
|
+
<Grid size={1} />
|
|
1240
|
+
</Grid>
|
|
1239
1241
|
</div>
|
|
1240
1242
|
)}
|
|
1241
1243
|
<div style={{ marginTop: 5 }}>
|
|
@@ -1243,13 +1245,13 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1243
1245
|
return (
|
|
1244
1246
|
<div key={index}>
|
|
1245
1247
|
{loc.type === 'exon' && (
|
|
1246
|
-
<
|
|
1248
|
+
<Grid
|
|
1247
1249
|
container
|
|
1248
1250
|
justifyContent="center"
|
|
1249
1251
|
alignItems="center"
|
|
1250
1252
|
style={{ textAlign: 'center' }}
|
|
1251
1253
|
>
|
|
1252
|
-
<
|
|
1254
|
+
<Grid size={1}>
|
|
1253
1255
|
{index !== 0 &&
|
|
1254
1256
|
getFivePrimeSpliceSite(loc, index).map((site, idx) => (
|
|
1255
1257
|
<Typography
|
|
@@ -1260,9 +1262,9 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1260
1262
|
{site.spliceSite}
|
|
1261
1263
|
</Typography>
|
|
1262
1264
|
))}
|
|
1263
|
-
</
|
|
1265
|
+
</Grid>
|
|
1264
1266
|
{strand === 1 ? (
|
|
1265
|
-
<
|
|
1267
|
+
<Grid size={4} style={{ padding: 0 }}>
|
|
1266
1268
|
<StyledTextField
|
|
1267
1269
|
margin="dense"
|
|
1268
1270
|
variant="outlined"
|
|
@@ -1276,9 +1278,9 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1276
1278
|
)
|
|
1277
1279
|
}}
|
|
1278
1280
|
/>
|
|
1279
|
-
</
|
|
1281
|
+
</Grid>
|
|
1280
1282
|
) : (
|
|
1281
|
-
<
|
|
1283
|
+
<Grid size={4} style={{ padding: 0 }}>
|
|
1282
1284
|
<StyledTextField
|
|
1283
1285
|
margin="dense"
|
|
1284
1286
|
variant="outlined"
|
|
@@ -1292,13 +1294,13 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1292
1294
|
)
|
|
1293
1295
|
}}
|
|
1294
1296
|
/>
|
|
1295
|
-
</
|
|
1297
|
+
</Grid>
|
|
1296
1298
|
)}
|
|
1297
|
-
<
|
|
1299
|
+
<Grid size={2}>
|
|
1298
1300
|
<Strand strand={feature.strand} />
|
|
1299
|
-
</
|
|
1301
|
+
</Grid>
|
|
1300
1302
|
{strand === 1 ? (
|
|
1301
|
-
<
|
|
1303
|
+
<Grid size={4} style={{ padding: 0 }}>
|
|
1302
1304
|
<StyledTextField
|
|
1303
1305
|
margin="dense"
|
|
1304
1306
|
variant="outlined"
|
|
@@ -1312,9 +1314,9 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1312
1314
|
)
|
|
1313
1315
|
}}
|
|
1314
1316
|
/>
|
|
1315
|
-
</
|
|
1317
|
+
</Grid>
|
|
1316
1318
|
) : (
|
|
1317
|
-
<
|
|
1319
|
+
<Grid size={4} style={{ padding: 0 }}>
|
|
1318
1320
|
<StyledTextField
|
|
1319
1321
|
margin="dense"
|
|
1320
1322
|
variant="outlined"
|
|
@@ -1328,9 +1330,9 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1328
1330
|
)
|
|
1329
1331
|
}}
|
|
1330
1332
|
/>
|
|
1331
|
-
</
|
|
1333
|
+
</Grid>
|
|
1332
1334
|
)}
|
|
1333
|
-
<
|
|
1335
|
+
<Grid size={1}>
|
|
1334
1336
|
{index !== transcriptExonParts.length - 1 &&
|
|
1335
1337
|
getThreePrimeSpliceSite(loc, index).map((site, idx) => (
|
|
1336
1338
|
<Typography
|
|
@@ -1341,8 +1343,8 @@ export const TranscriptWidgetEditLocation = observer(
|
|
|
1341
1343
|
{site.spliceSite}
|
|
1342
1344
|
</Typography>
|
|
1343
1345
|
))}
|
|
1344
|
-
</
|
|
1345
|
-
</
|
|
1346
|
+
</Grid>
|
|
1347
|
+
</Grid>
|
|
1346
1348
|
)}
|
|
1347
1349
|
</div>
|
|
1348
1350
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/unbound-method */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
|
4
|
+
import { type CheckResultI } from '@apollo-annotation/mst'
|
|
4
5
|
import { Menu, type MenuItem } from '@jbrowse/core/ui'
|
|
5
6
|
import {
|
|
6
7
|
type AbstractSessionModel,
|
|
@@ -9,50 +10,31 @@ import {
|
|
|
9
10
|
} from '@jbrowse/core/util'
|
|
10
11
|
import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
|
|
11
12
|
import ErrorIcon from '@mui/icons-material/Error'
|
|
13
|
+
import LockIcon from '@mui/icons-material/Lock'
|
|
12
14
|
import {
|
|
13
15
|
Alert,
|
|
14
16
|
Avatar,
|
|
17
|
+
Badge,
|
|
18
|
+
Box,
|
|
15
19
|
CircularProgress,
|
|
16
20
|
Tooltip,
|
|
17
21
|
useTheme,
|
|
18
22
|
} from '@mui/material'
|
|
19
23
|
import { observer } from 'mobx-react'
|
|
20
24
|
import React, { useEffect, useState } from 'react'
|
|
21
|
-
import { makeStyles } from 'tss-react/mui'
|
|
22
25
|
|
|
26
|
+
import {
|
|
27
|
+
type Coord,
|
|
28
|
+
clusterResultByMessage,
|
|
29
|
+
useStyles,
|
|
30
|
+
} from '../../util/displayUtils'
|
|
23
31
|
import { type LinearApolloDisplay as LinearApolloDisplayI } from '../stateModel'
|
|
24
32
|
|
|
25
33
|
interface LinearApolloDisplayProps {
|
|
26
34
|
model: LinearApolloDisplayI
|
|
27
35
|
}
|
|
28
|
-
export type Coord = [number, number]
|
|
29
36
|
|
|
30
|
-
|
|
31
|
-
canvasContainer: {
|
|
32
|
-
position: 'relative',
|
|
33
|
-
left: 0,
|
|
34
|
-
},
|
|
35
|
-
canvas: {
|
|
36
|
-
position: 'absolute',
|
|
37
|
-
left: 0,
|
|
38
|
-
},
|
|
39
|
-
ellipses: {
|
|
40
|
-
textOverflow: 'ellipsis',
|
|
41
|
-
overflow: 'hidden',
|
|
42
|
-
},
|
|
43
|
-
avatar: {
|
|
44
|
-
position: 'absolute',
|
|
45
|
-
color: theme.palette.warning.light,
|
|
46
|
-
backgroundColor: theme.palette.warning.contrastText,
|
|
47
|
-
},
|
|
48
|
-
loading: {
|
|
49
|
-
position: 'absolute',
|
|
50
|
-
right: theme.spacing(3),
|
|
51
|
-
zIndex: 10,
|
|
52
|
-
pointerEvents: 'none',
|
|
53
|
-
textAlign: 'right',
|
|
54
|
-
},
|
|
55
|
-
}))
|
|
37
|
+
// Lock icon when isLocked === true
|
|
56
38
|
|
|
57
39
|
export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
58
40
|
props: LinearApolloDisplayProps,
|
|
@@ -76,9 +58,8 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
76
58
|
setCanvas,
|
|
77
59
|
setCollaboratorCanvas,
|
|
78
60
|
setOverlayCanvas,
|
|
79
|
-
setSeqTrackCanvas,
|
|
80
|
-
setSeqTrackOverlayCanvas,
|
|
81
61
|
setTheme,
|
|
62
|
+
showCheckResults,
|
|
82
63
|
} = model
|
|
83
64
|
const { classes } = useStyles()
|
|
84
65
|
const lgv = getContainingView(model) as unknown as LinearGenomeViewModel
|
|
@@ -95,36 +76,6 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
95
76
|
const { assemblyManager } = session as unknown as AbstractSessionModel
|
|
96
77
|
return (
|
|
97
78
|
<>
|
|
98
|
-
{3 / lgv.bpPerPx >= 1 ? (
|
|
99
|
-
<div
|
|
100
|
-
className={classes.canvasContainer}
|
|
101
|
-
style={{
|
|
102
|
-
width: lgv.dynamicBlocks.totalWidthPx,
|
|
103
|
-
height: lgv.bpPerPx <= 1 ? 125 : 95,
|
|
104
|
-
}}
|
|
105
|
-
>
|
|
106
|
-
<canvas
|
|
107
|
-
ref={async (node: HTMLCanvasElement) => {
|
|
108
|
-
await Promise.resolve()
|
|
109
|
-
setSeqTrackCanvas(node)
|
|
110
|
-
}}
|
|
111
|
-
width={lgv.dynamicBlocks.totalWidthPx}
|
|
112
|
-
height={lgv.bpPerPx <= 1 ? 125 : 95}
|
|
113
|
-
className={classes.canvas}
|
|
114
|
-
data-testid="seqTrackCanvas"
|
|
115
|
-
/>
|
|
116
|
-
<canvas
|
|
117
|
-
ref={async (node: HTMLCanvasElement) => {
|
|
118
|
-
await Promise.resolve()
|
|
119
|
-
setSeqTrackOverlayCanvas(node)
|
|
120
|
-
}}
|
|
121
|
-
width={lgv.dynamicBlocks.totalWidthPx}
|
|
122
|
-
height={lgv.bpPerPx <= 1 ? 125 : 95}
|
|
123
|
-
className={classes.canvas}
|
|
124
|
-
data-testid="seqTrackOverlayCanvas"
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
) : null}
|
|
128
79
|
<div
|
|
129
80
|
className={classes.canvasContainer}
|
|
130
81
|
style={{
|
|
@@ -143,13 +94,22 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
143
94
|
}
|
|
144
95
|
}}
|
|
145
96
|
>
|
|
97
|
+
{session.isLocked ? (
|
|
98
|
+
<div className={classes.locked} data-testid="lock-icon">
|
|
99
|
+
<LockIcon />
|
|
100
|
+
</div>
|
|
101
|
+
) : null}
|
|
146
102
|
{loading ? (
|
|
147
103
|
<div className={classes.loading}>
|
|
148
104
|
<CircularProgress size="18px" />
|
|
149
105
|
</div>
|
|
150
106
|
) : null}
|
|
151
107
|
{message ? (
|
|
152
|
-
<Alert
|
|
108
|
+
<Alert
|
|
109
|
+
severity="warning"
|
|
110
|
+
classes={{ message: classes.ellipses }}
|
|
111
|
+
slotProps={{ root: { className: classes.center } }}
|
|
112
|
+
>
|
|
153
113
|
<Tooltip title={message}>
|
|
154
114
|
<div>{message}</div>
|
|
155
115
|
</Tooltip>
|
|
@@ -194,9 +154,12 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
194
154
|
data-testid="overlayCanvas"
|
|
195
155
|
/>
|
|
196
156
|
{lgv.displayedRegions.flatMap((region, idx) => {
|
|
157
|
+
const widthBp = lgv.bpPerPx * apolloRowHeight
|
|
197
158
|
const assembly = assemblyManager.get(region.assemblyName)
|
|
198
|
-
|
|
199
|
-
|
|
159
|
+
if (showCheckResults) {
|
|
160
|
+
const filteredCheckResults = [
|
|
161
|
+
...session.apolloDataStore.checkResults.values(),
|
|
162
|
+
].filter(
|
|
200
163
|
(checkResult) =>
|
|
201
164
|
assembly?.isValidRefName(checkResult.refSeq) &&
|
|
202
165
|
assembly.getCanonicalRefName(checkResult.refSeq) ===
|
|
@@ -208,14 +171,19 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
208
171
|
checkResult.end,
|
|
209
172
|
),
|
|
210
173
|
)
|
|
211
|
-
|
|
174
|
+
const checkResults = clusterResultByMessage<CheckResultI>(
|
|
175
|
+
filteredCheckResults,
|
|
176
|
+
widthBp,
|
|
177
|
+
true,
|
|
178
|
+
)
|
|
179
|
+
return checkResults.map((checkResult) => {
|
|
212
180
|
const left =
|
|
213
181
|
(lgv.bpToPx({
|
|
214
182
|
refName: region.refName,
|
|
215
183
|
coord: checkResult.start,
|
|
216
184
|
regionNumber: idx,
|
|
217
185
|
})?.offsetPx ?? 0) - lgv.offsetPx
|
|
218
|
-
const [feature] = checkResult.
|
|
186
|
+
const [feature] = checkResult.featureIds
|
|
219
187
|
if (!feature) {
|
|
220
188
|
return null
|
|
221
189
|
}
|
|
@@ -228,8 +196,8 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
228
196
|
const height = apolloRowHeight
|
|
229
197
|
return (
|
|
230
198
|
<Tooltip key={checkResult._id} title={checkResult.message}>
|
|
231
|
-
<
|
|
232
|
-
className={classes.
|
|
199
|
+
<Box
|
|
200
|
+
className={classes.box}
|
|
233
201
|
style={{
|
|
234
202
|
top,
|
|
235
203
|
left,
|
|
@@ -238,11 +206,29 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
238
206
|
pointerEvents: apolloDragging ? 'none' : 'auto',
|
|
239
207
|
}}
|
|
240
208
|
>
|
|
241
|
-
<
|
|
242
|
-
|
|
209
|
+
<Badge
|
|
210
|
+
className={classes.badge}
|
|
211
|
+
badgeContent={checkResult.count}
|
|
212
|
+
color="primary"
|
|
213
|
+
overlap="circular"
|
|
214
|
+
anchorOrigin={{
|
|
215
|
+
vertical: 'bottom',
|
|
216
|
+
horizontal: 'right',
|
|
217
|
+
}}
|
|
218
|
+
invisible={checkResult.count <= 1}
|
|
219
|
+
>
|
|
220
|
+
<Avatar className={classes.avatar}>
|
|
221
|
+
<ErrorIcon
|
|
222
|
+
data-testid={`ErrorIcon-${checkResult.start}`}
|
|
223
|
+
/>
|
|
224
|
+
</Avatar>
|
|
225
|
+
</Badge>
|
|
226
|
+
</Box>
|
|
243
227
|
</Tooltip>
|
|
244
228
|
)
|
|
245
229
|
})
|
|
230
|
+
}
|
|
231
|
+
return null
|
|
246
232
|
})}
|
|
247
233
|
<Menu
|
|
248
234
|
open={contextMenuItems.length > 0}
|
|
@@ -253,9 +239,11 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
|
|
|
253
239
|
onClose={() => {
|
|
254
240
|
setContextMenuItems([])
|
|
255
241
|
}}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
242
|
+
slotProps={{
|
|
243
|
+
transition: {
|
|
244
|
+
onExit: () => {
|
|
245
|
+
setContextMenuItems([])
|
|
246
|
+
},
|
|
259
247
|
},
|
|
260
248
|
}}
|
|
261
249
|
anchorReference="anchorPosition"
|