@apollo-annotation/jbrowse-plugin-apollo 0.3.7 → 0.3.9

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 (86) hide show
  1. package/dist/index.esm.js +11212 -10483
  2. package/dist/index.esm.js.map +1 -1
  3. package/dist/jbrowse-plugin-apollo.cjs.development.js +11251 -10509
  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 +7726 -9014
  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 +18 -18
  12. package/src/ApolloInternetAccount/model.ts +123 -70
  13. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +4 -4
  14. package/src/ApolloSequenceAdapter/ApolloSequenceAdapter.ts +9 -7
  15. package/src/BackendDrivers/CollaborationServerDriver.ts +72 -20
  16. package/src/BackendDrivers/DesktopFileDriver.ts +2 -2
  17. package/src/ChangeManager.ts +36 -14
  18. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +64 -5
  19. package/src/FeatureDetailsWidget/BasicInformation.tsx +6 -4
  20. package/src/FeatureDetailsWidget/NumberTextField.tsx +5 -2
  21. package/src/FeatureDetailsWidget/TranscriptSequence.tsx +70 -73
  22. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +72 -234
  23. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +92 -0
  24. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +23 -131
  25. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +50 -194
  26. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +279 -217
  27. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +53 -34
  28. package/src/LinearApolloDisplay/glyphs/Glyph.ts +7 -9
  29. package/src/LinearApolloDisplay/glyphs/util.ts +19 -0
  30. package/src/LinearApolloDisplay/stateModel/base.ts +34 -43
  31. package/src/LinearApolloDisplay/stateModel/layouts.ts +3 -2
  32. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +32 -261
  33. package/src/LinearApolloDisplay/stateModel/rendering.ts +43 -343
  34. package/src/LinearApolloReferenceSequenceDisplay/components/LinearApolloReferenceSequenceDisplay.tsx +87 -0
  35. package/src/LinearApolloReferenceSequenceDisplay/components/index.ts +1 -0
  36. package/src/LinearApolloReferenceSequenceDisplay/configSchema.ts +7 -0
  37. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +181 -0
  38. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceTrack.ts +218 -0
  39. package/src/LinearApolloReferenceSequenceDisplay/index.ts +3 -0
  40. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +227 -0
  41. package/src/LinearApolloReferenceSequenceDisplay/stateModel/index.ts +25 -0
  42. package/src/LinearApolloReferenceSequenceDisplay/stateModel/rendering.ts +157 -0
  43. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +101 -38
  44. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +334 -262
  45. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +12 -8
  46. package/src/LinearApolloSixFrameDisplay/stateModel/base.ts +42 -4
  47. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -8
  48. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +73 -97
  49. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +49 -61
  50. package/src/TabularEditor/HybridGrid/Feature.tsx +16 -14
  51. package/src/TabularEditor/HybridGrid/HybridGrid.tsx +7 -5
  52. package/src/components/AddAssembly.tsx +34 -38
  53. package/src/components/AddAssemblyAliases.tsx +1 -1
  54. package/src/components/AddChildFeature.tsx +5 -2
  55. package/src/components/AddFeature.tsx +30 -21
  56. package/src/components/AddRefSeqAliases.tsx +64 -50
  57. package/src/components/CopyFeature.tsx +4 -2
  58. package/src/components/CreateApolloAnnotation.tsx +22 -9
  59. package/src/components/DeleteAssembly.tsx +3 -10
  60. package/src/components/DownloadGFF3.tsx +2 -2
  61. package/src/components/EditZoomThresholdDialog.tsx +69 -0
  62. package/src/components/FilterFeatures.tsx +7 -7
  63. package/src/components/FilterTranscripts.tsx +6 -6
  64. package/src/components/ImportFeatures.tsx +1 -1
  65. package/src/components/ManageChecks.tsx +3 -10
  66. package/src/components/ManageUsers.tsx +23 -22
  67. package/src/components/MergeTranscripts.tsx +12 -15
  68. package/src/components/OntologyTermAutocomplete.tsx +1 -8
  69. package/src/components/OntologyTermMultiSelect.tsx +11 -11
  70. package/src/components/OpenLocalFile.tsx +11 -7
  71. package/src/components/ViewChangeLog.tsx +25 -50
  72. package/src/components/ViewCheckResults.tsx +2 -8
  73. package/src/components/index.ts +1 -0
  74. package/src/config.ts +6 -0
  75. package/src/index.ts +53 -115
  76. package/src/makeDisplayComponent.tsx +9 -14
  77. package/src/menus/index.ts +1 -0
  78. package/src/{ApolloInternetAccount/addMenuItems.ts → menus/topLevelMenu.ts} +56 -47
  79. package/src/menus/topLevelMenuAdmin.ts +154 -0
  80. package/src/session/ClientDataStore.ts +32 -14
  81. package/src/session/session.ts +159 -121
  82. package/src/util/annotationFeatureUtils.ts +15 -21
  83. package/src/util/displayUtils.ts +149 -0
  84. package/src/util/glyphUtils.ts +329 -0
  85. package/src/util/loadAssemblyIntoClient.ts +3 -2
  86. package/src/util/mouseEventsUtils.ts +32 -0
