@apollo-annotation/jbrowse-plugin-apollo 0.3.6 → 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 (84) hide show
  1. package/dist/index.esm.js +4603 -2045
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +4611 -2039
  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 +9387 -4016
  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 +42 -18
  15. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  16. package/src/FeatureDetailsWidget/Attributes.tsx +8 -3
  17. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -81
  18. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +946 -190
  19. package/src/FeatureDetailsWidget/TranscriptWidgetSummary.tsx +4 -0
  20. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +61 -73
  21. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +55 -211
  22. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +562 -108
  23. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +78 -14
  24. package/src/LinearApolloDisplay/glyphs/Glyph.ts +15 -9
  25. package/src/LinearApolloDisplay/stateModel/base.ts +63 -43
  26. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  27. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +79 -292
  28. package/src/LinearApolloDisplay/stateModel/rendering.ts +45 -344
  29. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  30. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  31. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  32. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  33. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  34. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +481 -0
  36. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +102 -40
  37. package/src/LinearApolloSixFrameDisplay/components/TrackLines.tsx +12 -20
  38. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +382 -243
  39. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  40. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +83 -4
  41. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +23 -11
  42. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +118 -123
  43. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +53 -63
  44. package/src/OntologyManager/index.ts +4 -1
  45. package/src/TabularEditor/HybridGrid/Feature.tsx +20 -14
  46. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  47. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +108 -16
  48. package/src/components/AddAssembly.tsx +1 -1
  49. package/src/components/AddAssemblyAliases.tsx +114 -0
  50. package/src/components/AddChildFeature.tsx +7 -7
  51. package/src/components/AddFeature.tsx +20 -15
  52. package/src/components/AddRefSeqAliases.tsx +9 -9
  53. package/src/components/CopyFeature.tsx +4 -4
  54. package/src/components/CreateApolloAnnotation.tsx +335 -151
  55. package/src/components/DeleteAssembly.tsx +1 -1
  56. package/src/components/DeleteFeature.tsx +358 -11
  57. package/src/components/DownloadGFF3.tsx +20 -1
  58. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  59. package/src/components/FilterFeatures.tsx +7 -7
  60. package/src/components/FilterTranscripts.tsx +86 -0
  61. package/src/components/ImportFeatures.tsx +1 -1
  62. package/src/components/ManageChecks.tsx +1 -1
  63. package/src/components/MergeExons.tsx +193 -0
  64. package/src/components/MergeTranscripts.tsx +182 -0
  65. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  66. package/src/components/OpenLocalFile.tsx +11 -7
  67. package/src/components/SplitExon.tsx +134 -0
  68. package/src/components/ViewCheckResults.tsx +1 -1
  69. package/src/components/index.ts +4 -0
  70. package/src/config.ts +11 -0
  71. package/src/extensions/annotationFromJBrowseFeature.ts +2 -0
  72. package/src/extensions/annotationFromPileup.ts +99 -89
  73. package/src/index.ts +42 -105
  74. package/src/makeDisplayComponent.tsx +0 -1
  75. package/src/menus/index.ts +1 -0
  76. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +60 -33
  77. package/src/menus/topLevelMenuAdmin.ts +154 -0
  78. package/src/session/session.ts +163 -104
  79. package/src/util/annotationFeatureUtils.ts +59 -0
  80. package/src/util/copyToClipboard.ts +21 -0
  81. package/src/util/displayUtils.ts +149 -0
  82. package/src/util/glyphUtils.ts +201 -0
  83. package/src/util/index.ts +2 -0
  84. package/src/util/mouseEventsUtils.ts +145 -0
@@ -37,6 +37,10 @@ export const TranscriptWidgetSummary = observer(
37
37
  <TableCell>{getFeatureId(feature)}</TableCell>
38
38
  </TableRow>
39
39
  )}
40
+ <TableRow>
41
+ <HeaderTableCell>Type</HeaderTableCell>
42
+ <TableCell>{feature.type}</TableCell>
43
+ </TableRow>
40
44
  <TableRow>
41
45
  <HeaderTableCell>Location</HeaderTableCell>
42
46
  <TableCell>
