jbrowse-plugin-graphgenomeviewer 1.0.0 → 3.0.3

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 (283) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +202 -24
  3. package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js +2 -0
  4. package/dist/chunks/AddTrackWorkflow-Y3OBHM33.js.map +7 -0
  5. package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js +8 -0
  6. package/dist/chunks/GbzBaseSyntenyAdapter-EFIOI2SN.js.map +7 -0
  7. package/dist/chunks/GraphGenomeView-UD5WCV3Q.js +14 -0
  8. package/dist/chunks/GraphGenomeView-UD5WCV3Q.js.map +7 -0
  9. package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js +2 -0
  10. package/dist/chunks/MinigraphBubbleAdapter-B75VQK7T.js.map +7 -0
  11. package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js +3 -0
  12. package/dist/chunks/RgfaTabixAdapter-IT7I5RR3.js.map +7 -0
  13. package/dist/chunks/bandage-layout-FIOZ7B3E.js +1660 -0
  14. package/dist/chunks/bandage-layout-FIOZ7B3E.js.map +7 -0
  15. package/dist/chunks/chunk-D7XFR2IV.js +2 -0
  16. package/dist/chunks/chunk-D7XFR2IV.js.map +7 -0
  17. package/dist/chunks/chunk-F4C3BEPV.js +2 -0
  18. package/dist/chunks/chunk-F4C3BEPV.js.map +7 -0
  19. package/dist/chunks/chunk-FCWXYIFC.js +2 -0
  20. package/dist/chunks/chunk-FCWXYIFC.js.map +7 -0
  21. package/dist/chunks/chunk-FY3BTKY4.js +2 -0
  22. package/dist/chunks/chunk-FY3BTKY4.js.map +7 -0
  23. package/dist/chunks/chunk-J4OLMLS5.js +2 -0
  24. package/dist/chunks/chunk-J4OLMLS5.js.map +7 -0
  25. package/dist/chunks/chunk-JCG7RLMB.js +2 -0
  26. package/dist/chunks/chunk-JCG7RLMB.js.map +7 -0
  27. package/dist/chunks/chunk-KWAXLUPQ.js +2 -0
  28. package/dist/chunks/chunk-KWAXLUPQ.js.map +7 -0
  29. package/dist/chunks/chunk-LGKM2PZH.js +2 -0
  30. package/dist/chunks/chunk-LGKM2PZH.js.map +7 -0
  31. package/dist/chunks/chunk-TN6FK5UD.js +2 -0
  32. package/dist/chunks/chunk-TN6FK5UD.js.map +7 -0
  33. package/dist/chunks/chunk-X3X74OGB.js +2 -0
  34. package/dist/chunks/chunk-X3X74OGB.js.map +7 -0
  35. package/dist/chunks/chunk-XEJ47DLD.js +2 -0
  36. package/dist/chunks/chunk-XEJ47DLD.js.map +7 -0
  37. package/dist/chunks/chunk-ZNGDWZ6Z.js +2 -0
  38. package/dist/chunks/chunk-ZNGDWZ6Z.js.map +7 -0
  39. package/dist/chunks/executeDiagonalize-XMPEDHVZ.js +2 -0
  40. package/dist/chunks/executeDiagonalize-XMPEDHVZ.js.map +7 -0
  41. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +3 -0
  42. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +7 -0
  43. package/package.json +94 -49
  44. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +427 -0
  45. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +461 -0
  46. package/src/GbzBaseSyntenyAdapter/configSchema.ts +181 -0
  47. package/src/GbzBaseSyntenyAdapter/index.ts +27 -0
  48. package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.gbz.db +0 -0
  49. package/src/GbzBaseSyntenyAdapter/test_data/micb-kir3dl1.haplotype-index.db +0 -0
  50. package/src/GetSubgraph.test.ts +193 -0
  51. package/src/GetSubgraph.ts +93 -0
  52. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +167 -0
  53. package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +150 -0
  54. package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +114 -0
  55. package/src/GraphAddTrackWorkflow/index.ts +20 -0
  56. package/src/GraphComputeLayout.ts +46 -0
  57. package/src/GraphGenomeView/anchoredNodes.ts +101 -0
  58. package/src/GraphGenomeView/bubbleSpreads.ts +102 -0
  59. package/src/GraphGenomeView/bubbles/bubbleHalos.test.ts +58 -0
  60. package/src/GraphGenomeView/bubbles/bubbleHalos.ts +149 -0
  61. package/src/GraphGenomeView/bubbles/bubbleRoutes.test.ts +141 -0
  62. package/src/GraphGenomeView/bubbles/bubblesFromGraph.test.ts +239 -0
  63. package/src/GraphGenomeView/bubbles/bubblesFromGraph.ts +210 -0
  64. package/src/GraphGenomeView/bubbles/classifyBubble.test.ts +172 -0
  65. package/src/GraphGenomeView/bubbles/classifyBubble.ts +138 -0
  66. package/src/GraphGenomeView/bubbles/popBubble.test.ts +73 -0
  67. package/src/GraphGenomeView/bubbles/popBubble.ts +26 -0
  68. package/src/GraphGenomeView/colorSchemes.ts +36 -0
  69. package/src/GraphGenomeView/components/BubbleHalos.tsx +162 -0
  70. package/src/GraphGenomeView/components/BubbleOverlay.tsx +181 -0
  71. package/src/GraphGenomeView/components/ColorSchemeSelect.tsx +41 -0
  72. package/src/GraphGenomeView/components/GenePins.tsx +107 -0
  73. package/src/GraphGenomeView/components/GraphCanvas.tsx +759 -0
  74. package/src/GraphGenomeView/components/GraphGenomeView.test.tsx +77 -0
  75. package/src/GraphGenomeView/components/GraphGenomeView.tsx +18 -17
  76. package/src/GraphGenomeView/components/GraphLoadStatus.tsx +84 -0
  77. package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +327 -0
  78. package/src/GraphGenomeView/components/GraphStats.tsx +82 -0
  79. package/src/GraphGenomeView/components/GraphToolbar.tsx +84 -0
  80. package/src/GraphGenomeView/components/ImportForm.tsx +124 -0
  81. package/src/GraphGenomeView/components/LabelChip.tsx +65 -0
  82. package/src/GraphGenomeView/components/LayoutSelect.tsx +56 -0
  83. package/src/GraphGenomeView/components/RepeatSelect.tsx +51 -0
  84. package/src/GraphGenomeView/components/SettingsMenu.tsx +82 -0
  85. package/src/GraphGenomeView/components/SubgraphContextSelect.tsx +75 -0
  86. package/src/GraphGenomeView/components/SubgraphHaplotypesField.tsx +86 -0
  87. package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +252 -0
  88. package/src/GraphGenomeView/components/WalkSelect.tsx +51 -0
  89. package/src/GraphGenomeView/components/overlayLabels.test.ts +56 -0
  90. package/src/GraphGenomeView/components/overlayLabels.ts +75 -0
  91. package/src/GraphGenomeView/deletionEdges.test.ts +198 -0
  92. package/src/GraphGenomeView/deletionEdges.ts +217 -0
  93. package/src/GraphGenomeView/genes/geneFeatures.ts +107 -0
  94. package/src/GraphGenomeView/genes/genePins.ts +130 -0
  95. package/src/GraphGenomeView/genes/genes.test.ts +153 -0
  96. package/src/GraphGenomeView/gfa/gfaConverter.test.ts +394 -0
  97. package/src/GraphGenomeView/gfa/gfaConverter.ts +235 -0
  98. package/src/GraphGenomeView/graphLabels.budget.test.ts +36 -0
  99. package/src/GraphGenomeView/graphLabels.test.ts +548 -0
  100. package/src/GraphGenomeView/graphLabels.ts +640 -0
  101. package/src/GraphGenomeView/index.ts +29 -7
  102. package/src/GraphGenomeView/laneRamp.test.ts +97 -0
  103. package/src/GraphGenomeView/laneRamp.ts +87 -0
  104. package/src/GraphGenomeView/launchKeys.test.ts +63 -0
  105. package/src/GraphGenomeView/layout/anchoredLayout.test.ts +213 -0
  106. package/src/GraphGenomeView/layout/anchoredLayout.ts +80 -0
  107. package/src/GraphGenomeView/layout/bubbleCrossing.test.ts +190 -0
  108. package/src/GraphGenomeView/layout/drawnScale.ts +199 -0
  109. package/src/GraphGenomeView/layout/mergeRuns.test.ts +100 -0
  110. package/src/GraphGenomeView/layout/mergeRuns.ts +203 -0
  111. package/src/GraphGenomeView/layout/orderedLayout.test.ts +212 -0
  112. package/src/GraphGenomeView/layout/orderedLayout.ts +396 -0
  113. package/src/GraphGenomeView/layout/orientToReference.test.ts +110 -0
  114. package/src/GraphGenomeView/layout/orientToReference.ts +79 -0
  115. package/src/GraphGenomeView/layout/placeOffReference.ts +147 -0
  116. package/src/GraphGenomeView/layout/referenceSeeds.test.ts +79 -0
  117. package/src/GraphGenomeView/layout/referenceSeeds.ts +89 -0
  118. package/src/GraphGenomeView/layout/rowSpacing.ts +5 -0
  119. package/src/GraphGenomeView/layout/sampleRowLayout.test.ts +318 -0
  120. package/src/GraphGenomeView/layout/sampleRowLayout.ts +134 -0
  121. package/src/GraphGenomeView/layout/variantMapLayout.test.ts +59 -0
  122. package/src/GraphGenomeView/layout/variantMapLayout.ts +20 -0
  123. package/src/GraphGenomeView/layout/walkRowLayout.ts +54 -0
  124. package/src/GraphGenomeView/layout/walkRows.test.ts +178 -0
  125. package/src/GraphGenomeView/layout/walkRows.ts +177 -0
  126. package/src/GraphGenomeView/layoutModes.ts +131 -0
  127. package/src/GraphGenomeView/model.test.ts +2696 -0
  128. package/src/GraphGenomeView/model.ts +2672 -94
  129. package/src/GraphGenomeView/nodeWidths.ts +67 -0
  130. package/src/GraphGenomeView/pathAnchoring.test.ts +252 -0
  131. package/src/GraphGenomeView/pathAnchoring.ts +164 -0
  132. package/src/GraphGenomeView/pathColors.test.ts +83 -0
  133. package/src/GraphGenomeView/pathColors.ts +122 -0
  134. package/src/GraphGenomeView/referenceSpan.test.ts +84 -0
  135. package/src/GraphGenomeView/referenceSpan.ts +133 -0
  136. package/src/GraphGenomeView/renderPasses.bench.ts +172 -0
  137. package/src/GraphGenomeView/renderPipeline.test.ts +226 -0
  138. package/src/GraphGenomeView/renderer/Canvas2DRenderer.test.ts +205 -0
  139. package/src/GraphGenomeView/renderer/Canvas2DRenderer.ts +246 -0
  140. package/src/GraphGenomeView/renderer/GeometryBuilder.test.ts +723 -0
  141. package/src/GraphGenomeView/renderer/GeometryBuilder.ts +882 -0
  142. package/src/GraphGenomeView/renderer/GraphRenderer.ts +13 -0
  143. package/src/GraphGenomeView/renderer/colorBits.ts +43 -0
  144. package/src/GraphGenomeView/renderer/recordingCanvas.ts +64 -0
  145. package/src/GraphGenomeView/renderer/types.ts +84 -0
  146. package/src/GraphGenomeView/renderer/walkFade.test.ts +100 -0
  147. package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +114 -0
  148. package/src/GraphGenomeView/repeats/repeatFeatures.ts +202 -0
  149. package/src/GraphGenomeView/repeats/test_data/abca7_trgt_walks.json +575 -0
  150. package/src/GraphGenomeView/repeats/walkCalls.test.ts +127 -0
  151. package/src/GraphGenomeView/repeats/walkCalls.ts +132 -0
  152. package/src/GraphGenomeView/stress.test.ts +214 -0
  153. package/src/GraphGenomeView/subgraphLoad.test.ts +245 -0
  154. package/src/GraphGenomeView/types.ts +141 -0
  155. package/src/GraphGenomeView/util/SpatialIndex.test.ts +73 -0
  156. package/src/GraphGenomeView/util/SpatialIndex.ts +312 -0
  157. package/src/GraphGenomeView/util/edgeCurves.test.ts +93 -0
  158. package/src/GraphGenomeView/util/edgeCurves.ts +94 -0
  159. package/src/GraphGenomeView/util/geometry.test.ts +447 -0
  160. package/src/GraphGenomeView/util/geometry.ts +656 -0
  161. package/src/GraphGenomeView/util/hitDetection.test.ts +307 -0
  162. package/src/GraphGenomeView/util/hitDetection.ts +330 -0
  163. package/src/GraphGenomeView/util/wheelZoom.test.ts +40 -0
  164. package/src/GraphGenomeView/util/wheelZoom.ts +26 -0
  165. package/src/GraphGenomeView/walkHighlight.test.ts +54 -0
  166. package/src/GraphGenomeView/walkHighlight.ts +56 -0
  167. package/src/LaunchGraphGenomeView.ts +46 -0
  168. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +139 -0
  169. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +86 -0
  170. package/src/MinigraphBubbleAdapter/bubbleLine.ts +92 -0
  171. package/src/MinigraphBubbleAdapter/configSchema.ts +109 -0
  172. package/src/MinigraphBubbleAdapter/index.ts +22 -0
  173. package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz +0 -0
  174. package/src/MinigraphBubbleAdapter/test_data/hprc_bubbles_mhc.bed.gz.tbi +0 -0
  175. package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +285 -0
  176. package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +194 -0
  177. package/src/RgfaTabixAdapter/bubbleTier.test.ts +84 -0
  178. package/src/RgfaTabixAdapter/configSchema.ts +142 -0
  179. package/src/RgfaTabixAdapter/index.ts +24 -0
  180. package/src/RgfaTabixAdapter/rgfaBed.test.ts +158 -0
  181. package/src/RgfaTabixAdapter/rgfaBed.ts +156 -0
  182. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz +0 -0
  183. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.links.bed.gz.tbi +0 -0
  184. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz +0 -0
  185. package/src/RgfaTabixAdapter/test_data/bubble_tier_chrY.segs.bed.gz.tbi +0 -0
  186. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz +0 -0
  187. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.links.bed.gz.tbi +0 -0
  188. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz +0 -0
  189. package/src/RgfaTabixAdapter/test_data/pggb_ecoli.segs.bed.gz.tbi +0 -0
  190. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz +0 -0
  191. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.links.bed.gz.tbi +0 -0
  192. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz +0 -0
  193. package/src/RgfaTabixAdapter/test_data/rgfa_ecoli.segs.bed.gz.tbi +0 -0
  194. package/src/alleleProjection/projectAlleles.test.ts +75 -0
  195. package/src/alleleProjection/projectAlleles.ts +190 -0
  196. package/src/bandage/README.md +121 -0
  197. package/src/bandage/bandage-layout.d.ts +14 -0
  198. package/src/bandage/bandage-layout.js +0 -0
  199. package/src/bandage/native/CMakeLists.txt +45 -0
  200. package/src/bandage/native/include/graph.h +188 -0
  201. package/src/bandage/native/include/graphlayout.h +74 -0
  202. package/src/bandage/native/include/settings.h +86 -0
  203. package/src/bandage/native/include/types.h +56 -0
  204. package/src/bandage/native/src/bindings.cpp +164 -0
  205. package/src/bandage/native/src/graphlayout.cpp +621 -0
  206. package/src/bandage/resizableDecode.test.ts +58 -0
  207. package/src/gfa-core/gfaParser.bench.ts +55 -0
  208. package/src/gfa-core/gfaParser.test.ts +168 -0
  209. package/src/gfa-core/gfaParser.ts +237 -0
  210. package/src/gfa-core/index.ts +10 -0
  211. package/src/hoverSync/GraphNodeHighlight.tsx +62 -0
  212. package/src/hoverSync/graphViewHighlights.test.ts +158 -0
  213. package/src/hoverSync/graphViewHighlights.ts +119 -0
  214. package/src/hoverSync/index.test.tsx +36 -0
  215. package/src/hoverSync/index.tsx +19 -0
  216. package/src/hoverSync/lgvHover.test.ts +202 -0
  217. package/src/hoverSync/lgvHover.ts +136 -0
  218. package/src/index.ts +46 -15
  219. package/src/launchFromGraph/contributors.test.ts +197 -0
  220. package/src/launchFromGraph/contributors.ts +0 -0
  221. package/src/launchFromGraph/graphMenuItems.ts +211 -0
  222. package/src/launchFromGraph/launchFromGraph.test.ts +489 -0
  223. package/src/launchFromGraph/launchFromGraph.ts +265 -0
  224. package/src/launchFromGraph/launchTracks.test.ts +72 -0
  225. package/src/launchFromGraph/launchTracks.ts +50 -0
  226. package/src/launchFromGraph/linearViewTarget.test.ts +82 -0
  227. package/src/launchFromGraph/linearViewTarget.ts +75 -0
  228. package/src/launchFromGraph/syntenyTracks.ts +47 -0
  229. package/src/launchSubgraph/index.ts +152 -0
  230. package/src/launchSubgraph/launchSubgraph.test.ts +264 -0
  231. package/src/launchSubgraph/launchSubgraphView.ts +128 -0
  232. package/src/launchSubgraph/linearViewMenuItems.test.ts +176 -0
  233. package/src/launchSubgraph/linearViewMenuItems.ts +107 -0
  234. package/src/launchSubgraph/multiWayLaunch.test.ts +289 -0
  235. package/src/launchSubgraph/subgraphMenuItems.ts +73 -0
  236. package/src/launchSubgraph/subgraphTracks.test.ts +165 -0
  237. package/src/launchSubgraph/subgraphTracks.ts +151 -0
  238. package/src/launchSubgraph/testEnv.ts +258 -0
  239. package/src/loadBandage.ts +22 -0
  240. package/src/panSNTabix.test.ts +72 -0
  241. package/src/panSNTabix.ts +149 -0
  242. package/src/pansn.ts +106 -0
  243. package/src/pluginName.ts +3 -0
  244. package/src/synteny/ComparativeAdapterBase.ts +77 -0
  245. package/src/synteny/SyntenyFeature.ts +116 -0
  246. package/src/synteny/clipFeatureToRegion.test.ts +329 -0
  247. package/src/synteny/clipFeatureToRegion.ts +167 -0
  248. package/src/version.ts +1 -0
  249. package/dist/GraphGenomeView/components/FeatureDialog.d.ts +0 -5
  250. package/dist/GraphGenomeView/components/FeatureDialog.js +0 -18
  251. package/dist/GraphGenomeView/components/FeatureDialog.js.map +0 -1
  252. package/dist/GraphGenomeView/components/GraphGenomeView.d.ts +0 -6
  253. package/dist/GraphGenomeView/components/GraphGenomeView.js +0 -17
  254. package/dist/GraphGenomeView/components/GraphGenomeView.js.map +0 -1
  255. package/dist/GraphGenomeView/components/GraphPanel.d.ts +0 -6
  256. package/dist/GraphGenomeView/components/GraphPanel.js +0 -17
  257. package/dist/GraphGenomeView/components/GraphPanel.js.map +0 -1
  258. package/dist/GraphGenomeView/components/Header.d.ts +0 -6
  259. package/dist/GraphGenomeView/components/Header.js +0 -59
  260. package/dist/GraphGenomeView/components/Header.js.map +0 -1
  261. package/dist/GraphGenomeView/components/SettingsDialog.d.ts +0 -7
  262. package/dist/GraphGenomeView/components/SettingsDialog.js +0 -24
  263. package/dist/GraphGenomeView/components/SettingsDialog.js.map +0 -1
  264. package/dist/GraphGenomeView/index.d.ts +0 -2
  265. package/dist/GraphGenomeView/index.js +0 -15
  266. package/dist/GraphGenomeView/index.js.map +0 -1
  267. package/dist/GraphGenomeView/model.d.ts +0 -80
  268. package/dist/GraphGenomeView/model.js +0 -96
  269. package/dist/GraphGenomeView/model.js.map +0 -1
  270. package/dist/GraphGenomeView/util.d.ts +0 -1
  271. package/dist/GraphGenomeView/util.js +0 -8
  272. package/dist/GraphGenomeView/util.js.map +0 -1
  273. package/dist/index.d.ts +0 -8
  274. package/dist/index.js +0 -24
  275. package/dist/index.js.map +0 -1
  276. package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js +0 -67
  277. package/dist/jbrowse-plugin-graphgenomeviewer.umd.production.min.js.map +0 -7
  278. package/src/GraphGenomeView/components/FeatureDialog.tsx +0 -51
  279. package/src/GraphGenomeView/components/GraphPanel.tsx +0 -37
  280. package/src/GraphGenomeView/components/Header.tsx +0 -90
  281. package/src/GraphGenomeView/components/SettingsDialog.tsx +0 -71
  282. package/src/GraphGenomeView/util.ts +0 -7
  283. package/src/declare.d.ts +0 -1