@@ -2,57 +2,23 @@
2
2
  /* eslint-disable @typescript-eslint/no-misused-promises */
3
3
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
4
4
  import { Menu, type MenuItem } from '@jbrowse/core/ui'
5
- import {
6
- type AbstractSessionModel,
7
- doesIntersect2,
8
- getContainingView,
9
- } from '@jbrowse/core/util'
5
+ import { getContainingView } from '@jbrowse/core/util'
10
6
  import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
11
- import ErrorIcon from '@mui/icons-material/Error'
12
- import {
13
- Alert,
14
- Avatar,
15
- CircularProgress,
16
- Tooltip,
17
- useTheme,
18
- } from '@mui/material'
7
+ import LockIcon from '@mui/icons-material/Lock'
8
+ import { Alert, CircularProgress, Tooltip, useTheme } from '@mui/material'
19
9
  import { observer } from 'mobx-react'
20
10
  import React, { useEffect, useState } from 'react'
21
- import { makeStyles } from 'tss-react/mui'
22
11
 
12
+ import { type Coord, useStyles } from '../../util/displayUtils'
23
13
  import { type LinearApolloDisplay as LinearApolloDisplayI } from '../stateModel'
24
14
 
15
+ import { CheckResultWarnings } from './CheckResultWarnings'
16
+
25
17
  interface LinearApolloDisplayProps {
26
18
  model: LinearApolloDisplayI
27
19
  }
28
- export type Coord = [number, number]
29
20
 
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
- }))
21
+ // Lock icon when isLocked === true
56
22
 
