@apollo-annotation/jbrowse-plugin-apollo 0.3.13 → 1.0.0

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 (192) hide show
  1. package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts +1 -1
  2. package/dist/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.d.ts.map +1 -1
  3. package/dist/BackendDrivers/BackendDriver.d.ts +29 -4
  4. package/dist/BackendDrivers/BackendDriver.d.ts.map +1 -1
  5. package/dist/BackendDrivers/CollaborationServerDriver.d.ts +3 -1
  6. package/dist/BackendDrivers/CollaborationServerDriver.d.ts.map +1 -1
  7. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts +22 -0
  8. package/dist/BackendDrivers/LocalDriver/LocalDriver.d.ts.map +1 -0
  9. package/dist/BackendDrivers/LocalDriver/db.d.ts +4 -0
  10. package/dist/BackendDrivers/LocalDriver/db.d.ts.map +1 -0
  11. package/dist/BackendDrivers/index.d.ts +1 -2
  12. package/dist/BackendDrivers/index.d.ts.map +1 -1
  13. package/dist/ChangeManager.d.ts +3 -3
  14. package/dist/ChangeManager.d.ts.map +1 -1
  15. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts +0 -6
  16. package/dist/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.d.ts.map +1 -1
  17. package/dist/FeatureDetailsWidget/TranscriptWidgetEditLocation.d.ts.map +1 -1
  18. package/dist/FeatureDetailsWidget/model.d.ts +0 -2
  19. package/dist/FeatureDetailsWidget/model.d.ts.map +1 -1
  20. package/dist/LinearApolloDisplay/components/CheckResultWarnings.d.ts.map +1 -1
  21. package/dist/LinearApolloDisplay/components/LinearApolloDisplay.d.ts.map +1 -1
  22. package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts +7 -0
  23. package/dist/LinearApolloDisplay/components/OverlayCanvas.d.ts.map +1 -0
  24. package/dist/LinearApolloDisplay/components/Tooltip.d.ts +10 -0
  25. package/dist/LinearApolloDisplay/components/Tooltip.d.ts.map +1 -0
  26. package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts +0 -1
  27. package/dist/LinearApolloDisplay/glyphs/BoxGlyph.d.ts.map +1 -1
  28. package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts +3 -0
  29. package/dist/LinearApolloDisplay/glyphs/CDSGlyph.d.ts.map +1 -0
  30. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts +3 -0
  31. package/dist/LinearApolloDisplay/glyphs/ExonGlyph.d.ts.map +1 -0
  32. package/dist/LinearApolloDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  33. package/dist/LinearApolloDisplay/glyphs/GenericChildGlyph.d.ts.map +1 -1
  34. package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts +26 -20
  35. package/dist/LinearApolloDisplay/glyphs/Glyph.d.ts.map +1 -1
  36. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts +3 -0
  37. package/dist/LinearApolloDisplay/glyphs/TranscriptGlyph.d.ts.map +1 -0
  38. package/dist/LinearApolloDisplay/glyphs/util.d.ts +13 -0
  39. package/dist/LinearApolloDisplay/glyphs/util.d.ts.map +1 -1
  40. package/dist/LinearApolloDisplay/stateModel/base.d.ts +17 -0
  41. package/dist/LinearApolloDisplay/stateModel/base.d.ts.map +1 -1
  42. package/dist/LinearApolloDisplay/stateModel/index.d.ts +35 -17
  43. package/dist/LinearApolloDisplay/stateModel/index.d.ts.map +1 -1
  44. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts +29 -7
  45. package/dist/LinearApolloDisplay/stateModel/layouts.d.ts.map +1 -1
  46. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts +69 -23
  47. package/dist/LinearApolloDisplay/stateModel/mouseEvents.d.ts.map +1 -1
  48. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts +26 -9
  49. package/dist/LinearApolloDisplay/stateModel/rendering.d.ts.map +1 -1
  50. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts +6 -0
  51. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/base.d.ts.map +1 -1
  52. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts +6 -0
  53. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/index.d.ts.map +1 -1
  54. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts +6 -0
  55. package/dist/LinearApolloReferenceSequenceDisplay/stateModel/rendering.d.ts.map +1 -1
  56. package/dist/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.d.ts.map +1 -1
  57. package/dist/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.d.ts.map +1 -1
  58. package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts +1 -1
  59. package/dist/LinearApolloSixFrameDisplay/glyphs/Glyph.d.ts.map +1 -1
  60. package/dist/LinearApolloSixFrameDisplay/stateModel/layouts.d.ts.map +1 -1
  61. package/dist/LinearApolloSixFrameDisplay/stateModel/rendering.d.ts.map +1 -1
  62. package/dist/OntologyManager/OntologyStore/fulltext.d.ts +1 -1
  63. package/dist/OntologyManager/OntologyStore/fulltext.d.ts.map +1 -1
  64. package/dist/OntologyManager/OntologyStore/index.d.ts +2 -2
  65. package/dist/OntologyManager/OntologyStore/index.d.ts.map +1 -1
  66. package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts +1 -1
  67. package/dist/OntologyManager/OntologyStore/indexeddb-storage.d.ts.map +1 -1
  68. package/dist/OntologyManager/OntologyStore/types.d.ts +18 -0
  69. package/dist/OntologyManager/OntologyStore/types.d.ts.map +1 -0
  70. package/dist/TabularEditor/HybridGrid/featureContextMenuItems.d.ts.map +1 -1
  71. package/dist/components/AddChildFeature.d.ts.map +1 -1
  72. package/dist/components/ColorFeature.d.ts +13 -0
  73. package/dist/components/ColorFeature.d.ts.map +1 -0
  74. package/dist/components/CreateApolloAnnotation.d.ts.map +1 -1
  75. package/dist/components/DownloadGFF3.d.ts +4 -1
  76. package/dist/components/DownloadGFF3.d.ts.map +1 -1
  77. package/dist/components/DuplicateTranscript.d.ts.map +1 -1
  78. package/dist/components/ViewChangeLog.d.ts +2 -1
  79. package/dist/components/ViewChangeLog.d.ts.map +1 -1
  80. package/dist/components/ViewCheckResults.d.ts +2 -1
  81. package/dist/components/ViewCheckResults.d.ts.map +1 -1
  82. package/dist/components/index.d.ts +1 -1
  83. package/dist/components/index.d.ts.map +1 -1
  84. package/dist/config.d.ts +4 -0
  85. package/dist/config.d.ts.map +1 -0
  86. package/dist/extensions/annotationFromJBrowseFeature.d.ts.map +1 -1
  87. package/dist/extensions/annotationFromPileup.d.ts.map +1 -1
  88. package/dist/index.d.ts +11 -0
  89. package/dist/index.d.ts.map +1 -0
  90. package/dist/index.esm.js +6325 -5997
  91. package/dist/index.esm.js.map +1 -1
  92. package/dist/jbrowse-plugin-apollo.cjs.development.js +5869 -5541
  93. package/dist/jbrowse-plugin-apollo.cjs.development.js.map +1 -1
  94. package/dist/jbrowse-plugin-apollo.cjs.production.min.js +1 -1
  95. package/dist/jbrowse-plugin-apollo.cjs.production.min.js.map +1 -1
  96. package/dist/jbrowse-plugin-apollo.umd.development.js +16782 -25897
  97. package/dist/jbrowse-plugin-apollo.umd.development.js.map +1 -1
  98. package/dist/jbrowse-plugin-apollo.umd.production.min.js +1 -1
  99. package/dist/jbrowse-plugin-apollo.umd.production.min.js.map +1 -1
  100. package/dist/makeDisplayComponent.d.ts.map +1 -1
  101. package/dist/menus/Icons.d.ts +3 -0
  102. package/dist/menus/Icons.d.ts.map +1 -0
  103. package/dist/menus/topLevelMenu.d.ts.map +1 -1
  104. package/dist/session/changeHandlers.d.ts +9 -0
  105. package/dist/session/changeHandlers.d.ts.map +1 -0
  106. package/dist/util/annotationFeatureUtils.d.ts +2 -1
  107. package/dist/util/annotationFeatureUtils.d.ts.map +1 -1
  108. package/dist/util/glyphUtils.d.ts +3 -3
  109. package/dist/util/glyphUtils.d.ts.map +1 -1
  110. package/dist/util/index.d.ts +0 -1
  111. package/dist/util/index.d.ts.map +1 -1
  112. package/package.json +4 -4
  113. package/src/ApolloInternetAccount/model.ts +68 -4
  114. package/src/ApolloRefNameAliasAdapter/ApolloRefNameAliasAdapter.ts +6 -3
  115. package/src/ApolloTextSearchAdapter/ApolloTextSearchAdapter.ts +1 -1
  116. package/src/BackendDrivers/BackendDriver.ts +36 -3
  117. package/src/BackendDrivers/CollaborationServerDriver.ts +78 -23
  118. package/src/BackendDrivers/LocalDriver/LocalDriver.ts +367 -0
  119. package/src/BackendDrivers/LocalDriver/db.ts +37 -0
  120. package/src/BackendDrivers/index.ts +1 -2
  121. package/src/ChangeManager.ts +27 -25
  122. package/src/FeatureDetailsWidget/ApolloTranscriptDetailsWidget.tsx +1 -1
  123. package/src/FeatureDetailsWidget/TranscriptWidgetEditLocation.tsx +69 -53
  124. package/src/LinearApolloDisplay/components/CheckResultWarnings.tsx +1 -5
  125. package/src/LinearApolloDisplay/components/LinearApolloDisplay.tsx +95 -115
  126. package/src/LinearApolloDisplay/components/OverlayCanvas.tsx +76 -0
  127. package/src/LinearApolloDisplay/components/Tooltip.tsx +42 -0
  128. package/src/LinearApolloDisplay/glyphs/BoxGlyph.ts +60 -302
  129. package/src/LinearApolloDisplay/glyphs/CDSGlyph.ts +145 -0
  130. package/src/LinearApolloDisplay/glyphs/ExonGlyph.ts +212 -0
  131. package/src/LinearApolloDisplay/glyphs/GeneGlyph.ts +65 -999
  132. package/src/LinearApolloDisplay/glyphs/GenericChildGlyph.ts +71 -181
  133. package/src/LinearApolloDisplay/glyphs/Glyph.ts +42 -66
  134. package/src/LinearApolloDisplay/glyphs/TranscriptGlyph.ts +291 -0
  135. package/src/LinearApolloDisplay/glyphs/util.ts +87 -0
  136. package/src/LinearApolloDisplay/stateModel/base.ts +83 -0
  137. package/src/LinearApolloDisplay/stateModel/layouts.ts +198 -138
  138. package/src/LinearApolloDisplay/stateModel/mouseEvents.ts +252 -158
  139. package/src/LinearApolloDisplay/stateModel/rendering.ts +103 -21
  140. package/src/LinearApolloReferenceSequenceDisplay/drawSequenceOverlay.ts +3 -3
  141. package/src/LinearApolloReferenceSequenceDisplay/stateModel/base.ts +20 -2
  142. package/src/LinearApolloSixFrameDisplay/components/LinearApolloSixFrameDisplay.tsx +7 -2
  143. package/src/LinearApolloSixFrameDisplay/glyphs/GeneGlyph.ts +8 -13
  144. package/src/LinearApolloSixFrameDisplay/glyphs/Glyph.ts +1 -1
  145. package/src/LinearApolloSixFrameDisplay/stateModel/layouts.ts +4 -3
  146. package/src/LinearApolloSixFrameDisplay/stateModel/mouseEvents.ts +1 -1
  147. package/src/LinearApolloSixFrameDisplay/stateModel/rendering.ts +2 -1
  148. package/src/OntologyManager/OntologyStore/__snapshots__/index.test.ts.snap +18262 -8519
  149. package/src/OntologyManager/OntologyStore/fulltext.ts +1 -2
  150. package/src/OntologyManager/OntologyStore/index.test.ts +5 -2
  151. package/src/OntologyManager/OntologyStore/index.ts +7 -8
  152. package/src/OntologyManager/OntologyStore/indexeddb-storage.ts +2 -2
  153. package/src/OntologyManager/OntologyStore/types.ts +27 -0
  154. package/src/OntologyManager/index.ts +15 -26
  155. package/src/TabularEditor/HybridGrid/featureContextMenuItems.ts +4 -5
  156. package/src/components/AddChildFeature.tsx +15 -8
  157. package/src/components/ColorFeature.tsx +167 -0
  158. package/src/components/CreateApolloAnnotation.tsx +35 -9
  159. package/src/components/DownloadGFF3.tsx +92 -121
  160. package/src/components/DuplicateTranscript.tsx +10 -0
  161. package/src/components/ViewChangeLog.tsx +123 -83
  162. package/src/components/ViewCheckResults.tsx +15 -73
  163. package/src/components/index.ts +1 -1
  164. package/src/config.ts +37 -19
  165. package/src/extensions/annotationFromJBrowseFeature.test.ts +1 -1
  166. package/src/extensions/annotationFromJBrowseFeature.ts +91 -63
  167. package/src/extensions/annotationFromPileup.ts +40 -40
  168. package/src/index.ts +45 -1
  169. package/src/makeDisplayComponent.tsx +10 -3
  170. package/src/menus/Icons.tsx +49 -0
  171. package/src/menus/topLevelMenu.ts +24 -96
  172. package/src/session/ClientDataStore.ts +16 -17
  173. package/src/session/changeHandlers.ts +261 -0
  174. package/src/session/session.ts +77 -46
  175. package/src/util/annotationFeatureUtils.ts +29 -1
  176. package/src/util/glyphUtils.ts +74 -31
  177. package/src/util/index.ts +0 -1
  178. package/dist/BackendDrivers/DesktopFileDriver.d.ts +0 -160
  179. package/dist/BackendDrivers/DesktopFileDriver.d.ts.map +0 -1
  180. package/dist/BackendDrivers/InMemoryFileDriver.d.ts +0 -162
  181. package/dist/BackendDrivers/InMemoryFileDriver.d.ts.map +0 -1
  182. package/dist/LinearApolloDisplay/glyphs/index.d.ts +0 -4
  183. package/dist/LinearApolloDisplay/glyphs/index.d.ts.map +0 -1
  184. package/dist/components/OpenLocalFile.d.ts +0 -15
  185. package/dist/components/OpenLocalFile.d.ts.map +0 -1
  186. package/dist/util/loadAssemblyIntoClient.d.ts +0 -5
  187. package/dist/util/loadAssemblyIntoClient.d.ts.map +0 -1
  188. package/src/BackendDrivers/DesktopFileDriver.ts +0 -184
  189. package/src/BackendDrivers/InMemoryFileDriver.ts +0 -107
  190. package/src/LinearApolloDisplay/glyphs/index.ts +0 -3
  191. package/src/components/OpenLocalFile.tsx +0 -189
  192. package/src/util/loadAssemblyIntoClient.ts +0 -94
