@jbrowse/plugin-linear-genome-view 5.0.0-beta.2 → 5.0.0-beta.20

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 (177) hide show
  1. package/esm/LinearGenomeView/afterAttach.d.ts +22 -5
  2. package/esm/LinearGenomeView/afterAttach.js +129 -106
  3. package/esm/LinearGenomeView/axisConfigSchema.d.ts +18 -0
  4. package/esm/LinearGenomeView/axisConfigSchema.js +22 -0
  5. package/esm/LinearGenomeView/components/CenterLine.js +10 -5
  6. package/esm/LinearGenomeView/components/ConnectedHoverHighlight.js +2 -3
  7. package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
  8. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +9 -4
  9. package/esm/LinearGenomeView/components/ExportSvgDialog.js +45 -8
  10. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  11. package/esm/LinearGenomeView/components/Header.js +11 -1
  12. package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
  13. package/esm/LinearGenomeView/components/HeaderPanControls.d.ts +3 -3
  14. package/esm/LinearGenomeView/components/HeaderPanControls.js +6 -3
  15. package/esm/LinearGenomeView/components/HeaderSearchBoxes.js +4 -1
  16. package/esm/LinearGenomeView/components/HeaderZoomControls.js +6 -35
  17. package/esm/LinearGenomeView/components/Highlight.js +27 -11
  18. package/esm/LinearGenomeView/components/HighlightBand.d.ts +11 -2
  19. package/esm/LinearGenomeView/components/HighlightBand.js +32 -13
  20. package/esm/LinearGenomeView/components/HighlightChip.d.ts +3 -3
  21. package/esm/LinearGenomeView/components/HighlightChip.js +13 -8
  22. package/esm/LinearGenomeView/components/HighlightLabelDialog.d.ts +6 -0
  23. package/esm/LinearGenomeView/components/HighlightLabelDialog.js +18 -0
  24. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  25. package/esm/LinearGenomeView/components/ImportForm.js +7 -9
  26. package/esm/LinearGenomeView/components/LinearGenomeView.js +5 -12
  27. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +13 -14
  28. package/esm/LinearGenomeView/components/MiniControls.js +11 -5
  29. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  30. package/esm/LinearGenomeView/components/OverviewHighlightBand.d.ts +3 -2
  31. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +6 -5
  32. package/esm/LinearGenomeView/components/OverviewRubberband.js +25 -48
  33. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  34. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  35. package/esm/LinearGenomeView/components/PaddingBlocks.js +3 -8
  36. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  37. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.d.ts +3 -2
  38. package/esm/LinearGenomeView/components/RegionWidthEditorDialog.js +4 -4
  39. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  40. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +74 -0
  41. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  42. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  43. package/esm/LinearGenomeView/components/SVGHighlightBand.d.ts +10 -1
  44. package/esm/LinearGenomeView/components/SVGHighlightBand.js +16 -5
  45. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  46. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  47. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +51 -36
  48. package/esm/LinearGenomeView/components/SearchBox.js +10 -16
  49. package/esm/LinearGenomeView/components/SearchPanelForm.d.ts +3 -2
  50. package/esm/LinearGenomeView/components/SearchPanelForm.js +4 -2
  51. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +4 -8
  52. package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
  53. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +4 -7
  54. package/esm/LinearGenomeView/components/SearchResultsTable.js +25 -53
  55. package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
  56. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  57. package/esm/LinearGenomeView/components/StrandCheckboxes.d.ts +3 -2
  58. package/esm/LinearGenomeView/components/StrandCheckboxes.js +4 -2
  59. package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
  60. package/esm/LinearGenomeView/components/TrackContainer.js +63 -10
  61. package/esm/LinearGenomeView/components/TrackLabel.js +14 -4
  62. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  63. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +4 -5
  64. package/esm/LinearGenomeView/components/TrackLabelMenu.js +11 -14
  65. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
  66. package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
  67. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +6 -4
  68. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +12 -24
  69. package/esm/LinearGenomeView/components/TracksContainer.js +11 -23
  70. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  71. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  72. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  73. package/esm/LinearGenomeView/components/fetchSequence.js +15 -7
  74. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  75. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  76. package/esm/LinearGenomeView/components/useRangeSelect.js +26 -61
  77. package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +3 -1
  78. package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +4 -2
  79. package/esm/LinearGenomeView/components/useSideScroll.d.ts +17 -2
  80. package/esm/LinearGenomeView/components/useSideScroll.js +156 -102
  81. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  82. package/esm/LinearGenomeView/components/util.js +4 -2
  83. package/esm/LinearGenomeView/consts.d.ts +2 -0
  84. package/esm/LinearGenomeView/consts.js +3 -1
  85. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  86. package/esm/LinearGenomeView/containingLgv.js +23 -0
  87. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  88. package/esm/LinearGenomeView/headerFit.js +20 -7
  89. package/esm/LinearGenomeView/index.d.ts +2 -7
  90. package/esm/LinearGenomeView/index.js +5 -7
  91. package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -7
  92. package/esm/LinearGenomeView/keyboardHandler.js +61 -23
  93. package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
  94. package/esm/LinearGenomeView/launchKeys.js +11 -6
  95. package/esm/LinearGenomeView/lazyDialogs.d.ts +16 -3
  96. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  97. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  98. package/esm/LinearGenomeView/linkedViewSync.js +27 -49
  99. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  100. package/esm/LinearGenomeView/menuItems.js +264 -164
  101. package/esm/LinearGenomeView/model.d.ts +1192 -560
  102. package/esm/LinearGenomeView/model.js +805 -590
  103. package/esm/LinearGenomeView/rExport/exportLinearView.d.ts +17 -0
  104. package/esm/LinearGenomeView/rExport/exportLinearView.js +117 -0
  105. package/esm/LinearGenomeView/rExport/rScript.d.ts +110 -0
  106. package/esm/LinearGenomeView/rExport/rScript.js +524 -0
  107. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  108. package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
  109. package/esm/LinearGenomeView/showFiles.d.ts +2 -0
  110. package/esm/LinearGenomeView/showFiles.js +19 -0
  111. package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
  112. package/esm/LinearGenomeView/showRegionsWithUndo.js +13 -5
  113. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  114. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  115. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  116. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  117. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  118. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.d.ts +3 -1
  119. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +9 -9
  120. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.d.ts +3 -1
  121. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +5 -5
  122. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +13 -18
  123. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  124. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  125. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  126. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  127. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +26 -0
  128. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  129. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +21 -13
  130. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -5
  131. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +7 -6
  132. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +2 -3
  133. package/esm/LinearGenomeView/svgcomponents/SVGView.js +17 -3
  134. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +24 -20
  135. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +42 -29
  136. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +74 -77
  137. package/esm/LinearGenomeView/svgcomponents/util.d.ts +28 -12
  138. package/esm/LinearGenomeView/svgcomponents/util.js +41 -29
  139. package/esm/LinearGenomeView/types.d.ts +13 -10
  140. package/esm/LinearGenomeView/types.js +0 -1
  141. package/esm/LinearGenomeView/util.d.ts +103 -46
  142. package/esm/LinearGenomeView/util.js +222 -170
  143. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +9 -6
  144. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  145. package/esm/MultiLevelRubberband/index.js +2 -0
  146. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  147. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  148. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  149. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  150. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  151. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  152. package/esm/MultiLevelRubberband/useRangeSelect.js +45 -59
  153. package/esm/TrackModifiers/index.d.ts +83 -0
  154. package/esm/TrackModifiers/index.js +231 -0
  155. package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -68
  156. package/esm/displayKitTests/perRegionTestEnv.js +30 -26
  157. package/esm/index.d.ts +21 -99
  158. package/esm/index.js +17 -38
  159. package/esm/searchUtils.d.ts +26 -10
  160. package/esm/searchUtils.js +137 -137
  161. package/esm/shared/RubberbandSpan.d.ts +3 -2
  162. package/esm/shared/RubberbandSpan.js +6 -3
  163. package/esm/shared/coordLabels.d.ts +11 -4
  164. package/esm/shared/coordLabels.js +20 -5
  165. package/esm/shared/useWindowDrag.d.ts +18 -0
  166. package/esm/shared/useWindowDrag.js +47 -0
  167. package/package.json +12 -11
  168. package/esm/FeatureTrack/configSchema.d.ts +0 -124
  169. package/esm/FeatureTrack/configSchema.js +0 -54
  170. package/esm/FeatureTrack/index.d.ts +0 -2
  171. package/esm/FeatureTrack/index.js +0 -13
  172. package/esm/LinearGenomeView/initKeys.d.ts +0 -12
  173. package/esm/LinearGenomeView/initKeys.js +0 -68
  174. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  175. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  176. package/esm/lazyPluginExports.d.ts +0 -23
  177. package/esm/lazyPluginExports.js +0 -36
