@jbrowse/app-core 4.3.0 → 5.0.0-beta.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 (158) hide show
  1. package/esm/AppSession/AppRootModel.d.ts +30 -0
  2. package/esm/AppSession/AppRootModel.js +1 -0
  3. package/esm/AppSession/AppSessionMixin.d.ts +1598 -0
  4. package/esm/AppSession/AppSessionMixin.js +62 -0
  5. package/esm/AppSession/index.d.ts +2 -0
  6. package/esm/AppSession/index.js +1 -0
  7. package/esm/Assemblies/AssembliesMixin.d.ts +225 -0
  8. package/esm/Assemblies/AssembliesMixin.js +149 -0
  9. package/esm/Assemblies/index.d.ts +1 -2
  10. package/esm/Assemblies/index.js +1 -2
  11. package/esm/HistoryManagement/index.d.ts +19 -0
  12. package/esm/HistoryManagement/index.js +42 -5
  13. package/esm/JBrowseConfig/RootConfiguration.d.ts +171 -78
  14. package/esm/JBrowseConfig/RootConfiguration.js +85 -7
  15. package/esm/JBrowseConfig/index.d.ts +173 -84
  16. package/esm/JBrowseConfig/index.js +62 -2
  17. package/esm/JBrowseModel/index.d.ts +180 -95
  18. package/esm/JBrowseModel/index.js +122 -21
  19. package/esm/JbApi/index.d.ts +2 -0
  20. package/esm/JbApi/index.js +1 -0
  21. package/esm/JbApi/jbApi.d.ts +325 -0
  22. package/esm/JbApi/jbApi.js +748 -0
  23. package/esm/RootMenu/index.d.ts +69 -3
  24. package/esm/RootMenu/index.js +96 -61
  25. package/esm/SessionSpec/index.d.ts +6 -0
  26. package/esm/SessionSpec/index.js +3 -0
  27. package/esm/SessionSpec/lgvUrlInit.d.ts +79 -0
  28. package/esm/SessionSpec/lgvUrlInit.js +158 -0
  29. package/esm/SessionSpec/loadSessionSpec.d.ts +21 -0
  30. package/esm/SessionSpec/loadSessionSpec.js +392 -0
  31. package/esm/SessionSpec/parseSessionSpecUrl.d.ts +13 -0
  32. package/esm/SessionSpec/parseSessionSpecUrl.js +196 -0
  33. package/esm/SessionSpec/types.d.ts +31 -0
  34. package/esm/SessionSpec/types.js +1 -0
  35. package/esm/WorkspaceLayout/LayoutRenderer.d.ts +24 -0
  36. package/esm/WorkspaceLayout/LayoutRenderer.js +185 -0
  37. package/esm/WorkspaceLayout/PanelView.d.ts +13 -0
  38. package/esm/WorkspaceLayout/PanelView.js +99 -0
  39. package/esm/WorkspaceLayout/TabStrip.d.ts +14 -0
  40. package/esm/WorkspaceLayout/TabStrip.js +226 -0
  41. package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +16 -0
  42. package/esm/WorkspaceLayout/WorkspaceContainer.js +105 -0
  43. package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +24 -0
  44. package/esm/WorkspaceLayout/WorkspacePanelActions.js +120 -0
  45. package/esm/WorkspaceLayout/WorkspaceTab.d.ts +27 -0
  46. package/esm/WorkspaceLayout/WorkspaceTab.js +109 -0
  47. package/esm/WorkspaceLayout/dockviewTheme.d.ts +69 -0
  48. package/esm/WorkspaceLayout/dockviewTheme.js +82 -0
  49. package/esm/WorkspaceLayout/dropZone.d.ts +105 -0
  50. package/esm/WorkspaceLayout/dropZone.js +113 -0
  51. package/esm/WorkspaceLayout/index.d.ts +8 -0
  52. package/esm/WorkspaceLayout/index.js +4 -0
  53. package/esm/WorkspaceLayout/model.d.ts +248 -0
  54. package/esm/WorkspaceLayout/model.js +562 -0
  55. package/esm/WorkspaceLayout/panelChrome.d.ts +19 -0
  56. package/esm/WorkspaceLayout/panelChrome.js +4 -0
  57. package/esm/WorkspaceLayout/spec.d.ts +88 -0
  58. package/esm/WorkspaceLayout/spec.js +226 -0
  59. package/esm/WorkspaceLayout/splitter.d.ts +25 -0
  60. package/esm/WorkspaceLayout/splitter.js +37 -0
  61. package/esm/WorkspaceLayout/tree.d.ts +158 -0
  62. package/esm/WorkspaceLayout/tree.js +379 -0
  63. package/esm/WorkspaceLayout/useLayoutDrag.d.ts +34 -0
  64. package/esm/WorkspaceLayout/useLayoutDrag.js +232 -0
  65. package/esm/index.d.ts +18 -9
  66. package/esm/index.js +16 -9
  67. package/esm/menus.d.ts +55 -68
  68. package/esm/menus.js +117 -86
  69. package/esm/queryParams.d.ts +9 -0
  70. package/esm/queryParams.js +92 -0
  71. package/esm/sessionModelFactory.d.ts +18 -0
  72. package/esm/sessionModelFactory.js +1 -0
  73. package/esm/ui/App/App.d.ts +3 -14
  74. package/esm/ui/App/App.js +30 -17
  75. package/esm/ui/App/AppFab.d.ts +12 -3
  76. package/esm/ui/App/AppFab.js +15 -8
  77. package/esm/ui/App/AppReadyMarker.d.ts +43 -0
  78. package/esm/ui/App/AppReadyMarker.js +56 -0
  79. package/esm/ui/App/AppToolbar.d.ts +2 -14
  80. package/esm/ui/App/AppToolbar.js +23 -24
  81. package/esm/ui/App/ClassicViewsContainer.d.ts +3 -9
  82. package/esm/ui/App/ClassicViewsContainer.js +9 -8
  83. package/esm/ui/App/DialogQueue.d.ts +17 -1
  84. package/esm/ui/App/DialogQueue.js +30 -1
  85. package/esm/ui/App/Drawer.d.ts +1 -1
  86. package/esm/ui/App/Drawer.js +10 -39
  87. package/esm/ui/App/DrawerControls.d.ts +1 -5
  88. package/esm/ui/App/DrawerControls.js +1 -25
  89. package/esm/ui/App/DrawerHeader.d.ts +2 -3
  90. package/esm/ui/App/DrawerHeader.js +24 -10
  91. package/esm/ui/App/DrawerHeaderHelpButton.d.ts +1 -1
  92. package/esm/ui/App/DrawerHeaderHelpButton.js +3 -3
  93. package/esm/ui/App/DrawerHeaderHelpDialog.d.ts +2 -2
  94. package/esm/ui/App/DrawerHeaderHelpDialog.js +3 -4
  95. package/esm/ui/App/DrawerWidget.d.ts +1 -1
  96. package/esm/ui/App/DrawerWidget.js +9 -21
  97. package/esm/ui/App/JBrowseTabMenu.d.ts +2 -3
  98. package/esm/ui/App/JBrowseTabMenu.js +24 -5
  99. package/esm/ui/App/ViewButtons.d.ts +2 -4
  100. package/esm/ui/App/ViewButtons.js +15 -3
  101. package/esm/ui/App/ViewContainer.d.ts +3 -2
  102. package/esm/ui/App/ViewContainer.js +144 -24
  103. package/esm/ui/App/ViewContainerTitle.d.ts +1 -1
  104. package/esm/ui/App/ViewContainerTitle.js +9 -2
  105. package/esm/ui/App/ViewErrorFallback.d.ts +18 -0
  106. package/esm/ui/App/ViewErrorFallback.js +42 -0
  107. package/esm/ui/App/ViewHeader.d.ts +3 -4
  108. package/esm/ui/App/ViewHeader.js +28 -17
  109. package/esm/ui/App/ViewLauncher.d.ts +2 -8
  110. package/esm/ui/App/ViewLauncher.js +7 -5
  111. package/esm/ui/App/ViewMenu.d.ts +3 -5
  112. package/esm/ui/App/ViewMenu.js +87 -92
  113. package/esm/ui/App/ViewStack.d.ts +7 -0
  114. package/esm/ui/App/ViewStack.js +42 -0
  115. package/esm/ui/App/ViewWrapper.d.ts +15 -1
  116. package/esm/ui/App/ViewWrapper.js +26 -10
  117. package/esm/ui/App/ViewsContainer.d.ts +3 -8
  118. package/esm/ui/App/ViewsContainer.js +9 -6
  119. package/esm/ui/App/copyView.js +1 -1
  120. package/esm/ui/App/index.d.ts +2 -1
  121. package/esm/ui/App/index.js +3 -2
  122. package/esm/ui/App/lazyChunk.d.ts +13 -0
  123. package/esm/ui/App/lazyChunk.js +91 -0
  124. package/esm/ui/App/types.d.ts +12 -3
  125. package/esm/ui/App/types.js +1 -1
  126. package/esm/ui/App/useViewVisibility.d.ts +34 -0
  127. package/esm/ui/App/useViewVisibility.js +87 -0
  128. package/esm/ui/App/viewTitle.d.ts +5 -0
  129. package/esm/ui/App/viewTitle.js +9 -0
  130. package/esm/ui/index.d.ts +1 -1
  131. package/esm/ui/index.js +1 -1
  132. package/package.json +12 -17
  133. package/esm/AppFocus/index.d.ts +0 -5
  134. package/esm/AppFocus/index.js +0 -12
  135. package/esm/Assemblies/SessionAssembliesMixin.d.ts +0 -92
  136. package/esm/Assemblies/SessionAssembliesMixin.js +0 -53
  137. package/esm/Assemblies/TemporaryAssembliesMixin.d.ts +0 -10
  138. package/esm/Assemblies/TemporaryAssembliesMixin.js +0 -37
  139. package/esm/DockviewLayout/index.d.ts +0 -35
  140. package/esm/DockviewLayout/index.js +0 -111
  141. package/esm/ui/App/DockviewContext.d.ts +0 -22
  142. package/esm/ui/App/DockviewContext.js +0 -29
  143. package/esm/ui/App/DockviewLeftHeaderActions.d.ts +0 -2
  144. package/esm/ui/App/DockviewLeftHeaderActions.js +0 -58
  145. package/esm/ui/App/DockviewRightHeaderActions.d.ts +0 -2
  146. package/esm/ui/App/DockviewRightHeaderActions.js +0 -100
  147. package/esm/ui/App/DrawerWidgetSelector.d.ts +0 -5
  148. package/esm/ui/App/DrawerWidgetSelector.js +0 -47
  149. package/esm/ui/App/JBrowseViewPanel.d.ts +0 -8
  150. package/esm/ui/App/JBrowseViewPanel.js +0 -40
  151. package/esm/ui/App/JBrowseViewTab.d.ts +0 -8
  152. package/esm/ui/App/JBrowseViewTab.js +0 -97
  153. package/esm/ui/App/ModalWidget.d.ts +0 -6
  154. package/esm/ui/App/ModalWidget.js +0 -48
  155. package/esm/ui/App/TiledViewsContainer.d.ts +0 -6
  156. package/esm/ui/App/TiledViewsContainer.js +0 -366
  157. package/esm/ui/App/dockviewUtils.d.ts +0 -46
  158. package/esm/ui/App/dockviewUtils.js +0 -55