@@ -0,0 +1,176 @@
1
+ // LAUNCH_LABEL rather than the literal: the host renamed this submenu from
2
+ // "Launch view" to "Launch" (packages/core/src/ui/launchViewMenu.ts says why),
3
+ // and a hardcoded copy here fails as "the item was never added" rather than as
4
+ // "the submenu is called something else".
5
+ import { LAUNCH_LABEL } from '@jbrowse/core/ui'
6
+
7
+ import { createTestEnvironment } from './testEnv'
8
+ import { MAX_GRAPH_REGION_BP } from '../GraphGenomeView/model'
9
+
10
+ import type { MenuItem } from '@jbrowse/core/ui'
11
+
12
+ const LABEL_VISIBLE = 'Graph genome view (this region)'
13
+ const LABEL_SELECTION = 'Graph genome view (this selection)'
14
+ const LABEL_FEATURE = 'Graph genome view (this feature)'
15
+
16
+ function launchItems(items: MenuItem[]) {
17
+ const item = items.find(i => 'label' in i && i.label === LAUNCH_LABEL)
18
+ return item && 'subMenu' in item ? item.subMenu : []
19
+ }
20
+
21
+ function find(items: MenuItem[], label: string) {
22
+ return items.find(i => 'label' in i && i.label === label)
23
+ }
24
+
25
+ function click(item: MenuItem | undefined) {
26
+ if (item && 'onClick' in item) {
27
+ item.onClick(undefined)
28
+ } else {
29
+ throw new Error('menu item has no onClick')
30
+ }
31
+ }
32
+
33
+ // A rubberband selection is the entry point that works at the zoom people
34
+ // actually browse at: the visible region is routinely wider than the cap, and
35
+ // selecting a range needs no navigating first.
36
+ test('the rubberband menu launches the selected range', () => {
37
+ const { createDisplay } = createTestEnvironment()
38
+ const { session, view } = createDisplay()
39
+ view.setOffsets(
40
+ { refName: 'ctgA', index: 0, offset: 1000, start: 0, end: 50_000 },
41
+ { refName: 'ctgA', index: 0, offset: 2000, start: 0, end: 50_000 },
42
+ )
43
+
44
+ const item = find(view.rubberBandMenuItems(), LABEL_SELECTION)
45
+ expect(item).toBeDefined()
46
+ click(item)
47
+
48
+ const [type, snapshot] = session.addedViews[0]!
49
+ expect(type).toBe('GraphGenomeView')
50
+ expect(snapshot.loadedTrackId).toBe('graph_track')
51
+ expect(snapshot.connectedViewId).toBe(view.id)
52
+ expect(snapshot.loadedRegion).toMatchObject({ refName: 'ctgA' })
53
+ })
54
+
55
+ test('the rubberband menu keeps the items it already had', () => {
56
+ const { createDisplay } = createTestEnvironment()
57
+ const { view } = createDisplay()
58
+ view.setOffsets(
59
+ { refName: 'ctgA', index: 0, offset: 1000, start: 0, end: 50_000 },
60
+ { refName: 'ctgA', index: 0, offset: 2000, start: 0, end: 50_000 },
61
+ )
62
+ const labels = view
63
+ .rubberBandMenuItems()
64
+ .flatMap((i: MenuItem) => ('label' in i ? [i.label] : []))
65
+ expect(labels).toContain('Zoom to region')
66
+ expect(labels).toContain(LABEL_SELECTION)
67
+ })
68
+
69
+ // Nothing selected is not a launchable region, so the item stays out of the menu
70
+ // rather than appearing dead.
71
+ test('no rubberband selection adds no item', () => {
72
+ const { createDisplay } = createTestEnvironment()
73
+ const { view } = createDisplay()
74
+ expect(find(view.rubberBandMenuItems(), LABEL_SELECTION)).toBeUndefined()
75
+ })
76
+
77
+ // The point of the view-level item: the graph track need not be the one whose
78
+ // menu you opened, or be open at all.
79
+ test('the view menu launches the visible region', () => {
80
+ const { createDisplay } = createTestEnvironment()
81
+ const { session, view } = createDisplay({ trackId: 'bubble_track' })
82
+
83
+ const item = find(launchItems(view.menuItems()), LABEL_VISIBLE)
84
+ expect(item).toBeDefined()
85
+ click(item)
86
+
87
+ expect(session.addedViews[0]![1].loadedTrackId).toBe('graph_track')
88
+ })
89
+
90
+ // A vanishing item teaches nothing; a greyed-out one with the size in its
91
+ // tooltip says what to do about it.
92
+ test('an over-cap region disables the item instead of hiding it', () => {
93
+ const { createDisplay } = createTestEnvironment()
94
+ const { session, view } = createDisplay()
95
+ view.setDisplayedRegions([
96
+ {
97
+ assemblyName: 'volvox',
98
+ refName: 'ctgA',
99
+ start: 0,
100
+ end: MAX_GRAPH_REGION_BP * 3,
101
+ },
102
+ ])
103
+ // the *visible* span is what's cut, so it has to be zoomed out past the cap,
104
+ // not merely have a large region loaded
105
+ view.showAllRegions()
106
+ expect(view.dynamicBlocks.totalBp).toBeGreaterThan(MAX_GRAPH_REGION_BP)
107
+
108
+ const item = find(launchItems(view.menuItems()), LABEL_VISIBLE)
109
+ expect(item).toMatchObject({ disabled: true })
110
+ expect(item && 'disabledHelpText' in item && item.disabledHelpText).toMatch(
111
+ /zoom in or select a smaller range/i,
112
+ )
113
+ expect(session.addedViews).toHaveLength(0)
114
+ })
115
+
116
+ // A graph is cut on its reference, the first assembly its track names, so a
117
+ // view of another one gets the reason rather than a cut in the wrong frame.
118
+ test('a view of a non-reference assembly disables the item and names the reference', () => {
119
+ const { createDisplay } = createTestEnvironment({
120
+ graphAssemblyNames: ['hg38', 'volvox'],
121
+ })
122
+ const { session, view } = createDisplay()
123
+ const item = find(launchItems(view.menuItems()), LABEL_VISIBLE)
124
+ expect(item).toMatchObject({ disabled: true })
125
+ expect(item && 'disabledHelpText' in item && item.disabledHelpText).toMatch(
126
+ /cut on its reference, hg38/,
127
+ )
128
+ expect(session.addedViews).toHaveLength(0)
129
+ })
130
+
131
+ // The bubble track marks exactly where haplotypes diverge, which is the most
132
+ // natural thing to right-click — but its adapter reads a summary index and
133
+ // cannot cut a graph, so the launch has to come from the graph track instead.
134
+ test('right-clicking a feature on a non-graph track cuts from the graph track', () => {
135
+ const { createDisplay } = createTestEnvironment()
136
+ const { session, display, view } = createDisplay({ trackId: 'bubble_track' })
137
+ display.openContextMenu({
138
+ item: { featureId: 'bubble1', startBp: 2000, endBp: 2400, name: 'bubble1' },
139
+ displayedRegionIndex: 0,
140
+ clientX: 0,
141
+ clientY: 0,
142
+ })
143
+
144
+ const item = find(launchItems(display.contextMenuItems()), LABEL_FEATURE)
145
+ expect(item).toBeDefined()
146
+ click(item)
147
+
148
+ const [, snapshot] = session.addedViews[0]!
149
+ expect(snapshot.loadedTrackId).toBe('graph_track')
150
+ expect(snapshot.connectedViewId).toBe(view.id)
151
+ // padded by half the feature's length on each side
152
+ expect(snapshot.loadedRegion).toEqual({
153
+ refName: 'ctgA',
154
+ assemblyName: 'volvox',
155
+ start: 1800,
156
+ end: 2600,
157
+ })
158
+ })
159
+
160
+ // No graph data in the session means no menu clutter anywhere.
161
+ test('no graph track in the session adds no items', () => {
162
+ const { createDisplay } = createTestEnvironment({ subgraphCapable: false })
163
+ const { view, display } = createDisplay({ trackId: 'bubble_track' })
164
+ display.openContextMenu({
165
+ item: { featureId: 'bubble1', startBp: 2000, endBp: 2400, name: 'bubble1' },
166
+ displayedRegionIndex: 0,
167
+ clientX: 0,
168
+ clientY: 0,
169
+ })
170
+
171
+ expect(find(launchItems(view.menuItems()), LABEL_VISIBLE)).toBeUndefined()
172
+ expect(find(view.rubberBandMenuItems(), LABEL_SELECTION)).toBeUndefined()
173
+ expect(
174
+ find(launchItems(display.contextMenuItems()), LABEL_FEATURE),
175
+ ).toBeUndefined()
176
+ })
@@ -0,0 +1,107 @@
1
+ import { pushLaunchViewMenuItem } from '@jbrowse/core/ui'
2
+ import { getSession } from '@jbrowse/core/util'
3
+
4
+ import { SUBGRAPH_REGION_LABEL, regionFromViewport } from './launchSubgraphView'
5
+ import { subgraphMenuItems } from './subgraphMenuItems'
6
+ import { subgraphTracks } from './subgraphTracks'
7
+
8
+ import type PluginManager from '@jbrowse/core/PluginManager'
9
+ import type { PluggableElementType } from '@jbrowse/core/pluggableElementTypes'
10
+ import type ViewType from '@jbrowse/core/pluggableElementTypes/ViewType'
11
+ import type { LinearGenomeViewModel } from '@jbrowse/plugin-linear-genome-view'
12
+
13
+ const SELECTION_LABEL = 'Graph genome view (this selection)'
14
+
15
+ function isLinearGenomeView(elt: { name: string }): elt is ViewType {
16
+ return elt.name === 'LinearGenomeView'
17
+ }
18
+
19
+ // Launch entry points on the linear view itself, rather than on a graph track's
20
+ // menu. Two reasons this is where they belong:
21
+ //
22
+ // - The graph track need not be in the view, or even turned on. Scanning the
23
+ // session for a subgraph-capable track gives a user browsing genes a way in;
24
+ // before, the only entry point was the menu of a track they might never have
25
+ // opened.
26
+ // - A rubberband selection picks a window directly, with no navigating first,
27
+ // and is the only way to ask about a range narrower than what is on screen.
28
+ export default function LinearViewMenuItemsF(pluginManager: PluginManager) {
29
+ pluginManager.addToExtensionPoint(
30
+ 'Core-extendPluggableElement',
31
+ (pluggableElement: PluggableElementType) => {
32
+ if (isLinearGenomeView(pluggableElement)) {
33
+ // extendStateModel, NOT `stateModel = stateModel.extend(...)`: see
34
+ // launchSubgraph/index.ts for what a synchronous read costs now that the
35
+ // host registers state models as lazy loaders.
36
+ pluggableElement.extendStateModel(stateModel =>
37
+ stateModel.extend(
38
+ // Annotated rather than cast: the extension point hands over an
39
+ // IAnyModelType, and stating the model shape here is what types
40
+ // `getSelectedRegions`/`dynamicBlocks` below without an `as`.
41
+ (self: LinearGenomeViewModel) => {
42
+ const superMenuItems = self.menuItems
43
+ const superRubberBandMenuItems = self.rubberBandMenuItems
44
+
45
+ // Graph tracks for this view's assembly. Empty means every item
46
+ // below is empty too, so a session with no graph data gains no menu
47
+ // clutter.
48
+ function tracks() {
49
+ const assemblyName = self.assemblyNames[0]
50
+ return assemblyName
51
+ ? subgraphTracks(
52
+ pluginManager,
53
+ getSession(self),
54
+ assemblyName,
55
+ self,
56
+ )
57
+ : []
58
+ }
59
+
60
+ return {
61
+ views: {
62
+ menuItems() {
63
+ const items = superMenuItems()
64
+ for (const item of subgraphMenuItems({
65
+ label: SUBGRAPH_REGION_LABEL,
66
+ region: regionFromViewport(
67
+ self.dynamicBlocks.contentBlocks,
68
+ ),
69
+ tracks: tracks(),
70
+ session: getSession(self),
71
+ connectedViewId: self.id,
72
+ })) {
73
+ pushLaunchViewMenuItem(items, item)
74
+ }
75
+ return items
76
+ },
77
+
78
+ // The rubberband menu is short and contextual, so these go in
79
+ // flat rather than under a "Launch view" submenu — that grouping
80
+ // earns its keep in the long track and view menus, not here.
81
+ rubberBandMenuItems() {
82
+ return [
83
+ ...superRubberBandMenuItems(),
84
+ ...subgraphMenuItems({
85
+ label: SELECTION_LABEL,
86
+ region: regionFromViewport(
87
+ self.getSelectedRegions(
88
+ self.leftOffset,
89
+ self.rightOffset,
90
+ ),
91
+ ),
92
+ tracks: tracks(),
93
+ session: getSession(self),
94
+ connectedViewId: self.id,
95
+ }),
96
+ ]
97
+ },
98
+ },
99
+ }
100
+ },
101
+ ),
102
+ )
103
+ }
104
+ return pluggableElement
105
+ },
106
+ )
107
+ }
@@ -0,0 +1,289 @@
1
+ import PluginManager from '@jbrowse/core/PluginManager'
2
+ import {
3
+ ConfigurationReference,
4
+ ConfigurationSchema,
5
+ getConf,
6
+ } from '@jbrowse/core/configuration'
7
+ import AdapterType from '@jbrowse/core/pluggableElementTypes/AdapterType'
8
+ import DisplayType from '@jbrowse/core/pluggableElementTypes/DisplayType'
9
+ import TrackType from '@jbrowse/core/pluggableElementTypes/TrackType'
10
+ import ViewType from '@jbrowse/core/pluggableElementTypes/ViewType'
11
+ import {
12
+ BaseDisplay,
13
+ createBaseTrackConfig,
14
+ createBaseTrackModel,
15
+ } from '@jbrowse/core/pluggableElementTypes/models'
16
+ import { LAUNCH_LABEL } from '@jbrowse/core/ui'
17
+ import { getContainingTrack } from '@jbrowse/core/util'
18
+ import { types } from '@jbrowse/mobx-state-tree'
19
+ import { linearGenomeViewStateModelFactory } from '@jbrowse/plugin-linear-genome-view'
20
+
21
+ import LaunchSubgraphMenuItemF from './index'
22
+ import LinearViewMenuItemsF from './linearViewMenuItems'
23
+
24
+ import type { MenuItem } from '@jbrowse/core/ui'
25
+ import type { Instance } from '@jbrowse/mobx-state-tree'
26
+
27
+ const LABEL = 'Graph genome view (this region)'
28
+
29
+ // A stand-in for core's MultiWaySyntenyDisplay with the four members the
30
+ // launch reads by duck type (subgraphTracks.ts): the lanes in force are the
31
+ // picker's choice, else the track's own lanes, and a hidden lane stays in
32
+ // force.
33
+ function laneDisplayType() {
34
+ const configSchema = ConfigurationSchema(
35
+ 'MultiWaySyntenyDisplay',
36
+ {},
37
+ { explicitIdentifier: 'displayId', explicitlyTyped: true },
38
+ )
39
+ const stateModel = types
40
+ .compose(
41
+ 'MultiWaySyntenyDisplay',
42
+ BaseDisplay,
43
+ types.model({
44
+ type: types.literal('MultiWaySyntenyDisplay'),
45
+ configuration: ConfigurationReference(configSchema),
46
+ }),
47
+ )
48
+ .volatile(() => ({
49
+ picked: undefined as string[] | undefined,
50
+ hidden: [] as string[],
51
+ }))
52
+ .views(self => ({
53
+ get laneSelection(): readonly string[] | undefined {
54
+ const configured = getConf(
55
+ getContainingTrack(self),
56
+ 'assemblyNames',
57
+ ).slice(1)
58
+ return self.picked ?? (configured.length ? configured : undefined)
59
+ },
60
+ get hiddenLanes(): readonly string[] {
61
+ return self.hidden
62
+ },
63
+ trackMenuItems(): MenuItem[] {
64
+ return []
65
+ },
66
+ }))
67
+ .actions(self => ({
68
+ setSelectedLanes(names: string[] | undefined) {
69
+ self.picked = names
70
+ },
71
+ hideLane(name: string) {
72
+ self.hidden = [...self.hidden, name]
73
+ },
74
+ }))
75
+ return new DisplayType({
76
+ name: 'MultiWaySyntenyDisplay',
77
+ configSchema,
78
+ stateModel,
79
+ trackType: 'SyntenyTrack',
80
+ viewType: 'LinearGenomeView',
81
+ ReactComponent: () => null,
82
+ })
83
+ }
84
+
85
+ function createEnv(trackLanes: string[] = []) {
86
+ console.warn = vi.fn()
87
+ console.error = vi.fn()
88
+ const pluginManager = new PluginManager()
89
+ pluginManager.addAdapterType(
90
+ () =>
91
+ new AdapterType({
92
+ name: 'GbzBaseSyntenyAdapter',
93
+ configSchema: ConfigurationSchema(
94
+ 'GbzBaseSyntenyAdapter',
95
+ {},
96
+ { explicitlyTyped: true },
97
+ ),
98
+ adapterCapabilities: ['getSubgraph', 'headerLanes'],
99
+ getAdapterClass: () => {
100
+ throw new Error('config-only')
101
+ },
102
+ }),
103
+ )
104
+ pluginManager.addTrackType(() => {
105
+ const schema = ConfigurationSchema(
106
+ 'SyntenyTrack',
107
+ {},
108
+ {
109
+ baseConfiguration: createBaseTrackConfig(pluginManager),
110
+ explicitIdentifier: 'trackId',
111
+ },
112
+ )
113
+ return new TrackType({
114
+ name: 'SyntenyTrack',
115
+ configSchema: schema,
116
+ stateModel: createBaseTrackModel(pluginManager, 'SyntenyTrack', schema),
117
+ })
118
+ })
119
+ pluginManager.addDisplayType(() => laneDisplayType())
120
+ pluginManager.addViewType(
121
+ () =>
122
+ new ViewType({
123
+ name: 'LinearGenomeView',
124
+ stateModel: linearGenomeViewStateModelFactory(pluginManager),
125
+ ReactComponent: () => null,
126
+ }),
127
+ )
128
+ LaunchSubgraphMenuItemF(pluginManager)
129
+ LinearViewMenuItemsF(pluginManager)
130
+ pluginManager.createPluggableElements()
131
+ pluginManager.configure()
132
+
133
+ const track = pluginManager.pluggableConfigSchemaType('track').create(
134
+ {
135
+ type: 'SyntenyTrack',
136
+ trackId: 'gbz_lanes',
137
+ name: 'GBZ lanes',
138
+ assemblyNames: ['volvox', ...trackLanes],
139
+ adapter: { type: 'GbzBaseSyntenyAdapter' },
140
+ displays: [
141
+ {
142
+ type: 'MultiWaySyntenyDisplay',
143
+ displayId: 'gbz_lanes-MultiWaySyntenyDisplay',
144
+ },
145
+ ],
146
+ },
147
+ { pluginManager },
148
+ )
149
+ const assemblyRegions = [
150
+ { refName: 'ctgA', start: 0, end: 50_000, assemblyName: 'volvox' },
151
+ ]
152
+ const assembly = {
153
+ initialized: true,
154
+ regions: assemblyRegions,
155
+ getCanonicalRefName: (refName: string) => refName,
156
+ getGeneticCodeId: () => undefined,
157
+ getRegionForRefName: (refName: string) =>
158
+ assemblyRegions.find(r => r.refName === refName),
159
+ configuration: { sequence: undefined },
160
+ }
161
+ const LGV = pluginManager.getViewType('LinearGenomeView').stateModel
162
+ const Session = types
163
+ .model({
164
+ name: 'testSession',
165
+ view: types.maybe(LGV),
166
+ configuration: types.map(types.frozen()),
167
+ })
168
+ .volatile(() => ({
169
+ tracks: [track],
170
+ connectionInstances: [],
171
+ addedViews: [] as [string, Record<string, unknown>][],
172
+ rpcManager: { call: vi.fn() },
173
+ assemblyManager: {
174
+ get: (name: string) => (name === 'volvox' ? assembly : undefined),
175
+ waitForAssembly: () => Promise.resolve(assembly),
176
+ isValidRefName: (refName: string) => refName === 'ctgA',
177
+ },
178
+ }))
179
+ .views(self => ({
180
+ getTrackById: (id: string) => (id === 'gbz_lanes' ? track : undefined),
181
+ get assemblies() {
182
+ return []
183
+ },
184
+ get views() {
185
+ return self.view ? [self.view] : []
186
+ },
187
+ getDisplayTypeDefault() {
188
+ return undefined
189
+ },
190
+ }))
191
+ .actions(self => ({
192
+ setView(view: Instance<typeof LGV>) {
193
+ self.view = view
194
+ return view
195
+ },
196
+ addView(type: string, snapshot: Record<string, unknown>) {
197
+ self.addedViews.push([type, snapshot])
198
+ return snapshot
199
+ },
200
+ notify() {},
201
+ notifyError() {},
202
+ }))
203
+ const session = Session.create({ configuration: {} }, { pluginManager })
204
+ const view = session.setView(
205
+ LGV.create({
206
+ type: 'LinearGenomeView',
207
+ tracks: [
208
+ {
209
+ type: 'SyntenyTrack',
210
+ configuration: 'gbz_lanes',
211
+ displays: [
212
+ {
213
+ type: 'MultiWaySyntenyDisplay',
214
+ configuration: 'gbz_lanes-MultiWaySyntenyDisplay',
215
+ },
216
+ ],
217
+ },
218
+ ],
219
+ }),
220
+ )
221
+ view.setWidth(800)
222
+ view.setDisplayedRegions([
223
+ { refName: 'ctgA', start: 0, end: 1000, assemblyName: 'volvox' },
224
+ ])
225
+ const display = view.tracks[0]!.displays[0]!
226
+ return { session, view, display }
227
+ }
228
+
229
+ function allLabels(items: MenuItem[]): string[] {
230
+ return items.flatMap(item => [
231
+ ...('label' in item ? [item.label] : []),
232
+ ...('subMenu' in item && Array.isArray(item.subMenu)
233
+ ? allLabels(item.subMenu)
234
+ : []),
235
+ ])
236
+ }
237
+
238
+ function launchRegion(view: { menuItems: () => MenuItem[] }) {
239
+ const launch = view
240
+ .menuItems()
241
+ .find(i => 'label' in i && i.label === LAUNCH_LABEL)
242
+ const item =
243
+ launch && 'subMenu' in launch
244
+ ? launch.subMenu.find(i => 'label' in i && i.label === LABEL)
245
+ : undefined
246
+ if (item && 'onClick' in item) {
247
+ item.onClick(undefined)
248
+ } else {
249
+ throw new Error(`no ${LABEL} in the view menu`)
250
+ }
251
+ }
252
+
253
+ // pangenome_hprc_part3 sends readers to the linear view's menu for this, since
254
+ // the lane track's own menu is core's and carries no graph launch.
255
+ test("the GBZ cut is on the linear view's menu, not the lane track's", () => {
256
+ const { view, display } = createEnv()
257
+ expect(allLabels(view.menuItems())).toContain(LABEL)
258
+ expect(allLabels(display.trackMenuItems())).not.toContain(LABEL)
259
+ })
260
+
261
+ test("a launch cuts for the lanes the reader picked over the track's lanes", () => {
262
+ const { session, view, display } = createEnv(['HG00097.1', 'HG00099.1'])
263
+ display.setSelectedLanes(['HG00128.1'])
264
+ launchRegion(view)
265
+ expect(session.addedViews[0]![1].subgraphHaplotypes).toEqual(['HG00128.1'])
266
+ })
267
+
268
+ test('a launch cuts for the pick where the track names no lanes', () => {
269
+ const { session, view, display } = createEnv()
270
+ display.setSelectedLanes(['HG00128.1'])
271
+ launchRegion(view)
272
+ expect(session.addedViews[0]![1].subgraphHaplotypes).toEqual(['HG00128.1'])
273
+ })
274
+
275
+ test('a lane hidden on the track is left out of the cut', () => {
276
+ const { session, view, display } = createEnv(['HG00097.1', 'HG00099.1'])
277
+ display.hideLane('HG00097.1')
278
+ launchRegion(view)
279
+ expect(session.addedViews[0]![1].subgraphHaplotypes).toEqual(['HG00099.1'])
280
+ })
281
+
282
+ test("with no pick a launch cuts for the track's lanes", () => {
283
+ const { session, view } = createEnv(['HG00097.1', 'HG00099.1'])
284
+ launchRegion(view)
285
+ expect(session.addedViews[0]![1].subgraphHaplotypes).toEqual([
286
+ 'HG00097.1',
287
+ 'HG00099.1',
288
+ ])
289
+ })
@@ -0,0 +1,73 @@
1
+ import BubbleChartIcon from '@mui/icons-material/BubbleChart'
2
+
3
+ import { launchSubgraphView, subgraphRegionProblem } from './launchSubgraphView'
4
+ import { offReferenceProblem } from './subgraphTracks'
5
+
6
+ import type {
7
+ SubgraphLaunchSession,
8
+ SubgraphRegion,
9
+ } from './launchSubgraphView'
10
+ import type { SubgraphTrack } from './subgraphTracks'
11
+ import type { MenuItem } from '@jbrowse/core/ui'
12
+
13
+ // Menu items that cut `region` out of each graph track that can supply it.
14
+ //
15
+ // One capable track is the common case and gets a single flat item — a submenu
16
+ // of one is a needless extra click. Several become a submenu naming each track,
17
+ // since which graph the subgraph comes from is then a real choice.
18
+ //
19
+ // A region a track can't be cut at, past the cap or off the graph's reference,
20
+ // yields a *disabled* item rather than none: an item that vanishes teaches the
21
+ // user nothing, while one greyed out with the reason in its tooltip says what
22
+ // to do about it.
23
+ export function subgraphMenuItems({
24
+ label,
25
+ region,
26
+ tracks,
27
+ session,
28
+ connectedViewId,
29
+ }: {
30
+ label: string
31
+ region: SubgraphRegion | undefined
32
+ tracks: SubgraphTrack[]
33
+ session: SubgraphLaunchSession
34
+ connectedViewId?: string
35
+ }): MenuItem[] {
36
+ let items: MenuItem[] = []
37
+ if (region && tracks.length > 0) {
38
+ const sizeProblem = subgraphRegionProblem(region)
39
+ const entry = (track: SubgraphTrack) => {
40
+ const problem =
41
+ offReferenceProblem(track.referenceAssembly, region.assemblyName) ??
42
+ sizeProblem
43
+ return {
44
+ disabled: problem !== undefined,
45
+ disabledHelpText: problem,
46
+ onClick: () => {
47
+ launchSubgraphView({
48
+ session,
49
+ region,
50
+ trackId: track.trackId,
51
+ connectedViewId,
52
+ haplotypes: track.haplotypes,
53
+ })
54
+ },
55
+ }
56
+ }
57
+ items =
58
+ tracks.length === 1
59
+ ? [{ label, icon: BubbleChartIcon, ...entry(tracks[0]!) }]
60
+ : [
61
+ {
62
+ label,
63
+ icon: BubbleChartIcon,
64
+ type: 'subMenu',
65
+ subMenu: tracks.map(track => ({
66
+ label: track.name,
67
+ ...entry(track),
68
+ })),
69
+ },
70
+ ]
71
+ }
72
+ return items
73
+ }