@jbrowse/plugin-linear-genome-view 5.0.0-beta.1 → 5.0.0-beta.11

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 (148) hide show
  1. package/esm/FeatureTrack/configSchema.d.ts +142 -50
  2. package/esm/LaunchLinearGenomeView/index.js +2 -13
  3. package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
  4. package/esm/LinearGenomeView/afterAttach.js +75 -62
  5. package/esm/LinearGenomeView/components/CenterLine.js +5 -3
  6. package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
  7. package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +3 -2
  8. package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
  9. package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
  10. package/esm/LinearGenomeView/components/Header.js +11 -1
  11. package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
  12. package/esm/LinearGenomeView/components/HeaderPanControls.js +2 -1
  13. package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
  14. package/esm/LinearGenomeView/components/Highlight.js +7 -7
  15. package/esm/LinearGenomeView/components/HighlightBand.d.ts +9 -1
  16. package/esm/LinearGenomeView/components/HighlightBand.js +15 -3
  17. package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
  18. package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
  19. package/esm/LinearGenomeView/components/HoverPositionHighlight.js +3 -3
  20. package/esm/LinearGenomeView/components/ImportForm.js +2 -2
  21. package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
  22. package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
  23. package/esm/LinearGenomeView/components/MiniControls.js +3 -2
  24. package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
  25. package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
  26. package/esm/LinearGenomeView/components/OverviewRubberband.js +20 -45
  27. package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
  28. package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
  29. package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
  30. package/esm/LinearGenomeView/components/PaddingBlocks.js +14 -9
  31. package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
  32. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
  33. package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +71 -0
  34. package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
  35. package/esm/LinearGenomeView/components/Rubberband.js +3 -2
  36. package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
  37. package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
  38. package/esm/LinearGenomeView/components/Scalebar.js +8 -5
  39. package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +49 -34
  40. package/esm/LinearGenomeView/components/SearchBox.js +8 -7
  41. package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +2 -1
  42. package/esm/LinearGenomeView/components/SearchResultsDialog.js +2 -2
  43. package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
  44. package/esm/LinearGenomeView/components/SearchResultsTable.js +12 -29
  45. package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
  46. package/esm/LinearGenomeView/components/TrackContainer.js +63 -9
  47. package/esm/LinearGenomeView/components/TrackLabel.js +10 -3
  48. package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
  49. package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
  50. package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
  51. package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +4 -1
  52. package/esm/LinearGenomeView/components/TrackRenderingContainer.js +11 -23
  53. package/esm/LinearGenomeView/components/TracksContainer.js +10 -22
  54. package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
  55. package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
  56. package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
  57. package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
  58. package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
  59. package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
  60. package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
  61. package/esm/LinearGenomeView/components/searchModes.js +1 -1
  62. package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
  63. package/esm/LinearGenomeView/components/useRangeSelect.js +20 -59
  64. package/esm/LinearGenomeView/components/useSideScroll.d.ts +16 -2
  65. package/esm/LinearGenomeView/components/useSideScroll.js +153 -103
  66. package/esm/LinearGenomeView/components/util.d.ts +4 -0
  67. package/esm/LinearGenomeView/components/util.js +4 -2
  68. package/esm/LinearGenomeView/consts.d.ts +2 -0
  69. package/esm/LinearGenomeView/consts.js +3 -1
  70. package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
  71. package/esm/LinearGenomeView/containingLgv.js +23 -0
  72. package/esm/LinearGenomeView/headerFit.d.ts +4 -2
  73. package/esm/LinearGenomeView/headerFit.js +20 -7
  74. package/esm/LinearGenomeView/index.d.ts +1 -5
  75. package/esm/LinearGenomeView/index.js +10 -7
  76. package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
  77. package/esm/LinearGenomeView/keyboardHandler.js +15 -1
  78. package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
  79. package/esm/LinearGenomeView/launchKeys.js +27 -0
  80. package/esm/LinearGenomeView/lazyDialogs.d.ts +2 -4
  81. package/esm/LinearGenomeView/lazyDialogs.js +1 -0
  82. package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
  83. package/esm/LinearGenomeView/linkedViewSync.js +27 -43
  84. package/esm/LinearGenomeView/menuItems.d.ts +16 -18
  85. package/esm/LinearGenomeView/menuItems.js +242 -151
  86. package/esm/LinearGenomeView/model.d.ts +2027 -200
  87. package/esm/LinearGenomeView/model.js +560 -395
  88. package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
  89. package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
  90. package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
  91. package/esm/LinearGenomeView/stickyChrome.js +5 -9
  92. package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
  93. package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
  94. package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
  95. package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +8 -8
  96. package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
  97. package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +7 -11
  98. package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
  99. package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
  100. package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
  101. package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
  102. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +27 -0
  103. package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
  104. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.d.ts +2 -1
  105. package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +29 -16
  106. package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -4
  107. package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +6 -5
  108. package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -1
  109. package/esm/LinearGenomeView/svgcomponents/SVGView.js +1 -1
  110. package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
  111. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.d.ts +1 -0
  112. package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
  113. package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +50 -68
  114. package/esm/LinearGenomeView/svgcomponents/util.d.ts +20 -8
  115. package/esm/LinearGenomeView/svgcomponents/util.js +50 -29
  116. package/esm/LinearGenomeView/types.d.ts +8 -11
  117. package/esm/LinearGenomeView/util.d.ts +60 -45
  118. package/esm/LinearGenomeView/util.js +131 -160
  119. package/esm/MultiLevelRubberband/MultiLevelRubberband.js +7 -4
  120. package/esm/MultiLevelRubberband/index.d.ts +2 -0
  121. package/esm/MultiLevelRubberband/index.js +2 -0
  122. package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
  123. package/esm/MultiLevelRubberband/menuItems.js +52 -0
  124. package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
  125. package/esm/MultiLevelRubberband/rowLabel.js +27 -0
  126. package/esm/MultiLevelRubberband/types.d.ts +1 -0
  127. package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
  128. package/esm/MultiLevelRubberband/useRangeSelect.js +31 -47
  129. package/esm/displayKitTests/perRegionTestEnv.d.ts +244 -86
  130. package/esm/displayKitTests/perRegionTestEnv.js +30 -29
  131. package/esm/index.d.ts +15 -85
  132. package/esm/index.js +13 -36
  133. package/esm/searchUtils.d.ts +18 -9
  134. package/esm/searchUtils.js +85 -69
  135. package/esm/shared/RubberbandSpan.js +2 -1
  136. package/esm/shared/coordLabels.d.ts +7 -0
  137. package/esm/shared/coordLabels.js +15 -1
  138. package/esm/shared/useWindowDrag.d.ts +18 -0
  139. package/esm/shared/useWindowDrag.js +45 -0
  140. package/package.json +10 -10
  141. package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
  142. package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
  143. package/esm/LinearGenomeView/initKeys.d.ts +0 -13
  144. package/esm/LinearGenomeView/initKeys.js +0 -104
  145. package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
  146. package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
  147. package/esm/lazyPluginExports.d.ts +0 -23
  148. package/esm/lazyPluginExports.js +0 -36