@@ -1,26 +1,33 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { FAB_Z_INDEX } from '@jbrowse/core/ui/zIndexes';
2
3
  import { makeStyles } from '@jbrowse/core/util/tss-react';
3
4
  import LaunchIcon from '@mui/icons-material/Launch';
4
5
  import { Fab, Tooltip } from '@mui/material';
5
6
  import { observer } from 'mobx-react';
7
+ // `absolute`, against the app root, rather than the `fixed` this used to
8
+ // carry. Fixed measures against the window, and the app root is only the
9
+ // window in a full-window app: under `--jbrowse-app-height` the app is a box
10
+ // somewhere on a host's page (see react-app's fit-to-container example), and
11
+ // this button was pinned to the page's corner instead of the app's. It is the
12
+ // only way back from a minimized drawer, so it cannot be somewhere else on the
13
+ // page.
6
14
  const useStyles = makeStyles()(theme => ({
7
- left: {
8
- zIndex: 10000,
9
- position: 'fixed',
15
+ fab: {
16
+ zIndex: FAB_Z_INDEX,
17
+ position: 'absolute',
10
18
  bottom: theme.spacing(2),
19
+ },
20
+ left: {
11
21
  left: theme.spacing(2),
12
22
  },
13
23
  right: {
14
- zIndex: 10000,
15
- position: 'fixed',
16
- bottom: theme.spacing(2),
17
24
  right: theme.spacing(2),
18
25
  },
19
26
  }));
20
27
  const AppFab = observer(function AppFab({ session, }) {
21
28
  const { minimized, activeWidgets, drawerPosition } = session;
22
- const { classes } = useStyles();
23
- return activeWidgets.size > 0 && minimized ? (_jsx(Tooltip, { title: "Open drawer widget", children: _jsx(Fab, { className: drawerPosition === 'right' ? classes.right : classes.left, color: "primary", "data-testid": "drawer-maximize", onClick: () => {
29
+ const { classes, cx } = useStyles();
30
+ return activeWidgets.size > 0 && minimized ? (_jsx(Tooltip, { title: "Open drawer widget", children: _jsx(Fab, { className: cx(classes.fab, drawerPosition === 'right' ? classes.right : classes.left), color: "primary", "data-testid": "drawer-maximize", onClick: () => {
24
31
  session.showWidgetDrawer();
25
32
  }, children: _jsx(LaunchIcon, {}) }) })) : null;
26
33
  });
@@ -0,0 +1,43 @@
1
+ import type { AppSession } from './types.ts';
2
+ /**
3
+ * One element that says whether the whole app has finished, and what is open.
4
+ *
5
+ * `[data-app-phase="ready"]` is the entire readiness contract for anything
6
+ * driving JBrowse from outside — a screenshot tool, a test, an agent. Wait for
7
+ * that selector and you are done; there is no chain to assemble and no
8
+ * per-display census to take.
9
+ *
10
+ * Why it exists when `data-view-phase` and `data-display-phase` already do:
11
+ * those are per-element and NEGATIVE. Asking them "is everything finished"
12
+ * means asserting that no element carries the loading value, which is equally
13
+ * true of an app that has not started — and the gap is real, measured at about
14
+ * a second on a two-track session. A positive attribute cannot be satisfied
15
+ * early, because something has to render it.
16
+ *
17
+ * The census attributes beside it — `data-app-views`, `data-app-assemblies`,
18
+ * `data-app-tracks` (the latter two JSON arrays) — publish WHAT is open the
19
+ * same way the phase publishes whether it is done, so an outside reader asking
20
+ * "is the track I requested actually open" reads one element instead of
21
+ * walking `window.JBrowseSession` with its own copy of the view nesting. The
22
+ * walk itself is the views' own `allViews`/`ownTracks` contract (BaseViewModel
23
+ * derives it), so this component knows nothing about which property a
24
+ * container view keeps its children on either.
25
+ *
26
+ * Its own component, and `hidden`, for two reasons: an observer here subscribes
27
+ * to every view's phase without re-rendering the app shell around it, and a
28
+ * marker that is never drawn cannot be mistaken for UI or shift a layout. It is
29
+ * still found by `querySelector`, which is what reads it.
30
+ *
31
+ * **What it is silent about**: a display in a TERMINAL state. `error` is a
32
+ * finished state, so this reads `ready` over a display whose fetch failed —
33
+ * correctly, since nothing is still working. A capture wants more than that: it
34
+ * wants a picture, and an error banner is not one. `waitForDisplaysDone` after
35
+ * this is what draws that line, keying on the `data-display-drawn` the two
36
+ * comparative canvases publish from their stricter `settled` gate (see
37
+ * `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
38
+ * and says why an error separates them). Both harnesses do it in that order.
39
+ */
40
+ declare const AppReadyMarker: ({ session, }: {
41
+ session: AppSession;
42
+ }) => import("react").JSX.Element;
43
+ export default AppReadyMarker;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { observer } from 'mobx-react';
3
+ function trackLoading(track) {
4
+ return track.displays.some(d => d.displayPhase === 'loading');
5
+ }
6
+ function viewLoading(view) {
7
+ const flags = view;
8
+ return flags.showLoading === true || flags.initialized === false;
9
+ }
10
+ /**
11
+ * One element that says whether the whole app has finished, and what is open.
12
+ *
13
+ * `[data-app-phase="ready"]` is the entire readiness contract for anything
14
+ * driving JBrowse from outside — a screenshot tool, a test, an agent. Wait for
15
+ * that selector and you are done; there is no chain to assemble and no
16
+ * per-display census to take.
17
+ *
18
+ * Why it exists when `data-view-phase` and `data-display-phase` already do:
19
+ * those are per-element and NEGATIVE. Asking them "is everything finished"
20
+ * means asserting that no element carries the loading value, which is equally
21
+ * true of an app that has not started — and the gap is real, measured at about
22
+ * a second on a two-track session. A positive attribute cannot be satisfied
23
+ * early, because something has to render it.
24
+ *
25
+ * The census attributes beside it — `data-app-views`, `data-app-assemblies`,
26
+ * `data-app-tracks` (the latter two JSON arrays) — publish WHAT is open the
27
+ * same way the phase publishes whether it is done, so an outside reader asking
28
+ * "is the track I requested actually open" reads one element instead of
29
+ * walking `window.JBrowseSession` with its own copy of the view nesting. The
30
+ * walk itself is the views' own `allViews`/`ownTracks` contract (BaseViewModel
31
+ * derives it), so this component knows nothing about which property a
32
+ * container view keeps its children on either.
33
+ *
34
+ * Its own component, and `hidden`, for two reasons: an observer here subscribes
35
+ * to every view's phase without re-rendering the app shell around it, and a
36
+ * marker that is never drawn cannot be mistaken for UI or shift a layout. It is
37
+ * still found by `querySelector`, which is what reads it.
38
+ *
39
+ * **What it is silent about**: a display in a TERMINAL state. `error` is a
40
+ * finished state, so this reads `ready` over a display whose fetch failed —
41
+ * correctly, since nothing is still working. A capture wants more than that: it
42
+ * wants a picture, and an error banner is not one. `waitForDisplaysDone` after
43
+ * this is what draws that line, keying on the `data-display-drawn` the two
44
+ * comparative canvases publish from their stricter `settled` gate (see
45
+ * `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
46
+ * and says why an error separates them). Both harnesses do it in that order.
47
+ */
48
+ const AppReadyMarker = observer(function AppReadyMarker({ session, }) {
49
+ const views = session.views.flatMap(v => v.allViews);
50
+ const tracks = views.flatMap(v => v.ownTracks);
51
+ const loading = views.some(viewLoading) || tracks.some(trackLoading);
52
+ return (_jsx("span", { hidden: true, "data-testid": "app-ready-marker", "data-app-phase": loading ? 'loading' : 'ready', "data-app-views": session.views.length, "data-app-assemblies": JSON.stringify([
53
+ ...new Set(views.flatMap(v => v.assemblyNames ?? [])),
54
+ ]), "data-app-tracks": JSON.stringify(tracks.map(t => t.configuration.trackId ?? '(unnamed)')) }));
55
+ });
56
+ export default AppReadyMarker;
@@ -1,18 +1,6 @@
1
- import type { MenuItem as JBMenuItem } from '@jbrowse/core/ui/Menu';
2
- import type { SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel';
3
- import type { SessionWithDrawerWidgets } from '@jbrowse/core/util';
4
- interface Menu {
5
- label: string;
6
- menuItems: JBMenuItem[] | (() => JBMenuItem[]);
7
- }
8
- type AppSession = SessionWithDrawerWidgets & {
9
- menus: () => Menu[];
10
- snackbarMessages: SnackbarMessage[];
11
- renameCurrentSession: (arg: string) => void;
12
- popSnackbarMessage: () => unknown;
13
- };
1
+ import type { AppSession } from './types.ts';
14
2
  declare const AppToolbar: ({ session, HeaderButtons, }: {
15
3
  HeaderButtons?: React.ReactElement;
16
4
  session: AppSession;
17
- }) => import("react/jsx-runtime").JSX.Element;
5
+ }) => import("react").JSX.Element;
18
6
  export default AppToolbar;
@@ -21,39 +21,38 @@ const useStyles = makeStyles()(theme => ({
21
21
  borderColor: theme.palette.secondary.main,
22
22
  backgroundColor: theme.palette.primary.light,
23
23
  },
24
+ logo: {
25
+ // stretch to the toolbar height so the logo's height follows the bar
26
+ // rather than a hardcoded pixel value
27
+ alignSelf: 'stretch',
28
+ display: 'flex',
29
+ alignItems: 'center',
30
+ // autofit any logo (custom <img> or the default svg): full toolbar height
31
+ // with auto width preserves aspect ratio, maxWidth caps horizontal room
32
+ '& img, & svg': {
33
+ height: '100%',
34
+ width: 'auto',
35
+ maxWidth: 150,
36
+ objectFit: 'contain',
37
+ },
38
+ },
24
39
  }));
25
- function wrapMenuItems(items, session) {
26
- return items.map(item => ({
27
- ...item,
28
- ...('onClick' in item
29
- ? {
30
- onClick: () => {
31
- item.onClick(session);
32
- },
33
- }
34
- : {}),
35
- ...('subMenu' in item
36
- ? { subMenu: wrapMenuItems(item.subMenu, session) }
37
- : {}),
38
- }));
39
- }
40
40
  const AppToolbar = observer(function AppToolbar({ session, HeaderButtons = _jsx("div", {}), }) {
41
41
  const { classes } = useStyles();
42
42
  const { name, menus } = session;
43
- return (_jsxs(Toolbar, { children: [menus().map(menu => (_jsx(DropDownMenu, { menuTitle: menu.label, menuItems: () => {
44
- const items = typeof menu.menuItems === 'function'
45
- ? menu.menuItems()
46
- : menu.menuItems;
47
- return wrapMenuItems(items, session);
43
+ return (_jsxs(Toolbar, { children: [menus().map(menu => (_jsx(DropDownMenu, { menuTitle: menu.label, menuItems: menu.menuItems,
44
+ // a root model's menu items take the session as their argument, and
45
+ // the renderer decides how to invoke onClick — so this is the whole
46
+ // binding. It used to be a recursive rewrite of every row of every
47
+ // menu, rebuilt on each open, to arrive at the same call
48
+ onMenuItemClick: callback => {
49
+ callback(session);
48
50
  } }, menu.label))), _jsx("div", { className: classes.grow }), _jsx(Tooltip, { title: "Rename session", arrow: true, children: _jsx(EditableTypography, { value: name, variant: "body1", classes: {
49
51
  inputBase: classes.inputBase,
50
52
  inputRoot: classes.inputRoot,
51
53
  inputFocused: classes.inputFocused,
52
54
  }, setValue: newName => {
53
55
  session.renameCurrentSession(newName);
54
- } }) }), HeaderButtons, _jsx("div", { className: classes.grow }), _jsx("div", { style: {
55
- width: 150,
56
- maxHeight: 48,
57
- }, children: _jsx(AppLogo, { session: session }) })] }));
56
+ } }) }), HeaderButtons, _jsx("div", { className: classes.grow }), _jsx("div", { className: classes.logo, children: _jsx(AppLogo, { session: session }) })] }));
58
57
  });
59
58
  export default AppToolbar;
@@ -1,12 +1,6 @@
1
- import type { SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel';
2
- import type { AbstractViewContainer, SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util';
3
- type SessionType = SessionWithFocusedViewAndDrawerWidgets & AbstractViewContainer & {
4
- renameCurrentSession: (arg: string) => void;
5
- snackbarMessages: SnackbarMessage[];
6
- popSnackbarMessage: () => unknown;
7
- };
1
+ import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util';
8
2
  interface Props {
9
- session: SessionType;
3
+ session: SessionWithFocusedViewAndDrawerWidgets;
10
4
  }
11
- declare const ClassicViewsContainer: ({ session, }: Props) => import("react/jsx-runtime").JSX.Element;
5
+ declare const ClassicViewsContainer: ({ session, }: Props) => import("react").JSX.Element;
12
6
  export default ClassicViewsContainer;
@@ -1,21 +1,22 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { persistentScrollbarStyle } from '@jbrowse/core/ui/persistentScrollbarStyle';
3
+ import { useScrollPortHeightVar } from '@jbrowse/core/util/hooks';
2
4
  import { makeStyles } from '@jbrowse/core/util/tss-react';
3
5
  import { observer } from 'mobx-react';
4
- import ViewContainer from "./ViewContainer.js";
5
- const useStyles = makeStyles()({
6
+ import ViewStack from './ViewStack.js';
7
+ const useStyles = makeStyles()(theme => ({
6
8
  container: {
7
9
  height: '100%',
8
10
  width: '100%',
9
11
  gridRow: 'components',
10
12
  overflowY: 'auto',
13
+ ...persistentScrollbarStyle(theme),
11
14
  },
12
- spacer: {
13
- height: 300,
14
- },
15
- });
15
+ }));
16
16
  const ClassicViewsContainer = observer(function ClassicViewsContainer({ session, }) {
17
17
  const { classes } = useStyles();
18
18
  const { views } = session;
19
- return (_jsxs("div", { className: classes.container, children: [views.map(view => (_jsx(ViewContainer, { view: view, session: session }, view.id))), _jsx("div", { className: classes.spacer })] }));
19
+ const ref = useScrollPortHeightVar();
20
+ return (_jsx("div", { ref: ref, className: classes.container, children: _jsx(ViewStack, { views: views, session: session }) }));
20
21
  });
21
22
  export default ClassicViewsContainer;
@@ -1,5 +1,21 @@
1
1
  import type { AbstractSessionModel } from '@jbrowse/core/util';
2
+ /**
3
+ * The session's dialog, and the boundary that keeps a dialog that throws from
4
+ * being an application-wide failure.
5
+ *
6
+ * `core/ui/Dialog` already wraps its own CHILDREN, which covers a dialog body
7
+ * that throws. It cannot cover the dialog component's own render, and that is
8
+ * where the reachable case is: a launch dialog resolves its source with
9
+ * `getSession` / `getContainingView` off a track, and "Replace current view"
10
+ * destroys the view that track lives in. This queue renders under `App`, above
11
+ * every per-view boundary, so without this the next one up is the product's —
12
+ * jbrowse-web's fatal-error dialog, which takes every view the user had open.
13
+ * ADR-069, and `cancer_sv/multihop_split_view` is the figure it took.
14
+ *
15
+ * Keyed on the component so the queue advancing to a different dialog clears a
16
+ * banner belonging to the one before it.
17
+ */
2
18
  declare const DialogQueue: ({ session, }: {
3
19
  session: AbstractSessionModel;
4
- }) => import("react/jsx-runtime").JSX.Element | null;
20
+ }) => import("react").JSX.Element | null;
5
21
  export default DialogQueue;
@@ -1,8 +1,37 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Suspense } from 'react';
3
+ import { Dialog, ErrorMessage } from '@jbrowse/core/ui';
4
+ import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary';
5
+ import { DialogContent } from '@mui/material';
3
6
  import { observer } from 'mobx-react';
7
+ // `removeActiveDialog` is BaseSession's, and every product session composes it,
8
+ // but `AbstractSessionModel` does not declare it — so it is asked for rather
9
+ // than assumed. A session without it leaves the fallback's close button doing
10
+ // nothing, which is still better than the page it replaces.
11
+ function dismissActiveDialog(session) {
12
+ ;
13
+ session.removeActiveDialog?.();
14
+ }
15
+ /**
16
+ * The session's dialog, and the boundary that keeps a dialog that throws from
17
+ * being an application-wide failure.
18
+ *
19
+ * `core/ui/Dialog` already wraps its own CHILDREN, which covers a dialog body
20
+ * that throws. It cannot cover the dialog component's own render, and that is
21
+ * where the reachable case is: a launch dialog resolves its source with
22
+ * `getSession` / `getContainingView` off a track, and "Replace current view"
23
+ * destroys the view that track lives in. This queue renders under `App`, above
24
+ * every per-view boundary, so without this the next one up is the product's —
25
+ * jbrowse-web's fatal-error dialog, which takes every view the user had open.
26
+ * ADR-069, and `cancer_sv/multihop_split_view` is the figure it took.
27
+ *
28
+ * Keyed on the component so the queue advancing to a different dialog clears a
29
+ * banner belonging to the one before it.
30
+ */
4
31
  const DialogQueue = observer(function DialogQueue({ session, }) {
5
32
  const { DialogComponent, DialogProps } = session;
6
- return DialogComponent ? (_jsx(Suspense, { fallback: null, children: _jsx(DialogComponent, { ...DialogProps }) })) : null;
33
+ return DialogComponent ? (_jsx(ErrorBoundary, { resetKeys: [DialogComponent], FallbackComponent: ({ error }) => (_jsx(Dialog, { open: true, title: "Error", onClose: () => {
34
+ dismissActiveDialog(session);
35
+ }, children: _jsx(DialogContent, { children: _jsx(ErrorMessage, { error: error }) }) })), children: _jsx(Suspense, { fallback: null, children: _jsx(DialogComponent, { ...DialogProps }) }) })) : null;
7
36
  });
8
37
  export default DialogQueue;
@@ -2,5 +2,5 @@ import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util/
2
2
  declare const Drawer: ({ children, session, }: {
3
3
  children: React.ReactNode;
4
4
  session: SessionWithFocusedViewAndDrawerWidgets;
5
- }) => import("react/jsx-runtime").JSX.Element;
5
+ }) => import("react").JSX.Element;
6
6
  export default Drawer;
@@ -1,45 +1,16 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef } from 'react';
3
- import ResizeHandle from '@jbrowse/core/ui/ResizeHandle';
4
- import { makeStyles } from '@jbrowse/core/util/tss-react';
5
- import { Paper } from '@mui/material';
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef } from 'react';
3
+ import { useFocusOnInteraction } from '@jbrowse/core/util/hooks';
4
+ import { Drawer as DrawerShell } from '@jbrowse/product-core';
6
5
  import { observer } from 'mobx-react';
