@jbrowse/app-core 4.2.1 → 5.0.0-beta.1

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 (154) 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 +167 -78
  14. package/esm/JBrowseConfig/RootConfiguration.js +85 -7
  15. package/esm/JBrowseConfig/index.d.ts +169 -84
  16. package/esm/JBrowseConfig/index.js +62 -2
  17. package/esm/JBrowseModel/index.d.ts +176 -95
  18. package/esm/JBrowseModel/index.js +122 -21
  19. package/esm/RootMenu/index.d.ts +77 -2
  20. package/esm/RootMenu/index.js +107 -61
  21. package/esm/SessionSpec/index.d.ts +6 -0
  22. package/esm/SessionSpec/index.js +3 -0
  23. package/esm/SessionSpec/lgvUrlInit.d.ts +79 -0
  24. package/esm/SessionSpec/lgvUrlInit.js +158 -0
  25. package/esm/SessionSpec/loadSessionSpec.d.ts +21 -0
  26. package/esm/SessionSpec/loadSessionSpec.js +311 -0
  27. package/esm/SessionSpec/parseSessionSpecUrl.d.ts +13 -0
  28. package/esm/SessionSpec/parseSessionSpecUrl.js +196 -0
  29. package/esm/SessionSpec/types.d.ts +33 -0
  30. package/esm/SessionSpec/types.js +1 -0
  31. package/esm/WorkspaceLayout/LayoutRenderer.d.ts +24 -0
  32. package/esm/WorkspaceLayout/LayoutRenderer.js +185 -0
  33. package/esm/WorkspaceLayout/PanelView.d.ts +13 -0
  34. package/esm/WorkspaceLayout/PanelView.js +99 -0
  35. package/esm/WorkspaceLayout/TabStrip.d.ts +14 -0
  36. package/esm/WorkspaceLayout/TabStrip.js +226 -0
  37. package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +16 -0
  38. package/esm/WorkspaceLayout/WorkspaceContainer.js +105 -0
  39. package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +24 -0
  40. package/esm/WorkspaceLayout/WorkspacePanelActions.js +120 -0
  41. package/esm/WorkspaceLayout/WorkspaceTab.d.ts +27 -0
  42. package/esm/WorkspaceLayout/WorkspaceTab.js +109 -0
  43. package/esm/WorkspaceLayout/dockviewTheme.d.ts +69 -0
  44. package/esm/WorkspaceLayout/dockviewTheme.js +82 -0
  45. package/esm/WorkspaceLayout/dropZone.d.ts +105 -0
  46. package/esm/WorkspaceLayout/dropZone.js +113 -0
  47. package/esm/WorkspaceLayout/index.d.ts +8 -0
  48. package/esm/WorkspaceLayout/index.js +4 -0
  49. package/esm/WorkspaceLayout/model.d.ts +240 -0
  50. package/esm/WorkspaceLayout/model.js +527 -0
  51. package/esm/WorkspaceLayout/panelChrome.d.ts +19 -0
  52. package/esm/WorkspaceLayout/panelChrome.js +4 -0
  53. package/esm/WorkspaceLayout/spec.d.ts +58 -0
  54. package/esm/WorkspaceLayout/spec.js +153 -0
  55. package/esm/WorkspaceLayout/splitter.d.ts +25 -0
  56. package/esm/WorkspaceLayout/splitter.js +37 -0
  57. package/esm/WorkspaceLayout/tree.d.ts +158 -0
  58. package/esm/WorkspaceLayout/tree.js +379 -0
  59. package/esm/WorkspaceLayout/useLayoutDrag.d.ts +34 -0
  60. package/esm/WorkspaceLayout/useLayoutDrag.js +232 -0
  61. package/esm/index.d.ts +16 -9
  62. package/esm/index.js +13 -9
  63. package/esm/menus.d.ts +63 -62
  64. package/esm/menus.js +131 -84
  65. package/esm/queryParams.d.ts +9 -0
  66. package/esm/queryParams.js +92 -0
  67. package/esm/sessionModelFactory.d.ts +18 -0
  68. package/esm/sessionModelFactory.js +1 -0
  69. package/esm/ui/App/App.d.ts +3 -14
  70. package/esm/ui/App/App.js +30 -17
  71. package/esm/ui/App/AppFab.d.ts +12 -3
  72. package/esm/ui/App/AppFab.js +15 -8
  73. package/esm/ui/App/AppReadyMarker.d.ts +34 -0
  74. package/esm/ui/App/AppReadyMarker.js +56 -0
  75. package/esm/ui/App/AppToolbar.d.ts +2 -14
  76. package/esm/ui/App/AppToolbar.js +23 -24
  77. package/esm/ui/App/ClassicViewsContainer.d.ts +3 -9
  78. package/esm/ui/App/ClassicViewsContainer.js +9 -8
  79. package/esm/ui/App/DialogQueue.d.ts +17 -1
  80. package/esm/ui/App/DialogQueue.js +30 -1
  81. package/esm/ui/App/Drawer.d.ts +1 -1
  82. package/esm/ui/App/Drawer.js +10 -39
  83. package/esm/ui/App/DrawerControls.d.ts +1 -5
  84. package/esm/ui/App/DrawerControls.js +1 -25
  85. package/esm/ui/App/DrawerHeader.d.ts +2 -3
  86. package/esm/ui/App/DrawerHeader.js +24 -10
  87. package/esm/ui/App/DrawerHeaderHelpButton.d.ts +1 -1
  88. package/esm/ui/App/DrawerHeaderHelpButton.js +3 -3
  89. package/esm/ui/App/DrawerHeaderHelpDialog.d.ts +2 -2
  90. package/esm/ui/App/DrawerHeaderHelpDialog.js +3 -4
  91. package/esm/ui/App/DrawerWidget.d.ts +1 -1
  92. package/esm/ui/App/DrawerWidget.js +9 -21
  93. package/esm/ui/App/JBrowseTabMenu.d.ts +2 -3
  94. package/esm/ui/App/JBrowseTabMenu.js +24 -5
  95. package/esm/ui/App/ViewButtons.d.ts +2 -4
  96. package/esm/ui/App/ViewButtons.js +15 -3
  97. package/esm/ui/App/ViewContainer.d.ts +3 -2
  98. package/esm/ui/App/ViewContainer.js +144 -24
  99. package/esm/ui/App/ViewContainerTitle.d.ts +1 -1
  100. package/esm/ui/App/ViewContainerTitle.js +9 -2
  101. package/esm/ui/App/ViewErrorFallback.d.ts +18 -0
  102. package/esm/ui/App/ViewErrorFallback.js +42 -0
  103. package/esm/ui/App/ViewHeader.d.ts +3 -4
  104. package/esm/ui/App/ViewHeader.js +28 -17
  105. package/esm/ui/App/ViewLauncher.d.ts +2 -8
  106. package/esm/ui/App/ViewLauncher.js +7 -5
  107. package/esm/ui/App/ViewMenu.d.ts +3 -5
  108. package/esm/ui/App/ViewMenu.js +87 -92
  109. package/esm/ui/App/ViewStack.d.ts +7 -0
  110. package/esm/ui/App/ViewStack.js +42 -0
  111. package/esm/ui/App/ViewWrapper.d.ts +15 -1
  112. package/esm/ui/App/ViewWrapper.js +26 -10
  113. package/esm/ui/App/ViewsContainer.d.ts +3 -8
  114. package/esm/ui/App/ViewsContainer.js +9 -6
  115. package/esm/ui/App/copyView.js +1 -1
  116. package/esm/ui/App/index.d.ts +2 -1
  117. package/esm/ui/App/index.js +3 -2
  118. package/esm/ui/App/lazyChunk.d.ts +13 -0
  119. package/esm/ui/App/lazyChunk.js +91 -0
  120. package/esm/ui/App/types.d.ts +12 -3
  121. package/esm/ui/App/types.js +1 -1
  122. package/esm/ui/App/useViewVisibility.d.ts +34 -0
  123. package/esm/ui/App/useViewVisibility.js +87 -0
  124. package/esm/ui/App/viewTitle.d.ts +5 -0
  125. package/esm/ui/App/viewTitle.js +9 -0
  126. package/esm/ui/index.d.ts +1 -1
  127. package/esm/ui/index.js +1 -1
  128. package/package.json +12 -17
  129. package/esm/AppFocus/index.d.ts +0 -5
  130. package/esm/AppFocus/index.js +0 -12
  131. package/esm/Assemblies/SessionAssembliesMixin.d.ts +0 -92
  132. package/esm/Assemblies/SessionAssembliesMixin.js +0 -53
  133. package/esm/Assemblies/TemporaryAssembliesMixin.d.ts +0 -10
  134. package/esm/Assemblies/TemporaryAssembliesMixin.js +0 -37
  135. package/esm/DockviewLayout/index.d.ts +0 -35
  136. package/esm/DockviewLayout/index.js +0 -111
  137. package/esm/ui/App/DockviewContext.d.ts +0 -22
  138. package/esm/ui/App/DockviewContext.js +0 -29
  139. package/esm/ui/App/DockviewLeftHeaderActions.d.ts +0 -2
  140. package/esm/ui/App/DockviewLeftHeaderActions.js +0 -58
  141. package/esm/ui/App/DockviewRightHeaderActions.d.ts +0 -2
  142. package/esm/ui/App/DockviewRightHeaderActions.js +0 -100
  143. package/esm/ui/App/DrawerWidgetSelector.d.ts +0 -5
  144. package/esm/ui/App/DrawerWidgetSelector.js +0 -47
  145. package/esm/ui/App/JBrowseViewPanel.d.ts +0 -8
  146. package/esm/ui/App/JBrowseViewPanel.js +0 -40
  147. package/esm/ui/App/JBrowseViewTab.d.ts +0 -8
  148. package/esm/ui/App/JBrowseViewTab.js +0 -97
  149. package/esm/ui/App/ModalWidget.d.ts +0 -6
  150. package/esm/ui/App/ModalWidget.js +0 -48
  151. package/esm/ui/App/TiledViewsContainer.d.ts +0 -6
  152. package/esm/ui/App/TiledViewsContainer.js +0 -366
  153. package/esm/ui/App/dockviewUtils.d.ts +0 -46
  154. 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,34 @@