@@ -0,0 +1,52 @@
1
+ import { rowLabels } from './rowLabel.js';
2
+ /**
3
+ * One row per genome row, each holding that row's own rubberband menu — Get
4
+ * sequence, Copy range, Highlight region, and whatever a plugin contributes
5
+ * under Launch.
6
+ *
7
+ * A drag on the multi-level strip commits offsets on EVERY row (see
8
+ * `useRangeSelect`), so each row's `rubberBandMenuItems()` already describes a
9
+ * real selection and was simply never offered: the stacked views' menu held
10
+ * "Zoom to region(s)" and nothing else, while the same drag in a plain linear
11
+ * genome view offered five actions. Per row rather than applied across rows
12
+ * because each row is its own assembly — one sequence to fetch, one range to
13
+ * copy, one highlight to place — so there is no "all rows" answer to give.
14
+ *
15
+ * Zoom is the exception and stays where each view already has it: zooming every
16
+ * row at once is what the gesture means, and the synteny view holds its follow
17
+ * anchor across the loop.
18
+ */
19
+ export function multiLevelRowMenuItems(views) {
20
+ return rowLabels(views).map((label, idx) => ({
21
+ label,
22
+ subMenu: views[idx].rubberBandMenuItems(),
23
+ }));
24
+ }
25
+ /**
26
+ * The same per-row rows for a bare CLICK on the strip rather than a drag — each
27
+ * row's Center view here, Zoom to base level and Copy coordinate, at the base
28
+ * that row paints under `px`.
29
+ *
30
+ * No all-rows row above them, unlike the drag's "Zoom to region(s)". A drag
31
+ * names a span in pixels and every row can act on it; a click names a
32
+ * COORDINATE, and the rows are different assemblies, so which genome's base the
33
+ * pointer is over is the question the menu has to ask rather than answer.
34
+ *
35
+ * `px` rather than a resolved offset: each row maps the pixel through its own
36
+ * `pxToBp`, so there is no one offset to pass.
37
+ */
38
+ export function multiLevelRowClickMenuItems(views, px) {
39
+ return rowLabels(views).flatMap((label, idx) => {
40
+ const view = views[idx];
41
+ // A row with no regions is dropped, because `pxToBp` THROWS on one rather
42
+ // than answering an offset that names no base — so gating on the built
43
+ // submenu being empty, which is what this did, was a guard that could never
44
+ // fire in front of a call that had already thrown. Reachable: a row's
45
+ // `initialized` is assembly readiness, and the stack mounts this strip once
46
+ // ANY row is ready.
47
+ if (!view.displayedRegions.length) {
48
+ return [];
49
+ }
50
+ return [{ label, subMenu: view.rubberbandClickMenuItems(view.pxToBp(px)) }];
51
+ });
52
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * What to call each genome row, one label per row. The assembly name where
3
+ * there is one, and the row's position where a row is still loading, so a label
4
+ * stays true whatever the assemblies turn out to be called.
5
+ *
6
+ * A NAME THAT REPEATS PICKS UP ITS ROW NUMBER, because a stack can hold one
7
+ * assembly twice — two loci of the same genome, a read against its own
8
+ * reference — and there the bare name gives two menu rows that open different
9
+ * things under identical text. Only the repeats pay for it: the ordinary
10
+ * pairwise view still reads "hg38" and "mm39".
11
+ *
12
+ * One function over the whole list rather than one row at a time, since
13
+ * uniqueness is a property of the list. Shared by every menu that names the
14
+ * rows — the track selectors, the row menus, the follow anchors, the
15
+ * rubberband — so they cannot drift into calling the same row two different
16
+ * things one click apart. Here rather than in the synteny view because the
17
+ * breakpoint split view is the same stack of rows and had been numbering them
18
+ * itself.
19
+ */
20
+ export declare function rowLabels(views: {
21
+ assemblyNames: string[];
22
+ }[]): string[];
@@ -0,0 +1,27 @@
1
+ /**
2
+ * What to call each genome row, one label per row. The assembly name where
3
+ * there is one, and the row's position where a row is still loading, so a label
4
+ * stays true whatever the assemblies turn out to be called.
5
+ *
6
+ * A NAME THAT REPEATS PICKS UP ITS ROW NUMBER, because a stack can hold one
7
+ * assembly twice — two loci of the same genome, a read against its own
8
+ * reference — and there the bare name gives two menu rows that open different
9
+ * things under identical text. Only the repeats pay for it: the ordinary
10
+ * pairwise view still reads "hg38" and "mm39".
11
+ *
12
+ * One function over the whole list rather than one row at a time, since
13
+ * uniqueness is a property of the list. Shared by every menu that names the
14
+ * rows — the track selectors, the row menus, the follow anchors, the
15
+ * rubberband — so they cannot drift into calling the same row two different
16
+ * things one click apart. Here rather than in the synteny view because the
17
+ * breakpoint split view is the same stack of rows and had been numbering them
18
+ * itself.
19
+ */
20
+ export function rowLabels(views) {
21
+ const names = views.map((view, idx) => view.assemblyNames[0] ?? `Row ${idx + 1}`);
22
+ const counts = new Map();
23
+ for (const name of names) {
24
+ counts.set(name, (counts.get(name) ?? 0) + 1);
25
+ }
26
+ return names.map((name, idx) => counts.get(name) > 1 ? `${name} (row ${idx + 1})` : name);
27
+ }
@@ -3,4 +3,5 @@ import type { MenuItem } from '@jbrowse/core/ui';
3
3
  export interface MultiLevelRubberbandModel {
4
4
  views: LinearGenomeViewModel[];
5
5
  rubberBandMenuItems: () => MenuItem[];
6
+ rubberbandClickMenuItemsAtPx: (px: number) => MenuItem[];
6
7
  }
@@ -4,9 +4,10 @@ interface AnchorPosition {
4
4
  offsetX: number;
5
5
  clientX: number;
6
6
  clientY: number;
7
+ isClick: boolean;
7
8
  }
8
9
  export declare function useRangeSelect(ref: React.RefObject<HTMLDivElement | null>, model: MultiLevelRubberbandModel): {
9
- open: boolean;
10
+ isClick: boolean | undefined;
10
11
  rubberbandOn: boolean;
11
12
  guideX: number | undefined;
12
13
  anchorPosition: AnchorPosition | undefined;
@@ -1,6 +1,7 @@
1
- import { useCallback, useEffect, useState } from 'react';
1
+ import { useCallback, useState } from 'react';
2
2
  import { getRelativeX } from '@jbrowse/core/util/getRelativeX';
3
3
  import { transaction } from 'mobx';
4
+ import { useWindowDrag } from '../shared/useWindowDrag.js';
4
5
  export function useRangeSelect(ref, model) {
5
6
  const [startX, setStartX] = useState();
6
7
  const [currentX, setCurrentX] = useState(0);
@@ -18,8 +19,15 @@ export function useRangeSelect(ref, model) {
18
19
  });
19
20
  }, [model]);