7
- const useStyles = makeStyles()(theme => ({
8
- paper: {
9
- overflowY: 'auto',
10
- height: '100%',
11
- position: 'relative',
12
- zIndex: theme.zIndex.drawer,
13
- outline: 'none',
14
- background: theme.palette.background.default,
15
- },
16
- resizeHandle: {
17
- width: 4,
18
- position: 'fixed',
19
- top: 0,
20
- zIndex: theme.zIndex.drawer + 1,
21
- },
22
- }));
23
6
  const Drawer = observer(function Drawer({ children, session, }) {
24
- const { drawerPosition, drawerWidth } = session;
25
- const { classes } = useStyles();
26
7
  const ref = useRef(null);
27
- useEffect(() => {
28
- function handleSelectView(e) {
29
- if (e.target instanceof Element && ref.current?.contains(e.target)) {
30
- const visibleWidgetId = session.visibleWidget?.view?.id;
31
- if (visibleWidgetId) {
32
- session.setFocusedViewId(visibleWidgetId);
33
- }
34
- }
8
+ useFocusOnInteraction(ref, () => {
9
+ const visibleWidgetId = session.visibleWidget?.view?.id;
10
+ if (visibleWidgetId) {
11
+ session.setFocusedViewId(visibleWidgetId);
35
12
  }
36
- document.addEventListener('mousedown', handleSelectView);
37
- document.addEventListener('keydown', handleSelectView);
38
- return () => {
39
- document.removeEventListener('mousedown', handleSelectView);
40
- document.removeEventListener('keydown', handleSelectView);
41
- };
42
- }, [session]);
43
- return (_jsxs(Paper, { ref: ref, className: classes.paper, elevation: 16, square: true, children: [drawerPosition === 'right' ? (_jsx(ResizeHandle, { onDrag: session.resizeDrawer, className: classes.resizeHandle, vertical: true })) : null, children, drawerPosition === 'left' ? (_jsx(ResizeHandle, { onDrag: session.resizeDrawer, className: classes.resizeHandle, style: { left: drawerWidth }, vertical: true })) : null] }));
13
+ });
14
+ return (_jsx(DrawerShell, { session: session, ref: ref, children: children }));
44
15
  });
45
16
  export default Drawer;
@@ -1,5 +1 @@
1
- import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util/types';
2
- declare const DrawerControls: ({ session, }: {
3
- session: SessionWithFocusedViewAndDrawerWidgets;
4
- }) => import("react/jsx-runtime").JSX.Element;
5
- export default DrawerControls;
1
+ export { DrawerControls as default } from '@jbrowse/product-core';
@@ -1,25 +1 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import CloseIcon from '@mui/icons-material/Close';
4
- import MinimizeIcon from '@mui/icons-material/Minimize';
5
- import MoreVertIcon from '@mui/icons-material/MoreVert';
6
- import { IconButton, Menu, MenuItem, Tooltip } from '@mui/material';
7
- import { observer } from 'mobx-react';
8
- const DrawerControls = observer(function DrawerControls({ session, }) {
9
- const [anchorEl, setAnchorEl] = useState(null);
10
- const { drawerPosition, visibleWidget } = session;
11
- return (_jsxs(_Fragment, { children: [_jsx(IconButton, { color: "inherit", onClick: event => {
12
- setAnchorEl(event.currentTarget);
13
- }, children: _jsx(MoreVertIcon, {}) }), _jsx(Tooltip, { title: "Minimize drawer", children: _jsx(IconButton, { "data-testid": "drawer-minimize", color: "inherit", onClick: () => {
14
- session.notify(`Drawer minimized, click button on ${drawerPosition} side of screen to re-open`, 'info');
15
- session.minimizeWidgetDrawer();
16
- }, children: _jsx(MinimizeIcon, {}) }) }), _jsx(Tooltip, { title: "Close drawer", children: _jsx(IconButton, { color: "inherit", onClick: () => {
17
- session.hideWidget(visibleWidget);
18
- }, children: _jsx(CloseIcon, {}) }) }), _jsx(Menu, { anchorEl: anchorEl, open: Boolean(anchorEl), onClose: () => {
19
- setAnchorEl(null);
20
- }, children: ['left', 'right'].map(option => (_jsx(MenuItem, { selected: drawerPosition === 'option', onClick: () => {
21
- session.setDrawerPosition(option);
22
- setAnchorEl(null);
23
- }, children: option }, option))) })] }));
24
- });
25
- export default DrawerControls;
1
+ export { DrawerControls as default } from '@jbrowse/product-core';
@@ -1,7 +1,6 @@
1
1
  import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util/types';
2
- declare const DrawerHeader: ({ session, setToolbarHeight, onPopoutDrawer, }: {
2
+ declare const DrawerHeader: ({ session, setToolbarHeight, }: {
3
3
  session: SessionWithFocusedViewAndDrawerWidgets;
4
4
  setToolbarHeight: (arg: number) => void;
5
- onPopoutDrawer: () => void;
6
- }) => import("react/jsx-runtime").JSX.Element;
5
+ }) => import("react").JSX.Element;
7
6
  export default DrawerHeader;
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Suspense, lazy } from 'react';
2
+ import { Suspense, lazy, useCallback } from 'react';
3
3
  import { getEnv } from '@jbrowse/core/util';