57
23
  export const LinearApolloDisplay = observer(function LinearApolloDisplay(
58
24
  props: LinearApolloDisplayProps,
@@ -61,8 +27,6 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
61
27
  const { model } = props
62
28
  const {
63
29
  loading,
64
- apolloDragging,
65
- apolloRowHeight,
66
30
  contextMenuItems: getContextMenuItems,
67
31
  cursor,
68
32
  featuresHeight,
@@ -76,8 +40,6 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
76
40
  setCanvas,
77
41
  setCollaboratorCanvas,
78
42
  setOverlayCanvas,
79
- setSeqTrackCanvas,
80
- setSeqTrackOverlayCanvas,
81
43
  setTheme,
82
44
  } = model
83
45
  const { classes } = useStyles()
@@ -92,39 +54,8 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
92
54
  if (!isShown) {
93
55
  return null
94
56
  }
95
- const { assemblyManager } = session as unknown as AbstractSessionModel
96
57
  return (
97
58
  <>
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
59
  <div
129
60
  className={classes.canvasContainer}
130
61
  style={{
@@ -143,13 +74,22 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
143
74
  }
144
75
  }}
145
76
  >
77
+ {session.isLocked ? (
78
+ <div className={classes.locked} data-testid="lock-icon">
79
+ <LockIcon />
80
+ </div>
81
+ ) : null}
146
82
  {loading ? (
147
83
  <div className={classes.loading}>
148
84
  <CircularProgress size="18px" />
149
85
  </div>
150
86
  ) : null}
151
87
  {message ? (
152
- <Alert severity="warning" classes={{ message: classes.ellipses }}>
88
+ <Alert
89
+ severity="warning"
90
+ classes={{ message: classes.ellipses }}
91
+ slotProps={{ root: { className: classes.center } }}
92
+ >
153
93
  <Tooltip title={message}>
154
94
  <div>{message}</div>
155
95
  </Tooltip>
@@ -193,57 +133,7 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
193
133
  style={{ cursor: cursor ?? 'default' }}
194
134
  data-testid="overlayCanvas"
195
135
  />
196
- {lgv.displayedRegions.flatMap((region, idx) => {
197
- const assembly = assemblyManager.get(region.assemblyName)
198
- return [...session.apolloDataStore.checkResults.values()]
199
- .filter(
200
- (checkResult) =>
201
- assembly?.isValidRefName(checkResult.refSeq) &&
202
- assembly.getCanonicalRefName(checkResult.refSeq) ===
203
- region.refName &&
204
- doesIntersect2(
205
- region.start,
206
- region.end,
207
- checkResult.start,
208
- checkResult.end,
209
- ),
210
- )
211
- .map((checkResult) => {
212
- const left =
213
- (lgv.bpToPx({
214
- refName: region.refName,
215
- coord: checkResult.start,
216
- regionNumber: idx,
217
- })?.offsetPx ?? 0) - lgv.offsetPx
218
- const [feature] = checkResult.ids
219
- if (!feature) {
220
- return null
221
- }
222
- let row = 0
223
- const featureLayout = model.getFeatureLayoutPosition(feature)
224
- if (featureLayout) {
225
- row = featureLayout.layoutRow + featureLayout.featureRow
226
- }
227
- const top = row * apolloRowHeight
228
- const height = apolloRowHeight
229
- return (
230
- <Tooltip key={checkResult._id} title={checkResult.message}>
231
- <Avatar
232
- className={classes.avatar}
233
- style={{
234
- top,
235
- left,
236
- height,
237
- width: height,
238
- pointerEvents: apolloDragging ? 'none' : 'auto',
239
- }}
240
- >
241
- <ErrorIcon data-testid="ErrorIcon" />
242
- </Avatar>
243
- </Tooltip>
244
- )
245
- })
246
- })}
136
+ <CheckResultWarnings display={model} />
247
137
  <Menu
248
138
  open={contextMenuItems.length > 0}
249
139
  onMenuItemClick={(_, callback) => {
@@ -253,9 +143,11 @@ export const LinearApolloDisplay = observer(function LinearApolloDisplay(
253
143
  onClose={() => {
254
144
  setContextMenuItems([])
255
145
  }}
256
- TransitionProps={{
257
- onExit: () => {
258
- setContextMenuItems([])
146
+ slotProps={{
147
+ transition: {
148
+ onExit: () => {
149
+ setContextMenuItems([])
150
+ },
259
151
  },
260
152
  }}
261
153
  anchorReference="anchorPosition"
@@ -1,16 +1,16 @@
1
1
  import { type AnnotationFeature } from '@apollo-annotation/mst'
2
2
  import { type MenuItem } from '@jbrowse/core/ui'
3
- import { type AbstractSessionModel } from '@jbrowse/core/util'
4
- import { type Theme, alpha } from '@mui/material'
3
+ import { alpha } from '@mui/material'
5
4
 
6
- import { AddChildFeature, CopyFeature, DeleteFeature } from '../../components'
7
- import { type LinearApolloDisplay } from '../stateModel'
8
5
  import {
9
- type LinearApolloDisplayMouseEvents,
10
6
  type MousePosition,
11
- type MousePositionWithFeatureAndGlyph,
12
- isMousePositionWithFeatureAndGlyph,
13
- } 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'
14
14
  import { type LinearApolloDisplayRendering } from '../stateModel/rendering'
15
15
  import { type CanvasMouseEvent } from '../types'
16
16
 
@@ -42,20 +42,6 @@ function drawBoxFill(
42
42
  drawBox(ctx, x + 1, y + 1, width - 2, height - 2, color)
43
43
  }
44
44
 
45
- function drawBoxText(
46
- ctx: CanvasRenderingContext2D,
47
- x: number,
48
- y: number,
49
- width: number,
50
- color: string,
51
- text: string,
52
- ) {
53
- ctx.fillStyle = color
54
- const textStart = Math.max(x + 1, 0)
55
- const textWidth = x - 1 + width - textStart
56
- ctx.fillText(text, textStart, y + 11, textWidth)
57
- }
58
-
59
45
  function draw(
60
46
  ctx: CanvasRenderingContext2D,
61
47
  feature: AnnotationFeature,
@@ -63,7 +49,7 @@ function draw(
63
49
  stateModel: LinearApolloDisplayRendering,
64
50
  displayedRegionIndex: number,
65
51
  ) {
66
- const { apolloRowHeight: heightPx, lgv, session, theme } = stateModel
52
+ const { apolloRowHeight: heightPx, lgv, selectedFeature, theme } = stateModel
67
53
  const { bpPerPx, displayedRegions, offsetPx } = lgv
68
54
  const displayedRegion = displayedRegions[displayedRegionIndex]
69
55
  const minX =
@@ -73,13 +59,11 @@ function draw(
73
59
  regionNumber: displayedRegionIndex,
74
60
  })?.offsetPx ?? 0) - offsetPx
75
61
  const { reversed } = displayedRegion
76
- const { apolloSelectedFeature } = session
77
62
  const widthPx = feature.length / bpPerPx
78
63
  const startPx = reversed ? minX - widthPx : minX
79
64
  const top = row * heightPx
80
- const isSelected = isSelectedFeature(feature, apolloSelectedFeature)
81
- const backgroundColor = getBackgroundColor(theme, isSelected)
82
- const textColor = getTextColor(theme, isSelected)
65
+ const backgroundColor = theme.palette.background.default
66
+ const textColor = theme.palette.text.primary
83
67
  const featureBox: [number, number, number, number] = [
84
68
  startPx,
85
69
  top,
@@ -93,7 +77,9 @@ function draw(
93
77
  }
94
78
 
95
79
  drawBoxFill(ctx, startPx, top, widthPx, heightPx, backgroundColor)
96
- drawBoxText(ctx, startPx, top, widthPx, textColor, feature.type)
80
+ if (isSelectedFeature(feature, selectedFeature)) {
81
+ drawHighlight(stateModel, ctx, feature, true)
82
+ }
97
83
  }
98
84
 
99
85
  function drawDragPreview(
@@ -119,22 +105,20 @@ function drawDragPreview(
119
105
  const rectY = row * apolloRowHeight
120
106
  const rectWidth = Math.abs(current.x - featureEdgePx)
121
107
  const rectHeight = apolloRowHeight * rowCount
122
- overlayCtx.strokeStyle = theme?.palette.info.main ?? 'rgb(255,0,0)'
108
+ overlayCtx.strokeStyle = theme.palette.info.main
123
109
  overlayCtx.setLineDash([6])
124
110
  overlayCtx.strokeRect(rectX, rectY, rectWidth, rectHeight)
125
- overlayCtx.fillStyle = alpha(theme?.palette.info.main ?? 'rgb(255,0,0)', 0.2)
111
+ overlayCtx.fillStyle = alpha(theme.palette.info.main, 0.2)
126
112
  overlayCtx.fillRect(rectX, rectY, rectWidth, rectHeight)
127
113
  }
128
114
 
129
- function drawHover(
130
- stateModel: LinearApolloDisplay,
115
+ function drawHighlight(
116
+ stateModel: LinearApolloDisplayRendering,
131
117
  ctx: CanvasRenderingContext2D,
118
+ feature: AnnotationFeature,
119
+ selected = false,
132
120
  ) {
133
- const { apolloHover, apolloRowHeight, lgv, theme } = stateModel
134
- if (!apolloHover) {
135
- return
136
- }
137
- const { feature } = apolloHover
121
+ const { apolloRowHeight, lgv, theme } = stateModel
138
122
  const position = stateModel.getFeatureLayoutPosition(feature)
139
123
  if (!position) {
140
124
  return
@@ -152,19 +136,32 @@ function drawHover(
152
136
  })?.offsetPx ?? 0) - offsetPx
153
137
  const top = layoutRow * apolloRowHeight
154
138
  const widthPx = length / bpPerPx
155
- 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
156
142
  ctx.fillRect(startPx, top, widthPx, apolloRowHeight)
157
143
  }
158
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
+
159
156
  function drawTooltip(
160
157
  display: LinearApolloDisplayMouseEvents,
161
158
  context: CanvasRenderingContext2D,
162
159
  ): void {
163
- const { apolloHover, apolloRowHeight, lgv, theme } = display
164
- if (!apolloHover) {
160
+ const { hoveredFeature, apolloRowHeight, lgv, theme } = display
161
+ if (!hoveredFeature) {
165
162
  return
166
163
  }
167
- const { feature } = apolloHover
164
+ const { feature } = hoveredFeature
168
165
  const position = display.getFeatureLayoutPosition(feature)
169
166
  if (!position) {
170
167
  return
@@ -201,14 +198,14 @@ function drawTooltip(
201
198
  const maxWidth = Math.max(...textWidth)
202
199
 
203
200
  startPx = startPx + widthPx + 5
204
- context.fillStyle = alpha(theme?.palette.text.primary ?? 'rgb(1, 1, 1)', 0.7)
201
+ context.fillStyle = alpha(theme.palette.text.primary, 0.7)
205
202
  context.fillRect(startPx, top, maxWidth + 4, textWidth.length === 3 ? 45 : 35)
206
203
  context.beginPath()
207
204
  context.moveTo(startPx, top)
208
205
  context.lineTo(startPx - 5, top + 5)
209
206
  context.lineTo(startPx, top + 10)
210
207
  context.fill()
211
- context.fillStyle = theme?.palette.background.default ?? 'rgba(255, 255, 255)'
208
+ context.fillStyle = theme.palette.background.default
212
209
  let textTop = top + 12
213
210
  context.fillText(featureType, startPx + 2, textTop)
214
211
  if (featureName) {
@@ -219,26 +216,6 @@ function drawTooltip(
219
216
  context.fillText(location, startPx + 2, textTop)
220
217
  }
221
218
 
222
- export function isSelectedFeature(
223
- feature: AnnotationFeature,
224
- selectedFeature: AnnotationFeature | undefined,
225
- ) {
226
- return Boolean(selectedFeature && feature._id === selectedFeature._id)
227
- }
228
-
229
- function getBackgroundColor(theme: Theme | undefined, selected: boolean) {
230
- return selected
231
- ? theme?.palette.text.primary ?? 'black'
232
- : theme?.palette.background.default ?? 'white'
233
- }
234
-
235
- function getTextColor(theme: Theme | undefined, selected: boolean) {
236
- return selected
237
- ? theme?.palette.getContrastText(getBackgroundColor(theme, selected)) ??
238
- 'white'
239
- : theme?.palette.text.primary ?? 'black'
240
- }
241
-
242
219
  export function drawBox(
243
220
  ctx: CanvasRenderingContext2D,
244
221
  x: number,
@@ -254,129 +231,11 @@ export function drawBox(
254
231
  function getContextMenuItems(
255
232
  display: LinearApolloDisplayMouseEvents,
256
233
  ): MenuItem[] {
257
- const { apolloHover } = display
258
- if (!apolloHover) {
234
+ const { hoveredFeature } = display
235
+ if (!hoveredFeature) {
259
236
  return []
260
237
  }
261
- const { feature: sourceFeature } = apolloHover
262
- return getContextMenuItemsForFeature(display, sourceFeature)
263
- }
264
-
265
- function makeFeatureLabel(feature: AnnotationFeature) {
266
- let name: string | undefined
267
- if (feature.attributes.get('gff_name')) {
268
- name = feature.attributes.get('gff_name')?.join(',')
269
- } else if (feature.attributes.get('gff_id')) {
270
- name = feature.attributes.get('gff_id')?.join(',')
271
- } else {
272
- name = feature._id
273
- }
274
- const coords = `(${(feature.min + 1).toLocaleString('en')}..${feature.max.toLocaleString('en')})`
275
- const maxLen = 60
276
- if (name && name.length + coords.length > maxLen + 5) {
277
- const trim = maxLen - coords.length
278
- name = trim > 0 ? name.slice(0, trim) : ''
279
- name = `${name}[...]`
280
- }
281
- return `${name} ${coords}`
282
- }
283
-
284
- function getContextMenuItemsForFeature(
285
- display: LinearApolloDisplayMouseEvents,
286
- sourceFeature: AnnotationFeature,
287
- ): MenuItem[] {
288
- const {
289
- apolloInternetAccount: internetAccount,
290
- changeManager,
291
- regions,
292
- selectedFeature,
293
- session,
294
- } = display
295
- const menuItems: MenuItem[] = []
296
- const role = internetAccount ? internetAccount.role : 'admin'
297
- const admin = role === 'admin'
298
- const readOnly = !(role && ['admin', 'user'].includes(role))
299
- const [region] = regions
300
- const sourceAssemblyId = display.getAssemblyId(region.assemblyName)
301
- const currentAssemblyId = display.getAssemblyId(region.assemblyName)
302
- const { featureTypeOntology } = session.apolloDataStore.ontologyManager
303
- if (!featureTypeOntology) {
304
- throw new Error('featureTypeOntology is undefined')
305
- }
306
-
307
- // Add only relevant options
308
- menuItems.push(
309
- {
310
- label: makeFeatureLabel(sourceFeature),
311
- type: 'subHeader',
312
- },
313
- {
314
- label: 'Add child feature',
315
- disabled: readOnly,
316
- onClick: () => {
317
- ;(session as unknown as AbstractSessionModel).queueDialog(
318
- (doneCallback) => [
319
- AddChildFeature,
320
- {
321
- session,
322
- handleClose: () => {
323
- doneCallback()
324
- },
325
- changeManager,
326
- sourceFeature,
327
- sourceAssemblyId,
328
- internetAccount,
329
- },
330
- ],
331
- )
332
- },
333
- },
334
- {
335
- label: 'Copy features and annotations',
336
- disabled: readOnly,
337
- onClick: () => {
338
- ;(session as unknown as AbstractSessionModel).queueDialog(
339
- (doneCallback) => [
340
- CopyFeature,
341
- {
342
- session,
343
- handleClose: () => {
344
- doneCallback()
345
- },
346
- changeManager,
347
- sourceFeature,
348
- sourceAssemblyId: currentAssemblyId,
349
- },
350
- ],
351
- )
352
- },
353
- },
354
- {
355
- label: 'Delete feature',
356
- disabled: !admin,
357
- onClick: () => {
358
- ;(session as unknown as AbstractSessionModel).queueDialog(
359
- (doneCallback) => [
360
- DeleteFeature,
361
- {
362
- session,
363
- handleClose: () => {
364
- doneCallback()
365
- },
366
- changeManager,
367
- sourceFeature,
368
- sourceAssemblyId: currentAssemblyId,
369
- selectedFeature,
370
- setSelectedFeature: (feature?: AnnotationFeature) => {
371
- display.setSelectedFeature(feature)
372
- },
373
- },
374
- ],
375
- )
376
- },
377
- },
378
- )
379
- return menuItems
238
+ return getContextMenuItemsForFeature(display, hoveredFeature.feature)
380
239
  }
381
240
 
382
241
  function getFeatureFromLayout(
@@ -400,13 +259,12 @@ function getRowForFeature(
400
259
 
401
260
  function onMouseDown(
402
261
  stateModel: LinearApolloDisplay,
403
- currentMousePosition: MousePositionWithFeatureAndGlyph,
262
+ currentMousePosition: MousePositionWithFeature,
404
263
  event: CanvasMouseEvent,
405
264
  ) {
406
- const { featureAndGlyphUnderMouse } = currentMousePosition
265
+ const { feature } = currentMousePosition
407
266
  // swallow the mouseDown if we are on the edge of the feature so that we
408
267
  // don't start dragging the view if we try to drag the feature edge
409
- const { feature } = featureAndGlyphUnderMouse
410
268
  const edge = isMouseOnFeatureEdge(currentMousePosition, feature, stateModel)
411
269
  if (edge) {
412
270
  event.stopPropagation()
@@ -422,10 +280,9 @@ function onMouseMove(
422
280
  stateModel: LinearApolloDisplay,
423
281
  mousePosition: MousePosition,
424
282
  ) {
425
- if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
426
- const { featureAndGlyphUnderMouse } = mousePosition
427
- stateModel.setApolloHover(featureAndGlyphUnderMouse)
428
- const { feature } = featureAndGlyphUnderMouse
283
+ if (isMousePositionWithFeature(mousePosition)) {
284
+ const { feature, bp } = mousePosition
285
+ stateModel.setHoveredFeature({ feature, bp })
429
286
  const edge = isMouseOnFeatureEdge(mousePosition, feature, stateModel)
430
287
  if (edge) {
431
288
  stateModel.setCursor('col-resize')
@@ -442,11 +299,10 @@ function onMouseUp(
442
299
  if (stateModel.apolloDragging) {
443
300
  return
444
301
  }
445
- const { featureAndGlyphUnderMouse } = mousePosition
446
- if (!featureAndGlyphUnderMouse) {
302
+ const { feature } = mousePosition
303
+ if (!feature) {
447
304
  return
448
305
  }
449
- const { feature } = featureAndGlyphUnderMouse
450
306
  stateModel.setSelectedFeature(feature)
451
307
  stateModel.showFeatureDetailsWidget(feature)
452
308
  }