@@ -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={{
@@ -139,17 +90,26 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
139
90
  } else {
140
91
  const coord: [number, number] = [event.clientX, event.clientY]
141
92
  setContextCoord(coord)
142
- setContextMenuItems(getContextMenuItems(coord))
93
+ setContextMenuItems(getContextMenuItems(event))
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"
@@ -1,20 +1,16 @@
1
1
  import { type AnnotationFeature } from '@apollo-annotation/mst'
2
2
  import { type MenuItem } from '@jbrowse/core/ui'
3
- import {
4
- type AbstractSessionModel,
5
- type SessionWithWidgets,
6
- isSessionModelWithWidgets,
7
- } from '@jbrowse/core/util'
8
- import { type Theme, alpha } from '@mui/material'
3
+ import { alpha } from '@mui/material'
9
4
 
10
- import { AddChildFeature, CopyFeature, DeleteFeature } from '../../components'
11
- import { type LinearApolloDisplay } from '../stateModel'
12
5
  import {
13
- type LinearApolloDisplayMouseEvents,
14
6
  type MousePosition,
15
- type MousePositionWithFeatureAndGlyph,
16
- isMousePositionWithFeatureAndGlyph,
17
- } from '../stateModel/mouseEvents'
7
+ type MousePositionWithFeature,
8
+ getContextMenuItemsForFeature,
9
+ isMousePositionWithFeature,
10
+ isSelectedFeature,
11
+ } from '../../util'
12
+ import { type LinearApolloDisplay } from '../stateModel'
13
+ import { type LinearApolloDisplayMouseEvents } from '../stateModel/mouseEvents'
18
14
  import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
19
15
  import { type CanvasMouseEvent } from '../types'
20
16
 
@@ -46,20 +42,6 @@ function drawBoxFill(
46
42
  drawBox(ctx, x + 1, y + 1, width - 2, height - 2, color)
47
43
  }
48
44
 
49
- function drawBoxText(
50
- ctx: CanvasRenderingContext2D,
51
- x: number,
52
- y: number,
53
- width: number,
54
- color: string,
55
- text: string,
56
- ) {
57
- ctx.fillStyle = color
58
- const textStart = Math.max(x + 1, 0)
59
- const textWidth = x - 1 + width - textStart
60
- ctx.fillText(text, textStart, y + 11, textWidth)
61
- }
62
-
63
45
  function draw(
64
46
  ctx: CanvasRenderingContext2D,
65
47
  feature: AnnotationFeature,
@@ -67,7 +49,7 @@ function draw(
67
49
  stateModel: LinearApolloDisplayRendering,
68
50
  displayedRegionIndex: number,
69
51
  ) {
70
- const { apolloRowHeight: heightPx, lgv, session, theme } = stateModel
52
+ const { apolloRowHeight: heightPx, lgv, selectedFeature, theme } = stateModel
71
53
  const { bpPerPx, displayedRegions, offsetPx } = lgv
72
54
  const displayedRegion = displayedRegions[displayedRegionIndex]
73
55
  const minX =
@@ -77,13 +59,11 @@ function draw(
77
59
  regionNumber: displayedRegionIndex,
78
60
  })?.offsetPx ?? 0) - offsetPx
79
61
  const { reversed } = displayedRegion
80
- const { apolloSelectedFeature } = session
81
62
  const widthPx = feature.length / bpPerPx
82
63
  const startPx = reversed ? minX - widthPx : minX
83
64
  const top = row * heightPx
84
- const isSelected = isSelectedFeature(feature, apolloSelectedFeature)
85
- const backgroundColor = getBackgroundColor(theme, isSelected)
86
- const textColor = getTextColor(theme, isSelected)
65
+ const backgroundColor = theme.palette.background.default
66
+ const textColor = theme.palette.text.primary
87
67
  const featureBox: [number, number, number, number] = [
88
68
  startPx,
89
69
  top,
@@ -97,7 +77,9 @@ function draw(
97
77
  }
98
78
 
99
79
  drawBoxFill(ctx, startPx, top, widthPx, heightPx, backgroundColor)
100
- drawBoxText(ctx, startPx, top, widthPx, textColor, feature.type)
80
+ if (isSelectedFeature(feature, selectedFeature)) {
81
+ drawHighlight(stateModel, ctx, feature, true)
82
+ }
101
83
  }
102
84
 
103
85
  function drawDragPreview(
@@ -123,22 +105,20 @@ function drawDragPreview(
123
105
  const rectY = row * apolloRowHeight
124
106
  const rectWidth = Math.abs(current.x - featureEdgePx)
125
107
  const rectHeight = apolloRowHeight * rowCount
126
- overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
108
+ overlayCtx.strokeStyle = theme.palette.info.main
127
109
  overlayCtx.setLineDash([6])
128
110
  overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
129
- overlayCtx.fillStyle = alpha(theme?.palette.info.main ?? 'rgb(255,0,0)', 0.2)
111
+ overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
130
112
  overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
131
113
  }
132
114
 
133
- function drawHover(
134
- stateModel: LinearApolloDisplay,
115
+ function drawHighlight(
116
+ stateModel: LinearApolloDisplayRendering,
135
117
  ctx: CanvasRenderingContext2D,
118
+ feature: AnnotationFeature,
119
+ selected = false,
136
120
  ) {
137
- const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
138
- if (!apolloHover) {
139
- return
140
- }
141
- const { feature } = apolloHover
121
+ const { apolloRowHeight, lgv, theme } = stateModel
142
122
  const position = stateModel.getFeatureLayoutPosition(feature)
143
123
  if (!position) {
144
124
  return
@@ -156,19 +136,32 @@ function drawHover(
156
136
  })?.offsetPx ?? 0) - offsetPx
157
137
  const top = layoutRow * apolloRowHeight
158
138
  const widthPx = length / bpPerPx
159
- ctx.fillStyle = theme?.palette.action.focus ?? 'rgba(0,0,0,0.04)'
139
+ ctx.fillStyle = selected
140
+ ? theme.palette.action.disabled
141
+ : theme.palette.action.focus
160
142
  ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
161
143
  }
162
144
 
145
+ function drawHover(
146
+ stateModel: LinearApolloDisplay,
147
+ ctx: CanvasRenderingContext2D,
148
+ ) {
149
+ const { hoveredFeature } = stateModel
150
+ if (!hoveredFeature) {
151
+ return
152
+ }
153
+ drawHighlight(stateModel, ctx, hoveredFeature.feature)
154
+ }
155
+
163
156
  function drawTooltip(
164
157
  display: LinearApolloDisplayMouseEvents,
165
158
  context: CanvasRenderingContext2D,
166
159
  ): void {
167
- const { apolloHover, apolloRowHeight, lgv, theme } = display
168
- if (!apolloHover) {
160
+ const { hoveredFeature, apolloRowHeight, lgv, theme } = display
161
+ if (!hoveredFeature) {
169
162
  return
170
163
  }
171
- const { feature } = apolloHover
164
+ const { feature } = hoveredFeature
172
165
  const position = display.getFeatureLayoutPosition(feature)
173
166
  if (!position) {
174
167
  return
@@ -205,14 +198,14 @@ function drawTooltip(
205
198
  const maxWidth = Math.max(...textWidth)
206
199
 
207
200
  startPx = startPx + widthPx + 5
208
- context.fillStyle = alpha(theme?.palette.text.primary ?? 'rgb(1, 1, 1)', 0.7)
201
+ context.fillStyle = alpha(theme.palette.text.primary, 0.7)
209
202
  context.fillRect(startPx, top, maxWidth + 4, textWidth.length === 3 ? 45 : 35)
210
203
  context.beginPath()
211
204
  context.moveTo(startPx, top)
212
205
  context.lineTo(startPx - 5, top + 5)
213
206
  context.lineTo(startPx, top + 10)
214
207
  context.fill()
215
- context.fillStyle = theme?.palette.background.default ?? 'rgba(255, 255, 255)'
208
+ context.fillStyle = theme.palette.background.default
216
209
  let textTop = top + 12
217
210
  context.fillText(featureType, startPx + 2, textTop)
218
211
  if (featureName) {
@@ -223,26 +216,6 @@ function drawTooltip(
223
216
  context.fillText(location, startPx + 2, textTop)
224
217
  }
225
218
 
226
- export function isSelectedFeature(
227
- feature: AnnotationFeature,
228
- selectedFeature: AnnotationFeature | undefined,
229
- ) {
230
- return Boolean(selectedFeature && feature._id === selectedFeature._id)
231
- }
232
-
233
- function getBackgroundColor(theme: Theme | undefined, selected: boolean) {
234
- return selected
235
- ? theme?.palette.text.primary ?? 'black'
236
- : theme?.palette.background.default ?? 'white'
237
- }
238
-
239
- function getTextColor(theme: Theme | undefined, selected: boolean) {
240
- return selected
241
- ? theme?.palette.getContrastText(getBackgroundColor(theme, selected)) ??
242
- 'white'
243
- : theme?.palette.text.primary ?? 'black'
244
- }
245
-
246
219
  export function drawBox(
247
220
  ctx: CanvasRenderingContext2D,
248
221
  x: number,
@@ -258,141 +231,11 @@ export function drawBox(
258
231
  function getContextMenuItems(
259
232
  display: LinearApolloDisplayMouseEvents,
260
233
  ): MenuItem[] {
261
- const {
262
- apolloHover,
263
- apolloInternetAccount: internetAccount,
264
- changeManager,
265
- regions,
266
- selectedFeature,
267
- session,
268
- } = display
269
- const menuItems: MenuItem[] = []
270
- if (!apolloHover) {
271
- return menuItems
272
- }
273
- const { feature: sourceFeature } = apolloHover
274
- const role = internetAccount ? internetAccount.role : 'admin'
275
- const admin = role === 'admin'
276
- const readOnly = !(role && ['admin', 'user'].includes(role))
277
- const [region] = regions
278
- const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
279
- const currentAssemblyId = display.getAssemblyId(region.assemblyName)
280
- menuItems.push(
281
- {
282
- label: 'Add child feature',
283
- disabled: readOnly,
284
- onClick: () => {
285
- ;(session as unknown as AbstractSessionModel).queueDialog(
286
- (doneCallback) => [
287
- AddChildFeature,
288
- {
289
- session,
290
- handleClose: () => {
291
- doneCallback()
292
- },
293
- changeManager,
294
- sourceFeature,
295
- sourceAssemblyId,
296
- internetAccount,
297
- },
298
- ],
299
- )
300
- },
301
- },
302
- {
303
- label: 'Copy features and annotations',
304
- disabled: readOnly,
305
- onClick: () => {
306
- ;(session as unknown as AbstractSessionModel).queueDialog(
307
- (doneCallback) => [
308
- CopyFeature,
309
- {
310
- session,
311
- handleClose: () => {
312
- doneCallback()
313
- },
314
- changeManager,
315
- sourceFeature,
316
- sourceAssemblyId: currentAssemblyId,
317
- },
318
- ],
319
- )
320
- },
321
- },
322
- {
323
- label: 'Delete feature',
324
- disabled: !admin,
325
- onClick: () => {
326
- ;(session as unknown as AbstractSessionModel).queueDialog(
327
- (doneCallback) => [
328
- DeleteFeature,
329
- {
330
- session,
331
- handleClose: () => {
332
- doneCallback()
333
- },
334
- changeManager,
335
- sourceFeature,
336
- sourceAssemblyId: currentAssemblyId,
337
- selectedFeature,
338
- setSelectedFeature: (feature?: AnnotationFeature) => {
339
- display.setSelectedFeature(feature)
340
- },
341
- },
342
- ],
343
- )
344
- },
345
- },
346
- {
347
- label: 'Edit feature details',
348
- onClick: () => {
349
- const apolloFeatureWidget = (
350
- session as unknown as SessionWithWidgets
351
- ).addWidget(
352
- 'ApolloFeatureDetailsWidget',
353
- 'apolloFeatureDetailsWidget',
354
- {
355
- feature: sourceFeature,
356
- assembly: currentAssemblyId,
357
- refName: region.refName,
358
- },
359
- )
360
- ;(session as unknown as SessionWithWidgets).showWidget(
361
- apolloFeatureWidget,
362
- )
363
- },
364
- },
365
- )
366
- const { featureTypeOntology } = session.apolloDataStore.ontologyManager
367
- if (!featureTypeOntology) {
368
- throw new Error('featureTypeOntology is undefined')
234
+ const { hoveredFeature } = display
235
+ if (!hoveredFeature) {
236
+ return []
369
237
  }
370
- if (
371
- (featureTypeOntology.isTypeOf(sourceFeature.type, 'transcript') ||
372
- featureTypeOntology.isTypeOf(
373
- sourceFeature.type,
374
- 'pseudogenic_transcript',
375
- )) &&
376
- isSessionModelWithWidgets(session)
377
- ) {
378
- menuItems.push({
379
- label: 'Edit transcript details',
380
- onClick: () => {
381
- const apolloTranscriptWidget = session.addWidget(
382
- 'ApolloTranscriptDetails',
383
- 'apolloTranscriptDetails',
384
- {
385
- feature: sourceFeature,
386
- assembly: currentAssemblyId,
387
- changeManager,
388
- refName: region.refName,
389
- },
390
- )
391
- session.showWidget(apolloTranscriptWidget)
392
- },
393
- })
394
- }
395
- return menuItems
238
+ return getContextMenuItemsForFeature(display, hoveredFeature.feature)
396
239
  }
397
240
 
398
241
  function getFeatureFromLayout(
@@ -416,13 +259,12 @@ function getRowForFeature(
416
259
 
417
260
  function onMouseDown(
418
261
  stateModel: LinearApolloDisplay,
419
- currentMousePosition: MousePositionWithFeatureAndGlyph,
262
+ currentMousePosition: MousePositionWithFeature,
420
263
  event: CanvasMouseEvent,
421
264
  ) {
422
- const { featureAndGlyphUnderMouse } = currentMousePosition
265
+ const { feature } = currentMousePosition
423
266
  // swallow the mouseDown if we are on the edge of the feature so that we
424
267
  // don't start dragging the view if we try to drag the feature edge
425
- const { feature } = featureAndGlyphUnderMouse
426
268
  const edge = isMouseOnFeatureEdge(currentMousePosition, feature, stateModel)
427
269
  if (edge) {
428
270
  event.stopPropagation()
@@ -438,10 +280,9 @@ function onMouseMove(
438
280
  stateModel: LinearApolloDisplay,
439
281
  mousePosition: MousePosition,
440
282
  ) {
441
- if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
442
- const { featureAndGlyphUnderMouse } = mousePosition
443
- stateModel.setApolloHover(featureAndGlyphUnderMouse)
444
- const { feature } = featureAndGlyphUnderMouse
283
+ if (isMousePositionWithFeature(mousePosition)) {
284
+ const { feature, bp } = mousePosition
285
+ stateModel.setHoveredFeature({ feature, bp })
445
286
  const edge = isMouseOnFeatureEdge(mousePosition, feature, stateModel)
446
287
  if (edge) {
447
288
  stateModel.setCursor('col-resize')
@@ -458,10 +299,12 @@ function onMouseUp(
458
299
  if (stateModel.apolloDragging) {
459
300
  return
460
301
  }
461
- const { featureAndGlyphUnderMouse } = mousePosition
462
- if (featureAndGlyphUnderMouse?.feature) {
463
- stateModel.setSelectedFeature(featureAndGlyphUnderMouse.feature)
302
+ const { feature } = mousePosition
303
+ if (!feature) {
304
+ return
464
305
  }
306
+ stateModel.setSelectedFeature(feature)
307
+ stateModel.showFeatureDetailsWidget(feature)
465
308
  }
466
309
 
467
310
  /** @returns undefined if mouse not on the edge of this feature, otherwise 'start' or 'end' depending on which edge */
@@ -496,6 +339,7 @@ export const boxGlyph: Glyph = {
496
339
  drawDragPreview,
497
340
  drawHover,
498
341
  drawTooltip,
342
+ getContextMenuItemsForFeature,
499
343
  getContextMenuItems,
500
344
  getFeatureFromLayout,
501
345
  getRowCount,