1
+ import type { AppSession } from './types.ts';
2
+ /**
3
+ * One element that says whether the whole app has finished.
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
+ * Its own component, and `hidden`, for two reasons: an observer here subscribes
18
+ * to every view's phase without re-rendering the app shell around it, and a
19
+ * marker that is never drawn cannot be mistaken for UI or shift a layout. It is
20
+ * still found by `querySelector`, which is what reads it.
21
+ *
22
+ * **What it is silent about**: a display in a TERMINAL state. `error` is a
23
+ * finished state, so this reads `ready` over a display whose fetch failed —
24
+ * correctly, since nothing is still working. A capture wants more than that: it
25
+ * wants a picture, and an error banner is not one. `waitForDisplaysDone` after
26
+ * this is what draws that line, keying on the `data-display-drawn` the two
27
+ * comparative canvases publish from their stricter `settled` gate (see
28
+ * `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
29
+ * and says why an error separates them). Both harnesses do it in that order.
30
+ */
31
+ declare const AppReadyMarker: ({ session, }: {
32
+ session: AppSession;
33
+ }) => import("react").JSX.Element;
34
+ export default AppReadyMarker;
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { observer } from 'mobx-react';
3
+ function containerLoading(container) {
4
+ return (container.tracks ?? []).some(track => (track.displays ?? []).some(d => d.displayPhase === 'loading'));
5
+ }
6
+ /**
7
+ * Is anything in this session still working?
8
+ *
9
+ * The same two facts the per-element attributes publish — a view still
10
+ * resolving its assembly, a display still in its own fetch — reduced to one
11
+ * answer for the whole app. Duck-typed rather than imported: every view type
12
+ * would otherwise have to be a dependency of the app shell.
13
+ */
14
+ function anythingLoading(views) {
15
+ return views.some(view => view.showLoading === true ||
16
+ view.initialized === false ||
17
+ containerLoading(view) ||
18
+ // A view whose tracks hang off something else — the synteny view's are on
19
+ // its levels, one list per band. `view.tracks` is empty there, so without
20
+ // this a stack of ribbons still fetching reads as idle.
21
+ (view.trackContainers ?? []).some(containerLoading) ||
22
+ anythingLoading(view.views ?? []));
23
+ }
24
+ /**
25
+ * One element that says whether the whole app has finished.
26
+ *
27
+ * `[data-app-phase="ready"]` is the entire readiness contract for anything
28
+ * driving JBrowse from outside — a screenshot tool, a test, an agent. Wait for
29
+ * that selector and you are done; there is no chain to assemble and no
30
+ * per-display census to take.
31
+ *
32
+ * Why it exists when `data-view-phase` and `data-display-phase` already do:
33
+ * those are per-element and NEGATIVE. Asking them "is everything finished"
34
+ * means asserting that no element carries the loading value, which is equally
35
+ * true of an app that has not started — and the gap is real, measured at about
36
+ * a second on a two-track session. A positive attribute cannot be satisfied
37
+ * early, because something has to render it.
38
+ *
39
+ * Its own component, and `hidden`, for two reasons: an observer here subscribes
40
+ * to every view's phase without re-rendering the app shell around it, and a
41
+ * marker that is never drawn cannot be mistaken for UI or shift a layout. It is
42
+ * still found by `querySelector`, which is what reads it.
43
+ *
44
+ * **What it is silent about**: a display in a TERMINAL state. `error` is a
45
+ * finished state, so this reads `ready` over a display whose fetch failed —
46
+ * correctly, since nothing is still working. A capture wants more than that: it
47
+ * wants a picture, and an error banner is not one. `waitForDisplaysDone` after
48
+ * this is what draws that line, keying on the `data-display-drawn` the two
49
+ * comparative canvases publish from their stricter `settled` gate (see
50
+ * `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
51
+ * and says why an error separates them). Both harnesses do it in that order.
52
+ */
53
+ const AppReadyMarker = observer(function AppReadyMarker({ session, }) {
54
+ return (_jsx("span", { hidden: true, "data-testid": "app-ready-marker", "data-app-phase": anythingLoading(session.views) ? 'loading' : 'ready' }));
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;