@@ -6,22 +6,52 @@ import {
6
6
  import type PluginManager from '@jbrowse/core/PluginManager'
7
7
  import type { AnyConfigurationSchemaType } from '@jbrowse/core/configuration'
8
8
  import type { MenuItem } from '@jbrowse/core/ui'
9
+ import { doesIntersect2 } from '@jbrowse/core/util'
9
10
  import { type Instance, addDisposer } from '@jbrowse/mobx-state-tree'
11
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
10
12
  import { autorun } from 'mobx'
11
13
  import type { CSSProperties } from 'react'
12
14
 
13
15
  import {
14
16
  type Edge,
15
- type MousePosition,
16
- type MousePositionWithFeature,
17
- getMousePosition,
17
+ getContextMenuItemsForFeature,
18
18
  getPropagatedLocationChanges,
19
- isMousePositionWithFeature,
19
+ isCDSFeature,
20
+ isExonFeature,
21
+ selectFeatureAndOpenWidget,
20
22
  } from '../../util'
23
+ import { isMouseOnFeatureEdge } from '../glyphs/util'
21
24
  import type { CanvasMouseEvent } from '../types'
22
25
 
23
26
  import { renderingModelFactory } from './rendering'
24
27
 
28
+ export interface MousePosition {
29
+ x: number
30
+ y: number
31
+ assemblyName: string
32
+ refName: string
33
+ bp: number
34
+ regionNumber: number
35
+ }
36
+
37
+ export function getMousePosition(
38
+ event: React.MouseEvent,
39
+ lgv: LinearGenomeViewModel,
40
+ ): MousePosition {
41
+ const canvas = event.currentTarget
42
+ const { clientX, clientY } = event
43
+ const { left, top } = canvas.getBoundingClientRect()
44
+ const x = clientX - left
45
+ const y = clientY - top
46
+ const {
47
+ coord: bp,
48
+ index: regionNumber,
49
+ assemblyName,
50
+ refName,
51
+ } = lgv.pxToBp(x)
52
+ return { x, y, assemblyName, refName, bp, regionNumber }
53
+ }
54
+
25
55
  export function mouseEventsModelIntermediateFactory(
26
56
  pluginManager: PluginManager,
27
57
  configSchema: AnyConfigurationSchemaType,
@@ -44,45 +74,16 @@ export function mouseEventsModelIntermediateFactory(
44
74
  }))