20
21
  // releases the committed offsets too, since mouseOut no longer does while the
21
- // menu is open — closing the menu (button, Escape, or a bare click) is the
22
- // point the selection is actually finished with
22
+ // menu is open — dismissing the menu, by its backdrop or Escape, is the point
23
+ // the selection is actually finished with. A bare click on the strip no longer
24
+ // comes through here: it opens the click menu instead.
25
+ //
26
+ // `CascadingMenu` dismisses BEFORE it runs the clicked row's callback, so this
27
+ // clears the offsets out from under it. Every menu built here captures them
28
+ // when the rows are built, which is why that works — the plain linear genome
29
+ // view's host never clears, so a row written to read them live works there
30
+ // and silently no-ops from a stacked row.
23
31
  const handleClose = useCallback(() => {
24
32
  setAnchorPosition(undefined);
25
33
  setStartX(undefined);
@@ -27,71 +35,48 @@ export function useRangeSelect(ref, model) {
27
35
  setGuideX(undefined);
28
36
  clearOffsets();
29
37
  }, [clearOffsets]);
30
- useEffect(() => {
31
- const el = ref.current;
32
- if (!mouseDragging || !el) {
33
- return;
34
- }
35
- // the container's left edge is fixed for the duration of a drag, so measure
36
- // it once here rather than calling getBoundingClientRect on every mousemove
37
- const { left } = el.getBoundingClientRect();
38
- function globalMouseMove(event) {
39
- setCurrentX(event.clientX - left);
40
- }
41
- function globalMouseUp(event) {
42
- if (startX === undefined) {
38
+ useWindowDrag(ref, mouseDragging ? startX : undefined, {
39
+ onMove: setCurrentX,
40
+ onEnd: ({ startX, endX, isClick, clientX, clientY }) => {
41
+ setAnchorPosition({ offsetX: endX, clientX, clientY, isClick });
42
+ if (isClick) {
43
+ // the click menu names a coordinate, not a span, so nothing is
44
+ // committed; the guide stays at the click so the menu says where
45
+ setGuideX(endX);
43
46
  return;
44
47
  }
45
- const offsetX = event.clientX - left;
46
- if (Math.abs(offsetX - startX) <= 3) {
47
- handleClose();
48
- return;
49
- }
50
- setAnchorPosition({
51
- offsetX,
52
- clientX: event.clientX,
53
- clientY: event.clientY,
54
- });
55
- const leftPx = Math.min(startX, offsetX);
56
- const rightPx = Math.max(startX, offsetX);
48
+ const leftPx = Math.min(startX, endX);
49
+ const rightPx = Math.max(startX, endX);
57
50
  transaction(() => {
58
51
  for (const view of model.views) {
59
52
  view.setOffsets(view.pxToBp(leftPx), view.pxToBp(rightPx));
60
53
  }
61
54
  });
62
55
  setGuideX(undefined);
63
- }
64
- function globalKeyDown(event) {
65
- if (event.key === 'Escape') {
66
- handleClose();
67
- }
68
- }
69
- window.addEventListener('mousemove', globalMouseMove);
70
- window.addEventListener('mouseup', globalMouseUp);
71
- window.addEventListener('keydown', globalKeyDown);
72
- return () => {
73
- window.removeEventListener('mousemove', globalMouseMove);
74
- window.removeEventListener('mouseup', globalMouseUp);
75
- window.removeEventListener('keydown', globalKeyDown);
76
- };
77
- }, [startX, mouseDragging, model, ref, handleClose]);
56
+ },
57
+ onCancel: handleClose,
58
+ });
78
59
  function mouseDown(event) {
79
- event.preventDefault();
80
- event.stopPropagation();
81
- // clear any leftover menu/selection so a fresh drag isn't blocked by a
82
- // stale anchorPosition keeping mouseDragging false
83
- setAnchorPosition(undefined);
84
- // the guide is what the pointer was showing up to now, and mouseMove won't
85
- // touch it during the drag — leaving it set keeps the selection span from
86
- // ever rendering, since the guide wins that either/or
87
- setGuideX(undefined);
88
- const relativeX = getRelativeX(event, ref.current);
89
- setStartX(relativeX);
90
- setCurrentX(relativeX);
60
+ if (event.button === 0) {
61
+ event.preventDefault();
62
+ event.stopPropagation();
63
+ // clear any leftover menu/selection so a fresh drag isn't blocked by a
64
+ // stale anchorPosition keeping mouseDragging false
65
+ setAnchorPosition(undefined);
66
+ // the guide is what the pointer was showing up to now, and mouseMove
67
+ // won't touch it during the drag — leaving it set keeps the selection
68
+ // span from ever rendering, since the guide wins that either/or
69
+ setGuideX(undefined);
70
+ const relativeX = getRelativeX(event, ref.current);
71
+ setStartX(relativeX);
72
+ setCurrentX(relativeX);
73
+ }
91
74
  }
92
75
  function mouseMove(event) {
93
- // Don't update guideX while the menu is open (rubberband locked) or while a
94
- // drag-selection is in progress (the rubberband should show, not the guide)
76
+ // Don't update guideX while a menu is open — the guide is what the click
77
+ // menu's rows refer to, so it stays at the click rather than following the
78
+ // pointer to the menu — or while a drag-selection is in progress (the
79
+ // rubberband should show, not the guide)
95
80
  if (anchorPosition || mouseDragging) {
96
81
  return;
97
82
  }
@@ -112,13 +97,14 @@ export function useRangeSelect(ref, model) {
112
97
  callback();
113
98
  handleClose();
114
99
  }
115
- const rubberbandOn = startX !== undefined;
100
+ const isClick = anchorPosition?.isClick;
101
+ const rubberbandOn = startX !== undefined && !isClick;
116
102
  const right = anchorPosition?.offsetX ?? currentX;
117
103
  const left = rubberbandOn ? Math.min(right, startX) : 0;
118
104
  const width = rubberbandOn ? Math.abs(right - startX) : 0;
119
105
  const views = rubberbandOn ? model.views : [];
120
106
  return {
121
- open: Boolean(anchorPosition),
107
+ isClick,
122
108
  rubberbandOn,
123
109
  guideX,
124
110
  anchorPosition,
@@ -0,0 +1,83 @@
1
+ import type { FieldPredicate } from '@jbrowse/core/util/fieldPredicate';
2
+ import type { TrackInit } from '@jbrowse/core/util/tracks';
3
+ import type { DisplaySettings } from '@jbrowse/launch-args/displaySettings';
4
+ import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
5
+ /** The linear view a command line's tracks open in, as these helpers read it. */
6
+ export interface ModifierView {
7
+ centerLineInfo?: {
8
+ refName?: string;
9
+ offset: number;
10
+ start: number;
11
+ end: number;
12
+ reversed?: boolean;
13
+ oob?: boolean;
14
+ };
15
+ launchTrack: (trackId: string, trackSnapshot?: object, displaySnapshot?: object) => Promise<{
16
+ displays: unknown[];
17
+ } | undefined>;
18
+ showTrack: (trackId: string, trackSnapshot?: object, displaySnapshot?: object) => unknown;
19
+ tracks: {
20
+ configuration: {
21
+ trackId: string;
22
+ };
23
+ displays: unknown[];
24
+ activeDisplay: {
25
+ configuration: Record<string, unknown>;
26
+ };
27
+ }[];
28
+ }
29
+ /**
30
+ * `displayTypeOf` for a track the session holds, undefined for one it does
31
+ * not; `displayType` is the display the caller opens it as when no
32
+ * `display:` modifier names one.
33
+ */
34
+ export declare function openedDisplayType(session: {
35
+ getTrackById: (trackId: string) => unknown;
36
+ }, trackId: string, modifiers: string[], displayType?: string): string | undefined;
37
+ /**
38
+ * Write the `filter` of `trackId`'s `settings` onto its display open in
39
+ * `view`, by field over the configured filter, as the track menu would.
40
+ * Returns the display's filter as written, which reopens the track as drawn,
41
+ * or undefined when the settings write no filter.
42
+ */
43
+ export declare function writeFilter(view: Pick<ModifierView, 'tracks' | 'showTrack'>, trackId: string, settings: DisplaySettings): FieldPredicate[] | undefined;
44
+ /**
45
+ * @deprecated `writeFilter`, which every display category takes. This one
46
+ * writes an alignments `display`'s filter only.
47
+ */
48
+ export declare function writeReadFilter(display: unknown, displayType: string | undefined, settings: DisplaySettings): FieldPredicate[] | undefined;
49
+ interface LaunchOptions {
50
+ /** the track entry's own display settings, which the modifiers write over */
51
+ displaySnapshot?: Record<string, unknown>;
52
+ trackSnapshot?: Record<string, unknown>;
53
+ /** changes the settings after the center-line sort resolves, before the open */
54
+ adjust?: (settings: DisplaySettings) => void;
55
+ warn?: (message: string) => void;
56
+ }
57
+ /**
58
+ * Open `trackId` in `view` with its command-line modifiers applied, and return
59
+ * the session-spec track entry that reopens it as drawn, or undefined when the
60
+ * open fails, which `launchTrack` reports to the session.
61
+ */
62
+ export declare function launchTrackWithModifiers(view: ModifierView, trackId: string, displayType: string | undefined, modifiers: string[], { displaySnapshot, trackSnapshot, adjust, warn, }?: LaunchOptions): Promise<{
63
+ trackId: string;
64
+ filter?: FieldPredicate[] | undefined;
65
+ } | undefined>;
66
+ /**
67
+ * Open a session spec's track entry in a navigated `view`, its `modifiers`
68
+ * written over its display settings. When a modifier or the open fails, the
69
+ * session's snackbar says why and the track opens with its own settings.
70
+ */
71
+ export declare function launchTrackInit(view: ModifierView & IAnyStateTreeNode, t: TrackInit): Promise<void>;
72
+ /**
73
+ * Write a track's `memberWrites` onto what its display in `view` already has,
74
+ * after every other modifier, so a member write keeps the rest of the setting
75
+ * whatever order the command line gives them in. A display replaces a color
76
+ * or facet object whole, which is why these cannot ride in on the launch
77
+ * snapshot. A keyed object (`rows`, ADR-257) seeds only its field and the
78
+ * objects written whole that the writes reach, such as `rows.color`: seeded
79
+ * whole it would restate the arrangement a field write must drop, and
80
+ * unseeded a spec holding the patch would lose the field and the channel.
81
+ */
82
+ export declare function writeMembers(view: Pick<ModifierView, 'tracks' | 'showTrack'>, trackId: string, members: string[]): Record<string, unknown>;
83
+ export {};
@@ -0,0 +1,231 @@
1
+ import { keyedWritesAt, mergeConfSettings, writesWholeAt, } from '@jbrowse/core/configuration';
2
+ import { getEnv, getSession } from '@jbrowse/core/util';
3
+ import { basePaintedAt } from '@jbrowse/core/util/Base1DUtils';
4
+ import { predicatesOf, testsSomething, withFields, } from '@jbrowse/core/util/fieldPredicate';
5
+ import { normalizeTrackInit, trackDisplayType } from '@jbrowse/core/util/tracks';
6
+ import { buildDisplaySnapshot, displayCategory, memberPatch, memberWrites, mergeSettings, requestedDisplayType, } from '@jbrowse/launch-args/displaySettings';
7
+ import { getSnapshot, getType, isArrayType, isStateTreeNode, } from '@jbrowse/mobx-state-tree';
8
+ /**
9
+ * The display a linear view opens `conf` as, read off the display picker so it
10
+ * follows `display:` and the adapter rather than a list of track types.
11
+ */
12
+ function displayTypeOf(pluginManager, conf, modifiers, displayType) {
13
+ return trackDisplayType(pluginManager, conf, 'LinearGenomeView', requestedDisplayType(modifiers) ?? displayType);
14
+ }
15
+ /**
16
+ * `displayTypeOf` for a track the session holds, undefined for one it does
17
+ * not; `displayType` is the display the caller opens it as when no
18
+ * `display:` modifier names one.
19
+ */
20
+ export function openedDisplayType(session, trackId, modifiers, displayType) {
21
+ const conf = session.getTrackById(trackId);
22
+ return conf
23
+ ? displayTypeOf(getEnv(session).pluginManager, (isStateTreeNode(conf) ? getSnapshot(conf) : conf), modifiers, displayType)
24
+ : undefined;
25
+ }
26
+ const consoleWarn = (message) => {
27
+ console.warn(`Warning: ${message}`);
28
+ };
29
+ function modifierLaunch(displayType, modifiers, warn = consoleWarn) {
30
+ return {
31
+ displayType,
32
+ settings: buildDisplaySnapshot(displayType, modifiers, warn),
33
+ members: memberWrites(displayType, modifiers),
34
+ };
35
+ }
36
+ // the entries grouped by field, as `setFilterFields` takes them: a field whose
37
+ // entries are all bare is `[]`, which clears it, defaults included
38
+ function filterPatch(entries) {
39
+ const patch = {};
40
+ for (const p of entries) {
41
+ patch[p.field] = [
42
+ ...(patch[p.field] ?? []),
43
+ ...(testsSomething(p) ? [p] : []),
44
+ ];
45
+ }
46
+ return patch;
47
+ }
48
+ // the display snapshot a track opens with. `filter` stays out of it:
49
+ // `writeFilter` writes it once the track is open, since the list in a
50
+ // snapshot would replace the configured one
51
+ function launchSnapshot({ settings }) {
52
+ const { snap, displayType } = settings;
53
+ const { filter: _filter, ...unfiltered } = snap;
54
+ return {
55
+ ...unfiltered,
56
+ ...(displayType ? { type: displayType } : {}),
57
+ };
58
+ }
59
+ /**
60
+ * Write the `filter` of `trackId`'s `settings` onto its display open in
61
+ * `view`, by field over the configured filter, as the track menu would.
62
+ * Returns the display's filter as written, which reopens the track as drawn,
63
+ * or undefined when the settings write no filter.
64
+ */
65
+ export function writeFilter(view, trackId, settings) {
66
+ const { filter } = settings.snap;
67
+ const track = view.tracks.find(t => t.configuration.trackId === trackId);
68
+ if (!filter || !track) {
69
+ return undefined;
70
+ }
71
+ const patch = filterPatch(filter);
72
+ const display = track.displays[0];
73
+ if (display?.setFilterFields) {
74
+ display.setFilterFields(patch);
75
+ return display.writtenFilter;
76
+ }
77
+ const written = withFields(predicatesOf(track.activeDisplay.configuration.filter), patch);
78
+ view.showTrack(trackId, {}, { filter: written });
79
+ return written;
80
+ }
81
+ /**
82
+ * @deprecated `writeFilter`, which every display category takes. This one
83
+ * writes an alignments `display`'s filter only.
84
+ */
85
+ export function writeReadFilter(display, displayType, settings) {
86
+ const { filter } = settings.snap;
87
+ if (displayCategory(displayType) !== 'alignments' || !filter) {
88
+ return undefined;
89
+ }
90
+ const filterDisplay = display;
91
+ filterDisplay?.setFilterFields?.(filterPatch(filter));
92
+ return filterDisplay?.writtenFilter;
93
+ }
94
+ // the pivot is the base under the view's center, which a view has only once
95
+ // it shows a region
96
+ function centerSort(view, trackId, field, warn) {
97
+ const center = view.centerLineInfo;
98
+ if (center && !center.oob) {
99
+ return {
100
+ field,
101
+ pos: basePaintedAt(center, center.offset),
102
+ refName: center.refName ?? '',
103
+ };
104
+ }
105
+ else {
106
+ warn(`sort:${field} on "${trackId}" ignored — the view has no center position to sort at (pass --loc)`);
107
+ return undefined;
108
+ }
109
+ }
110
+ // The modifiers' settings written over the entry's own as a settings bag
111
+ // writes them (`mergeConfSettings`): a namespace such as `pileup` member by
112
+ // member, a bare value lifted first, and a channel such as `encoding.color`
113
+ // whole, so a `color:` modifier is not mixed into the entry's color by field.
114
+ // Plain JSON where no registered display type says which is which.
115
+ function overEntry(view, displayType, entry, over) {
116
+ const pluginManager = isStateTreeNode(view)
117
+ ? getEnv(view).pluginManager
118
+ : undefined;
119
+ return displayType && pluginManager?.displayTypes.has(displayType)
120
+ ? mergeConfSettings(pluginManager.getDisplayType(displayType).configSchema, entry, over)
121
+ : mergeSettings(structuredClone(entry), over);
122
+ }
123
+ /**
124
+ * Open `trackId` in `view` with its command-line modifiers applied, and return
125
+ * the session-spec track entry that reopens it as drawn, or undefined when the
126
+ * open fails, which `launchTrack` reports to the session.
127
+ */
128
+ export async function launchTrackWithModifiers(view, trackId, displayType, modifiers, { displaySnapshot = {}, trackSnapshot = {}, adjust, warn = consoleWarn, } = {}) {
129
+ const launch = modifierLaunch(displayType, modifiers, warn);
130
+ const { snap, sort } = launch.settings;
131
+ const at = sort && centerSort(view, trackId, sort.field, warn);
132
+ if (at) {
133
+ snap.sort = at;
134
+ }
135
+ adjust?.(launch.settings);
136
+ const launched = overEntry(view, displayType, displaySnapshot, launchSnapshot(launch));
137
+ const opened = await view.launchTrack(trackId, trackSnapshot, launched);
138
+ if (!opened) {
139
+ return undefined;
140
+ }
141
+ const drawnFilter = writeFilter(view, trackId, launch.settings);
142
+ const members = writeMembers(view, trackId, launch.members);
143
+ return {
144
+ trackId,
145
+ ...launched,
146
+ ...(drawnFilter ? { filter: drawnFilter } : {}),
147
+ ...members,
148
+ };
149
+ }
150
+ /**
151
+ * Open a session spec's track entry in a navigated `view`, its `modifiers`
152
+ * written over its display settings. When a modifier or the open fails, the
153
+ * session's snackbar says why and the track opens with its own settings.
154
+ */
155
+ export async function launchTrackInit(view, t) {
156
+ const { trackId, trackSnapshot, displaySnapshot, modifiers } = normalizeTrackInit(t);
157
+ if (modifiers.length === 0) {
158
+ await view.launchTrack(trackId, trackSnapshot, displaySnapshot);
159
+ return;
160
+ }
161
+ const session = getSession(view);
162
+ const { type } = displaySnapshot;
163
+ const notes = [];
164
+ try {
165
+ await launchTrackWithModifiers(view, trackId, openedDisplayType(session, trackId, modifiers, typeof type === 'string' ? type : undefined), modifiers, {
166
+ trackSnapshot,
167
+ displaySnapshot,
168
+ warn: message => notes.push(message),
169
+ });
170
+ }
171
+ catch (e) {
172
+ console.error(e);
173
+ session.notifyError(`"${trackId}" ${modifiers.join(' ')}: ${e}`, e);
174
+ }
175
+ // launchTrack reports its own failure, such as a display:
176
+ // the track cannot open as
177
+ if (!view.tracks.some(t => t.configuration.trackId === trackId)) {
178
+ await view.launchTrack(trackId, trackSnapshot, displaySnapshot);
179
+ }
180
+ if (notes.length) {
181
+ session.notify(notes.join('; '), 'warning');
182
+ }
183
+ }
184
+ // the named members of a keyed object as configured, undefined when it
185
+ // states none of them
186
+ function keyedSeed(member, names) {
187
+ const configured = isStateTreeNode(member)
188
+ ? getSnapshot(member)
189
+ : {};
190
+ const seed = Object.fromEntries(names.flatMap(name => configured[name] === undefined ? [] : [[name, configured[name]]]));
191
+ return Object.keys(seed).length > 0 ? seed : undefined;
192
+ }
193
+ /**
194
+ * Write a track's `memberWrites` onto what its display in `view` already has,
195
+ * after every other modifier, so a member write keeps the rest of the setting
196
+ * whatever order the command line gives them in. A display replaces a color
197
+ * or facet object whole, which is why these cannot ride in on the launch
198
+ * snapshot. A keyed object (`rows`, ADR-257) seeds only its field and the
199
+ * objects written whole that the writes reach, such as `rows.color`: seeded
200
+ * whole it would restate the arrangement a field write must drop, and
201
+ * unseeded a spec holding the patch would lose the field and the channel.
202
+ */
203
+ export function writeMembers(view, trackId, members) {
204
+ if (members.length === 0) {
205
+ return {};
206
+ }
207
+ const track = view.tracks.find(t => t.configuration.trackId === trackId);
208
+ if (!track) {
209
+ throw new Error(`"${trackId}" is not open, so ${members.join(' ')} has nothing to write to`);
210
+ }
211
+ const { configuration } = track.activeDisplay;
212
+ const configured = getSnapshot(configuration);
213
+ const type = getType(configuration);
214
+ const patch = memberPatch(members, (root, reached) => {
215
+ // a list at a default the snapshot strips, Manhattan's marks, still seeds
216
+ // the write, so `marks.0.mark=bar` edits the mark drawn
217
+ const member = configuration[root];
218
+ const keyed = keyedWritesAt(type, [root]);
219
+ return keyed
220
+ ? keyedSeed(member, [
221
+ keyed.keyedBy,
222
+ ...reached.filter(m => writesWholeAt(type, [root, m])),
223
+ ])
224
+ : (configured[root] ??
225
+ (isStateTreeNode(member) && isArrayType(getType(member))
226
+ ? getSnapshot(member)
227
+ : undefined));
228
+ });
229
+ view.showTrack(trackId, {}, patch);
230
+ return patch;
231
+ }