jbrowse-plugin-graphgenomeviewer 7.1.0 → 7.1.2

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 (233) hide show
  1. package/README.md +17 -8
  2. package/dist/chunks/AddTrackWorkflow-RPH4ZFSX.js +2 -0
  3. package/dist/chunks/{AddTrackWorkflow-Q6RY23LF.js.map → AddTrackWorkflow-RPH4ZFSX.js.map} +2 -2
  4. package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js → ChooseSamplesDialog-JRN4YLBY.js} +2 -2
  5. package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js → ChooseWalksDialog-UXLFZTZJ.js} +2 -2
  6. package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js +2 -0
  7. package/dist/chunks/GbzBaseSyntenyAdapter-LRECKL74.js.map +7 -0
  8. package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js → GraphGenomeView-NJ5XCREB.js} +2 -2
  9. package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js +6 -0
  10. package/dist/chunks/GraphTrackSettingsDialog-47PQ6LM7.js.map +7 -0
  11. package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js → HighlightColorDialog-UK5VDDV6.js} +2 -2
  12. package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js → LinearGraphDisplay-TXTKAACH.js} +2 -2
  13. package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js +2 -0
  14. package/dist/chunks/MinigraphBubbleAdapter-HW7XQMAI.js.map +7 -0
  15. package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js +3 -0
  16. package/dist/chunks/RgfaTabixAdapter-PSKYWD2X.js.map +7 -0
  17. package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js → TrackWarningsDialog-TRYBOIHX.js} +2 -2
  18. package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js +2 -0
  19. package/dist/chunks/WalkTabixSyntenyAdapter-U53JLYVU.js.map +7 -0
  20. package/dist/chunks/chunk-2MCOY3YG.js +3 -0
  21. package/dist/chunks/chunk-2MCOY3YG.js.map +7 -0
  22. package/dist/chunks/{chunk-EWFJBQDV.js → chunk-5O6YRJHL.js} +2 -2
  23. package/dist/chunks/{chunk-EWFJBQDV.js.map → chunk-5O6YRJHL.js.map} +1 -1
  24. package/dist/chunks/chunk-BUY2PV7D.js +2 -0
  25. package/dist/chunks/chunk-BUY2PV7D.js.map +7 -0
  26. package/dist/chunks/chunk-DVCZ3BOC.js +2 -0
  27. package/dist/chunks/chunk-DVCZ3BOC.js.map +7 -0
  28. package/dist/chunks/{chunk-SAMJE6GH.js → chunk-EGDLEQ47.js} +3 -3
  29. package/dist/chunks/{chunk-SAMJE6GH.js.map → chunk-EGDLEQ47.js.map} +4 -4
  30. package/dist/chunks/chunk-FHZXW7IG.js +2 -0
  31. package/dist/chunks/chunk-FHZXW7IG.js.map +7 -0
  32. package/dist/chunks/{chunk-DZ4OUKQV.js → chunk-FITPB5X6.js} +2 -2
  33. package/dist/chunks/{chunk-DZ4OUKQV.js.map → chunk-FITPB5X6.js.map} +1 -1
  34. package/dist/chunks/chunk-FVK2THE4.js +2 -0
  35. package/dist/chunks/chunk-FVK2THE4.js.map +7 -0
  36. package/dist/chunks/{chunk-AHFQHYJI.js → chunk-IBWORBGY.js} +2 -2
  37. package/dist/chunks/chunk-MND7GAOG.js +2 -0
  38. package/dist/chunks/chunk-MND7GAOG.js.map +7 -0
  39. package/dist/chunks/chunk-MURN3JPK.js +2 -0
  40. package/dist/chunks/chunk-MURN3JPK.js.map +7 -0
  41. package/dist/chunks/{chunk-3Q4RBDCL.js → chunk-N6PQZSAX.js} +1 -1
  42. package/dist/chunks/{chunk-3Q4RBDCL.js.map → chunk-N6PQZSAX.js.map} +1 -1
  43. package/dist/chunks/chunk-NWJYLP6Z.js +4 -0
  44. package/dist/chunks/chunk-NWJYLP6Z.js.map +7 -0
  45. package/dist/chunks/{chunk-YJHYBDYF.js → chunk-S75W27NT.js} +2 -2
  46. package/dist/chunks/{chunk-YJHYBDYF.js.map → chunk-S75W27NT.js.map} +1 -1
  47. package/dist/chunks/{chunk-IXQK7JAW.js → chunk-T4HOC6O3.js} +1 -1
  48. package/dist/chunks/{chunk-IXQK7JAW.js.map → chunk-T4HOC6O3.js.map} +1 -1
  49. package/dist/chunks/{chunk-AU73K7LX.js → chunk-TUKJRVBS.js} +1 -1
  50. package/dist/chunks/{chunk-AU73K7LX.js.map → chunk-TUKJRVBS.js.map} +1 -1
  51. package/dist/chunks/chunk-UGCNPRKC.js +2 -0
  52. package/dist/chunks/{chunk-E3WYW7Z6.js.map → chunk-UGCNPRKC.js.map} +2 -2
  53. package/dist/chunks/{chunk-YSTMDXXX.js → chunk-VJ3SO4CH.js} +1 -1
  54. package/dist/chunks/{chunk-YSTMDXXX.js.map → chunk-VJ3SO4CH.js.map} +1 -1
  55. package/dist/chunks/{chunk-XT766CZ5.js → chunk-W6EQLQ6H.js} +1 -1
  56. package/dist/chunks/{chunk-XT766CZ5.js.map → chunk-W6EQLQ6H.js.map} +1 -1
  57. package/dist/chunks/chunk-WAMBI7OX.js +2 -0
  58. package/dist/chunks/chunk-WAMBI7OX.js.map +7 -0
  59. package/dist/chunks/chunk-WCGVUZWP.js +2 -0
  60. package/dist/chunks/chunk-WCGVUZWP.js.map +7 -0
  61. package/dist/chunks/chunk-WEGWTC52.js +2 -0
  62. package/dist/chunks/chunk-WEGWTC52.js.map +7 -0
  63. package/dist/chunks/chunk-YU7T5WOQ.js +2 -0
  64. package/dist/chunks/{chunk-6OPB7FOJ.js.map → chunk-YU7T5WOQ.js.map} +2 -2
  65. package/dist/chunks/{chunk-446RWIA3.js → chunk-ZMOMFU2B.js} +1 -1
  66. package/dist/chunks/{chunk-446RWIA3.js.map → chunk-ZMOMFU2B.js.map} +1 -1
  67. package/dist/chunks/renderSvg-KSQI5DH6.js +2 -0
  68. package/dist/chunks/renderSvg-KSQI5DH6.js.map +7 -0
  69. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js +4 -4
  70. package/dist/jbrowse-plugin-graphgenomeviewer.esm.js.map +4 -4
  71. package/package.json +8 -9
  72. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.test.ts +12 -10
  73. package/src/GbzBaseSyntenyAdapter/GbzBaseSyntenyAdapter.ts +31 -39
  74. package/src/GbzBaseSyntenyAdapter/configSchema.ts +5 -29
  75. package/src/GetGraphHaplotypes.ts +6 -15
  76. package/src/GetGraphReads.ts +7 -14
  77. package/src/GetSubgraph.ts +11 -20
  78. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.test.tsx +73 -0
  79. package/src/GraphAddTrackWorkflow/AddTrackWorkflow.tsx +1 -0
  80. package/src/GraphAddTrackWorkflow/buildTrackConfig.test.ts +3 -2
  81. package/src/GraphAddTrackWorkflow/buildTrackConfig.ts +2 -11
  82. package/src/GraphAddTrackWorkflow/graphManifest.ts +15 -3
  83. package/src/GraphGenomeView/components/BubbleHalos.tsx +5 -24
  84. package/src/GraphGenomeView/components/ElTree.tsx +6 -1
  85. package/src/GraphGenomeView/components/FacetPanels.test.tsx +65 -0
  86. package/src/GraphGenomeView/components/FacetPanels.tsx +17 -12
  87. package/src/GraphGenomeView/components/GenePins.tsx +5 -22
  88. package/src/GraphGenomeView/components/GraphCanvas.tsx +40 -234
  89. package/src/GraphGenomeView/components/GraphSettingsDialog.tsx +102 -108
  90. package/src/GraphGenomeView/components/GraphToolbar.tsx +5 -6
  91. package/src/GraphGenomeView/components/HoverLayer.tsx +3 -7
  92. package/src/GraphGenomeView/components/HoverTooltip.test.tsx +39 -0
  93. package/src/GraphGenomeView/components/HoverTooltip.tsx +122 -0
  94. package/src/GraphGenomeView/components/LabelChip.tsx +3 -5
  95. package/src/GraphGenomeView/components/LabelLayer.tsx +4 -17
  96. package/src/GraphGenomeView/components/LabelledSelect.tsx +1 -1
  97. package/src/GraphGenomeView/components/Legend.tsx +14 -23
  98. package/src/GraphGenomeView/components/PaneContextMenu.test.tsx +48 -0
  99. package/src/GraphGenomeView/components/PaneContextMenu.tsx +144 -0
  100. package/src/GraphGenomeView/components/PaneLayer.tsx +91 -0
  101. package/src/GraphGenomeView/components/PaneSvg.tsx +32 -50
  102. package/src/GraphGenomeView/components/ReferenceStripOverlay.test.tsx +48 -0
  103. package/src/GraphGenomeView/components/ReferenceStripOverlay.tsx +13 -42
  104. package/src/GraphGenomeView/components/SettingsMenu.test.tsx +29 -0
  105. package/src/GraphGenomeView/components/SettingsMenu.tsx +56 -65
  106. package/src/GraphGenomeView/components/TubeMapOverlay.tsx +15 -46
  107. package/src/GraphGenomeView/components/WalkRowsOverlay.tsx +58 -132
  108. package/src/GraphGenomeView/components/WalkStrip.tsx +17 -20
  109. package/src/GraphGenomeView/components/WalkStripLocator.tsx +8 -15
  110. package/src/GraphGenomeView/components/usePaneGestures.test.tsx +35 -0
  111. package/src/GraphGenomeView/components/usePaneGestures.ts +88 -91
  112. package/src/GraphGenomeView/genes/geneFeatures.ts +27 -45
  113. package/src/GraphGenomeView/graphLayers.ts +4 -4
  114. package/src/GraphGenomeView/host.ts +1 -1
  115. package/src/GraphGenomeView/legendSections.test.ts +23 -1
  116. package/src/GraphGenomeView/legendSections.ts +68 -74
  117. package/src/GraphGenomeView/model.test.ts +373 -39
  118. package/src/GraphGenomeView/model.ts +67 -11
  119. package/src/GraphGenomeView/pane/fitViews.ts +57 -514
  120. package/src/GraphGenomeView/pane/graphViews.ts +219 -160
  121. package/src/GraphGenomeView/pane/hostViews.ts +2 -10
  122. package/src/GraphGenomeView/pane/keepInView.ts +5 -4
  123. package/src/GraphGenomeView/pane/launchActions.ts +117 -139
  124. package/src/GraphGenomeView/pane/launchViews.ts +8 -27
  125. package/src/GraphGenomeView/pane/loadActions.ts +208 -187
  126. package/src/GraphGenomeView/pane/morph.ts +3 -9
  127. package/src/GraphGenomeView/pane/paneBase.ts +22 -11
  128. package/src/GraphGenomeView/pane/renderingActions.ts +65 -11
  129. package/src/GraphGenomeView/pane/settingActions.ts +17 -42
  130. package/src/GraphGenomeView/pane/tubeMapViews.ts +445 -0
  131. package/src/GraphGenomeView/pane/walkRowViews.ts +29 -37
  132. package/src/GraphGenomeView/renderPipeline.test.ts +1 -1
  133. package/src/GraphGenomeView/repeats/repeatFeatures.test.ts +23 -0
  134. package/src/GraphGenomeView/repeats/repeatFeatures.ts +16 -23
  135. package/src/GraphGenomeView/repeats/walkCalls.test.ts +18 -0
  136. package/src/GraphGenomeView/repeats/walkCalls.ts +46 -38
  137. package/src/GraphGenomeView/subgraphLoad.test.ts +67 -3
  138. package/src/GraphGenomeView/trackFeatures.ts +24 -0
  139. package/src/GraphGenomeView/tubeMapPanels.ts +28 -36
  140. package/src/GraphGenomeView/viewModel.ts +8 -56
  141. package/src/GraphGenomeView/walkRowGroups.ts +104 -30
  142. package/src/GraphGenomeView/walkRowSpans.test.ts +6 -0
  143. package/src/GraphGenomeView/walkRowSpans.ts +22 -31
  144. package/src/GraphGenomeView/walkRowsCut.ts +8 -17
  145. package/src/LinearGraphDisplay/LinearGraphDisplay.test.ts +173 -26
  146. package/src/LinearGraphDisplay/components/SubgraphContextSelect.test.tsx +1 -1
  147. package/src/LinearGraphDisplay/components/SubgraphContextSelect.tsx +3 -3
  148. package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.test.tsx +3 -3
  149. package/src/LinearGraphDisplay/components/SubgraphHaplotypesField.tsx +7 -10
  150. package/src/LinearGraphDisplay/configSchema.ts +0 -3
  151. package/src/LinearGraphDisplay/denseWindow.ts +8 -11
  152. package/src/LinearGraphDisplay/model.ts +89 -82
  153. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.test.ts +21 -0
  154. package/src/MinigraphBubbleAdapter/MinigraphBubbleAdapter.ts +6 -14
  155. package/src/MinigraphBubbleAdapter/configSchema.ts +3 -9
  156. package/src/MinigraphBubbleAdapter/index.ts +1 -2
  157. package/src/RgfaTabixAdapter/RgfaTabixAdapter.test.ts +45 -1
  158. package/src/RgfaTabixAdapter/RgfaTabixAdapter.ts +111 -276
  159. package/src/RgfaTabixAdapter/configSchema.ts +15 -49
  160. package/src/RgfaTabixAdapter/index.ts +1 -2
  161. package/src/RgfaTabixAdapter/rgfaBed.ts +25 -15
  162. package/src/RgfaTabixAdapter/rgfaCut.ts +139 -0
  163. package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz +0 -0
  164. package/src/RgfaTabixAdapter/test_data/walks2_twopart.GRCh38.walks.bed.gz.tbi +0 -0
  165. package/src/RgfaTabixAdapter/walkFiles.test.ts +33 -0
  166. package/src/RgfaTabixAdapter/walkReader.ts +103 -27
  167. package/src/RgfaTabixAdapter/walkRowRuns.test.ts +53 -0
  168. package/src/RgfaTabixAdapter/walkRowRuns.ts +42 -24
  169. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.test.ts +37 -6
  170. package/src/WalkTabixSyntenyAdapter/WalkTabixSyntenyAdapter.ts +23 -45
  171. package/src/WalkTabixSyntenyAdapter/configSchema.ts +11 -24
  172. package/src/WalkTabixSyntenyAdapter/index.ts +1 -2
  173. package/src/WalkTabixSyntenyAdapter/walkLanes.ts +35 -43
  174. package/src/adapterImplementing.ts +27 -0
  175. package/src/graphTrackDefaults/index.ts +14 -20
  176. package/src/hoverSync/lgvHover.ts +2 -4
  177. package/src/launchFromGraph/contributors.ts +1 -5
  178. package/src/launchFromGraph/graphMenuItems.ts +3 -4
  179. package/src/launchFromGraph/launchFromGraph.test.ts +0 -1
  180. package/src/launchFromGraph/launchFromGraph.ts +8 -13
  181. package/src/panSNTabix.test.ts +1 -26
  182. package/src/panSNTabix.ts +54 -51
  183. package/src/synteny/lanePairs.ts +8 -0
  184. package/src/synteny/matchFeature.ts +52 -0
  185. package/src/tabixRange.ts +13 -10
  186. package/src/version.ts +1 -1
  187. package/dist/chunks/AddTrackWorkflow-Q6RY23LF.js +0 -2
  188. package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js +0 -2
  189. package/dist/chunks/GbzBaseSyntenyAdapter-TIXR4Y3J.js.map +0 -7
  190. package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js +0 -6
  191. package/dist/chunks/GraphTrackSettingsDialog-TAM6PMXD.js.map +0 -7
  192. package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js +0 -2
  193. package/dist/chunks/MinigraphBubbleAdapter-AGI2UYQR.js.map +0 -7
  194. package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js +0 -3
  195. package/dist/chunks/RgfaTabixAdapter-2OL5X3HF.js.map +0 -7
  196. package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js +0 -2
  197. package/dist/chunks/WalkTabixSyntenyAdapter-UKFHCFOF.js.map +0 -7
  198. package/dist/chunks/chunk-5SFGHDQA.js +0 -2
  199. package/dist/chunks/chunk-5SFGHDQA.js.map +0 -7
  200. package/dist/chunks/chunk-6OPB7FOJ.js +0 -2
  201. package/dist/chunks/chunk-AMFIBQVR.js +0 -2
  202. package/dist/chunks/chunk-AMFIBQVR.js.map +0 -7
  203. package/dist/chunks/chunk-BJMRBVG3.js +0 -2
  204. package/dist/chunks/chunk-BJMRBVG3.js.map +0 -7
  205. package/dist/chunks/chunk-E3WYW7Z6.js +0 -2
  206. package/dist/chunks/chunk-EHCF6AXH.js +0 -2
  207. package/dist/chunks/chunk-EHCF6AXH.js.map +0 -7
  208. package/dist/chunks/chunk-GOFS7BXK.js +0 -3
  209. package/dist/chunks/chunk-GOFS7BXK.js.map +0 -7
  210. package/dist/chunks/chunk-IJQTSJGT.js +0 -4
  211. package/dist/chunks/chunk-IJQTSJGT.js.map +0 -7
  212. package/dist/chunks/chunk-J6NZIAZF.js +0 -2
  213. package/dist/chunks/chunk-J6NZIAZF.js.map +0 -7
  214. package/dist/chunks/chunk-LUTMKRXY.js +0 -2
  215. package/dist/chunks/chunk-LUTMKRXY.js.map +0 -7
  216. package/dist/chunks/chunk-MHPJB22Z.js +0 -2
  217. package/dist/chunks/chunk-MHPJB22Z.js.map +0 -7
  218. package/dist/chunks/chunk-TXI43PEN.js +0 -2
  219. package/dist/chunks/chunk-TXI43PEN.js.map +0 -7
  220. package/dist/chunks/chunk-WRH2EJKX.js +0 -2
  221. package/dist/chunks/chunk-WRH2EJKX.js.map +0 -7
  222. package/dist/chunks/chunk-WX26KTZM.js +0 -2
  223. package/dist/chunks/chunk-WX26KTZM.js.map +0 -7
  224. package/dist/chunks/renderSvg-ST2GBBOC.js +0 -2
  225. package/dist/chunks/renderSvg-ST2GBBOC.js.map +0 -7
  226. package/src/GraphGenomeView/components/WalkRowContextMenu.tsx +0 -105
  227. /package/dist/chunks/{ChooseSamplesDialog-BNVYBGJQ.js.map → ChooseSamplesDialog-JRN4YLBY.js.map} +0 -0
  228. /package/dist/chunks/{ChooseWalksDialog-EN3C6AQA.js.map → ChooseWalksDialog-UXLFZTZJ.js.map} +0 -0
  229. /package/dist/chunks/{GraphGenomeView-Z2WZKEEO.js.map → GraphGenomeView-NJ5XCREB.js.map} +0 -0
  230. /package/dist/chunks/{HighlightColorDialog-EYBKFSJU.js.map → HighlightColorDialog-UK5VDDV6.js.map} +0 -0
  231. /package/dist/chunks/{LinearGraphDisplay-WIBVWRQE.js.map → LinearGraphDisplay-TXTKAACH.js.map} +0 -0
  232. /package/dist/chunks/{TrackWarningsDialog-QT4YEXGD.js.map → TrackWarningsDialog-TRYBOIHX.js.map} +0 -0
  233. /package/dist/chunks/{chunk-AHFQHYJI.js.map → chunk-IBWORBGY.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { sameBubble } from '@jbrowse/bandage-core/bubbles/bubbleLine'
2
2
  import { bubbleSegmentIds } from '@jbrowse/bandage-core/bubbles/classifyBubble'
3
3
  import { foldVariants } from '@jbrowse/bandage-core/foldVariants'
4
+ import { regionLabel } from '@jbrowse/bandage-core/hoverText'
4
5
  import {
5
6
  clipToWindow,
6
7
  trimToWindow,
@@ -32,7 +33,6 @@ import {
32
33
  import { withSettingActions } from './settingActions'
33
34
  import { namesReads } from '../../GetGraphReads'
34
35
  import { walkRowsOf } from '../../RgfaTabixAdapter/walkRowRuns.ts'
35
- import { locLabel } from '../../launchFromGraph/contributors'
36
36
  import { geneModelsFrom } from '../genes/geneFeatures'
37
37
  import { repeatArraysFrom } from '../repeats/repeatFeatures'
38
38
  import {
@@ -54,7 +54,19 @@ import type { Graph, LayoutResult } from '@jbrowse/bandage-core/types'
54
54
  import type { Feature } from '@jbrowse/core/util'
55
55
  import type { FileLocation } from '@jbrowse/core/util/types'
56
56
 
57
- export const withLoadActions = withSettingActions.actions(self => {
57
+ const withEngineSettings = withSettingActions.views(self => ({
58
+ get engineSettings() {
59
+ return engineSettingsOf({
60
+ engine: self.layoutEngine,
61
+ quality: self.layoutQuality,
62
+ linearLayout: self.linearLayout,
63
+ bubbleSpread: self.bubbleSpread,
64
+ showDeletionEdges: self.showDeletionEdges,
65
+ })
66
+ },
67
+ }))
68
+
69
+ export const withLoadActions = withEngineSettings.actions(self => {
58
70
  let loadController: AbortController | undefined
59
71
  // the samples TSV location the table was read from, or is being read from
60
72
  let sampleTableKey: string | undefined
@@ -78,16 +90,6 @@ export const withLoadActions = withSettingActions.actions(self => {
78
90
  }) as Promise<{ result: LayoutResult; duration: number }>
79
91
  }
80
92
 
81
- function engineSettings() {
82
- return engineSettingsOf({
83
- engine: self.layoutEngine,
84
- quality: self.layoutQuality,
85
- linearLayout: self.linearLayout,
86
- bubbleSpread: self.bubbleSpread,
87
- showDeletionEdges: self.showDeletionEdges,
88
- })
89
- }
90
-
91
93
  // Single dispatch point for every layout mode. A mode that returns a
92
94
  // result computed it locally; one that returns undefined can't draw this
93
95
  // graph and hands off to the remote FMMM engine, which is also how
@@ -145,7 +147,7 @@ export const withLoadActions = withSettingActions.actions(self => {
145
147
  mode: self.chosenLayoutMode,
146
148
  }
147
149
  }
148
- const settings = engineSettings()
150
+ const settings = self.engineSettings
149
151
  const hit = forceLayouts.ready(graph, settings)
150
152
  if (hit) {
151
153
  return {
@@ -165,9 +167,9 @@ export const withLoadActions = withSettingActions.actions(self => {
165
167
  }
166
168
 
167
169
  // Which layout request is the live one. A layout is async and nothing in
168
- // the UI waits for it, so several are routinely in flight: every control
169
- // in the settings dialog fires `recomputeLayout` on change, and a force
170
- // layout takes seconds where an anchored one is instant.
170
+ // the UI waits for it, so several are routinely in flight: every engine
171
+ // setting lays the graph out again on change, and a force layout takes
172
+ // seconds where an anchored one is instant.
171
173
  //
172
174
  // The graph identity below is not enough to order them, because the
173
175
  // competing requests are usually layouts of the SAME graph. Left to
@@ -177,6 +179,7 @@ export const withLoadActions = withSettingActions.actions(self => {
177
179
  // one over the top of the chosen one seconds later, with the dropdown
178
180
  // still naming the choice that was discarded.
179
181
  let liveRequest = 0
182
+ let liveRequestPending = false
180
183
  // the error the last failed layout raised, which the next one to land clears
181
184
  let layoutError: unknown
182
185
  // the graph the layout on screen was computed for
@@ -184,11 +187,20 @@ export const withLoadActions = withSettingActions.actions(self => {
184
187
  // a haplotype's genes by track and contig span
185
188
  const walkGeneCache = new Map<string, GeneModel[]>()
186
189
 
190
+ function dropLayoutOfReplacedGraph() {
191
+ if (self.graph !== laidOutGraph) {
192
+ laidOutGraph = undefined
193
+ self.layoutResult = undefined
194
+ self.drawnLayoutMode = undefined
195
+ }
196
+ }
197
+
187
198
  // Applied under a guard because a layout is async and the user can load a
188
199
  // different graph, or ask for a different layout of it, while one is in
189
200
  // flight; the stale result must not land.
190
201
  function* layoutInto(graph: Graph) {
191
202
  const request = ++liveRequest
203
+ liveRequestPending = true
192
204
  awaitedRun = undefined
193
205
  const isLive = () => self.graph === graph && request === liveRequest
194
206
  const signal = loadController?.signal
@@ -211,7 +223,13 @@ export const withLoadActions = withSettingActions.actions(self => {
211
223
  return false
212
224
  }
213
225
  layoutError = e
226
+ dropLayoutOfReplacedGraph()
214
227
  throw e
228
+ } finally {
229
+ if (request === liveRequest) {
230
+ liveRequestPending = false
231
+ settle()
232
+ }
215
233
  }
216
234
  const live = isLive()
217
235
  if (live) {
@@ -220,12 +238,7 @@ export const withLoadActions = withSettingActions.actions(self => {
220
238
  }
221
239
  layoutError = undefined
222
240
  const previous = self.layoutResult
223
- const before = {
224
- scaleX: self.scaleX,
225
- scaleY: self.scaleY,
226
- translateX: self.translateX,
227
- translateY: self.translateY,
228
- }
241
+ const before = self.screenTransform
229
242
  laidOutGraph = graph
230
243
  self.layoutResult = computed.result
231
244
  self.drawnLayoutMode = computed.mode
@@ -276,6 +289,33 @@ export const withLoadActions = withSettingActions.actions(self => {
276
289
  return parsed
277
290
  }
278
291
 
292
+ // Replaces the graph on screen and the cut it came from, and drops the
293
+ // annotations read beside the last graph. It drops the hover and the
294
+ // selection too, because hoveredEdge is an index into graph.edges and
295
+ // hoveredNode/selectedNode are ids, which would point the tooltip and the
296
+ // highlight at whatever sits there in the new graph.
297
+ function resetCut(cut?: {
298
+ graph: Graph
299
+ walkCut: WalkCut | undefined
300
+ region: SubgraphRegion | undefined
301
+ loadedReferencePath: string | undefined
302
+ foldedBelowBp: number | undefined
303
+ readsShown: { shown: number; total: number } | undefined
304
+ }) {
305
+ self.graph = cut?.graph
306
+ self.walkCut = cut?.walkCut
307
+ self.graphRegion = cut?.region
308
+ self.loadedReferencePath = cut?.loadedReferencePath
309
+ self.foldedBelowBp = cut?.foldedBelowBp
310
+ self.readsShown = cut?.readsShown
311
+ self.indexBubbles = undefined
312
+ self.geneTrackFeatures = undefined
313
+ self.walkGeneFeatures = undefined
314
+ walkGeneCache.clear()
315
+ self.repeatArrays = undefined
316
+ self.clearInteractionState()
317
+ }
318
+
279
319
  // `keepSelection` for a re-cut of the same source: node ids survive one
280
320
  // where edge indexes do not, so the selection is found again by id.
281
321
  // `readsOf` fetches the reads over the parsed graph before its one
@@ -291,7 +331,6 @@ export const withLoadActions = withSettingActions.actions(self => {
291
331
  foldBelowBp = 0,
292
332
  ) {
293
333
  const signal = loadController?.signal
294
- self.setStatusMessage('Parsing GFA')
295
334
  const walkCut =
296
335
  typeof source !== 'string' && source.walkRowRuns ? source : undefined
297
336
  const parsed = parseCut(source, name, region)
@@ -328,27 +367,19 @@ export const withLoadActions = withSettingActions.actions(self => {
328
367
  }
329
368
  }
330
369
  const selected = keepSelection ? self.selectedNode : null
331
- self.graph = graph
332
- self.foldedBelowBp = folded === anchored ? undefined : foldBelowBp
333
- self.readsShown = readsShown
334
- self.walkCut = walkCut
335
- self.graphRegion = region
336
- self.loadedReferencePath = loaded
337
- self.indexBubbles = undefined
338
- self.geneTrackFeatures = undefined
339
- self.walkGeneFeatures = undefined
340
- walkGeneCache.clear()
341
- self.repeatArrays = undefined
370
+ resetCut({
371
+ graph,
372
+ walkCut,
373
+ region,
374
+ loadedReferencePath: loaded,
375
+ foldedBelowBp: folded === anchored ? undefined : foldBelowBp,
376
+ readsShown,
377
+ })
342
378
  // a re-cut keeps the bubbles still in it open
343
379
  const names = new Set(graph.nodes.map(n => n.name))
344
380
  self.openBubbles = self.openBubbles.filter(b =>
345
381
  bubbleSegmentIds(b).some(id => names.has(id)),
346
382
  )
347
- // hoveredEdge is an index into graph.edges and hoveredNode/selectedNode
348
- // are ids, so all three address the graph being replaced here. Carrying
349
- // them over points the tooltip and the highlight at whatever now happens
350
- // to sit at that index.
351
- self.clearInteractionState()
352
383
  // against the new graph's own nodes: nodeById reads through the layout,
353
384
  // which is still the last graph's
354
385
  if (selected !== null && graph.nodes.some(n => n.id === selected)) {
@@ -378,14 +409,64 @@ export const withLoadActions = withSettingActions.actions(self => {
378
409
  }
379
410
  }
380
411
 
412
+ // The spinner stays up while a load or the layout last asked for is in
413
+ // flight. Clearing it sooner reports "done" while the layout the user
414
+ // asked for is still computing, the common case when a cheap choice
415
+ // follows an expensive one.
416
+ function settle() {
417
+ if (loadInFlight === undefined && !liveRequestPending) {
418
+ self.finishLoading()
419
+ }
420
+ }
421
+
381
422
  function endLoad() {
382
423
  loadInFlight = undefined
383
- self.finishLoading()
424
+ settle()
384
425
  }
385
426
 
386
- function layoutSettled() {
387
- if (loadInFlight === undefined) {
388
- self.finishLoading()
427
+ // Runs `body` as a load. It raises the spinner and clears the error first,
428
+ // and while it is the live load it reports a failure and ends the spinner.
429
+ function* runLoad(
430
+ label: string,
431
+ body: (load: {
432
+ isLive: () => boolean
433
+ signal: AbortSignal
434
+ }) => Generator<PromiseLike<unknown>, void, unknown>,
435
+ ) {
436
+ const load = beginLoad()
437
+ self.isLoading = true
438
+ self.error = undefined
439
+ try {
440
+ yield* body(load)
441
+ } catch (e) {
442
+ if (load.isLive()) {
443
+ console.error(`[GraphGenomeView.${label}]`, e)
444
+ self.error = e
445
+ }
446
+ } finally {
447
+ if (load.isLive()) {
448
+ endLoad()
449
+ }
450
+ }
451
+ }
452
+
453
+ // Features over the cut. A failed read warns and returns undefined, so an
454
+ // annotation never fails the load.
455
+ function* readFeatures(
456
+ adapterConfig: Record<string, unknown>,
457
+ region: SubgraphRegion,
458
+ what: string,
459
+ signal?: AbortSignal,
460
+ ) {
461
+ try {
462
+ return (yield getSession(self).rpcManager.call(
463
+ getRpcSessionId(self),
464
+ 'CoreGetFeatures',
465
+ { adapterConfig, regions: [region], signal },
466
+ )) as Feature[]
467
+ } catch (e) {
468
+ console.warn(`[GraphGenomeView] no ${what} for this graph`, e)
469
+ return undefined
389
470
  }
390
471
  }
391
472
 
@@ -408,81 +489,63 @@ export const withLoadActions = withSettingActions.actions(self => {
408
489
  if (adapterConfig.type !== 'RgfaTabixAdapter' || prefix === undefined) {
409
490
  return
410
491
  }
411
- try {
412
- const features = (yield getSession(self).rpcManager.call(
413
- getRpcSessionId(self),
414
- 'CoreGetFeatures',
415
- {
416
- adapterConfig: {
417
- type: 'MinigraphBubbleAdapter',
418
- uri: `${prefix}.bubbles.bed.gz`,
419
- baseUri: adapterConfig.baseUri,
420
- assemblyNameToPanSN: adapterConfig.assemblyNameToPanSN,
421
- },
422
- regions: [region],
423
- signal,
424
- },
425
- )) as Feature[]
426
- if (isLive()) {
427
- const bubbles = features.flatMap(f => {
428
- const start = f.get('start')
429
- const end = f.get('end')
430
- const longestAlleleLength = f.get('longestAlleleLength') as number
431
- return Math.max(end - start, longestAlleleLength) < foldBelowBp
432
- ? []
433
- : [
434
- {
435
- refName: region.refName,
436
- start,
437
- end,
438
- segmentCount: f.get('segmentCount') as number,
439
- pathCount: (f.get('pathCount') as number | undefined) ?? 0,
440
- inversion: f.get('inversion') as boolean,
441
- shortestAlleleLength: f.get('shortestAlleleLength') as number,
442
- longestAlleleLength,
443
- segments: f.get('segments') as string,
444
- shortestAllele: undefined,
445
- longestAllele: undefined,
446
- },
447
- ]
448
- })
449
- self.indexBubbles = bubbles
450
- }
451
- } catch (e) {
452
- console.warn('[GraphGenomeView] no bubble index for this graph', e)
492
+ const features = yield* readFeatures(
493
+ {
494
+ type: 'MinigraphBubbleAdapter',
495
+ uri: `${prefix}.bubbles.bed.gz`,
496
+ baseUri: adapterConfig.baseUri,
497
+ assemblyNameToPanSN: adapterConfig.assemblyNameToPanSN,
498
+ },
499
+ region,
500
+ 'bubble index',
501
+ signal,
502
+ )
503
+ if (features && isLive()) {
504
+ self.indexBubbles = features.flatMap(f => {
505
+ const start = f.get('start')
506
+ const end = f.get('end')
507
+ const longestAlleleLength = f.get('longestAlleleLength') as number
508
+ return Math.max(end - start, longestAlleleLength) < foldBelowBp
509
+ ? []
510
+ : [
511
+ {
512
+ refName: region.refName,
513
+ start,
514
+ end,
515
+ segmentCount: f.get('segmentCount') as number,
516
+ pathCount: (f.get('pathCount') as number | undefined) ?? 0,
517
+ inversion: f.get('inversion') as boolean,
518
+ shortestAlleleLength: f.get('shortestAlleleLength') as number,
519
+ longestAlleleLength,
520
+ segments: f.get('segments') as string,
521
+ shortestAllele: undefined,
522
+ longestAllele: undefined,
523
+ },
524
+ ]
525
+ })
453
526
  }
454
527
  }
455
528
 
456
- // A session track's features over the cut. Undefined when the session
457
- // has no such track or the read fails: an annotation is never the
458
- // graph's problem.
529
+ // A session track's features over the cut, undefined when the session has
530
+ // no such track
459
531
  function* trackFeatures(
460
532
  trackId: string | undefined,
461
533
  region: SubgraphRegion,
462
534
  what: string,
463
535
  signal?: AbortSignal,
464
536
  ) {
465
- const session = getSession(self)
466
537
  const config = trackId
467
- ? session.tracks.find(t => t.trackId === trackId)
538
+ ? getSession(self).tracks.find(t => t.trackId === trackId)
468
539
  : undefined
469
540
  if (!config) {
470
541
  return undefined
471
542
  }
472
- try {
473
- return (yield session.rpcManager.call(
474
- getRpcSessionId(self),
475
- 'CoreGetFeatures',
476
- {
477
- adapterConfig: readConfObject(config, 'adapter'),
478
- regions: [region],
479
- signal,
480
- },
481
- )) as Feature[]
482
- } catch (e) {
483
- console.warn(`[GraphGenomeView] no ${what} for this graph`, e)
484
- return undefined
485
- }
543
+ return yield* readFeatures(
544
+ readConfObject(config, 'adapter'),
545
+ region,
546
+ what,
547
+ signal,
548
+ )
486
549
  }
487
550
 
488
551
  // The annotation tracks read over the cut: genes, which `geneFeatures`
@@ -504,6 +567,8 @@ export const withLoadActions = withSettingActions.actions(self => {
504
567
  }
505
568
  type Annotation = keyof typeof annotations
506
569
 
570
+ // Lands only while the track it read is still the one chosen, so a slow
571
+ // read cannot land over the pick that followed it
507
572
  function* loadAnnotation(
508
573
  kind: Annotation,
509
574
  region: SubgraphRegion,
@@ -511,58 +576,55 @@ export const withLoadActions = withSettingActions.actions(self => {
511
576
  signal?: AbortSignal,
512
577
  ) {
513
578
  const { trackId, set } = annotations[kind]
514
- const features = yield* trackFeatures(trackId(), region, kind, signal)
515
- if (features && isLive()) {
579
+ const track = trackId()
580
+ const features = yield* trackFeatures(track, region, kind, signal)
581
+ if (features && isLive() && trackId() === track) {
516
582
  set(features)
517
583
  }
518
584
  }
519
585
 
520
- // One annotation read again alone, for a track change. Live only while
521
- // the track it read is still the one chosen, so a slow read cannot land
522
- // over the pick that followed it.
586
+ // The annotation reads over a cut, each landing as it arrives
587
+ function annotationReads(
588
+ region: SubgraphRegion,
589
+ isLive: () => boolean,
590
+ signal: AbortSignal,
591
+ ) {
592
+ return (['genes', 'repeats'] as const).map(kind =>
593
+ flow(loadAnnotation)(kind, region, isLive, signal),
594
+ )
595
+ }
596
+
597
+ // One annotation read again alone, for a track change
523
598
  function* reloadAnnotation(kind: Annotation) {
524
- const { trackId, set } = annotations[kind]
525
599
  const region = self.graphRegion
526
- const track = trackId()
527
600
  if (region) {
528
- set(undefined)
529
- yield* loadAnnotation(
530
- kind,
531
- region,
532
- () => self.graphRegion === region && trackId() === track,
533
- )
601
+ annotations[kind].set(undefined)
602
+ yield* loadAnnotation(kind, region, () => self.graphRegion === region)
534
603
  }
535
604
  }
536
605
 
537
606
  // Raises `isLoading` before any fetch, so a view waiting on a remote file
538
607
  // shows its loading state instead of the import form. Text already in
539
608
  // hand is parsed without yielding first. `region` is the window a
540
- // declared file was stated beside, which the parse anchors on.
609
+ // declared file was stated beside, which the parse anchors on and the
610
+ // annotations are read over.
541
611
  function* loadWholeGFA(
542
612
  name: string,
543
613
  source: string | ((signal: AbortSignal) => Promise<string>),
544
614
  region?: SubgraphRegion,
545
615
  ) {
546
- const { isLive, signal } = beginLoad()
547
- self.isLoading = true
548
- self.error = undefined
549
- try {
616
+ yield* runLoad('loadWholeGFA', function* ({ isLive, signal }) {
550
617
  const text =
551
618
  typeof source === 'string' ? source : ((yield source(signal)) as string)
552
- if (isLive()) {
553
- yield* parseAndLayout(text, name, region)
554
- }
555
- } catch (e) {
556
- if (isLive()) {
557
- console.error('[GraphGenomeView.loadWholeGFA]', e)
558
- self.error = e
619
+ if (!isLive()) {
620
+ return
559
621
  }
560
- } finally {
561
- if (isLive()) {
562
- endLoad()
622
+ yield* parseAndLayout(text, name, region)
623
+ if (region && isLive()) {
624
+ self.setStatusMessage('Reading annotations')
625
+ yield Promise.all(annotationReads(region, isLive, signal))
563
626
  }
564
- }
565
- return isLive()
627
+ })
566
628
  }
567
629
 
568
630
  function abortLoad() {
@@ -571,6 +633,7 @@ export const withLoadActions = withSettingActions.actions(self => {
571
633
  loadInFlight = undefined
572
634
  liveLoad++
573
635
  liveRequest++
636
+ liveRequestPending = false
574
637
  awaitedRun = undefined
575
638
  }
576
639
 
@@ -592,27 +655,18 @@ export const withLoadActions = withSettingActions.actions(self => {
592
655
  // would otherwise land its graph afterwards
593
656
  function dropGraph() {
594
657
  abortLoad()
595
- self.graph = undefined
596
- self.walkCut = undefined
597
- self.graphRegion = undefined
598
- self.loadedReferencePath = undefined
658
+ resetCut()
659
+ laidOutGraph = undefined
660
+ layoutError = undefined
599
661
  self.layoutResult = undefined
600
662
  self.drawnLayoutMode = undefined
601
- self.indexBubbles = undefined
602
- self.foldedBelowBp = undefined
603
- self.geneTrackFeatures = undefined
604
- self.walkGeneFeatures = undefined
605
- walkGeneCache.clear()
606
- self.repeatArrays = undefined
607
663
  self.walkRowSampleTable = undefined
608
664
  sampleTableKey = undefined
609
- self.readsShown = undefined
610
665
  self.openBubbles = []
611
666
  self.error = undefined
612
667
  self.isLoading = false
613
668
  self.loadCanceled = false
614
669
  self.statusMessage = ''
615
- self.clearInteractionState()
616
670
  self.clearPerfMetrics()
617
671
  // the next graph has nothing to do with where the user left this one
618
672
  if (self.viewportOwner === 'user') {
@@ -636,11 +690,7 @@ export const withLoadActions = withSettingActions.actions(self => {
636
690
  // The user's stop, leaving whatever is drawn under it
637
691
  stopLoad() {
638
692
  abortLoad()
639
- // a graph that landed ahead of its layout has nothing of its own drawn
640
- if (self.graph !== laidOutGraph) {
641
- self.layoutResult = undefined
642
- self.drawnLayoutMode = undefined
643
- }
693
+ dropLayoutOfReplacedGraph()
644
694
  self.isLoading = false
645
695
  self.statusMessage = ''
646
696
  self.loadCanceled = true
@@ -656,18 +706,11 @@ export const withLoadActions = withSettingActions.actions(self => {
656
706
  region?: SubgraphRegion,
657
707
  ) {
658
708
  self.setStatusMessage('Fetching GFA')
659
- const live = yield* loadWholeGFA(
709
+ yield* loadWholeGFA(
660
710
  fileName(location) || 'GFA',
661
711
  signal => openLocation(location).readFile({ encoding: 'utf8', signal }),
662
712
  region,
663
713
  )
664
- if (region && live && self.graph) {
665
- const isLive = () => self.graphRegion === region
666
- yield Promise.all([
667
- flow(loadAnnotation)('genes', region, isLive),
668
- flow(loadAnnotation)('repeats', region, isLive),
669
- ])
670
- }
671
714
  }),
672
715
  // One cut of a region, laid out with the annotations over it. Whether
673
716
  // the region may be cut is the caller's call; overlapping cuts are
@@ -678,11 +721,8 @@ export const withLoadActions = withSettingActions.actions(self => {
678
721
  opts: SubgraphCutOptions = {},
679
722
  foldBelowBp = 0,
680
723
  ) {
681
- const { isLive, signal } = beginLoad()
682
- self.isLoading = true
683
- self.error = undefined
684
- self.setStatusMessage('Fetching subgraph')
685
- try {
724
+ yield* runLoad('cutSubgraph', function* ({ isLive, signal }) {
725
+ self.setStatusMessage('Fetching subgraph')
686
726
  const fetchStart = performance.now()
687
727
  const cut = (yield getSession(self).rpcManager.call(
688
728
  getRpcSessionId(self),
@@ -700,7 +740,7 @@ export const withLoadActions = withSettingActions.actions(self => {
700
740
  }
701
741
  yield* parseAndLayout(
702
742
  cut,
703
- locLabel(region),
743
+ regionLabel(region),
704
744
  region,
705
745
  true,
706
746
  namesReads(adapterConfig)
@@ -734,19 +774,9 @@ export const withLoadActions = withSettingActions.actions(self => {
734
774
  signal,
735
775
  self.foldedBelowBp,
736
776
  ),
737
- flow(loadAnnotation)('genes', region, isLive, signal),
738
- flow(loadAnnotation)('repeats', region, isLive, signal),
777
+ ...annotationReads(region, isLive, signal),
739
778
  ])
740
- } catch (e) {
741
- if (isLive()) {
742
- console.error('[GraphGenomeView.cutSubgraph]', e)
743
- self.error = e
744
- }
745
- } finally {
746
- if (isLive()) {
747
- endLoad()
748
- }
749
- }
779
+ })
750
780
  }),
751
781
  // A track change re-reads that annotation alone. The graph, its layout
752
782
  // and any open bubble stay as they are.
@@ -754,7 +784,6 @@ export const withLoadActions = withSettingActions.actions(self => {
754
784
  yield* reloadAnnotation('repeats')
755
785
  }),
756
786
  reloadGenes: flow(function* () {
757
- walkGeneCache.clear()
758
787
  yield* reloadAnnotation('genes')
759
788
  }),
760
789
  // The source track's samples TSV, read once per location, for walk
@@ -866,19 +895,11 @@ export const withLoadActions = withSettingActions.actions(self => {
866
895
  }
867
896
  self.isLoading = true
868
897
  self.setStatusMessage('Computing layout')
869
-
870
898
  try {
871
- // A superseded request leaves the spinner to the request that
872
- // replaced it: clearing it here reports "done" while the layout the
873
- // user actually asked for is still being computed, which is the
874
- // common case when a cheap choice follows an expensive one.
875
- if (yield* layoutInto(graph)) {
876
- layoutSettled()
877
- }
899
+ yield* layoutInto(graph)
878
900
  } catch (e) {
879
901
  console.error('[GraphGenomeView.recomputeLayout]', e)
880
902
  self.error = e
881
- layoutSettled()
882
903
  }
883
904
  }),
884
905
  }
@@ -1,6 +1,7 @@
1
1
  import { fadeAbgr } from '@jbrowse/bandage-core/renderer/colorBits'
2
2
  import { strandSides } from '@jbrowse/bandage-core/util/geometry'
3
3
 
4
+ import type { ScreenTransform } from './keepInView'
4
5
  import type { RenderBatch, Run } from '@jbrowse/bandage-core/renderer/types'
5
6
  import type { Graph, NodeSegment } from '@jbrowse/bandage-core/types'
6
7
  import type { EdgeSides } from '@jbrowse/bandage-core/util/geometry'
@@ -14,13 +15,6 @@ interface Attachment {
14
15
  side: EdgeSides['from']
15
16
  }
16
17
 
17
- export interface PaneTransform {
18
- scaleX: number
19
- scaleY: number
20
- translateX: number
21
- translateY: number
22
- }
23
-
24
18
  // Morphs above this many nodes snap instead: each frame rebuilds the
25
19
  // geometry, which past it no longer fits a frame (GRAPH_SCALE_AND_LOD.md)
26
20
  export const MORPH_MAX_NODES = 3000
@@ -84,9 +78,9 @@ const RELAX_TOLERANCE_PX = 0.1
84
78
  // end's point count, so a frame is a pointwise blend.
85
79
  export function morphStarts(
86
80
  prev: Positions,
87
- prevTransform: PaneTransform,
81
+ prevTransform: ScreenTransform,
88
82
  next: Positions,
89
- nextTransform: PaneTransform,
83
+ nextTransform: ScreenTransform,
90
84
  edges: Graph['edges'],
91
85
  ): Positions {
92
86
  const toNext = (p: NodeSegment) => ({