45
75
  .views((self) => ({
46
76
  getMousePosition(event: React.MouseEvent): MousePosition {
47
- const mousePosition = getMousePosition(event, self.lgv)
48
- const { bp, regionNumber, y } = mousePosition
77
+ return getMousePosition(event, self.lgv)
78
+ },
79
+ getFeaturesAtMousePosition(mousePosition: MousePosition) {
80
+ const { bp, assemblyName, refName, y } = mousePosition
49
81
  const row = Math.floor(y / self.apolloRowHeight)
50
- const featureLayout = self.featureLayouts[regionNumber]
51
- const layoutRow = featureLayout.get(row)
52
- if (!layoutRow) {
53
- return mousePosition
54
- }
55
- const foundFeature = layoutRow.find((f) => {
56
- const feature = self.getAnnotationFeatureById(f[1])
57
- return feature && bp >= feature.min && bp <= feature.max
58
- })
59
- if (!foundFeature) {
60
- return mousePosition
61
- }
62
- const [featureRow, topLevelFeatureId] = foundFeature
63
- const topLevelFeature = self.getAnnotationFeatureById(topLevelFeatureId)
64
- if (!topLevelFeature) {
65
- return mousePosition
66
- }
67
- const glyph = self.getGlyph(topLevelFeature)
68
- const { featureTypeOntology } =
69
- self.session.apolloDataStore.ontologyManager
70
- if (!featureTypeOntology) {
71
- throw new Error('featureTypeOntology is undefined')
72
- }
73
- const feature = glyph.getFeatureFromLayout(
74
- topLevelFeature,
75
- bp,
76
- featureRow,
77
- featureTypeOntology,
78
- )
79
- if (!feature) {
80
- return mousePosition
81
- }
82
- return {
83
- ...mousePosition,
84
- feature,
82
+ const featureLayout = self.layouts.get(assemblyName)?.get(refName)
83
+ if (!featureLayout) {
84
+ return []
85
85
  }
86
+ return self.getFeaturesAtPosition(assemblyName, refName, row, bp)
86
87
  },
87
88
  }))
88
89
  .actions((self) => ({
@@ -129,91 +130,116 @@ export function mouseEventsModelFactory(
129
130
 
130
131
  return LinearApolloDisplayMouseEvents.views((self) => ({
131
132
  contextMenuItems(event: React.MouseEvent<HTMLDivElement>): MenuItem[] {
132
- const { hoveredFeature } = self
133
- if (!hoveredFeature) {
134
- return []
135
- }
136
133
  const mousePosition = self.getMousePosition(event)
137
- const { topLevelFeature } = hoveredFeature.feature
138
- const glyph = self.getGlyph(topLevelFeature)
139
- if (isMousePositionWithFeature(mousePosition)) {
140
- return glyph.getContextMenuItems(self, mousePosition)
134
+ const features = self.getFeaturesAtMousePosition(mousePosition)
135
+ if (features.length === 1) {
136
+ return getContextMenuItemsForFeature(self, features[0])
141
137
  }
142
- return []
138
+ const menuItems: MenuItem[] = []
139
+ for (const feature of features) {
140
+ const glyph = self.getGlyph(feature)
141
+ menuItems.push({
142
+ label: feature.type,
143
+ subMenu: [
144
+ ...getContextMenuItemsForFeature(self, feature),
145
+ // @ts-expect-error ts doesn't understand mst extension
146
+ ...glyph.getContextMenuItems(self, feature),
147
+ ],
148
+ })
149
+ }
150
+ return menuItems
143
151
  },
144
152
  }))
145
- .actions((self) => ({
146
- // explicitly pass in a feature in case it's not the same as the one in
147
- // mousePosition (e.g. if features are drawn overlapping).
148
- startDrag(
149
- mousePosition: MousePositionWithFeature,
150
- feature: AnnotationFeature,
151
- edge: Edge,
152
- shrinkParent = false,
153
- ) {
154
- self.apolloDragging = {
155
- start: mousePosition,
156
- current: mousePosition,
157
- feature,
158
- edge,
159
- shrinkParent,
160
- }
161
- },
162
- endDrag() {
163
- if (!self.apolloDragging) {
164
- throw new Error('endDrag() called with no current drag in progress')
165
- }
166
- const { current, edge, feature, start, shrinkParent } =
167
- self.apolloDragging
168
- // don't do anything if it was only dragged a tiny bit
169
- if (Math.abs(current.x - start.x) <= 4) {
153
+ .actions((self) => {
154
+ function cancelDragListener(event: KeyboardEvent) {
155
+ if (event.key === 'Escape') {
170
156
  self.setDragging()
171
- self.setCursor()
172
- return
173
157
  }
174
- const { displayedRegions } = self.lgv
175
- const region = displayedRegions[start.regionNumber]
176
- const assembly = self.getAssemblyId(region.assemblyName)
177
- const changes = getPropagatedLocationChanges(
178
- feature,
179
- current.bp,
180
- edge,
181
- shrinkParent,
182
- )
158
+ }
159
+ return {
160
+ // explicitly pass in a feature in case it's not the same as the one in
161
+ // mousePosition (e.g. if features are drawn overlapping).
162
+ startDrag(
163
+ mousePosition: MousePosition,
164
+ feature: AnnotationFeature,
165
+ edge: Edge,
166
+ shrinkParent = false,
167
+ ) {
168
+ globalThis.addEventListener('keydown', cancelDragListener, true)
169
+ self.apolloDragging = {
170
+ start: mousePosition,
171
+ current: mousePosition,
172
+ feature,
173
+ edge,
174
+ shrinkParent,
175
+ }
176
+ },
177
+ endDrag() {
178
+ globalThis.removeEventListener('keydown', cancelDragListener, true)
179
+ if (!self.apolloDragging) {
180
+ throw new Error('endDrag() called with no current drag in progress')
181
+ }
182
+ const { current, edge, feature, start, shrinkParent } =
183
+ self.apolloDragging
184
+ // don't do anything if it was only dragged a tiny bit
185
+ if (Math.abs(current.x - start.x) <= 4) {
186
+ self.setDragging()
187
+ self.setCursor()
188
+ return
189
+ }
190
+ const { displayedRegions } = self.lgv
191
+ const region = displayedRegions[start.regionNumber]
192
+ const assembly = self.getAssemblyId(region.assemblyName)
193
+ const changes = getPropagatedLocationChanges(
194
+ feature,
195
+ current.bp,
196
+ edge,
197
+ shrinkParent,
198
+ )
183
199
 
184
- const change: LocationEndChange | LocationStartChange =
185
- edge === 'max'
186
- ? new LocationEndChange({
187
- typeName: 'LocationEndChange',
188
- changedIds: changes.map((c) => c.featureId),
189
- changes: changes.map((c) => ({
190
- featureId: c.featureId,
191
- oldEnd: c.oldLocation,
192
- newEnd: c.newLocation,
193
- })),
194
- assembly,
195
- })
196
- : new LocationStartChange({
197
- typeName: 'LocationStartChange',
198
- changedIds: changes.map((c) => c.featureId),
199
- changes: changes.map((c) => ({
200
- featureId: c.featureId,
201
- oldStart: c.oldLocation,
202
- newStart: c.newLocation,
203
- })),
204
- assembly,
205
- })
206
- void self.changeManager.submit(change)
207
- self.setDragging()
208
- self.setCursor()
209
- },
210
- }))
200
+ const change: LocationEndChange | LocationStartChange =
201
+ edge === 'max'
202
+ ? new LocationEndChange({
203
+ typeName: 'LocationEndChange',
204
+ changedIds: changes.map((c) => c.featureId),
205
+ changes: changes.map((c) => ({
206
+ featureId: c.featureId,
207
+ oldEnd: c.oldLocation,
208
+ newEnd: c.newLocation,
209
+ })),
210
+ assembly,
211
+ })
212
+ : new LocationStartChange({
213
+ typeName: 'LocationStartChange',
214
+ changedIds: changes.map((c) => c.featureId),
215
+ changes: changes.map((c) => ({
216
+ featureId: c.featureId,
217
+ oldStart: c.oldLocation,
218
+ newStart: c.newLocation,
219
+ })),
220
+ assembly,
221
+ })
222
+ void self.changeManager.submit(change)
223
+ self.setDragging()
224
+ self.setCursor()
225
+ },
226
+ }
227
+ })
211
228
  .actions((self) => ({
212
229
  onMouseDown(event: CanvasMouseEvent) {
213
230
  const mousePosition = self.getMousePosition(event)
214
- if (isMousePositionWithFeature(mousePosition)) {
215
- const glyph = self.getGlyph(mousePosition.feature)
216
- glyph.onMouseDown(self, mousePosition, event)
231
+ const features = self.getFeaturesAtMousePosition(mousePosition)
232
+ for (const feature of features.toReversed()) {
233
+ const glyph = self.getGlyph(feature)
234
+ if (glyph.isDraggable) {
235
+ // @ts-expect-error ts doesn't understand mst extension
236
+ const edge = isMouseOnFeatureEdge(mousePosition, feature, self)
237
+ if (edge) {
238
+ event.stopPropagation()
239
+ self.startDrag(mousePosition, feature, edge, true)
240
+ return
241
+ }
242
+ }
217
243
  }
218
244
  },
219
245
  onMouseMove(event: CanvasMouseEvent) {
@@ -223,36 +249,58 @@ export function mouseEventsModelFactory(
223
249
  self.continueDrag(mousePosition, event)
224
250
  return
225
251
  }
226
- if (isMousePositionWithFeature(mousePosition)) {
227
- const glyph = self.getGlyph(mousePosition.feature)
228
- glyph.onMouseMove(self, mousePosition, event)
252
+ const features = self.getFeaturesAtMousePosition(mousePosition)
253
+ let topFeature = features.at(-1)
254
+ // TODO: can we get this special case for CDS to fit nicely in the glyph?
255
+ if (topFeature && isCDSFeature(topFeature, self.session)) {
256
+ const nextFeature = features.at(-2)
257
+ if (nextFeature && !isExonFeature(nextFeature, self.session)) {
258
+ topFeature = nextFeature
259
+ }
260
+ }
261
+ if (topFeature) {
262
+ self.setHoveredFeature({ feature: topFeature, bp: mousePosition.bp })
229
263
  } else {
230
264
  self.setHoveredFeature()
231
- self.setCursor()
232
265
  }
266
+ for (const feature of features.toReversed()) {
267
+ const glyph = self.getGlyph(feature)
268
+ if (
269
+ glyph.isDraggable &&
270
+ // @ts-expect-error ts doesn't understand mst extension
271
+ isMouseOnFeatureEdge(mousePosition, feature, self)
272
+ ) {
273
+ self.setCursor('col-resize')
274
+ return
275
+ }
276
+ }
277
+ self.setCursor()
233
278
  },
234
- onMouseLeave(event: CanvasMouseEvent) {
279
+ onMouseLeave(_event: CanvasMouseEvent) {
235
280
  self.setDragging()
236
281
  self.setHoveredFeature()
237
-
238
- const mousePosition = self.getMousePosition(event)
239
- if (isMousePositionWithFeature(mousePosition)) {
240
- const glyph = self.getGlyph(mousePosition.feature)
241
- glyph.onMouseLeave(self, mousePosition, event)
242
- }
243
282
  },
244
283
  onMouseUp(event: CanvasMouseEvent) {
284
+ if (self.apolloDragging) {
285
+ self.endDrag()
286
+ return
287
+ }
245
288
  const mousePosition = self.getMousePosition(event)
246
- if (isMousePositionWithFeature(mousePosition)) {
247
- const glyph = self.getGlyph(mousePosition.feature)
248
- glyph.onMouseUp(self, mousePosition, event)
289
+ const features = self.getFeaturesAtMousePosition(mousePosition)
290
+ let topFeature = features.at(-1)
291
+ // TODO: can we get this special case for CDS to fit nicely in the glyph?
292
+ if (topFeature && isCDSFeature(topFeature, self.session)) {
293
+ const nextFeature = features.at(-2)
294
+ if (nextFeature && !isExonFeature(nextFeature, self.session)) {
295
+ topFeature = nextFeature
296
+ }
297
+ }
298
+ if (topFeature) {
299
+ selectFeatureAndOpenWidget(self, topFeature)
300
+ self.setSelectedFeature(topFeature)
249
301
  } else {
250
302
  self.setSelectedFeature()
251
303
  }
252
-
253
- if (self.apolloDragging) {
254
- self.endDrag()
255
- }
256
304
  },
257
305
  }))
258
306
  .actions((self) => ({
@@ -261,42 +309,88 @@ export function mouseEventsModelFactory(
261
309
  self,
262
310
  autorun(
263
311
  () => {
264
- // This type is wrong in @jbrowse/core
265
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
266
- if (!self.lgv.initialized || self.regionCannotBeRendered()) {
312
+ const { lgv, overlayCanvas } = self
313
+ if (
314
+ !lgv.initialized ||
315
+ // This type is wrong in @jbrowse/core
316
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
317
+ self.regionCannotBeRendered() ||
318
+ !overlayCanvas
319
+ ) {
267
320
  return
268
321
  }
269
- const ctx = self.overlayCanvas?.getContext('2d')
322
+ const { dynamicBlocks, offsetPx } = lgv
323
+ const ctx = overlayCanvas.getContext('2d')
270
324
  if (!ctx) {
271
325
  return
272
326
  }
273
- ctx.clearRect(
274
- 0,
275
- 0,
276
- self.lgv.dynamicBlocks.totalWidthPx,
277
- self.featuresHeight,
278
- )
279
-
327
+ ctx.clearRect(0, 0, overlayCanvas.width, overlayCanvas.height)
280
328
  const { apolloDragging, hoveredFeature } = self
281
329
  if (!hoveredFeature) {
282
330
  return
283
331
  }
284
- const glyph = self.getGlyph(hoveredFeature.feature)
332
+ const { feature } = hoveredFeature
333
+ const glyph = self.getGlyph(feature)
334
+ const row = self.getRowForFeature(feature)
335
+ if (row === undefined) {
336
+ return
337
+ }
285
338
 
286
- // draw mouseover hovers
287
- glyph.drawHover(self, ctx)
339
+ for (const block of dynamicBlocks.contentBlocks) {
340
+ const blockLeftPx = block.offsetPx - offsetPx
341
+ ctx.save()
342
+ ctx.beginPath()
343
+ ctx.rect(blockLeftPx, 0, block.widthPx, overlayCanvas.height)
344
+ ctx.clip()
345
+ if (
346
+ block.assemblyName === feature.assemblyId &&
347
+ doesIntersect2(
348
+ block.start,
349
+ block.end,
350
+ feature.min,
351
+ feature.max,
352
+ )
353
+ ) {
354
+ // draw mouseover hovers
355
+ glyph.drawOverlay(
356
+ // @ts-expect-error ts doesn't understand mst extension
357
+ self,
358
+ ctx,
359
+ feature,
360
+ row,
361
+ block,
362
+ 'hover',
363
+ )
364
+ }
365
+ if (apolloDragging) {
366
+ const {
367
+ current,
368
+ start,
369
+ feature: dragFeature,
370
+ } = apolloDragging
371
+ const dragMin = Math.min(current.bp, start.bp)
372
+ const dragMax = Math.max(current.bp, start.bp)
373
+ if (
374
+ doesIntersect2(block.start, block.end, dragMin, dragMax)
375
+ ) {
376
+ const dragGlyph = self.getGlyph(dragFeature)
377
+ const row = self.getRowForFeature(dragFeature)
288
378
 
289
- // draw tooltip on hover
290
- glyph.drawTooltip(self, ctx)
379
+ if (row !== undefined) {
380
+ // draw dragging previews
381
+ dragGlyph.drawDragPreview(
382
+ // @ts-expect-error ts doesn't understand mst extension
383
+ self,
384
+ ctx,
385
+ dragFeature,
386
+ row,
387
+ block,
388
+ )
389
+ }
390
+ }
391
+ }
291
392
 
292
- // dragging previews
293
- if (apolloDragging) {
294
- // NOTE: the glyph where the drag started is responsible for drawing the preview.
295
- // it can call methods in other glyphs to help with this though.
296
- const glyph = self.getGlyph(
297
- apolloDragging.feature.topLevelFeature,
298
- )
299
- glyph.drawDragPreview(self, ctx)
393
+ ctx.restore()
300
394
  }
301
395
  },
302
396
  { name: 'LinearApolloDisplayRenderMouseoverAndDrag' },
@@ -24,7 +24,6 @@ export function renderingModelFactory(
24
24
  apolloRowHeight: 20,
25
25
  detailsMinHeight: 200,
26
26
  detailsHeight: 200,
27
- lastRowTooltipBufferHeight: 40,
28
27
  isShown: true,
29
28
  filteredTranscripts: types.array(types.string),
30
29
  })
@@ -35,11 +34,51 @@ export function renderingModelFactory(
35
34
  theme: createTheme(),
36
35
  }))
37
36
  .views((self) => ({
38
- get featuresHeight() {
39
- return (
40
- (self.highestRow + 1) * self.apolloRowHeight +
41
- self.lastRowTooltipBufferHeight
42
- )
37
+ featuresHeight(assemblyName: string) {
38
+ return (self.highestRow(assemblyName) + 1) * self.apolloRowHeight
39
+ },
40
+ get canvasPatterns(): Record<
41
+ 'forward' | 'backward',
42
+ CanvasPattern | null
43
+ > {
44
+ const patterns: Record<'forward' | 'backward', CanvasPattern | null> = {
45
+ forward: null,
46
+ backward: null,
47
+ }
48
+ const canvas = document.createElement('canvas')
49
+ const ctx = canvas?.getContext('2d')
50
+ if (!ctx) {
51
+ return patterns
52
+ }
53
+ const canvasSize = 10
54
+ canvas.width = canvas.height = canvasSize
55
+ const { theme } = self
56
+ const stripeColor1 =
57
+ theme.palette.mode === 'light' ? 'rgba(0,0,0,0)' : 'rgba(0,0,0,0.75)'
58
+ const stripeColor2 =
59
+ theme.palette.mode === 'light'
60
+ ? 'rgba(255,255,255,0.25)'
61
+ : 'rgba(0,0,0,0.50)'
62
+ const directions = ['forward', 'backward'] as const
63
+ for (const direction of directions) {
64
+ const gradient =
65
+ direction === 'forward'
66
+ ? ctx.createLinearGradient(0, canvasSize, canvasSize, 0)
67
+ : ctx.createLinearGradient(0, 0, canvasSize, canvasSize)
68
+ gradient.addColorStop(0, stripeColor1)
69
+ gradient.addColorStop(0.25, stripeColor1)
70
+ gradient.addColorStop(0.25, stripeColor2)
71
+ gradient.addColorStop(0.5, stripeColor2)
72
+ gradient.addColorStop(0.5, stripeColor1)
73
+ gradient.addColorStop(0.75, stripeColor1)
74
+ gradient.addColorStop(0.75, stripeColor2)
75
+ gradient.addColorStop(1, stripeColor2)
76
+ ctx.fillStyle = gradient
77
+ ctx.clearRect(0, 0, canvasSize, canvasSize)
78
+ ctx.fillRect(0, 0, canvasSize, canvasSize)
79
+ patterns[direction] = ctx.createPattern(canvas, 'repeat')
80
+ }
81
+ return patterns
43
82
  },
44
83
  }))
45
84
  .actions((self) => ({
@@ -84,7 +123,7 @@ export function renderingModelFactory(
84
123
  0,
85
124
  0,
86
125
  self.lgv.dynamicBlocks.totalWidthPx,
87
- self.featuresHeight,
126
+ self.featuresHeight(self.lgv.assemblyNames[0]),
88
127
  )
89
128
  for (const collaborator of (
90
129
  self.session as unknown as ApolloSessionModel
@@ -132,38 +171,81 @@ export function renderingModelFactory(
132
171
  self,
133
172
  autorun(
134
173
  () => {
135
- const { canvas, featureLayouts, featuresHeight, lgv } = self
136
- if (!lgv.initialized || self.regionCannotBeRendered()) {
174
+ const { canvas, layouts, lgv } = self
175
+ if (
176
+ !lgv.initialized ||
177
+ self.regionCannotBeRendered() ||
178
+ !canvas
179
+ ) {
137
180
  return
138
181
  }
139
- const { displayedRegions, dynamicBlocks } = lgv
182
+ const { dynamicBlocks, offsetPx } = lgv
140
183
 
141
- const ctx = canvas?.getContext('2d')
184
+ const ctx = canvas.getContext('2d')
142
185
  if (!ctx) {
143
186
  return
144
187
  }
145
- ctx.clearRect(0, 0, dynamicBlocks.totalWidthPx, featuresHeight)
146
- for (const [idx, featureLayout] of featureLayouts.entries()) {
147
- const displayedRegion = displayedRegions[idx]
148
- for (const [row, featureLayoutRow] of featureLayout.entries()) {
149
- for (const [featureRow, featureId] of featureLayoutRow) {
150
- const feature = self.getAnnotationFeatureById(featureId)
151
- if (featureRow > 0 || !feature) {
188
+ const featureLayouts = layouts.get(lgv.assemblyNames[0])
189
+ if (!featureLayouts) {
190
+ return
191
+ }
192
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
193
+ for (const block of dynamicBlocks.contentBlocks) {
194
+ const blockLeftPx = block.offsetPx - offsetPx
195
+ ctx.save()
196
+ ctx.beginPath()
197
+ ctx.rect(blockLeftPx, 0, block.widthPx, canvas.height)
198
+ ctx.clip()
199
+ const layout = featureLayouts.get(block.refName)
200
+ if (!layout) {
201
+ return
202
+ }
203
+ const { byRow } = layout
204
+ for (const [row, layoutRow] of byRow.entries()) {
205
+ for (const layoutFeature of layoutRow) {
206
+ const { feature, rowInFeature } = layoutFeature
207
+ if (
208
+ !doesIntersect2(
209
+ block.start,
210
+ block.end,
211
+ feature.min,
212
+ feature.max,
213
+ )
214
+ ) {
152
215
  continue
153
216
  }
217
+ self
218
+ .getGlyph(feature)
219
+ // @ts-expect-error ts doesn't understand mst extension
220
+ .draw(self, ctx, feature, row, rowInFeature, block)
221
+ }
222
+ }
223
+ for (const [row, layoutRow] of byRow.entries()) {
224
+ for (const layoutFeature of layoutRow) {
225
+ const { feature, rowInFeature } = layoutFeature
154
226
  if (
155
227
  !doesIntersect2(
156
- displayedRegion.start,
157
- displayedRegion.end,
228
+ block.start,
229
+ block.end,
158
230
  feature.min,
159
231
  feature.max,
160
232
  )
161
233
  ) {
162
234
  continue
163
235
  }
164
- self.getGlyph(feature).draw(ctx, feature, row, self, idx)
236
+ self.getGlyph(feature).drawOverlay(
237
+ // @ts-expect-error ts doesn't understand mst extension
238
+ self,
239
+ ctx,
240
+ feature,
241
+ row,
242
+ block,
243
+ 'highlight',
244
+ rowInFeature,
245
+ )
165
246
  }
166
247
  }
248
+ ctx.restore()
167
249
  }
168
250
  },
169
251
  { name: 'LinearApolloDisplayRenderFeatures' },