@jbrowse/app-core 4.3.0 → 5.0.0-beta.10

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 (165) 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 +1324 -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 +294 -0
  8. package/esm/Assemblies/AssembliesMixin.js +154 -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 +11 -0
  12. package/esm/HistoryManagement/index.js +43 -10
  13. package/esm/JBrowseConfig/RootConfiguration.d.ts +123 -78
  14. package/esm/JBrowseConfig/RootConfiguration.js +79 -7
  15. package/esm/JBrowseConfig/defaultShareUrl.d.ts +1 -0
  16. package/esm/JBrowseConfig/defaultShareUrl.js +5 -0
  17. package/esm/JBrowseConfig/index.d.ts +175 -84
  18. package/esm/JBrowseConfig/index.js +64 -2
  19. package/esm/JBrowseModel/index.d.ts +175 -97
  20. package/esm/JBrowseModel/index.js +132 -31
  21. package/esm/JbApi/index.d.ts +2 -0
  22. package/esm/JbApi/index.js +1 -0
  23. package/esm/JbApi/jbApi.d.ts +2767 -0
  24. package/esm/JbApi/jbApi.js +1383 -0
  25. package/esm/RootMenu/index.d.ts +69 -3
  26. package/esm/RootMenu/index.js +96 -61
  27. package/esm/SessionSpec/index.d.ts +6 -0
  28. package/esm/SessionSpec/index.js +3 -0
  29. package/esm/SessionSpec/lgvUrlInit.d.ts +79 -0
  30. package/esm/SessionSpec/lgvUrlInit.js +161 -0
  31. package/esm/SessionSpec/loadSessionSpec.d.ts +75 -0
  32. package/esm/SessionSpec/loadSessionSpec.js +473 -0
  33. package/esm/SessionSpec/parseSessionSpecUrl.d.ts +7 -0
  34. package/esm/SessionSpec/parseSessionSpecUrl.js +181 -0
  35. package/esm/SessionSpec/types.d.ts +37 -0
  36. package/esm/SessionSpec/types.js +1 -0
  37. package/esm/WorkspaceLayout/LayoutRenderer.d.ts +24 -0
  38. package/esm/WorkspaceLayout/LayoutRenderer.js +185 -0
  39. package/esm/WorkspaceLayout/PanelView.d.ts +13 -0
  40. package/esm/WorkspaceLayout/PanelView.js +99 -0
  41. package/esm/WorkspaceLayout/TabStrip.d.ts +14 -0
  42. package/esm/WorkspaceLayout/TabStrip.js +226 -0
  43. package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +16 -0
  44. package/esm/WorkspaceLayout/WorkspaceContainer.js +107 -0
  45. package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +24 -0
  46. package/esm/WorkspaceLayout/WorkspacePanelActions.js +120 -0
  47. package/esm/WorkspaceLayout/WorkspaceTab.d.ts +27 -0
  48. package/esm/WorkspaceLayout/WorkspaceTab.js +105 -0
  49. package/esm/WorkspaceLayout/dockviewTheme.d.ts +69 -0
  50. package/esm/WorkspaceLayout/dockviewTheme.js +82 -0
  51. package/esm/WorkspaceLayout/dropZone.d.ts +105 -0
  52. package/esm/WorkspaceLayout/dropZone.js +113 -0
  53. package/esm/WorkspaceLayout/index.d.ts +8 -0
  54. package/esm/WorkspaceLayout/index.js +4 -0
  55. package/esm/WorkspaceLayout/model.d.ts +260 -0
  56. package/esm/WorkspaceLayout/model.js +586 -0
  57. package/esm/WorkspaceLayout/panelChrome.d.ts +19 -0
  58. package/esm/WorkspaceLayout/panelChrome.js +4 -0
  59. package/esm/WorkspaceLayout/spec.d.ts +88 -0
  60. package/esm/WorkspaceLayout/spec.js +226 -0
  61. package/esm/WorkspaceLayout/splitter.d.ts +25 -0
  62. package/esm/WorkspaceLayout/splitter.js +37 -0
  63. package/esm/WorkspaceLayout/tree.d.ts +158 -0
  64. package/esm/WorkspaceLayout/tree.js +379 -0
  65. package/esm/WorkspaceLayout/useLayoutDrag.d.ts +34 -0
  66. package/esm/WorkspaceLayout/useLayoutDrag.js +232 -0
  67. package/esm/index.d.ts +18 -9
  68. package/esm/index.js +16 -9
  69. package/esm/menus.d.ts +55 -68
  70. package/esm/menus.js +117 -86
  71. package/esm/queryParams.d.ts +9 -0
  72. package/esm/queryParams.js +93 -0
  73. package/esm/sessionModelFactory.d.ts +18 -0
  74. package/esm/sessionModelFactory.js +1 -0
  75. package/esm/ui/App/App.d.ts +3 -14
  76. package/esm/ui/App/App.js +39 -19
  77. package/esm/ui/App/AppFab.d.ts +12 -3
  78. package/esm/ui/App/AppFab.js +15 -8
  79. package/esm/ui/App/AppToolbar.d.ts +2 -14
  80. package/esm/ui/App/AppToolbar.js +45 -31
  81. package/esm/ui/App/ClassicViewsContainer.d.ts +4 -10
  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/DrawerHeader.d.ts +2 -3
  88. package/esm/ui/App/DrawerHeader.js +24 -11
  89. package/esm/ui/App/DrawerHeaderHelpButton.d.ts +1 -1
  90. package/esm/ui/App/DrawerHeaderHelpButton.js +3 -3
  91. package/esm/ui/App/DrawerHeaderHelpDialog.d.ts +2 -2
  92. package/esm/ui/App/DrawerHeaderHelpDialog.js +3 -4
  93. package/esm/ui/App/DrawerWidget.d.ts +1 -1
  94. package/esm/ui/App/DrawerWidget.js +9 -21
  95. package/esm/ui/App/JBrowseTabMenu.d.ts +2 -3
  96. package/esm/ui/App/JBrowseTabMenu.js +24 -5
  97. package/esm/ui/App/ThemeModeButton.d.ts +12 -0
  98. package/esm/ui/App/ThemeModeButton.js +34 -0
  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 +128 -24
  103. package/esm/ui/App/ViewContainerTitle.d.ts +1 -1
  104. package/esm/ui/App/ViewContainerTitle.js +21 -11
  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 +48 -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 +4 -11
  118. package/esm/ui/App/ViewsContainer.js +6 -9
  119. package/esm/ui/App/index.d.ts +2 -1
  120. package/esm/ui/App/index.js +3 -2
  121. package/esm/ui/App/lazyChunk.d.ts +13 -0
  122. package/esm/ui/App/lazyChunk.js +91 -0
  123. package/esm/ui/App/lazyParts.d.ts +32 -0
  124. package/esm/ui/App/lazyParts.js +8 -0
  125. package/esm/ui/App/preloadAppShell.d.ts +14 -0
  126. package/esm/ui/App/preloadAppShell.js +41 -0
  127. package/esm/ui/App/types.d.ts +17 -3
  128. package/esm/ui/App/types.js +1 -1
  129. package/esm/ui/App/useViewVisibility.d.ts +35 -0
  130. package/esm/ui/App/useViewVisibility.js +88 -0
  131. package/esm/ui/App/viewTitle.d.ts +12 -0
  132. package/esm/ui/App/viewTitle.js +17 -0
  133. package/esm/ui/index.d.ts +1 -1
  134. package/esm/ui/index.js +1 -1
  135. package/package.json +12 -17
  136. package/esm/AppFocus/index.d.ts +0 -5
  137. package/esm/AppFocus/index.js +0 -12
  138. package/esm/Assemblies/SessionAssembliesMixin.d.ts +0 -92
  139. package/esm/Assemblies/SessionAssembliesMixin.js +0 -53
  140. package/esm/Assemblies/TemporaryAssembliesMixin.d.ts +0 -10
  141. package/esm/Assemblies/TemporaryAssembliesMixin.js +0 -37
  142. package/esm/DockviewLayout/index.d.ts +0 -35
  143. package/esm/DockviewLayout/index.js +0 -111
  144. package/esm/ui/App/DockviewContext.d.ts +0 -22
  145. package/esm/ui/App/DockviewContext.js +0 -29
  146. package/esm/ui/App/DockviewLeftHeaderActions.d.ts +0 -2
  147. package/esm/ui/App/DockviewLeftHeaderActions.js +0 -58
  148. package/esm/ui/App/DockviewRightHeaderActions.d.ts +0 -2
  149. package/esm/ui/App/DockviewRightHeaderActions.js +0 -100
  150. package/esm/ui/App/DrawerControls.d.ts +0 -5
  151. package/esm/ui/App/DrawerControls.js +0 -25
  152. package/esm/ui/App/DrawerWidgetSelector.d.ts +0 -5
  153. package/esm/ui/App/DrawerWidgetSelector.js +0 -47
  154. package/esm/ui/App/JBrowseViewPanel.d.ts +0 -8
  155. package/esm/ui/App/JBrowseViewPanel.js +0 -40
  156. package/esm/ui/App/JBrowseViewTab.d.ts +0 -8
  157. package/esm/ui/App/JBrowseViewTab.js +0 -97
  158. package/esm/ui/App/ModalWidget.d.ts +0 -6
  159. package/esm/ui/App/ModalWidget.js +0 -48
  160. package/esm/ui/App/TiledViewsContainer.d.ts +0 -6
  161. package/esm/ui/App/TiledViewsContainer.js +0 -366
  162. package/esm/ui/App/copyView.d.ts +0 -1
  163. package/esm/ui/App/copyView.js +0 -29
  164. package/esm/ui/App/dockviewUtils.d.ts +0 -46
  165. package/esm/ui/App/dockviewUtils.js +0 -55
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Suspense, useCallback, useEffect, useMemo } from 'react';
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
+ import { autorun } from 'mobx';
5
+ import { observer } from 'mobx-react';
6
+ import ViewStack from '../ui/App/ViewStack.js';
7
+ import { ViewLauncher } from '../ui/App/lazyParts.js';
8
+ import { LayoutRenderer } from './LayoutRenderer.js';
9
+ import { WorkspacePanelActions } from './WorkspacePanelActions.js';
10
+ import { WorkspaceTab } from './WorkspaceTab.js';
11
+ import { useLayoutDrag } from './useLayoutDrag.js';
12
+ const useStyles = makeStyles()(theme => ({
13
+ container: {
14
+ height: '100%',
15
+ width: '100%',
16
+ display: 'flex',
17
+ gridRow: 'components',
18
+ background: theme.palette.background.default,
19
+ },
20
+ stack: {
21
+ display: 'flex',
22
+ flexDirection: 'column',
23
+ minWidth: 0,
24
+ },
25
+ empty: {
26
+ display: 'flex',
27
+ alignItems: 'center',
28
+ justifyContent: 'center',
29
+ height: '100%',
30
+ },
31
+ }));
32
+ /**
33
+ * The workspace. Compare `TiledViewsContainer` + `useDockviewController`, which
34
+ * this replaces: there is no api to hold, no `onReady`, no event to subscribe
35
+ * to, and no reconciliation — the layout is session state and this renders it.
36
+ *
37
+ * The one reaction left is homing: `session.views` is owned by the session, so
38
+ * a view launched from a menu arrives belonging to no tab and has to land
39
+ * somewhere. That is one-directional and idempotent, and nothing reads back.
40
+ */
41
+ export const WorkspaceContainer = observer(function WorkspaceContainer({ session, }) {
42
+ const { classes } = useStyles();
43
+ const { drag, handlers } = useLayoutDrag(session);
44
+ // #region autorunInEffect
45
+ useEffect(() => autorun(() => {
46
+ // reads session.views itself, so it re-runs when the view set changes;
47
+ // homeUnassignedViews is an action and would not be tracked from inside
48
+ session.homeUnassignedViews(session.views.map(v => v.id));
49
+ }), [session]);
50
+ // #endregion
51
+ // The layout does not own views, so closing anything that holds them is
52
+ // explicitly the pair. Stated ONCE, here, because every spelling of it is a
53
+ // chance to drop the node and leave its views in the session forever — and
54
+ // three gestures want it now: the tab's ⋮ menu, middle-clicking the tab, and
55
+ // the cell's ×.
56
+ const closeViews = useCallback((viewIds) => {
57
+ for (const view of viewsOf(session, viewIds)) {
58
+ session.removeView(view);
59
+ }
60
+ }, [session]);
61
+ const closeTab = useCallback((tabId) => {
62
+ const tab = session.findTab(tabId)?.tab;
63
+ if (!tab) {
64
+ return;
65
+ }
66
+ closeViews(tab.viewIds);
67
+ session.closeTab(tabId);
68
+ }, [session, closeViews]);
69
+ const closePanel = useCallback((panelId) => {
70
+ const panel = session.panels.find(p => p.id === panelId);
71
+ if (!panel) {
72
+ return;
73
+ }
74
+ closeViews(panel.tabs.flatMap(t => t.viewIds));
75
+ session.closePanel(panelId);
76
+ }, [session, closeViews]);
77
+ /**
78
+ * Memoised because this reaches every panel, so a fresh one per render
79
+ * defeats `observer`'s memo for all of them and each re-render rebuilds a
80
+ * `ViewStack`. `drag` is deliberately NOT in here — it goes down its own prop
81
+ * so it reaches only the cell it describes.
82
+ *
83
+ * Nothing does this for us: `observer(function(){})` is not compiled by the
84
+ * React Compiler.
85
+ */
86
+ const chrome = useMemo(() => ({
87
+ dragHandlers: handlers,
88
+ onTabClose: closeTab,
89
+ renderPanelActions: panel => (_jsx(WorkspacePanelActions, { panel: panel, session: session, onClose: () => {
90
+ closePanel(panel.id);
91
+ } })),
92
+ renderTabLabel: tab => (_jsx(WorkspaceTab, { tab: tab, views: viewsOf(session, tab.viewIds), session: session, layout: session, onClose: () => {
93
+ closeTab(tab.id);
94
+ } })),
95
+ renderTabContent: tab => {
96
+ const views = viewsOf(session, tab.viewIds);
97
+ return views.length > 0 ? (_jsx(ViewStack, { views: views, session: session, className: classes.stack })) : (_jsx("div", { className: classes.empty, children: _jsx(Suspense, { fallback: null, children: _jsx(ViewLauncher, { session: session }) }) }));
98
+ },
99
+ }), [session, handlers, closeTab, closePanel, classes.stack, classes.empty]);
100
+ return (_jsx("div", { className: classes.container, "data-testid": "workspace", children: _jsx(LayoutRenderer, { node: session.visibleTree, layout: session, drag: drag, chrome: chrome }) }));
101
+ });
102
+ // `session.views` is the one ordering of views, in both layout modes, so a
103
+ // tab's membership list is filtered through it rather than read as an order.
104
+ function viewsOf(session, viewIds) {
105
+ const members = new Set(viewIds);
106
+ return session.views.filter(v => members.has(v.id));
107
+ }
@@ -0,0 +1,24 @@
1
+ import type { WorkspaceSessionType } from '../ui/App/types.ts';
2
+ import type { WorkspaceLayout } from './model.ts';
3
+ import type { PanelNode } from './tree.ts';
4
+ /**
5
+ * The per-cell buttons: new tab, split, close.
6
+ *
7
+ * Every one of these is a single MST action on the layout, where the dockview
8
+ * versions were an api call plus a session write that had to be kept consistent
9
+ * with it — `handleSplit` was `containerApi.addGroup(...)` followed by
10
+ * `addEmptyTab(newGroup)`, and the gap between those two was where the
11
+ * "resource is already disposed" crash lived.
12
+ */
13
+ export declare const WorkspacePanelActions: ({ panel, session, onClose, }: {
14
+ panel: PanelNode;
15
+ session: WorkspaceSessionType & WorkspaceLayout;
16
+ /**
17
+ * Closing a cell closes the views its tabs held, and the layout does not own
18
+ * views — so the pair is one function at the call site, the same way
19
+ * `WorkspaceTab` takes its `onClose`. Spelled here as well, this file would
20
+ * be the third place stating "and also remove the views", which is one more
21
+ * than the number that can be kept in step.
22
+ */
23
+ onClose: () => void;
24
+ }) => import("react").JSX.Element;
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
+ import AddIcon from '@mui/icons-material/Add';
5
+ import CloseIcon from '@mui/icons-material/Close';
6
+ import CloseFullscreenIcon from '@mui/icons-material/CloseFullscreen';
7
+ import DynamicFeedIcon from '@mui/icons-material/DynamicFeed';
8
+ import HorizontalSplitIcon from '@mui/icons-material/HorizontalSplit';
9
+ import OpenInFullIcon from '@mui/icons-material/OpenInFull';
10
+ import TabIcon from '@mui/icons-material/Tab';
11
+ import TableRowsIcon from '@mui/icons-material/TableRows';
12
+ import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
13
+ import ViewColumnIcon from '@mui/icons-material/ViewColumn';
14
+ import ViewModuleIcon from '@mui/icons-material/ViewModule';
15
+ import { IconButton, Tooltip } from '@mui/material';
16
+ import { observer } from 'mobx-react';
17
+ import { dv } from './dockviewTheme.js';
18
+ const useStyles = makeStyles()({
19
+ actions: { display: 'flex', alignItems: 'center', flex: '0 0 auto' },
20
+ // dockview's `.dv-default-tab-action`: inherits the strip's colour, and takes
21
+ // a small rounded chip on hover rather than MUI's circular ripple surface
22
+ button: {
23
+ padding: 4,
24
+ borderRadius: 2,
25
+ color: dv.activeGroupHiddenTabColor,
26
+ '&:hover': { background: dv.iconHoverBackground, borderRadius: 2 },
27
+ },
28
+ icon: { fontSize: 16 },
29
+ });
30
+ /**
31
+ * The per-cell buttons: new tab, split, close.
32
+ *
33
+ * Every one of these is a single MST action on the layout, where the dockview
34
+ * versions were an api call plus a session write that had to be kept consistent
35
+ * with it — `handleSplit` was `containerApi.addGroup(...)` followed by
36
+ * `addEmptyTab(newGroup)`, and the gap between those two was where the
37
+ * "resource is already disposed" crash lived.
38
+ */
39
+ export const WorkspacePanelActions = observer(function WorkspacePanelActions({ panel, session, onClose, }) {
40
+ const { classes } = useStyles();
41
+ const canClose = session.panels.length > 1;
42
+ const maximized = session.maximizedPanelId === panel.id;
43
+ const tile = (mode) => {
44
+ session.tileViews(mode, session.views.map(v => v.id));
45
+ };
46
+ return (_jsxs("div", { className: classes.actions, children: [_jsx(CascadingMenuButton, { menuItems: [
47
+ {
48
+ label: 'New empty tab',
49
+ icon: TabIcon,
50
+ onClick: () => {
51
+ session.addTab(panel.id);
52
+ },
53
+ },
54
+ {
55
+ label: 'New empty split horizontal',
56
+ icon: VerticalSplitIcon,
57
+ onClick: () => {
58
+ session.splitPanel(panel.id, 'row');
59
+ },
60
+ },
61
+ {
62
+ label: 'New empty split vertical',
63
+ icon: HorizontalSplitIcon,
64
+ onClick: () => {
65
+ session.splitPanel(panel.id, 'column');
66
+ },
67
+ },
68
+ // The strip's double-click is the gesture; this is how anyone finds
69
+ // it, and the only way to reach it from the keyboard. Gated on there
70
+ // being another cell to hide, since maximizing the only one is a
71
+ // no-op with a label promising otherwise.
72
+ ...(session.panels.length > 1 || maximized
73
+ ? [
74
+ {
75
+ label: maximized ? 'Restore panel' : 'Maximize panel',
76
+ icon: maximized ? CloseFullscreenIcon : OpenInFullIcon,
77
+ onClick: () => {
78
+ session.toggleMaximizedPanel(panel.id);
79
+ },
80
+ },
81
+ ]
82
+ : []),
83
+ // The whole-workspace commands, kept behind the same "Global:" prefix
84
+ // and the same >1-view gate they had on the dockview header, so a
85
+ // returning user finds them where they were and the label still says
86
+ // that the button in THIS cell rearranges every cell.
87
+ ...(session.views.length > 1
88
+ ? [
89
+ {
90
+ label: 'Global: change layout into set of tabs',
91
+ icon: DynamicFeedIcon,
92
+ onClick: () => {
93
+ tile('tabs');
94
+ },
95
+ },
96
+ {
97
+ label: 'Global: tile horizontally',
98
+ icon: ViewColumnIcon,
99
+ onClick: () => {
100
+ tile('horizontal');
101
+ },
102
+ },
103
+ {
104
+ label: 'Global: tile vertically',
105
+ icon: TableRowsIcon,
106
+ onClick: () => {
107
+ tile('vertical');
108
+ },
109
+ },
110
+ {
111
+ label: 'Global: tile grid',
112
+ icon: ViewModuleIcon,
113
+ onClick: () => {
114
+ tile('grid');
115
+ },
116
+ },
117
+ ]
118
+ : []),
119
+ ], size: "small", className: classes.button, children: _jsx(AddIcon, { className: classes.icon }) }), canClose ? (_jsx(Tooltip, { title: "Close panel", children: _jsx(IconButton, { size: "small", className: classes.button, onClick: onClose, children: _jsx(CloseIcon, { className: classes.icon }) }) })) : null] }));
120
+ });
@@ -0,0 +1,27 @@
1
+ import type { WorkspaceSessionType } from '../ui/App/types.ts';
2
+ import type { WorkspaceLayout } from './model.ts';
3
+ import type { TabNode } from './tree.ts';
4
+ import type { AbstractViewModel } from '@jbrowse/core/util';
5
+ /**
6
+ * A tab's name is **derived** from the views it holds, unless the user set one.
7
+ *
8
+ * `title === undefined` is the sentinel for "not renamed", which is a plain
9
+ * `maybe` on the tab rather than the comparison the dockview version needed:
10
+ * dockview restores an unset title as the panel id, so telling a real title
11
+ * from a restored one meant testing `title !== panelId` and hoping nobody named
12
+ * a tab after a panel.
13
+ */
14
+ export declare function tabDisplayName(tab: TabNode, views: AbstractViewModel[], session: WorkspaceSessionType): string;
15
+ export declare const WorkspaceTab: ({ tab, views, session, layout, onClose, }: {
16
+ tab: TabNode;
17
+ views: AbstractViewModel[];
18
+ session: WorkspaceSessionType;
19
+ layout: WorkspaceLayout;
20
+ /**
21
+ * Closing a tab closes the views it held, and the layout does not own views —
22
+ * so the pair is one function at the call site rather than spelled out here.
23
+ * The strip's middle-click needs the same pair, and two spellings of "and
24
+ * also remove the views" is how one of them comes to be missing it.
25
+ */
26
+ onClose: () => void;
27
+ }) => import("react").JSX.Element;
@@ -0,0 +1,105 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { makeStyles } from '@jbrowse/core/util/tss-react';
4
+ import { InputBase, Typography } from '@mui/material';
5
+ import { observer } from 'mobx-react';
6
+ import JBrowseTabMenu from '../ui/App/JBrowseTabMenu.js';
7
+ import { viewName } from '../ui/App/viewTitle.js';
8
+ const useStyles = makeStyles()(theme => ({
9
+ // Only the label and its menu. The background, the selected state and the
10
+ // hover live on the strip's own `[role=tab]` wrapper, which is where the
11
+ // selection is known — two places styling one tab is how they drift.
12
+ tab: {
13
+ display: 'flex',
14
+ alignItems: 'center',
15
+ gap: 4,
16
+ minWidth: 0,
17
+ },
18
+ // dockview's `.dv-default-tab-content`: takes the space, truncates with an
19
+ // ellipsis, and takes its colour from the tab, which is what encodes
20
+ // active/inactive
21
+ title: {
22
+ flexGrow: 1,
23
+ marginRight: 4,
24
+ overflow: 'hidden',
25
+ textOverflow: 'ellipsis',
26
+ whiteSpace: 'nowrap',
27
+ fontSize: 'inherit',
28
+ color: 'inherit',
29
+ },
30
+ editInput: {
31
+ fontSize: 'inherit',
32
+ padding: '2px 4px',
33
+ color: theme.palette.text.primary,
34
+ backgroundColor: theme.palette.background.paper,
35
+ borderRadius: theme.shape.borderRadius,
36
+ flex: 1,
37
+ },
38
+ }));
39
+ /**
40
+ * A tab's name is **derived** from the views it holds, unless the user set one.
41
+ *
42
+ * `title === undefined` is the sentinel for "not renamed", which is a plain
43
+ * `maybe` on the tab rather than the comparison the dockview version needed:
44
+ * dockview restores an unset title as the panel id, so telling a real title
45
+ * from a restored one meant testing `title !== panelId` and hoping nobody named
46
+ * a tab after a panel.
47
+ */
48
+ export function tabDisplayName(tab, views, session) {
49
+ if (tab.title) {
50
+ return tab.title;
51
+ }
52
+ if (views.length === 0) {
53
+ return 'Empty';
54
+ }
55
+ if (views.length === 1) {
56
+ return viewName(views[0], r => session.assemblyManager.getDisplayName(r));
57
+ }
58
+ return `${views.length} views`;
59
+ }
60
+ export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session, layout, onClose, }) {
61
+ const { classes } = useStyles();
62
+ const [editing, setEditing] = useState(false);
63
+ const [draft, setDraft] = useState('');
64
+ const title = tabDisplayName(tab, views, session);
65
+ const save = () => {
66
+ // An empty box means "go back to the automatic name". `title === undefined`
67
+ // is the sentinel for "not renamed" and `renameTab` takes it, but nothing
68
+ // in the UI passed it: clearing the box just discarded the edit, so a
69
+ // rename could be made and never unmade.
70
+ layout.renameTab(tab.id, draft.trim() || undefined);
71
+ setEditing(false);
72
+ };
73
+ return (_jsx("div", { className: classes.tab, children: editing ? (_jsx(InputBase, { autoFocus: true, className: classes.editInput, value: draft, onChange: e => {
74
+ setDraft(e.target.value);
75
+ }, onFocus: e => {
76
+ e.target.select();
77
+ }, onBlur: save, onKeyDown: e => {
78
+ // The box sits INSIDE the `role="tab"` that owns the strip's roving
79
+ // tabindex, and every key it handles — the arrows, Home, End, Enter
80
+ // and Space — it also preventDefault()s. All of them reach that
81
+ // handler by bubbling out of here, so while the box is open the
82
+ // arrows jumped to the next tab instead of moving the caret and a
83
+ // space never reached the input at all: a tab could not be given a
84
+ // name with a space in it.
85
+ e.stopPropagation();
86
+ if (e.key === 'Enter') {
87
+ save();
88
+ }
89
+ else if (e.key === 'Escape') {
90
+ setEditing(false);
91
+ }
92
+ },
93
+ // the tab strip starts a drag on pointerdown; renaming must not
94
+ onPointerDown: e => {
95
+ e.stopPropagation();
96
+ }, onClick: e => {
97
+ e.stopPropagation();
98
+ } })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { className: classes.title, variant: "body2", onDoubleClick: () => {
99
+ setDraft(title);
100
+ setEditing(true);
101
+ }, children: title }), _jsx(JBrowseTabMenu, { onRename: () => {
102
+ setDraft(title);
103
+ setEditing(true);
104
+ }, onClose: onClose })] })) }));
105
+ });
@@ -0,0 +1,69 @@
1
+ /**
2
+ * dockview's `dockview-theme-dark`, transcribed.
3
+ *
4
+ * We render the workspace ourselves now, but the look is deliberately not a new
5
+ * design — it is the theme we already shipped, so nothing about the chrome
6
+ * changed for users when the engine underneath it did. Every value below is
7
+ * copied from `dockview/dist/styles/dockview.css`'s `.dockview-theme-dark`
8
+ * block (v8.0.0); the names are its CSS custom properties with the `--dv-`
9
+ * prefix dropped.
10
+ *
11
+ * **Fixed, not theme-derived.** These apply in a light JBrowse theme too, the
12
+ * same way the app bar is dark in both — the chrome frames the content rather
13
+ * than matching it. Don't wire them to `theme.palette`: a light strip reads as
14
+ * content, which is exactly what it looked like before this file existed.
15
+ *
16
+ * **This is the chrome only, and the panel BODY is not chrome.** dockview also
17
+ * has a `--dv-group-view-background-color` (`#1e1e1e`) for the surface a tab's
18
+ * content sits on, and transcribing it is what made a light JBrowse theme come
19
+ * up dark below the views — the frame swallowing the thing it frames. The body
20
+ * follows `theme.palette.background.default` in `PanelView` instead, so the
21
+ * cell matches every other surface the app draws content on. It is deliberately
22
+ * absent here rather than present and unused, since an unused colour named
23
+ * after the panel reads as a value someone forgot to wire up.
24
+ *
25
+ * A consequence worth knowing before "fixing" it: dockview's selected tab is
26
+ * `#1e1e1e` **because** the body was, so the tab merged into its content. Ours
27
+ * cannot merge into a light body, and the four values below are kept anyway —
28
+ * darker-than-the-strip still reads as selected, and it is the one cue that
29
+ * survives being the only dark band on screen.
30
+ *
31
+ * Kept in one module because four components draw parts of the same chrome, and
32
+ * the four-way tab colouring below is a rule about the whole, not about any one
33
+ * of them.
34
+ */
35
+ export declare const dv: {
36
+ /** the tab strip itself */
37
+ readonly tabsBackground: '#252526';
38
+ readonly activeGroupVisibleTabBackground: '#1e1e1e';
39
+ readonly activeGroupHiddenTabBackground: '#2d2d2d';
40
+ readonly inactiveGroupVisibleTabBackground: '#1e1e1e';
41
+ readonly inactiveGroupHiddenTabBackground: '#2d2d2d';
42
+ readonly activeGroupVisibleTabColor: 'white';
43
+ readonly activeGroupHiddenTabColor: '#969696';
44
+ readonly inactiveGroupVisibleTabColor: '#8f8f8f';
45
+ readonly inactiveGroupHiddenTabColor: '#626262';
46
+ readonly tabDividerColor: '#1e1e1e';
47
+ readonly separatorBorder: 'rgb(68, 68, 68)';
48
+ readonly iconHoverBackground: 'rgba(90, 93, 94, 0.31)';
49
+ readonly dragOverBackground: 'rgba(83, 89, 93, 0.5)';
50
+ readonly edgeDockIndicatorColor: 'rgba(56, 139, 253, 0.9)';
51
+ readonly tabsHeight: 35;
52
+ readonly tabsFontSize: 13;
53
+ /** the sash is 4px of grab area with a 1px line drawn inside it */
54
+ readonly sashSize: 4;
55
+ };
56
+ /** The background and text a tab takes, given where it sits. */
57
+ export declare function tabColors(groupActive: boolean, visible: boolean): {
58
+ background: "#1e1e1e";
59
+ color: "white";
60
+ } | {
61
+ background: "#2d2d2d";
62
+ color: "#969696";
63
+ } | {
64
+ background: "#1e1e1e";
65
+ color: "#8f8f8f";
66
+ } | {
67
+ background: "#2d2d2d";
68
+ color: "#626262";
69
+ };
@@ -0,0 +1,82 @@
1
+ /**
2
+ * dockview's `dockview-theme-dark`, transcribed.
3
+ *
4
+ * We render the workspace ourselves now, but the look is deliberately not a new
5
+ * design — it is the theme we already shipped, so nothing about the chrome
6
+ * changed for users when the engine underneath it did. Every value below is
7
+ * copied from `dockview/dist/styles/dockview.css`'s `.dockview-theme-dark`
8
+ * block (v8.0.0); the names are its CSS custom properties with the `--dv-`
9
+ * prefix dropped.
10
+ *
11
+ * **Fixed, not theme-derived.** These apply in a light JBrowse theme too, the
12
+ * same way the app bar is dark in both — the chrome frames the content rather
13
+ * than matching it. Don't wire them to `theme.palette`: a light strip reads as
14
+ * content, which is exactly what it looked like before this file existed.
15
+ *
16
+ * **This is the chrome only, and the panel BODY is not chrome.** dockview also
17
+ * has a `--dv-group-view-background-color` (`#1e1e1e`) for the surface a tab's
18
+ * content sits on, and transcribing it is what made a light JBrowse theme come
19
+ * up dark below the views — the frame swallowing the thing it frames. The body
20
+ * follows `theme.palette.background.default` in `PanelView` instead, so the
21
+ * cell matches every other surface the app draws content on. It is deliberately
22
+ * absent here rather than present and unused, since an unused colour named
23
+ * after the panel reads as a value someone forgot to wire up.
24
+ *
25
+ * A consequence worth knowing before "fixing" it: dockview's selected tab is
26
+ * `#1e1e1e` **because** the body was, so the tab merged into its content. Ours
27
+ * cannot merge into a light body, and the four values below are kept anyway —
28
+ * darker-than-the-strip still reads as selected, and it is the one cue that
29
+ * survives being the only dark band on screen.
30
+ *
31
+ * Kept in one module because four components draw parts of the same chrome, and
32
+ * the four-way tab colouring below is a rule about the whole, not about any one
33
+ * of them.
34
+ */
35
+ export const dv = {
36
+ /** the tab strip itself */
37
+ tabsBackground: '#252526',
38
+ // A tab's colours depend on TWO things: whether its panel is the active one,
39
+ // and whether it is the tab that panel is showing. dockview enumerates all
40
+ // four rather than dimming one axis, so the focused panel's selected tab is
41
+ // the only fully-white label on screen.
42
+ activeGroupVisibleTabBackground: '#1e1e1e',
43
+ activeGroupHiddenTabBackground: '#2d2d2d',
44
+ inactiveGroupVisibleTabBackground: '#1e1e1e',
45
+ inactiveGroupHiddenTabBackground: '#2d2d2d',
46
+ activeGroupVisibleTabColor: 'white',
47
+ activeGroupHiddenTabColor: '#969696',
48
+ inactiveGroupVisibleTabColor: '#8f8f8f',
49
+ inactiveGroupHiddenTabColor: '#626262',
50
+ tabDividerColor: '#1e1e1e',
51
+ separatorBorder: 'rgb(68, 68, 68)',
52
+ iconHoverBackground: 'rgba(90, 93, 94, 0.31)',
53
+ dragOverBackground: 'rgba(83, 89, 93, 0.5)',
54
+ edgeDockIndicatorColor: 'rgba(56, 139, 253, 0.9)',
55
+ tabsHeight: 35,
56
+ tabsFontSize: 13,
57
+ /** the sash is 4px of grab area with a 1px line drawn inside it */
58
+ sashSize: 4,
59
+ };
60
+ /** The background and text a tab takes, given where it sits. */
61
+ export function tabColors(groupActive, visible) {
62
+ if (groupActive) {
63
+ return visible
64
+ ? {
65
+ background: dv.activeGroupVisibleTabBackground,
66
+ color: dv.activeGroupVisibleTabColor,
67
+ }
68
+ : {
69
+ background: dv.activeGroupHiddenTabBackground,
70
+ color: dv.activeGroupHiddenTabColor,
71
+ };
72
+ }
73
+ return visible
74
+ ? {
75
+ background: dv.inactiveGroupVisibleTabBackground,
76
+ color: dv.inactiveGroupVisibleTabColor,
77
+ }
78
+ : {
79
+ background: dv.inactiveGroupHiddenTabBackground,
80
+ color: dv.inactiveGroupHiddenTabColor,
81
+ };
82
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Where a pointer sitting over a panel would drop a view.
3
+ *
4
+ * Kept as geometry over a plain rect rather than reading the DOM, because this
5
+ * is the part of drag-and-drop with actual decisions in it — the edge band, the
6
+ * tie-break between two edges, what happens in the middle — and all of it is
7
+ * checkable without rendering anything or synthesising a pointer.
8
+ */
9
+ export type DropZone = 'center' | 'left' | 'right' | 'top' | 'bottom';
10
+ export interface Rect {
11
+ left: number;
12
+ top: number;
13
+ width: number;
14
+ height: number;
15
+ }
16
+ /**
17
+ * Where a drop would land. `zone` alone says which half of a cell; `strip` is
18
+ * set when the pointer is on the tab strip itself, which is a finer answer than
19
+ * `center` — that one appends, this one says where in the order.
20
+ */
21
+ export interface DropTarget {
22
+ zone: DropZone;
23
+ strip?: StripDrop;
24
+ }
25
+ /** A drop on the strip: the position in the tab order, and where to draw it. */
26
+ export interface StripDrop {
27
+ /** insertion index into the target panel's tabs, as they are ordered NOW */
28
+ index: number;
29
+ /** x of that gap, in the same space as the rects it was computed from */
30
+ left: number;
31
+ }
32
+ /**
33
+ * `center` means "add as a tab in this panel"; an edge means "split this panel
34
+ * and put the view in the new half".
35
+ *
36
+ * The bands are proportional rather than a fixed pixel depth so a narrow panel
37
+ * stays droppable — at a fixed 40px a 100px-wide panel would be all edge and
38
+ * have no centre to drop a tab into. `edgeFraction` is capped below 0.5 for the
39
+ * same reason: at 0.5 the centre vanishes entirely.
40
+ *
41
+ * Corners go to whichever edge the pointer is proportionally deeper into, so
42
+ * the diagonal is the tie-break and neither axis silently wins. **This is a
43
+ * deliberate divergence from dockview**, whose `calculateQuadrantAsPercentage`
44
+ * tests left, right, top, bottom in that order and returns the first hit — so
45
+ * in its top-left corner `left` always wins, however far into the top band the
46
+ * pointer is. Its band is 20% where this defaults to 25%.
47
+ */
48
+ export declare function dropZoneAt(rect: Rect, x: number, y: number, edgeFraction?: number): DropZone;
49
+ /**
50
+ * Which gap between tabs a pointer at `x` is nearest — the insertion index a
51
+ * drop there would use, and the x to draw the caret at.
52
+ *
53
+ * The test is each tab's MIDPOINT rather than its edges, so every x belongs to
54
+ * exactly one gap and there is no dead band between tabs where a drop would
55
+ * have to fall back to appending. `rects` are in strip order and `x` is in
56
+ * their coordinate space, whatever that is — this function does not know
57
+ * whether it was handed viewport or panel-relative numbers.
58
+ *
59
+ * An empty strip is index 0, which is the only sensible place for a first tab.
60
+ */
61
+ export declare function stripDropAt(rects: Rect[], x: number): StripDrop;
62
+ /**
63
+ * The split a drop on `zone` asks for: which way the panel divides, and whether
64
+ * the new half goes first. `center` is not a split.
65
+ */
66
+ export declare function splitForZone(zone: DropZone): {
67
+ readonly direction: 'row';
68
+ readonly before: true;
69
+ } | {
70
+ readonly direction: 'row';
71
+ readonly before: false;
72
+ } | {
73
+ readonly direction: 'column';
74
+ readonly before: true;
75
+ } | {
76
+ readonly direction: 'column';
77
+ readonly before: false;
78
+ } | undefined;
79
+ /** Where the drop indicator goes, as CSS percentages of the panel. */
80
+ export declare function indicatorRect(zone: DropZone): {
81
+ readonly left: '0%';
82
+ readonly top: '0%';
83
+ readonly width: '50%';
84
+ readonly height: '100%';
85
+ } | {
86
+ readonly left: '50%';
87
+ readonly top: '0%';
88
+ readonly width: '50%';
89
+ readonly height: '100%';
90
+ } | {
91
+ readonly left: '0%';
92
+ readonly top: '0%';
93
+ readonly width: '100%';
94
+ readonly height: '50%';
95
+ } | {
96
+ readonly left: '0%';
97
+ readonly top: '50%';
98
+ readonly width: '100%';
99
+ readonly height: '50%';
100
+ } | {
101
+ readonly left: '0%';
102
+ readonly top: '0%';
103
+ readonly width: '100%';
104
+ readonly height: '100%';
105
+ };