@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
@@ -1,10 +1,8 @@
1
1
  import { type AnnotationFeature } from '@apollo-annotation/mst'
2
2
  import { type MenuItem } from '@jbrowse/core/ui'
3
3
 
4
- import {
5
- type LinearApolloSixFrameDisplayMouseEvents,
6
- type MousePositionWithFeatureAndGlyph,
7
- } from '../stateModel/mouseEvents'
4
+ import { type MousePositionWithFeature } from '../../util'
5
+ import { type LinearApolloSixFrameDisplayMouseEvents } from '../stateModel/mouseEvents'
8
6
  import { type LinearApolloSixFrameDisplayRendering } from '../stateModel/rendering'
9
7
  import { type CanvasMouseEvent } from '../types'
10
8
 
@@ -30,25 +28,25 @@ export interface Glyph {
30
28
 
31
29
  onMouseDown(
32
30
  display: LinearApolloSixFrameDisplayMouseEvents,
33
- currentMousePosition: MousePositionWithFeatureAndGlyph,
31
+ currentMousePosition: MousePositionWithFeature,
34
32
  event: CanvasMouseEvent,
35
33
  ): void
36
34
 
37
35
  onMouseMove(
38
36
  display: LinearApolloSixFrameDisplayMouseEvents,
39
- currentMousePosition: MousePositionWithFeatureAndGlyph,
37
+ currentMousePosition: MousePositionWithFeature,
40
38
  event: CanvasMouseEvent,
41
39
  ): void
42
40
 
43
41
  onMouseLeave(
44
42
  display: LinearApolloSixFrameDisplayMouseEvents,
45
- currentMousePosition: MousePositionWithFeatureAndGlyph,
43
+ currentMousePosition: MousePositionWithFeature,
46
44
  event: CanvasMouseEvent,
47
45
  ): void
48
46
 
49
47
  onMouseUp(
50
48
  display: LinearApolloSixFrameDisplayMouseEvents,
51
- currentMousePosition: MousePositionWithFeatureAndGlyph,
49
+ currentMousePosition: MousePositionWithFeature,
52
50
  event: CanvasMouseEvent,
53
51
  ): void
54
52
 
@@ -57,7 +55,13 @@ export interface Glyph {
57
55
  context: CanvasRenderingContext2D,
58
56
  ): void
59
57
 
58
+ getContextMenuItemsForFeature(
59
+ display: LinearApolloSixFrameDisplayMouseEvents,
60
+ sourceFeature: AnnotationFeature,
61
+ ): MenuItem[]
62
+
60
63
  getContextMenuItems(
61
64
  display: LinearApolloSixFrameDisplayMouseEvents,
65
+ currentMousePosition: MousePositionWithFeature,
62
66
  ): MenuItem[]
63
67
  }
@@ -10,6 +10,7 @@ import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/con
10
10
  import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes'
11
11
  import {
12
12
  type AbstractSessionModel,
13
+ type SessionWithWidgets,
13
14
  getContainingView,
14
15
  getSession,
15
16
  } from '@jbrowse/core/util'
@@ -21,8 +22,9 @@ import { addDisposer, cast, getRoot, getSnapshot, types } from 'mobx-state-tree'
21
22
 
22
23
  import { type ApolloInternetAccountModel } from '../../ApolloInternetAccount/model'
23
24
  import { FilterFeatures } from '../../components/FilterFeatures'
24
- import { type ApolloSessionModel } from '../../session'
25
+ import { type ApolloSessionModel, type HoveredFeature } from '../../session'
25
26
  import { type ApolloRootModel } from '../../types'
27
+ import { EditZoomThresholdDialog } from '../../util/displayUtils'
26
28
 
27
29
  const minDisplayHeight = 20
28
30
 