@@ -1,52 +1,36 @@
1
- import { addDisposer, addMiddleware, getPath } from '@jbrowse/mobx-state-tree';
2
- function applyViewAction(view, name, args) {
3
- switch (name) {
4
- case 'horizontalScroll':
5
- view.horizontalScroll(args[0]);
6
- break;
7
- case 'zoomTo':
8
- view.zoomTo(args[0]);
9
- break;
10
- case 'showTrack':
11
- view.showTrack(args[0]);
12
- break;
13
- case 'toggleTrack':
14
- view.toggleTrack(args[0]);
15
- break;
16
- case 'hideTrack':
17
- view.hideTrack(args[0]);
18
- break;
19
- case 'setTrackLabels':
20
- view.setTrackLabels(args[0]);
21
- break;
22
- case 'setShowCenterLine':
23
- view.setShowCenterLine(args[0]);
24
- break;
25
- }
1
+ import { addDisposer, addMiddleware } from '@jbrowse/mobx-state-tree';
2
+ const LINKED_ACTIONS = [
3
+ 'horizontalScroll',
4
+ 'zoomTo',
5
+ 'showTrack',
6
+ 'toggleTrack',
7
+ 'launchTrack',
8
+ 'launchToggleTrack',
9
+ 'hideTrack',
10
+ 'setTrackLabels',
11
+ 'setShowCenterLine',
12
+ ];
13
+ function isLinkedAction(name) {
14
+ return LINKED_ACTIONS.includes(name);
26
15
  }
27
16
  /**
28
- * Install a middleware that, while `linkViews` is on, replays each listed
29
- * action onto every other sub-view so panning/zooming/track-toggling stays in
30
- * sync. Used by linear-comparative-view and breakpoint-split-view.
17
+ * While `linkViews` is on, replay each pan, zoom and track toggle made on one
18
+ * sub-view onto every other, with the same arguments.
31
19
  */
32
- export function installLinkedViewSync(self, syncActions) {
33
- addDisposer(self, addMiddleware(self, (rawCall, next) => {
34
- const handler = rawCall.type === 'action' &&
35
- rawCall.id === rawCall.rootId &&
36
- self.linkViews
37
- ? syncActions.find(action => action === rawCall.name)
38
- : undefined;
39
- if (handler) {
40
- const sourcePath = getPath(rawCall.context);
41
- next(rawCall);
20
+ export function installLinkedViewSync(self) {
21
+ addDisposer(self, addMiddleware(self, (call, next) => {
22
+ next(call);
23
+ const { name, args, context } = call;
24
+ if (call.type === 'action' &&
25
+ call.id === call.rootId &&
26
+ self.linkViews &&
27
+ isLinkedAction(name)) {
42
28
  for (const view of self.views) {
43
- if (getPath(view) !== sourcePath) {
44
- applyViewAction(view, handler, rawCall.args);
29
+ if (view !== context) {
30
+ const action = view[name];
31
+ void action(...args);
45
32
  }
46
33
  }
47
34
  }
48
- else {
49
- next(rawCall);
50
- }
51
35
  }));
52
36
  }
@@ -1,29 +1,27 @@
1
1
  import type { LinearGenomeViewModel } from './model.ts';
2
2
  import type { BpOffset } from './types.ts';
3
3
  import type { MenuItem } from '@jbrowse/core/ui';
4
+ export declare function zoomMenuItems(self: LinearGenomeViewModel): MenuItem[];
5
+ export declare function buildHighlightsSubMenuItems(self: LinearGenomeViewModel): MenuItem[];
4
6
  /**
5
- * Zoom all the way out, shared by the view menu and the header's zoom menu —
6
- * one definition so the two cannot drift in label, and the view menu is the
7
- * only one of the two that survives `hideHeader`.
8
- *
9
- * The zoom menu earns it on the merits: this is the bottom of the same "Zoom
10
- * out 100x" ladder, and it is where someone already zooming looks.
11
- *
12
- * The label is the import form's button text verbatim (`ImportForm.tsx`), which
13
- * is where most people meet the phrase. No icon: the four-arrows glyph it
14
- * carried reads as "fullscreen", and nothing else names the same gesture, so a
15
- * substitute would be decoration rather than a distinction.
7
+ * Build the main view menu items. A row stacked in another view has no import
8
+ * form of its own, so only a top-level view offers the way back to one.
16
9
  */
17
- export declare function showAllRegionsMenuItem(self: LinearGenomeViewModel): MenuItem;
10
+ export declare function buildMenuItems(self: LinearGenomeViewModel): MenuItem[];
18
11
  /**
19
- * Build the main view menu items
12
+ * Open a copy of the view, tracks and their settings included, framed on the
13
+ * span between two offsets. The same copy as the view menu's "Copy view", and
14
+ * nothing links it back to the original.
20
15
  */
21
- export declare function buildMenuItems(self: LinearGenomeViewModel): MenuItem[];
16
+ export declare function openSpanInNewView(view: LinearGenomeViewModel, start?: BpOffset, end?: BpOffset): import("@jbrowse/core/util").AbstractViewModel | undefined;
22
17
  /**
23
- * Build rubberband selection menu items. `launchItems` are the plugin-supplied
24
- * things a selection can start (`rubberBandLaunchMenuItems()`); they collect
25
- * under one "Launch" submenu so the menu stays three actions plus a group
26
- * however many plugins are loaded, and vanish entirely when none apply.
18
+ * Build rubberband selection menu items. What a selection can open collects
19
+ * under one "Launch" submenu, which sorts last, below any row a plugin appends
20
+ * to the menu itself: a new linear genome view of the span first, then
21
+ * `launchItems`, the plugin-supplied ones (`rubberBandLaunchMenuItems()`).
22
+ *
23
+ * The new view is a copy of this one, so only a top-level view offers it: a row
24
+ * of a comparative view carries that view's framing, such as a hidden header.
27
25
  */
28
26
  export declare function buildRubberBandMenuItems(self: LinearGenomeViewModel, launchItems: MenuItem[]): MenuItem[];
29
27
  /**
@@ -1,41 +1,30 @@
1
- import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons';
1
+ import { Highlighter, TrackSelector as TrackSelectorIcon, } from '@jbrowse/core/ui/Icons';
2
2
  import { radioItems } from '@jbrowse/core/ui/menuItems';
3
+ import { makeShowSubMenu } from '@jbrowse/core/ui/showSubMenu';
3
4
  import { assembleLocStrings, getDialogHost, getSession, isSessionWithAddSessionTrack, toLocale, } from '@jbrowse/core/util';
4
- import { basePaintedAt } from '@jbrowse/core/util/Base1DUtils';
5
+ import { basePaintedAt, computeMoveToLayout, } from '@jbrowse/core/util/Base1DUtils';
5
6
  import { copyText } from '@jbrowse/core/util/copyText';
7
+ import { renameIds } from '@jbrowse/core/util/types/mst';
8
+ import { getSnapshot } from '@jbrowse/mobx-state-tree';
6
9
  import CenterFocusStrongIcon from '@mui/icons-material/CenterFocusStrong';
7
10
  import ContentCopyIcon from '@mui/icons-material/ContentCopy';
8
- import ExpandLessIcon from '@mui/icons-material/ExpandLess';
9
- import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
11
+ import FitScreenIcon from '@mui/icons-material/FitScreen';
10
12
  import FolderOpenIcon from '@mui/icons-material/FolderOpen';
13
+ import LabelIcon from '@mui/icons-material/Label';
11
14
  import LaunchIcon from '@mui/icons-material/Launch';
12
15
  import MenuOpenIcon from '@mui/icons-material/MenuOpen';
13
- import PaletteIcon from '@mui/icons-material/Palette';
16
+ import OpenInNewIcon from '@mui/icons-material/OpenInNew';
14
17
  import PhotoCameraIcon from '@mui/icons-material/PhotoCamera';
15
18
  import SearchIcon from '@mui/icons-material/Search';
16
19
  import SyncAltIcon from '@mui/icons-material/SyncAlt';
17
- import VisibilityIcon from '@mui/icons-material/Visibility';
18
20
  import ZoomInIcon from '@mui/icons-material/ZoomIn';
19
- import { ExportSvgDialog, GetSequenceDialog, ReturnToImportFormDialog, SequenceSearchDialog, } from './lazyDialogs.js';
21
+ import { ExportSvgDialog, GetSequenceDialog, RegionWidthEditorDialog, ReturnToImportFormDialog, SequenceSearchDialog, } from './lazyDialogs.js';
20
22
  const TRACK_LABEL_OPTIONS = [
21
23
  { value: 'overlapping', label: 'Overlapping' },
22
24
  { value: 'offset', label: 'Offset' },
23
25
  { value: 'hidden', label: 'Hidden' },
24
26
  ];
25
- /**
26
- * Zoom all the way out, shared by the view menu and the header's zoom menu —
27
- * one definition so the two cannot drift in label, and the view menu is the
28
- * only one of the two that survives `hideHeader`.
29
- *
30
- * The zoom menu earns it on the merits: this is the bottom of the same "Zoom
31
- * out 100x" ladder, and it is where someone already zooming looks.
32
- *
33
- * The label is the import form's button text verbatim (`ImportForm.tsx`), which
34
- * is where most people meet the phrase. No icon: the four-arrows glyph it
35
- * carried reads as "fullscreen", and nothing else names the same gesture, so a
36
- * substitute would be decoration rather than a distinction.
37
- */
38
- export function showAllRegionsMenuItem(self) {
27
+ function showAllRegionsMenuItem(self) {
39
28
  return {
40
29
  label: 'Show all regions in assembly',
41
30
  onClick: () => {
@@ -43,8 +32,56 @@ export function showAllRegionsMenuItem(self) {
43
32
  },
44
33
  };
45
34
  }
35
+ export function zoomMenuItems(self) {
36
+ return [
37
+ ...[10, 50, 100].map(r => ({
38
+ label: `Zoom in ${r}x`,
39
+ onClick: () => {
40
+ self.zoom(self.bpPerPx / r);
41
+ },
42
+ })),
43
+ ...[10, 50, 100].map(r => ({
44
+ label: `Zoom out ${r}x`,
45
+ onClick: () => {
46
+ self.zoom(self.bpPerPx * r);
47
+ },
48
+ })),
49
+ showAllRegionsMenuItem(self),
50
+ {
51
+ label: 'Custom zoom',
52
+ onClick: () => {
53
+ getDialogHost(self).queueDialog(handleClose => [
54
+ RegionWidthEditorDialog,
55
+ { model: self, handleClose },
56
+ ]);
57
+ },
58
+ },
59
+ ];
60
+ }
61
+ export function buildHighlightsSubMenuItems(self) {
62
+ const session = getSession(self);
63
+ return [
64
+ {
65
+ label: 'Show highlights',
66
+ type: 'checkbox',
67
+ checked: session.highlightsVisible,
68
+ onClick: () => {
69
+ session.setHighlightsVisible(!session.highlightsVisible);
70
+ },
71
+ },
72
+ {
73
+ label: 'Show highlight labels',
74
+ type: 'checkbox',
75
+ checked: session.highlightLabelsVisible,
76
+ onClick: () => {
77
+ session.setHighlightLabelsVisible(!session.highlightLabelsVisible);
78
+ },
79
+ },
80
+ ];
81
+ }
46
82
  /**
47
- * Build the main view menu items
83
+ * Build the main view menu items. A row stacked in another view has no import
84
+ * form of its own, so only a top-level view offers the way back to one.
48
85
  */
49
86
  export function buildMenuItems(self) {
50
87
  if (!self.hasDisplayedRegions) {
@@ -53,24 +90,24 @@ export function buildMenuItems(self) {
53
90
  const session = getSession(self);
54
91
  const menuItems = [
55
92
  {
56
- label: self.scalebarOnly ? 'Expand tracks' : 'Collapse to ruler',
57
- icon: self.scalebarOnly ? ExpandMoreIcon : ExpandLessIcon,
93
+ label: 'Open track selector',
58
94
  onClick: () => {
59
- self.setScalebarOnly(!self.scalebarOnly);
95
+ self.activateTrackSelector();
60
96
  },
97
+ icon: TrackSelectorIcon,
61
98
  },
62
99
  {
63
- label: 'Return to import form',
100
+ label: 'Export SVG',
101
+ icon: PhotoCameraIcon,
64
102
  onClick: () => {
65
103
  session.queueDialog(handleClose => [
66
- ReturnToImportFormDialog,
104
+ ExportSvgDialog,
67
105
  {
68
106
  model: self,
69
107
  handleClose,
70
108
  },
71
109
  ]);
72
110
  },
73
- icon: FolderOpenIcon,
74
111
  },
75
112
  ...(isSessionWithAddSessionTrack(session)
76
113
  ? [
@@ -89,30 +126,35 @@ export function buildMenuItems(self) {
89
126
  },
90
127
  ]
91
128
  : []),
92
- {
93
- label: 'Export SVG',
94
- icon: PhotoCameraIcon,
95
- onClick: () => {
96
- session.queueDialog(handleClose => [
97
- ExportSvgDialog,
98
- {
99
- model: self,
100
- handleClose,
129
+ ...(self.isTopLevelView
130
+ ? [
131
+ {
132
+ label: 'Return to import form',
133
+ onClick: () => {
134
+ session.queueDialog(handleClose => [
135
+ ReturnToImportFormDialog,
136
+ {
137
+ model: self,
138
+ handleClose,
139
+ },
140
+ ]);
101
141
  },
102
- ]);
103
- },
104
- },
105
- {
106
- label: 'Open track selector',
107
- onClick: () => {
108
- self.activateTrackSelector();
109
- },
110
- icon: TrackSelectorIcon,
111
- },
112
- // Top-level rather than under a "Navigation" group: with scroll-to-zoom
113
- // gone from here the group held two rows, and a popup for two is a click
114
- // charged for nothing. Not under "Show...", which is visibility toggles.
115
- showAllRegionsMenuItem(self),
142
+ icon: FolderOpenIcon,
143
+ },
144
+ ]
145
+ : []),
146
+ ...(self.isTopLevelView && self.tracks.length
147
+ ? [
148
+ {
149
+ label: 'Fit tracks to window',
150
+ icon: FitScreenIcon,
151
+ disabled: !self.scrollPortExcess,
152
+ onClick: () => {
153
+ self.fitTracksToWindow();
154
+ },
155
+ },
156
+ ]
157
+ : []),
116
158
  {
117
159
  label: 'Horizontally flip',
118
160
  icon: SyncAltIcon,
@@ -120,115 +162,138 @@ export function buildMenuItems(self) {
120
162
  self.horizontallyFlip();
121
163
  },
122
164
  },
123
- {
124
- label: 'Color CDS by reading frame',
125
- type: 'checkbox',
126
- checked: self.colorByCDS,
127
- icon: PaletteIcon,
128
- onClick: () => {
129
- self.setColorByCDS(!self.colorByCDS);
165
+ ...makeShowSubMenu([
166
+ showAllRegionsMenuItem(self),
167
+ {
168
+ label: 'Show ruler only',
169
+ type: 'checkbox',
170
+ checked: self.scalebarOnly,
171
+ onClick: () => {
172
+ self.setScalebarOnly(!self.scalebarOnly);
173
+ },
130
174
  },
131
- },
132
- {
133
- label: 'Show...',
134
- icon: VisibilityIcon,
135
- subMenu: [
136
- {
137
- label: 'Show center line',
138
- type: 'checkbox',
139
- checked: self.showCenterLine,
140
- onClick: () => {
141
- self.setShowCenterLine(!self.showCenterLine);
142
- },
175
+ {
176
+ label: 'Show header',
177
+ type: 'checkbox',
178
+ checked: !self.hideHeader,
179
+ // opts out of the checkbox "stay open" default: with the header
180
+ // hidden these same items are reachable from MiniControls, which this
181
+ // row unmounts — leaving the menu anchored to a removed node
182
+ keepMenuOpen: false,
183
+ onClick: () => {
184
+ self.setHideHeader(!self.hideHeader);
143
185
  },
144
- {
145
- label: 'Show header',
146
- type: 'checkbox',
147
- checked: !self.hideHeader,
148
- // opts out of the checkbox "stay open" default: with the header
149
- // hidden these same items are reachable from MiniControls, which this
150
- // row unmounts — leaving the menu anchored to a removed node
151
- keepMenuOpen: false,
152
- onClick: () => {
153
- self.setHideHeader(!self.hideHeader);
154
- },
186
+ },
187
+ {
188
+ label: 'Show header overview',
189
+ type: 'checkbox',
190
+ checked: !self.hideHeaderOverview,
191
+ onClick: () => {
192
+ self.setHideHeaderOverview(!self.hideHeaderOverview);
155
193
  },
156
- {
157
- label: 'Show track outlines',
158
- type: 'checkbox',
159
- checked: self.showTrackOutlines,
160
- onClick: () => {
161
- self.setShowTrackOutlines(!self.showTrackOutlines);
194
+ disabled: self.hideHeader,
195
+ },
196
+ ...(self.canShowCytobands
197
+ ? [
198
+ {
199
+ label: 'Show ideogram',
200
+ type: 'checkbox',
201
+ checked: self.showCytobands,
202
+ onClick: () => {
203
+ self.setShowCytobands(!self.showCytobands);
204
+ },
162
205
  },
206
+ ]
207
+ : []),
208
+ {
209
+ label: 'Show center line',
210
+ type: 'checkbox',
211
+ checked: self.showCenterLine,
212
+ onClick: () => {
213
+ self.setShowCenterLine(!self.showCenterLine);
163
214
  },
164
- {
165
- label: 'Show header overview',
166
- type: 'checkbox',
167
- checked: !self.hideHeaderOverview,
168
- onClick: () => {
169
- self.setHideHeaderOverview(!self.hideHeaderOverview);
170
- },
171
- disabled: self.hideHeader,
215
+ },
216
+ {
217
+ label: 'Show guidelines',
218
+ type: 'checkbox',
219
+ checked: self.showGridlines,
220
+ onClick: () => {
221
+ self.setShowGridlines(!self.showGridlines);
172
222
  },
173
- {
174
- label: 'Show no tracks active button',
175
- type: 'checkbox',
176
- checked: !self.hideNoTracksActive,
177
- onClick: () => {
178
- self.setHideNoTracksActive(!self.hideNoTracksActive);
179
- },
223
+ },
224
+ {
225
+ label: 'Show track outlines',
226
+ type: 'checkbox',
227
+ checked: self.showTrackOutlines,
228
+ onClick: () => {
229
+ self.setShowTrackOutlines(!self.showTrackOutlines);
180
230
  },
181
- {
182
- // no icon: the palette it used to carry sat directly against the
183
- // color-by-CDS one above it, which is the reason it moved here
184
- label: 'Show amino acids when zoomed in',
185
- type: 'checkbox',
186
- checked: self.showAminoAcids,
187
- onClick: () => {
188
- self.setShowAminoAcids(!self.showAminoAcids);
189
- },
231
+ },
232
+ {
233
+ label: 'Show no tracks active button',
234
+ type: 'checkbox',
235
+ checked: !self.hideNoTracksActive,
236
+ onClick: () => {
237
+ self.setHideNoTracksActive(!self.hideNoTracksActive);
190
238
  },
191
- {
192
- label: 'Show guidelines',
193
- type: 'checkbox',
194
- checked: self.showGridlines,
195
- onClick: () => {
196
- self.setShowGridlines(!self.showGridlines);
197
- },
239
+ },
240
+ { type: 'subHeader', label: 'Sequence' },
241
+ {
242
+ label: 'Show CDS reading frame colors',
243
+ type: 'checkbox',
244
+ checked: self.colorByCDS,
245
+ onClick: () => {
246
+ self.setColorByCDS(!self.colorByCDS);
198
247
  },
199
- ...(self.canShowCytobands
200
- ? [
201
- {
202
- label: 'Show ideogram',
203
- type: 'checkbox',
204
- checked: self.showCytobands,
205
- onClick: () => {
206
- self.setShowCytobands(!self.showCytobands);
207
- },
208
- },
209
- ]
210
- : []),
211
- // Where a track's name is drawn, and "Hidden" is one of the three
212
- // answers, so this is a visibility setting like everything above it.
213
- // Inline under a subheader rather than in a submenu of its own: it was a
214
- // top-level row for three radios, and nesting it here instead would have
215
- // put those radios a popup further from the hamburger than they were.
216
- // The icons went with it — all three rows carried the same one, which
217
- // told a reader nothing about which to pick.
218
- { type: 'subHeader', label: 'Track labels' },
219
- ...radioItems(TRACK_LABEL_OPTIONS, self.effectiveTrackLabels, setting => {
220
- self.setTrackLabels(setting);
221
- }),
222
- ],
248
+ },
249
+ {
250
+ label: 'Show amino acids when zoomed in',
251
+ type: 'checkbox',
252
+ checked: self.showAminoAcids,
253
+ onClick: () => {
254
+ self.setShowAminoAcids(!self.showAminoAcids);
255
+ },
256
+ },
257
+ ]),
258
+ {
259
+ label: 'Highlights',
260
+ icon: Highlighter,
261
+ subMenu: self.highlightsSubMenuItems(),
262
+ },
263
+ {
264
+ label: 'Track labels',
265
+ icon: LabelIcon,
266
+ subMenu: radioItems(TRACK_LABEL_OPTIONS, self.effectiveTrackLabels, setting => {
267
+ self.setTrackLabels(setting);
268
+ }),
223
269
  },
224
270
  ];
225
271
  return menuItems;
226
272
  }
227
273
  /**
228
- * Build rubberband selection menu items. `launchItems` are the plugin-supplied
229
- * things a selection can start (`rubberBandLaunchMenuItems()`); they collect
230
- * under one "Launch" submenu so the menu stays three actions plus a group
231
- * however many plugins are loaded, and vanish entirely when none apply.
274
+ * Open a copy of the view, tracks and their settings included, framed on the
275
+ * span between two offsets. The same copy as the view menu's "Copy view", and
276
+ * nothing links it back to the original.
277
+ */
278
+ export function openSpanInNewView(view, start, end) {
279
+ if (!start || !end) {
280
+ return undefined;
281
+ }
282
+ const { bpPerPx, offsetPx } = computeMoveToLayout(view, start, end);
283
+ return getSession(view).addView(view.type, {
284
+ ...renameIds(getSnapshot(view)),
285
+ windowWidthBp: bpPerPx * view.width,
286
+ windowStartBp: offsetPx * bpPerPx,
287
+ });
288
+ }
289
+ /**
290
+ * Build rubberband selection menu items. What a selection can open collects
291
+ * under one "Launch" submenu, which sorts last, below any row a plugin appends
292
+ * to the menu itself: a new linear genome view of the span first, then
293
+ * `launchItems`, the plugin-supplied ones (`rubberBandLaunchMenuItems()`).
294
+ *
295
+ * The new view is a copy of this one, so only a top-level view offers it: a row
296
+ * of a comparative view carries that view's framing, such as a hidden header.
232
297
  */
233
298
  export function buildRubberBandMenuItems(self, launchItems) {
234
299
  // captured once here and used by the items below rather than re-read inside
@@ -242,6 +307,21 @@ export function buildRubberBandMenuItems(self, launchItems) {
242
307
  // in ascending order, and a `leftRef === rightRef` test calls a selection
243
308
  // crossing a collapsed intron one range when it is two.
244
309
  const rangeString = assembleLocStrings(self.getSelectedRegions(leftOffset, rightOffset));
310
+ const launch = [
311
+ ...(self.isTopLevelView
312
+ ? [
313
+ {
314
+ label: 'Linear genome view',
315
+ icon: OpenInNewIcon,
316
+ helpText: 'A new view of this span, with the same tracks. It scrolls and zooms on its own.',
317
+ onClick: () => {
318
+ openSpanInNewView(self, leftOffset, rightOffset);
319
+ },
320
+ },
321
+ ]
322
+ : []),
323
+ ...launchItems,
324
+ ];
245
325
  return [
246
326
  {
247
327
  label: 'Zoom to region',
@@ -275,13 +355,24 @@ export function buildRubberBandMenuItems(self, launchItems) {
275
355
  void copyText(self, rangeString, 'range');
276
356
  },
277
357
  },
278
- ...(launchItems.length
358
+ {
359
+ label: 'Highlight region',
360
+ icon: Highlighter,
361
+ onClick: () => {
362
+ const [region] = self.getSelectedRegions(leftOffset, rightOffset);
363
+ if (region) {
364
+ getSession(self).addHighlight(region);
365
+ }
366
+ },
367
+ },
368
+ ...(launch.length
279
369
  ? [
280
370
  {
281
371
  label: 'Launch',
282
372
  icon: LaunchIcon,
283
373
  type: 'subMenu',
284
- subMenu: launchItems,
374
+ priority: -1000,
375
+ subMenu: launch,
285
376
  },
286
377
  ]
287
378
  : []),
@@ -291,7 +382,7 @@ export function buildRubberBandMenuItems(self, launchItems) {
291
382
  * Build rubberband click menu items (single click on rubberband area)
292
383
  */
293
384
  export function buildRubberbandClickMenuItems(self, clickOffset) {
294
- const { refName, start, end, reversed, offset } = clickOffset;
385
+ const { refName, start, end, reversed, offset, index } = clickOffset;
295
386
  if (refName === undefined || start === undefined || end === undefined) {
296
387
  return [];
297
388
  }
@@ -309,15 +400,15 @@ export function buildRubberbandClickMenuItems(self, clickOffset) {
309
400
  label: 'Center view here',
310
401
  icon: CenterFocusStrongIcon,
311
402
  onClick: () => {
312
- self.centerAt(coord0, refName);
403
+ self.centerAt(coord0, refName, index);
313
404
  },
314
405
  },
315
406
  {
316
407
  label: 'Zoom to base level',
317
408
  icon: ZoomInIcon,
318
409
  onClick: () => {
319
- self.centerAt(coord0, refName);
320
410
  self.zoomTo(self.minBpPerPx);
411
+ self.centerAt(coord0, refName, index);
321
412
  },
322
413
  },
323
414
  {