@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.
Files changed (71) hide show
  1. package/dist/index.esm.js +2371 -1642
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +2384 -1641
  4. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  5. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  6. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  7. package/dist/jbrowse-plugin-apollo.umd.development.js +4387 -2952
  8. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  9. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  10. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  11. package/package.json +15 -15
  12. package/src/ApolloInternetAccount/model.ts +48 -13
  13. package/src/BackendDrivers/CollaborationServerDriver.ts +23 -2
  14. package/src/ChangeManager.ts +33 -13
  15. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  16. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  17. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +33 -31
  18. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +60 -72
  19. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  20. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +441 -180
  21. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  22. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  23. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  24. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  25. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  26. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  27. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  28. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  29. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  30. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  31. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  32. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  33. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
  34. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +95 -38
  35. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +221 -201
  36. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  37. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  38. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  39. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  40. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  41. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  42. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  43. package/src/components/AddAssembly.tsx +1 -1
  44. package/src/components/AddAssemblyAliases.tsx +1 -1
  45. package/src/components/AddChildFeature.tsx +5 -2
  46. package/src/components/AddFeature.tsx +9 -3
  47. package/src/components/AddRefSeqAliases.tsx +9 -9
  48. package/src/components/CopyFeature.tsx +3 -1
  49. package/src/components/CreateApolloAnnotation.tsx +1 -0
  50. package/src/components/DeleteAssembly.tsx +1 -1
  51. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  52. package/src/components/FilterFeatures.tsx +7 -7
  53. package/src/components/FilterTranscripts.tsx +6 -6
  54. package/src/components/ImportFeatures.tsx +1 -1
  55. package/src/components/ManageChecks.tsx +1 -1
  56. package/src/components/MergeTranscripts.tsx +12 -15
  57. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  58. package/src/components/OpenLocalFile.tsx +11 -7
  59. package/src/components/ViewCheckResults.tsx +1 -1
  60. package/src/components/index.ts +1 -0
  61. package/src/config.ts +6 -0
  62. package/src/index.ts +42 -105
  63. package/src/makeDisplayComponent.tsx +0 -1
  64. package/src/menus/index.ts +1 -0
  65. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  66. package/src/menus/topLevelMenuAdmin.ts +154 -0
  67. package/src/session/session.ts +162 -116
  68. package/src/util/annotationFeatureUtils.ts +15 -21
  69. package/src/util/displayUtils.ts +149 -0
  70. package/src/util/glyphUtils.ts +152 -0
  71. 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
- sequenceLines: string[]
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
- sequenceLines,
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
- const [previousSegmentFirstLine, ...previousSegmentFollowingLines] =
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
- sequenceLines: [newSegmentFirstLine, ...newSegmentRemainderLines],
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
- wholeSequence += getSequence(loc.min, loc.max)
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
- if (strand === -1) {
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
- wholeSequence += getSequence(loc.min, loc.max)
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
- const sequenceLines = splitStringIntoChunks(protein, SEQUENCE_WRAP_LENGTH)
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 key={option} value={option}>
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.map((segment, index) => (
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
- Grid2,
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
- <Grid2
1159
+ <Grid
1158
1160
  container
1159
1161
  justifyContent="center"
1160
1162
  alignItems="center"
1161
1163
  style={{ textAlign: 'center', marginTop: 10 }}
1162
1164
  >
1163
- <Grid2 size={1} />
1165
+ <Grid size={1} />
1164
1166
  {strand === 1 ? (
1165
- <Grid2 size={4}>
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
- </Grid2>
1182
+ </Grid>
1181
1183
  ) : (
1182
- <Grid2 size={4}>
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
- </Grid2>
1199
+ </Grid>
1198
1200
  )}
1199
- <Grid2 size={2}>
1201
+ <Grid size={2}>
1200
1202
  <Typography component={'span'}>CDS</Typography>
1201
- </Grid2>
1203
+ </Grid>
1202
1204
  {strand === 1 ? (
1203
- <Grid2 size={4}>
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
- </Grid2>
1220
+ </Grid>
1219
1221
  ) : (
1220
- <Grid2 size={4}>
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
- </Grid2>
1237
+ </Grid>
1236
1238
  )}
1237
- <Grid2 size={1} />
1238
- </Grid2>
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
- <Grid2
1248
+ <Grid
1247
1249
  container
1248
1250
  justifyContent="center"
1249
1251
  alignItems="center"
1250
1252
  style={{ textAlign: 'center' }}
1251
1253
  >
1252
- <Grid2 size={1}>
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
- </Grid2>
1265
+ </Grid>
1264
1266
  {strand === 1 ? (
1265
- <Grid2 size={4} style={{ padding: 0 }}>
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
- </Grid2>
1281
+ </Grid>
1280
1282
  ) : (
1281
- <Grid2 size={4} style={{ padding: 0 }}>
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
- </Grid2>
1297
+ </Grid>
1296
1298
  )}
1297
- <Grid2 size={2}>
1299
+ <Grid size={2}>
1298
1300
  <Strand strand={feature.strand} />
1299
- </Grid2>
1301
+ </Grid>
1300
1302
  {strand === 1 ? (
1301
- <Grid2 size={4} style={{ padding: 0 }}>
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
- </Grid2>
1317
+ </Grid>
1316
1318
  ) : (
1317
- <Grid2 size={4} style={{ padding: 0 }}>
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
- </Grid2>
1333
+ </Grid>
1332
1334
  )}
1333
- <Grid2 size={1}>
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
- </Grid2>
1345
- </Grid2>
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
- const useStyles = makeStyles()((theme) => ({
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 severity="warning" classes={{ message: classes.ellipses }}>
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
- return [...session.apolloDataStore.checkResults.values()]
199
- .filter(
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
- .map((checkResult) => {
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.ids
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
- <Avatar
232
- className={classes.avatar}
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
- <ErrorIcon data-testid="ErrorIcon" />
242
- </Avatar>
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
- TransitionProps={{
257
- onExit: () => {
258
- setContextMenuItems([])
242
+ slotProps={{
243
+ transition: {
244
+ onExit: () => {
245
+ setContextMenuItems([])
246
+ },
259
247
  },
260
248
  }}
261
249
  anchorReference="anchorPosition"