@@ -36,6 +38,9 @@ export function baseModelFactory(
36
38
  configuration: ConfigurationReference(configSchema),
37
39
  graphical: true,
38
40
  table: false,
41
+ showFeatureLabels: true,
42
+ showCheckResults: true,
43
+ zoomThreshold: 200,
39
44
  heightPreConfig: types.maybe(
40
45
  types.refinement(
41
46
  'displayHeight',
@@ -72,10 +77,13 @@ export function baseModelFactory(
72
77
  return 500
73
78
  }
74
79
  if (self.graphical) {
75
- return 200
80
+ return self.showFeatureLabels ? 400 : 200
76
81
  }
77
82
  return 300
78
83
  },
84
+ get zoomThresholdSetting() {
85
+ return self.zoomThreshold ?? getConf(self, 'zoomThreshold')
86
+ },
79
87
  }))
80
88
  .views((self) => ({
81
89
  get rendererTypeName() {
@@ -96,7 +104,7 @@ export function baseModelFactory(
96
104
  return regions
97
105
  },
98
106
  regionCannotBeRendered(/* region */) {
99
- if (self.lgv && self.lgv.bpPerPx >= 200) {
107
+ if (self.lgv && self.lgv.bpPerPx >= self.zoomThreshold) {
100
108
  return 'Zoom in to see annotations'
101
109
  }
102
110
  return
@@ -138,6 +146,10 @@ export function baseModelFactory(
138
146
  return (self.session as unknown as ApolloSessionModel)
139
147
  .apolloSelectedFeature
140
148
  },
149
+ get hoveredFeature(): HoveredFeature | undefined {
150
+ return (self.session as unknown as ApolloSessionModel)
151
+ .apolloHoveredFeature
152
+ },
141
153
  }))
142
154
  .actions((self) => ({
143
155
  setScrollTop(scrollTop: number) {
@@ -164,15 +176,24 @@ export function baseModelFactory(
164
176
  self.graphical = true
165
177
  self.table = true
166
178
  },
179
+ toggleShowFeatureLabels() {
180
+ self.showFeatureLabels = !self.showFeatureLabels
181
+ },
182
+ toggleShowCheckResults() {
183
+ self.showCheckResults = !self.showCheckResults
184
+ },
167
185
  updateFilteredFeatureTypes(types: string[]) {
168
186
  self.filteredFeatureTypes = cast(types)
169
187
  },
188
+ setZoomThresholdSetting({ zoomThreshold }: { zoomThreshold: number }) {
189
+ self.zoomThreshold = zoomThreshold
190
+ },
170
191
  }))
171
192
  .views((self) => {
172
193
  const { filteredFeatureTypes, trackMenuItems: superTrackMenuItems } = self
173
194
  return {
174
195
  trackMenuItems() {
175
- const { graphical, table } = self
196
+ const { graphical, table, showFeatureLabels, showCheckResults } = self
176
197
  return [
177
198
  ...superTrackMenuItems(),
178
199
  {
@@ -203,6 +224,31 @@ export function baseModelFactory(
203
224
  self.showGraphicalAndTable()
204
225
  },
205
226
  },
227
+ {
228
+ label: 'Feature Labels',
229
+ type: 'checkbox',
230
+ checked: showFeatureLabels,
231
+ onClick: () => {
232
+ self.toggleShowFeatureLabels()
233
+ },
234
+ },
235
+ {
236
+ label: 'Check Results',
237
+ type: 'checkbox',
238
+ checked: showCheckResults,
239
+ onClick: () => {
240
+ self.toggleShowCheckResults()
241
+ },
242
+ },
243
+ {
244
+ label: 'Change zoom threshold',
245
+ onClick: () => {
246
+ getSession(self).queueDialog((handleClose) => [
247
+ EditZoomThresholdDialog,
248
+ { model: self, handleClose },
249
+ ])
250
+ },
251
+ },
206
252
  ],
207
253
  },
208
254
  {
@@ -236,6 +282,39 @@ export function baseModelFactory(
236
282
  self.session as unknown as ApolloSessionModel
237
283
  ).apolloSetSelectedFeature(feature)
238
284
  },
285
+ setHoveredFeature(hoveredFeature?: HoveredFeature) {
286
+ ;(
287
+ self.session as unknown as ApolloSessionModel
288
+ ).apolloSetHoveredFeature(hoveredFeature)
289
+ },
290
+ showFeatureDetailsWidget(
291
+ feature: AnnotationFeature,
292
+ customWidgetNameAndId?: [string, string],
293
+ ) {
294
+ const [region] = self.regions
295
+ const { assemblyName, refName } = region
296
+ const assembly = self.getAssemblyId(assemblyName)
297
+ if (!assembly) {
298
+ return
299
+ }
300
+ const { session } = self
301
+ const { changeManager } = session.apolloDataStore
302
+ const [widgetName, widgetId] = customWidgetNameAndId ?? [
303
+ 'ApolloFeatureDetailsWidget',
304
+ 'apolloFeatureDetailsWidget',
305
+ ]
306
+ const apolloFeatureWidget = (
307
+ session as unknown as SessionWithWidgets
308
+ ).addWidget(widgetName, widgetId, {
309
+ feature,
310
+ assembly,
311
+ refName,
312
+ changeManager,
313
+ })
314
+ ;(session as unknown as SessionWithWidgets).showWidget(
315
+ apolloFeatureWidget,
316
+ )
317
+ },
239
318
  afterAttach() {
240
319
  addDisposer(
241
320
  self,
@@ -1,9 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
2
2
 
3
- import {
4
- type AnnotationFeature,
5
- type TranscriptPartCoding,
6
- } from '@apollo-annotation/mst'
3
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
7
4
  import type PluginManager from '@jbrowse/core/PluginManager'
8
5
  import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
9
6
  import {
@@ -22,7 +19,6 @@ import { baseModelFactory } from './base'
22
19
  export interface LayoutRow {
23
20
  rowNum: number
24
21
  feature: AnnotationFeature
25
- cds: TranscriptPartCoding | null
26
22
  }
27
23
 
28
24
  export function layoutsModelFactory(
@@ -82,6 +78,9 @@ export function layoutsModelFactory(
82
78
  getGlyph(_feature: AnnotationFeature) {
83
79
  return geneGlyph
84
80
  },
81
+ featureLabelSpacer(elem: number): number {
82
+ return self.showFeatureLabels ? elem * 2 - 1 : elem
83
+ },
85
84
  }))
86
85
  .actions((self) => ({
87
86
  addSeenFeature(feature: AnnotationFeature) {
@@ -91,6 +90,11 @@ export function layoutsModelFactory(
91
90
  self.seenFeatures.delete(featureId)
92
91
  },
93
92
  }))
93
+ .views((self) => ({
94
+ get geneTrackRowNums() {
95
+ return [4, 5].map((elem) => self.featureLabelSpacer(elem))
96
+ },
97
+ }))
94
98
  .views((self) => ({
95
99
  get featureLayouts() {
96
100
  const { assemblyManager } =
@@ -120,12 +124,15 @@ export function layoutsModelFactory(
120
124
  throw new Error('featureTypeOntology is undefined')
121
125
  }
122
126
  if (feature.looksLikeGene) {
123
- const rowNum = feature.strand == 1 ? 4 : 5
127
+ const rowNum =
128
+ feature.strand == 1
129
+ ? self.geneTrackRowNums[0]
130
+ : self.geneTrackRowNums[1]
124
131
  if (!featureLayout.get(rowNum)) {
125
132
  featureLayout.set(rowNum, [])
126
133
  }
127
134
  const layoutRow = featureLayout.get(rowNum)
128
- layoutRow?.push({ rowNum, feature, cds: null })
135
+ layoutRow?.push({ rowNum, feature })
129
136
  const { children } = feature
130
137
  if (!children) {
131
138
  continue
@@ -142,9 +149,12 @@ export function layoutsModelFactory(
142
149
  if (!featureTypeOntology.isTypeOf(exon.type, 'exon')) {
143
150
  continue
144
151
  }
145
- const rowNum = exon.strand == 1 ? 4 : 5
152
+ const rowNum =
153
+ exon.strand == 1
154
+ ? self.geneTrackRowNums[0]
155
+ : self.geneTrackRowNums[1]
146
156
  const layoutRow = featureLayout.get(rowNum)
147
- layoutRow?.push({ rowNum, feature: exon, cds: null })
157
+ layoutRow?.push({ rowNum, feature: exon })
148
158
  }
149
159
  }
150
160
  for (const cdsRow of cdsLocations) {
@@ -155,12 +165,14 @@ export function layoutsModelFactory(
155
165
  strand ?? 1,
156
166
  cds.phase,
157
167
  )
158
- rowNum = rowNum < 0 ? -1 * rowNum + 5 : rowNum
168
+ rowNum = self.featureLabelSpacer(
169
+ rowNum < 0 ? -1 * rowNum + 5 : rowNum,
170
+ )
159
171
  if (!featureLayout.get(rowNum)) {
160
172
  featureLayout.set(rowNum, [])
161
173
  }
162
174
  const layoutRow = featureLayout.get(rowNum)
163
- layoutRow?.push({ rowNum, feature: child, cds })
175
+ layoutRow?.push({ rowNum, feature: child })
164
176
  }
165
177
  }
166
178
  }
@@ -1,7 +1,4 @@
1
- import {
2
- type AnnotationFeature,
3
- type TranscriptPartCoding,
4
- } from '@apollo-annotation/mst'
1
+ import { type AnnotationFeature } from '@apollo-annotation/mst'
5
2
  import {
6
3
  LocationEndChange,
7
4
  LocationStartChange,
@@ -9,55 +6,23 @@ import {
9
6
  import type PluginManager from '@jbrowse/core/PluginManager'
10
7
  import { type AnyConfigurationSchemaType } from '@jbrowse/core/configuration/configurationSchema'
11
8
  import { type MenuItem } from '@jbrowse/core/ui'
12
- import { type LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
9
+ import { getFrame } from '@jbrowse/core/util'
13
10
  import { autorun } from 'mobx'
14
- import { type Instance, addDisposer } from 'mobx-state-tree'
11
+ import { type Instance, addDisposer, cast } from 'mobx-state-tree'
15
12
  import { type CSSProperties } from 'react'
16
13
 
17
- import { type Coord } from '../components'
18
- import { type Glyph } from '../glyphs/Glyph'
14
+ import {
15
+ type Edge,
16
+ type MousePosition,
17
+ type MousePositionWithFeature,
18
+ getMousePosition,
19
+ getPropagatedLocationChanges,
20
+ isMousePositionWithFeature,
21
+ } from '../../util'
19
22
  import { type CanvasMouseEvent } from '../types'
20
23
 
21
24
  import { renderingModelFactory } from './rendering'
22
25
 
23
- export interface FeatureAndGlyphUnderMouse {
24
- cds: TranscriptPartCoding | null
25
- feature: AnnotationFeature
26
- topLevelFeature: AnnotationFeature
27
- glyph: Glyph
28
- }
29
-
30
- /** extended information about the position of the mouse on the canvas, including the refName, bp, and displayedRegion number */
31
- export interface MousePosition {
32
- x: number
33
- y: number
34
- refName: string
35
- bp: number
36
- regionNumber: number
37
- featureAndGlyphUnderMouse?: FeatureAndGlyphUnderMouse
38
- }
39
-
40
- export type MousePositionWithFeatureAndGlyph = Required<MousePosition>
41
-
42
- export function isMousePositionWithFeatureAndGlyph(
43
- mousePosition: MousePosition,
44
- ): mousePosition is MousePositionWithFeatureAndGlyph {
45
- return 'featureAndGlyphUnderMouse' in mousePosition
46
- }
47
-
48
- function getMousePosition(
49
- event: CanvasMouseEvent,
50
- lgv: LinearGenomeViewModel,
51
- ): MousePosition {
52
- const canvas = event.currentTarget
53
- const { clientX, clientY } = event
54
- const { left, top } = canvas.getBoundingClientRect()
55
- const x = clientX - left
56
- const y = clientY - top
57
- const { coord: bp, index: regionNumber, refName } = lgv.pxToBp(x)
58
- return { x, y, refName, bp, regionNumber }
59
- }
60
-
61
26
  export function mouseEventsModelIntermediateFactory(
62
27
  pluginManager: PluginManager,
63
28
  configSchema: AnyConfigurationSchemaType,
@@ -75,13 +40,13 @@ export function mouseEventsModelIntermediateFactory(
75
40
  start: MousePosition
76
41
  current: MousePosition
77
42
  feature: AnnotationFeature
78
- edge: 'min' | 'max'
43
+ edge: Edge
44
+ shrinkParent: boolean
79
45
  } | null,
80
46
  cursor: undefined as CSSProperties['cursor'] | undefined,
81
- apolloHover: undefined as FeatureAndGlyphUnderMouse | undefined,
82
47
  }))
83
48
  .views((self) => ({
84
- getMousePosition(event: CanvasMouseEvent): MousePosition {
49
+ getMousePosition(event: React.MouseEvent): MousePosition {
85
50
  const mousePosition = getMousePosition(event, self.lgv)
86
51
  const { bp, regionNumber, y } = mousePosition
87
52
  const row = Math.floor(y / self.apolloRowHeight) + 1
@@ -90,8 +55,13 @@ export function mouseEventsModelIntermediateFactory(
90
55
  if (!layoutRow) {
91
56
  return mousePosition
92
57
  }
58
+ const { featureTypeOntology } =
59
+ self.session.apolloDataStore.ontologyManager
60
+ if (!featureTypeOntology) {
61
+ throw new Error('featureTypeOntology is undefined')
62
+ }
93
63
  let foundFeature
94
- if ([4, 5].includes(row)) {
64
+ if (self.geneTrackRowNums.includes(row)) {
95
65
  foundFeature = layoutRow.find(
96
66
  (f) =>
97
67
  f.feature.type == 'exon' &&
@@ -104,19 +74,45 @@ export function mouseEventsModelIntermediateFactory(
104
74
  )
105
75
  }
106
76
  } else {
107
- foundFeature = layoutRow.find(
108
- (f) => f.cds != null && bp >= f.cds.min && bp <= f.cds.max,
109
- )
77
+ foundFeature = layoutRow.find((f) => {
78
+ const { feature } = f
79
+ const featureID = feature.attributes.get('gff_id')?.toString()
80
+ const isTranscript = featureTypeOntology.isTypeOf(
81
+ feature.type,
82
+ 'transcript',
83
+ )
84
+ if (!isTranscript) {
85
+ return false
86
+ }
87
+ for (const loc of feature.cdsLocations) {
88
+ for (const cds of loc) {
89
+ let rowNum: number = getFrame(
90
+ cds.min,
91
+ cds.max,
92
+ feature.strand ?? 1,
93
+ cds.phase,
94
+ )
95
+ rowNum = self.featureLabelSpacer(
96
+ rowNum < 0 ? -1 * rowNum + 5 : rowNum,
97
+ )
98
+ if (row === rowNum && bp >= cds.min && bp <= cds.max) {
99
+ return (
100
+ featureID === undefined ||
101
+ !self.filteredTranscripts.includes(featureID)
102
+ )
103
+ }
104
+ }
105
+ }
106
+ return false
107
+ })
110
108
  }
111
109
  if (!foundFeature) {
112
110
  return mousePosition
113
111
  }
114
- const { feature, cds } = foundFeature
115
- const { topLevelFeature } = feature
116
- const glyph = self.getGlyph(feature)
112
+ const { feature } = foundFeature
117
113
  return {
118
114
  ...mousePosition,
119
- featureAndGlyphUnderMouse: { cds, feature, topLevelFeature, glyph },
115
+ feature,
120
116
  }
121
117
  },
122
118
  }))
@@ -135,14 +131,14 @@ export function mouseEventsModelIntermediateFactory(
135
131
  },
136
132
  }))
137
133
  .actions((self) => ({
138
- setApolloHover(n?: (typeof self)['apolloHover']) {
139
- self.apolloHover = n
140
- },
141
134
  setCursor(cursor?: CSSProperties['cursor']) {
142
135
  if (self.cursor !== cursor) {
143
136
  self.cursor = cursor
144
137
  }
145
138
  },
139
+ updateFilteredTranscripts(forms: string[]): void {
140
+ self.filteredTranscripts = cast(forms)
141
+ },
146
142
  }))
147
143
  .actions(() => ({
148
144
  // onClick(event: CanvasMouseEvent) {
@@ -160,36 +156,43 @@ export function mouseEventsModelFactory(
160
156
  mouseEventsModelIntermediateFactory(pluginManager, configSchema)
161
157
 
162
158
  return LinearApolloSixFrameDisplayMouseEvents.views((self) => ({
163
- contextMenuItems(contextCoord?: Coord): MenuItem[] {
164
- const { apolloHover } = self
165
- if (!(apolloHover && contextCoord)) {
159
+ contextMenuItems(event: React.MouseEvent<HTMLDivElement>): MenuItem[] {
160
+ const { hoveredFeature } = self
161
+ if (!hoveredFeature) {
166
162
  return []
167
163
  }
168
- const { topLevelFeature } = apolloHover
164
+ const mousePosition = self.getMousePosition(event)
165
+ const { topLevelFeature } = hoveredFeature.feature
169
166
  const glyph = self.getGlyph(topLevelFeature)
170
- return glyph.getContextMenuItems(self)
167
+ if (isMousePositionWithFeature(mousePosition)) {
168
+ return glyph.getContextMenuItems(self, mousePosition)
169
+ }
170
+ return []
171
171
  },
172
172
  }))
173
173
  .actions((self) => ({
174
174
  // explicitly pass in a feature in case it's not the same as the one in
175
175
  // mousePosition (e.g. if features are drawn overlapping).
176
176
  startDrag(
177
- mousePosition: MousePositionWithFeatureAndGlyph,
177
+ mousePosition: MousePositionWithFeature,
178
178
  feature: AnnotationFeature,
179
- edge: 'min' | 'max',
179
+ edge: Edge,
180
+ shrinkParent = false,
180
181
  ) {
181
182
  self.apolloDragging = {
182
183
  start: mousePosition,
183
184
  current: mousePosition,
184
185
  feature,
185
186
  edge,
187
+ shrinkParent,
186
188
  }
187
189
  },
188
190
  endDrag() {
189
191
  if (!self.apolloDragging) {
190
192
  throw new Error('endDrag() called with no current drag in progress')
191
193
  }
192
- const { current, edge, feature, start } = self.apolloDragging
194
+ const { current, edge, feature, start, shrinkParent } =
195
+ self.apolloDragging
193
196
  // don't do anything if it was only dragged a tiny bit
194
197
  if (Math.abs(current.x - start.x) <= 4) {
195
198
  self.setDragging()
@@ -199,33 +202,35 @@ export function mouseEventsModelFactory(
199
202
  const { displayedRegions } = self.lgv
200
203
  const region = displayedRegions[start.regionNumber]
201
204
  const assembly = self.getAssemblyId(region.assemblyName)
205
+ const changes = getPropagatedLocationChanges(
206
+ feature,
207
+ current.bp,
208
+ edge,
209
+ shrinkParent,
210
+ )
202
211
 
203
- let change: LocationEndChange | LocationStartChange
204
- if (edge === 'max') {
205
- const featureId = feature._id
206
- const oldEnd = feature.max
207
- const newEnd = current.bp
208
- change = new LocationEndChange({
209
- typeName: 'LocationEndChange',
210
- changedIds: [featureId],
211
- featureId,
212
- oldEnd,
213
- newEnd,
214
- assembly,
215
- })
216
- } else {
217
- const featureId = feature._id
218
- const oldStart = feature.min
219
- const newStart = current.bp
220
- change = new LocationStartChange({
221
- typeName: 'LocationStartChange',
222
- changedIds: [featureId],
223
- featureId,
224
- oldStart,
225
- newStart,
226
- assembly,
227
- })
228
- }
212
+ const change: LocationEndChange | LocationStartChange =
213
+ edge === 'max'
214
+ ? new LocationEndChange({
215
+ typeName: 'LocationEndChange',
216
+ changedIds: changes.map((c) => c.featureId),
217
+ changes: changes.map((c) => ({
218
+ featureId: c.featureId,
219
+ oldEnd: c.oldLocation,
220
+ newEnd: c.newLocation,
221
+ })),
222
+ assembly,
223
+ })
224
+ : new LocationStartChange({
225
+ typeName: 'LocationStartChange',
226
+ changedIds: changes.map((c) => c.featureId),
227
+ changes: changes.map((c) => ({
228
+ featureId: c.featureId,
229
+ oldStart: c.oldLocation,
230
+ newStart: c.newLocation,
231
+ })),
232
+ assembly,
233
+ })
229
234
  void self.changeManager.submit(change)
230
235
  self.setDragging()
231
236
  self.setCursor()
@@ -234,12 +239,9 @@ export function mouseEventsModelFactory(
234
239
  .actions((self) => ({
235
240
  onMouseDown(event: CanvasMouseEvent) {
236
241
  const mousePosition = self.getMousePosition(event)
237
- if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
238
- mousePosition.featureAndGlyphUnderMouse.glyph.onMouseDown(
239
- self,
240
- mousePosition,
241
- event,
242
- )
242
+ if (isMousePositionWithFeature(mousePosition)) {
243
+ const glyph = self.getGlyph(mousePosition.feature)
244
+ glyph.onMouseDown(self, mousePosition, event)
243
245
  }
244
246
  },
245
247
  onMouseMove(event: CanvasMouseEvent) {
@@ -249,38 +251,31 @@ export function mouseEventsModelFactory(
249
251
  self.continueDrag(mousePosition, event)
250
252
  return
251
253
  }
252
- if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
253
- mousePosition.featureAndGlyphUnderMouse.glyph.onMouseMove(
254
- self,
255
- mousePosition,
256
- event,
257
- )
254
+ if (isMousePositionWithFeature(mousePosition)) {
255
+ const glyph = self.getGlyph(mousePosition.feature)
256
+ glyph.onMouseMove(self, mousePosition, event)
258
257
  } else {
259
- self.setApolloHover()
258
+ self.setHoveredFeature()
260
259
  self.setCursor()
261
260
  }
262
261
  },
263
262
  onMouseLeave(event: CanvasMouseEvent) {
264
263
  self.setDragging()
265
- self.setApolloHover()
264
+ self.setHoveredFeature()
266
265
 
267
266
  const mousePosition = self.getMousePosition(event)
268
- if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
269
- mousePosition.featureAndGlyphUnderMouse.glyph.onMouseLeave(
270
- self,
271
- mousePosition,
272
- event,
273
- )
267
+ if (isMousePositionWithFeature(mousePosition)) {
268
+ const glyph = self.getGlyph(mousePosition.feature)
269
+ glyph.onMouseLeave(self, mousePosition, event)
274
270
  }
275
271
  },
276
272
  onMouseUp(event: CanvasMouseEvent) {
277
273
  const mousePosition = self.getMousePosition(event)
278
- if (isMousePositionWithFeatureAndGlyph(mousePosition)) {
279
- mousePosition.featureAndGlyphUnderMouse.glyph.onMouseUp(
280
- self,
281
- mousePosition,
282
- event,
283
- )
274
+ if (isMousePositionWithFeature(mousePosition)) {
275
+ const glyph = self.getGlyph(mousePosition.feature)
276
+ glyph.onMouseUp(self, mousePosition, event)
277
+ } else {
278
+ self.setSelectedFeature()
284
279
  }
285
280
 
286
281
  if (self.apolloDragging) {
@@ -310,11 +305,11 @@ export function mouseEventsModelFactory(
310
305
  self.featuresHeight,
311
306
  )
312
307
 
313
- const { apolloDragging, apolloHover } = self
314
- if (!apolloHover) {
308
+ const { apolloDragging, hoveredFeature } = self
309
+ if (!hoveredFeature) {
315
310
  return
316
311
  }
317
- const { glyph } = apolloHover
312
+ const glyph = self.getGlyph(hoveredFeature.feature)
318
313
 
319
314
  // draw mouseover hovers
320
315
  glyph.drawHover(self, ctx)