4
4
  import { makeStyles } from '@jbrowse/core/util/tss-react';
5
+ import { DrawerWidgetSelector } from '@jbrowse/product-core';
5
6
  import LaunchIcon from '@mui/icons-material/Launch';
6
7
  import { AppBar, IconButton, Toolbar, Tooltip } from '@mui/material';
7
8
  import { observer } from 'mobx-react';
8
- import DrawerControls from "./DrawerControls.js";
9
- import DrawerWidgetSelector from "./DrawerWidgetSelector.js";
10
- const DrawerHeaderHelpButton = lazy(() => import("./DrawerHeaderHelpButton.js"));
9
+ import DrawerControls from './DrawerControls.js';
10
+ const DrawerHeaderHelpButton = lazy(() => import('./DrawerHeaderHelpButton.js'));
11
11
  const useStyles = makeStyles()(theme => ({
12
12
  spacer: {
13
13
  flexGrow: 1,
@@ -19,7 +19,7 @@ const useStyles = makeStyles()(theme => ({
19
19
  background: theme.palette.secondary.dark,
20
20
  },
21
21
  }));
22
- const DrawerHeader = observer(function DrawerHeader({ session, setToolbarHeight, onPopoutDrawer, }) {
22
+ const DrawerHeader = observer(function DrawerHeader({ session, setToolbarHeight, }) {
23
23
  const { classes } = useStyles();
24
24
  const focusedViewId = session.focusedViewId;
25
25
  const { visibleWidget } = session;
@@ -28,13 +28,27 @@ const DrawerHeader = observer(function DrawerHeader({ session, setToolbarHeight,
28
28
  const widgetType = visibleWidget
29
29
  ? pluginManager.getWidgetType(visibleWidget.type)
30
30
  : undefined;
31
- const { helpText } = widgetType || {};
31
+ const { helpText } = widgetType ?? {};
32
+ // Measured, and re-measured: the toolbar's height is the room a virtualized
33
+ // widget subtracts from the box it is given, and it changes without this
34
+ // component re-rendering -- MUI's Toolbar is 56px under the `sm` breakpoint
35
+ // and 64 above it, and grows again with the root font size. Read once at
36
+ // mount, a widget's list was that much too tall for the rest of the session.
37
+ const appBarRef = useCallback((node) => {
38
+ const publish = () => {
39
+ setToolbarHeight(node.getBoundingClientRect().height);
40
+ };
41
+ publish();
42
+ const observer = new ResizeObserver(publish);
43
+ observer.observe(node);
44
+ return () => {
45
+ observer.disconnect();
46
+ };
47
+ }, [setToolbarHeight]);
32
48
  return (_jsx(AppBar, { position: "sticky", className: focusedViewId === viewWidgetId
33
49
  ? classes.headerFocused
34
- : classes.headerUnfocused, ref: ref => {
35
- setToolbarHeight(ref?.getBoundingClientRect().height || 0);
36
- }, children: _jsxs(Toolbar, { disableGutters: true, children: [_jsx(DrawerWidgetSelector, { session: session }), _jsx(Tooltip, { title: "Open drawer in dialog", children: _jsx(IconButton, { color: "inherit", onClick: () => {
37
- onPopoutDrawer();
50
+ : classes.headerUnfocused, ref: appBarRef, children: _jsxs(Toolbar, { disableGutters: true, children: [_jsx(DrawerWidgetSelector, { session: session }), _jsx(Tooltip, { title: "Open drawer in dialog", children: _jsx(IconButton, { color: "inherit", "data-testid": "drawer-popout", onClick: () => {
51
+ session.popoutWidget();
38
52
  }, children: _jsx(LaunchIcon, {}) }) }), helpText ? (_jsx(Suspense, { fallback: null, children: _jsx(DrawerHeaderHelpButton, { helpText: helpText, session: session }) })) : null, _jsx("div", { className: classes.spacer }), _jsx(DrawerControls, { session: session })] }) }));
39
53
  });
40
54
  export default DrawerHeader;
@@ -2,5 +2,5 @@ import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util/
2
2
  declare const DrawerHeaderHelpButton: ({ session, helpText, }: {
3
3
  session: SessionWithFocusedViewAndDrawerWidgets;
4
4
  helpText: React.ReactNode;
5
- }) => import("react/jsx-runtime").JSX.Element;
5
+ }) => import("react").JSX.Element;
6
6
  export default DrawerHeaderHelpButton;
@@ -1,9 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { lazy } from 'react';
3
- import HelpOutline from '@mui/icons-material/HelpOutline';
3
+ import HelpOutlined from '@mui/icons-material/HelpOutlined';
4
4
  import { IconButton, Tooltip } from '@mui/material';
5
5
  import { observer } from 'mobx-react';
6
- const DrawerHeaderHelpDialog = lazy(() => import("./DrawerHeaderHelpDialog.js"));
6
+ const DrawerHeaderHelpDialog = lazy(() => import('./DrawerHeaderHelpDialog.js'));
7
7
  const DrawerHeaderHelpButton = observer(function DrawerHeaderHelpButton({ session, helpText, }) {
8
8
  return (_jsx(Tooltip, { title: "Help", children: _jsx(IconButton, { color: "inherit", onClick: () => {
9
9
  session.queueDialog(handleClose => [
@@ -13,6 +13,6 @@ const DrawerHeaderHelpButton = observer(function DrawerHeaderHelpButton({ sessio
13
13
  helpText,
14
14
  },
15
15
  ]);
16
- }, children: _jsx(HelpOutline, {}) }) }));
16
+ }, children: _jsx(HelpOutlined, {}) }) }));
17
17
  });
18
18
  export default DrawerHeaderHelpButton;
@@ -1,4 +1,4 @@
1
1
  export default function DrawerHeaderHelpDialog({ onClose, helpText, }: {
2
- onClose: (event: React.MouseEvent | React.KeyboardEvent) => void;
2
+ onClose: () => void;
3
3
  helpText: React.ReactNode;
4
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;
@@ -1,7 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import Dialog from '@jbrowse/core/ui/Dialog';
2
+ import InfoDialog from '@jbrowse/core/ui/InfoDialog';
3
3
  import { makeStyles } from '@jbrowse/core/util/tss-react';
4
- import { DialogContent } from '@mui/material';
5
4
  const useStyles = makeStyles()({
6
5
  max: {
7
6
  minWidth: '40em',
@@ -9,7 +8,7 @@ const useStyles = makeStyles()({
9
8
  });
10
9
  export default function DrawerHeaderHelpDialog({ onClose, helpText, }) {
11
10
  const { classes } = useStyles();
12
- return (_jsx(Dialog, { open: true, onClose: onClose, title: "Help", maxWidth: "xl", onClick: e => {
11
+ return (_jsx(InfoDialog, { open: true, onClose: onClose, title: "Help", maxWidth: "xl", onClick: e => {
13
12
  e.stopPropagation();
14
- }, children: _jsx(DialogContent, { className: classes.max, children: helpText }) }));
13
+ }, children: _jsx("div", { className: classes.max, children: helpText }) }));
15
14
  }
@@ -1,5 +1,5 @@
1
1
  import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util/types';
2
2
  declare const DrawerWidget: ({ session, }: {
3
3
  session: SessionWithFocusedViewAndDrawerWidgets;
4
- }) => import("react/jsx-runtime").JSX.Element;
4
+ }) => import("react").JSX.Element;
5
5
  export default DrawerWidget;
@@ -1,27 +1,15 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Suspense, lazy, useState } from 'react';
3
- import { ErrorMessage, LoadingEllipses } from '@jbrowse/core/ui';
4
- import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary';
5
- import { getEnv } from '@jbrowse/core/util';
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { WidgetBody } from '@jbrowse/product-core';
6
4
  import { observer } from 'mobx-react';
7
- import Drawer from "./Drawer.js";
8
- import DrawerHeader from "./DrawerHeader.js";
9
- const ModalWidget = lazy(() => import("./ModalWidget.js"));
5
+ import Drawer from './Drawer.js';
6
+ import DrawerHeader from './DrawerHeader.js';
10
7
  const DrawerWidget = observer(function DrawerWidget({ session, }) {
11
8
  const { visibleWidget } = session;
12
- const { pluginManager } = getEnv(session);
13
- const DrawerComponent = visibleWidget
14
- ? pluginManager.evaluateExtensionPoint('Core-replaceWidget', pluginManager.getWidgetType(visibleWidget.type).ReactComponent, {
15
- session,
16
- model: visibleWidget,
17
- })
18
- : null;
9
+ // we track the toolbar height because components that use virtualized
10
+ // height want to be able to fill the contained, minus the toolbar height
11
+ // (the position static/sticky is included in AutoSizer estimates)
19
12
  const [toolbarHeight, setToolbarHeight] = useState(0);
20
- const [popoutDrawer, setPopoutDrawer] = useState(false);
21
- return (_jsxs(Drawer, { session: session, children: [_jsx(DrawerHeader, { onPopoutDrawer: () => {
22
- setPopoutDrawer(true);
23
- }, session: session, setToolbarHeight: setToolbarHeight }), _jsx(Suspense, { fallback: _jsx(LoadingEllipses, {}), children: _jsx(ErrorBoundary, { FallbackComponent: ({ error }) => _jsx(ErrorMessage, { error: error }), children: DrawerComponent ? (popoutDrawer ? (_jsxs(_Fragment, { children: [_jsx("div", { children: "Opened in dialog..." }), _jsx(ModalWidget, { session: session, onClose: () => {
24
- setPopoutDrawer(false);
25
- } })] })) : (_jsxs(_Fragment, { children: [_jsx(DrawerComponent, { model: visibleWidget, session: session, toolbarHeight: toolbarHeight }), _jsx("div", { style: { height: 300 } })] }))) : null }) })] }));
13
+ return (_jsxs(Drawer, { session: session, children: [_jsx(DrawerHeader, { session: session, setToolbarHeight: setToolbarHeight }), visibleWidget ? (_jsx(WidgetBody, { session: session, widget: visibleWidget, toolbarHeight: toolbarHeight })) : null] }));
26
14
  });
27
15
  export default DrawerWidget;
@@ -1,5 +1,4 @@
1
- export default function JBrowseTabMenu({ isHovered, onRename, onClose, }: {
2
- isHovered: boolean;
1
+ export default function JBrowseTabMenu({ onRename, onClose, }: {
3
2
  onRename: () => void;
4
3
  onClose: () => void;
5
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;