@jbrowse/app-core 5.0.0-beta.11 → 5.0.0-beta.14
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.
- package/esm/AppSession/AppSessionMixin.d.ts +110 -92
- package/esm/Assemblies/AssembliesMixin.d.ts +30 -0
- package/esm/HistoryManagement/index.d.ts +2 -0
- package/esm/HistoryManagement/index.js +2 -0
- package/esm/JBrowseConfig/RootConfiguration.d.ts +4 -10
- package/esm/JBrowseConfig/RootConfiguration.js +5 -8
- package/esm/JBrowseConfig/index.d.ts +3 -11
- package/esm/JBrowseConfig/index.js +2 -3
- package/esm/JBrowseModel/index.d.ts +10 -9
- package/esm/JBrowseModel/index.js +8 -7
- package/esm/JbApi/jbApi.d.ts +2 -2
- package/esm/JbApi/jbApi.js +16 -11
- package/esm/RootMenu/index.d.ts +2 -0
- package/esm/RootMenu/index.js +2 -0
- package/esm/SessionSpec/index.d.ts +2 -2
- package/esm/SessionSpec/index.js +1 -1
- package/esm/SessionSpec/loadSessionSpec.js +2 -12
- package/esm/SessionSpec/parseSessionSpecUrl.d.ts +19 -0
- package/esm/SessionSpec/parseSessionSpecUrl.js +29 -0
- package/esm/WorkspaceLayout/LayoutRenderer.d.ts +0 -10
- package/esm/WorkspaceLayout/LayoutRenderer.js +34 -101
- package/esm/WorkspaceLayout/PanelView.js +19 -45
- package/esm/WorkspaceLayout/TabStrip.d.ts +2 -2
- package/esm/WorkspaceLayout/TabStrip.js +36 -104
- package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +0 -9
- package/esm/WorkspaceLayout/WorkspaceContainer.js +9 -42
- package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +1 -16
- package/esm/WorkspaceLayout/WorkspacePanelActions.js +46 -51
- package/esm/WorkspaceLayout/WorkspaceTab.d.ts +3 -19
- package/esm/WorkspaceLayout/WorkspaceTab.js +12 -48
- package/esm/WorkspaceLayout/dropZone.d.ts +7 -37
- package/esm/WorkspaceLayout/dropZone.js +7 -44
- package/esm/WorkspaceLayout/model.d.ts +45 -120
- package/esm/WorkspaceLayout/model.js +145 -303
- package/esm/WorkspaceLayout/panelChrome.d.ts +4 -7
- package/esm/WorkspaceLayout/panelChrome.js +0 -2
- package/esm/WorkspaceLayout/spec.d.ts +10 -51
- package/esm/WorkspaceLayout/spec.js +12 -80
- package/esm/WorkspaceLayout/splitter.d.ts +3 -19
- package/esm/WorkspaceLayout/splitter.js +5 -21
- package/esm/WorkspaceLayout/tabName.d.ts +8 -0
- package/esm/WorkspaceLayout/tabName.js +14 -0
- package/esm/WorkspaceLayout/tree.d.ts +19 -79
- package/esm/WorkspaceLayout/tree.js +34 -131
- package/esm/WorkspaceLayout/useLayoutDrag.d.ts +4 -22
- package/esm/WorkspaceLayout/useLayoutDrag.js +55 -177
- package/esm/WorkspaceLayout/usePointerGesture.d.ts +24 -0
- package/esm/WorkspaceLayout/usePointerGesture.js +83 -0
- package/esm/WorkspaceLayout/workspaceTheme.d.ts +27 -0
- package/esm/WorkspaceLayout/workspaceTheme.js +37 -0
- package/esm/index.d.ts +5 -3
- package/esm/index.js +3 -2
- package/esm/queryParams.d.ts +2 -0
- package/esm/queryParams.js +16 -5
- package/esm/sessionSummary.d.ts +16 -0
- package/esm/sessionSummary.js +57 -0
- package/esm/ui/App/App.js +3 -0
- package/esm/ui/App/DrawerHeaderHelpDialog.d.ts +3 -2
- package/esm/ui/App/DrawerHeaderHelpDialog.js +4 -2
- package/esm/ui/App/JBrowseTabMenu.d.ts +3 -2
- package/esm/ui/App/JBrowseTabMenu.js +5 -3
- package/esm/ui/App/ThemeModeButton.d.ts +4 -5
- package/esm/ui/App/ThemeModeButton.js +22 -14
- package/esm/ui/App/ViewContainer.js +7 -1
- package/esm/ui/App/ViewMenu.js +69 -42
- package/esm/ui/App/ViewWrapper.js +2 -2
- package/esm/ui/App/ViewsContainer.js +3 -4
- package/esm/ui/App/lazyParts.d.ts +0 -5
- package/esm/ui/App/lazyParts.js +0 -1
- package/esm/ui/App/preloadAppShell.js +3 -6
- package/esm/ui/App/types.d.ts +3 -2
- package/esm/ui/App/viewTitle.d.ts +6 -1
- package/esm/ui/App/viewTitle.js +7 -2
- package/package.json +7 -7
- package/esm/WorkspaceLayout/dockviewTheme.d.ts +0 -69
- package/esm/WorkspaceLayout/dockviewTheme.js +0 -82
- package/esm/ui/App/ClassicViewsContainer.d.ts +0 -6
- package/esm/ui/App/ClassicViewsContainer.js +0 -22
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense, useCallback,
|
|
2
|
+
import { Suspense, useCallback, useMemo } from 'react';
|
|
3
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
-
import { autorun } from 'mobx';
|
|
5
4
|
import { observer } from 'mobx-react';
|
|
6
5
|
import ViewStack from '../ui/App/ViewStack.js';
|
|
7
6
|
import { ViewLauncher } from '../ui/App/lazyParts.js';
|
|
@@ -17,11 +16,6 @@ const useStyles = makeStyles()(theme => ({
|
|
|
17
16
|
gridRow: 'components',
|
|
18
17
|
background: theme.palette.background.default,
|
|
19
18
|
},
|
|
20
|
-
stack: {
|
|
21
|
-
display: 'flex',
|
|
22
|
-
flexDirection: 'column',
|
|
23
|
-
minWidth: 0,
|
|
24
|
-
},
|
|
25
19
|
empty: {
|
|
26
20
|
display: 'flex',
|
|
27
21
|
alignItems: 'center',
|
|
@@ -29,30 +23,11 @@ const useStyles = makeStyles()(theme => ({
|
|
|
29
23
|
height: '100%',
|
|
30
24
|
},
|
|
31
25
|
}));
|
|
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
26
|
export const WorkspaceContainer = observer(function WorkspaceContainer({ session, }) {
|
|
42
27
|
const { classes } = useStyles();
|
|
43
28
|
const { drag, handlers } = useLayoutDrag(session);
|
|
44
|
-
//
|
|
45
|
-
|
|
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 ×.
|
|
29
|
+
// the layout does not own views, so every close gesture goes through here
|
|
30
|
+
// or leaks them
|
|
56
31
|
const closeViews = useCallback((viewIds) => {
|
|
57
32
|
for (const view of viewsOf(session, viewIds)) {
|
|
58
33
|
session.removeView(view);
|
|
@@ -74,33 +49,25 @@ export const WorkspaceContainer = observer(function WorkspaceContainer({ session
|
|
|
74
49
|
closeViews(panel.tabs.flatMap(t => t.viewIds));
|
|
75
50
|
session.closePanel(panelId);
|
|
76
51
|
}, [session, closeViews]);
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
*/
|
|
52
|
+
// memoised by hand (the React Compiler skips `observer`): a fresh object
|
|
53
|
+
// re-renders every panel's ViewStack. `drag` stays out for the same reason.
|
|
86
54
|
const chrome = useMemo(() => ({
|
|
87
55
|
dragHandlers: handlers,
|
|
88
56
|
onTabClose: closeTab,
|
|
89
57
|
renderPanelActions: panel => (_jsx(WorkspacePanelActions, { panel: panel, session: session, onClose: () => {
|
|
90
58
|
closePanel(panel.id);
|
|
91
59
|
} })),
|
|
92
|
-
renderTabLabel: tab => (_jsx(WorkspaceTab, { tab: tab, views: viewsOf(session, tab.viewIds), session: session,
|
|
60
|
+
renderTabLabel: tab => (_jsx(WorkspaceTab, { tab: tab, views: viewsOf(session, tab.viewIds), session: session, onClose: () => {
|
|
93
61
|
closeTab(tab.id);
|
|
94
62
|
} })),
|
|
95
63
|
renderTabContent: tab => {
|
|
96
64
|
const views = viewsOf(session, tab.viewIds);
|
|
97
|
-
return views.length > 0 ? (_jsx(ViewStack, { views: views, session: session
|
|
65
|
+
return views.length > 0 ? (_jsx(ViewStack, { views: views, session: session })) : (_jsx("div", { className: classes.empty, children: _jsx(Suspense, { fallback: null, children: _jsx(ViewLauncher, { session: session }) }) }));
|
|
98
66
|
},
|
|
99
|
-
}), [session, handlers, closeTab, closePanel, classes.
|
|
67
|
+
}), [session, handlers, closeTab, closePanel, classes.empty]);
|
|
100
68
|
return (_jsx("div", { className: classes.container, "data-testid": "workspace", children: _jsx(LayoutRenderer, { node: session.visibleTree, layout: session, drag: drag, chrome: chrome }) }));
|
|
101
69
|
});
|
|
102
|
-
// `session.views` is the
|
|
103
|
-
// tab's membership list is filtered through it rather than read as an order.
|
|
70
|
+
// `session.views` is the order; a tab's `viewIds` is membership only
|
|
104
71
|
function viewsOf(session, viewIds) {
|
|
105
72
|
const members = new Set(viewIds);
|
|
106
73
|
return session.views.filter(v => members.has(v.id));
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import type { WorkspaceSessionType } from '../ui/App/types.ts';
|
|
2
2
|
import type { WorkspaceLayout } from './model.ts';
|
|
3
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
4
|
export declare const WorkspacePanelActions: ({ panel, session, onClose, }: {
|
|
14
5
|
panel: PanelNode;
|
|
15
6
|
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
|
-
*/
|
|
7
|
+
/** closes the cell and the views its tabs hold */
|
|
23
8
|
onClose: () => void;
|
|
24
9
|
}) => import("react").JSX.Element;
|
|
@@ -14,34 +14,26 @@ import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
|
|
14
14
|
import ViewModuleIcon from '@mui/icons-material/ViewModule';
|
|
15
15
|
import { IconButton, Tooltip } from '@mui/material';
|
|
16
16
|
import { observer } from 'mobx-react';
|
|
17
|
-
import {
|
|
17
|
+
import { stripIconColor, workspaceTheme } from './workspaceTheme.js';
|
|
18
18
|
const useStyles = makeStyles()({
|
|
19
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
20
|
button: {
|
|
23
21
|
padding: 4,
|
|
24
22
|
borderRadius: 2,
|
|
25
|
-
color:
|
|
26
|
-
'&:hover': {
|
|
23
|
+
color: stripIconColor,
|
|
24
|
+
'&:hover': {
|
|
25
|
+
background: workspaceTheme.iconHoverBackground,
|
|
26
|
+
borderRadius: 2,
|
|
27
|
+
},
|
|
27
28
|
},
|
|
28
29
|
icon: { fontSize: 16 },
|
|
29
30
|
});
|
|
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
31
|
export const WorkspacePanelActions = observer(function WorkspacePanelActions({ panel, session, onClose, }) {
|
|
40
32
|
const { classes } = useStyles();
|
|
41
33
|
const canClose = session.panels.length > 1;
|
|
42
34
|
const maximized = session.maximizedPanelId === panel.id;
|
|
43
35
|
const tile = (mode) => {
|
|
44
|
-
session.tileViews(mode
|
|
36
|
+
session.tileViews(mode);
|
|
45
37
|
};
|
|
46
38
|
return (_jsxs("div", { className: classes.actions, children: [_jsx(CascadingMenuButton, { menuItems: [
|
|
47
39
|
{
|
|
@@ -52,23 +44,21 @@ export const WorkspacePanelActions = observer(function WorkspacePanelActions({ p
|
|
|
52
44
|
},
|
|
53
45
|
},
|
|
54
46
|
{
|
|
55
|
-
label: 'New empty split
|
|
47
|
+
label: 'New empty split (right)',
|
|
56
48
|
icon: VerticalSplitIcon,
|
|
57
49
|
onClick: () => {
|
|
58
50
|
session.splitPanel(panel.id, 'row');
|
|
59
51
|
},
|
|
60
52
|
},
|
|
61
53
|
{
|
|
62
|
-
label: 'New empty split
|
|
54
|
+
label: 'New empty split (below)',
|
|
63
55
|
icon: HorizontalSplitIcon,
|
|
64
56
|
onClick: () => {
|
|
65
57
|
session.splitPanel(panel.id, 'column');
|
|
66
58
|
},
|
|
67
59
|
},
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
// being another cell to hide, since maximizing the only one is a
|
|
71
|
-
// no-op with a label promising otherwise.
|
|
60
|
+
// the keyboard route to the strip's double-click; hidden when there
|
|
61
|
+
// is no other cell to hide
|
|
72
62
|
...(session.panels.length > 1 || maximized
|
|
73
63
|
? [
|
|
74
64
|
{
|
|
@@ -80,41 +70,46 @@ export const WorkspacePanelActions = observer(function WorkspacePanelActions({ p
|
|
|
80
70
|
},
|
|
81
71
|
]
|
|
82
72
|
: []),
|
|
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
73
|
...(session.views.length > 1
|
|
88
74
|
? [
|
|
89
75
|
{
|
|
90
|
-
label: '
|
|
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',
|
|
76
|
+
label: 'Arrange all views',
|
|
112
77
|
icon: ViewModuleIcon,
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
78
|
+
type: 'subMenu',
|
|
79
|
+
subMenu: [
|
|
80
|
+
{
|
|
81
|
+
label: 'As tabs',
|
|
82
|
+
icon: DynamicFeedIcon,
|
|
83
|
+
onClick: () => {
|
|
84
|
+
tile('tabs');
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: 'Side by side',
|
|
89
|
+
icon: ViewColumnIcon,
|
|
90
|
+
onClick: () => {
|
|
91
|
+
tile('horizontal');
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
label: 'Stacked',
|
|
96
|
+
icon: TableRowsIcon,
|
|
97
|
+
onClick: () => {
|
|
98
|
+
tile('vertical');
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
label: 'Grid',
|
|
103
|
+
icon: ViewModuleIcon,
|
|
104
|
+
onClick: () => {
|
|
105
|
+
tile('grid');
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
],
|
|
116
109
|
},
|
|
117
110
|
]
|
|
118
111
|
: []),
|
|
119
|
-
], size: "small", className: classes.button, children: _jsx(AddIcon, { className: classes.icon }) }),
|
|
112
|
+
], tooltip: "Panel menu", size: "small", className: classes.button, children: _jsx(AddIcon, { className: classes.icon }) }), maximized ? (_jsx(Tooltip, { title: "Restore panel", children: _jsx(IconButton, { size: "small", className: classes.button, onClick: () => {
|
|
113
|
+
session.restorePanels();
|
|
114
|
+
}, children: _jsx(CloseFullscreenIcon, { className: classes.icon }) }) })) : null, canClose ? (_jsx(Tooltip, { title: "Close panel", children: _jsx(IconButton, { size: "small", className: classes.button, onClick: onClose, children: _jsx(CloseIcon, { className: classes.icon }) }) })) : null] }));
|
|
120
115
|
});
|
|
@@ -2,26 +2,10 @@ import type { WorkspaceSessionType } from '../ui/App/types.ts';
|
|
|
2
2
|
import type { WorkspaceLayout } from './model.ts';
|
|
3
3
|
import type { TabNode } from './tree.ts';
|
|
4
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, }: {
|
|
5
|
+
export declare const WorkspaceTab: ({ tab, views, session, onClose, }: {
|
|
16
6
|
tab: TabNode;
|
|
17
7
|
views: AbstractViewModel[];
|
|
18
|
-
session: WorkspaceSessionType;
|
|
19
|
-
|
|
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
|
-
*/
|
|
8
|
+
session: WorkspaceSessionType & WorkspaceLayout;
|
|
9
|
+
/** closes the tab and the views it holds */
|
|
26
10
|
onClose: () => void;
|
|
27
11
|
}) => import("react").JSX.Element;
|
|
@@ -4,20 +4,15 @@ import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
|
4
4
|
import { InputBase, Typography } from '@mui/material';
|
|
5
5
|
import { observer } from 'mobx-react';
|
|
6
6
|
import JBrowseTabMenu from '../ui/App/JBrowseTabMenu.js';
|
|
7
|
-
import {
|
|
7
|
+
import { tabDisplayName } from './tabName.js';
|
|
8
8
|
const useStyles = makeStyles()(theme => ({
|
|
9
|
-
//
|
|
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.
|
|
9
|
+
// the `[role=tab]` wrapper in TabStrip owns background and selected state
|
|
12
10
|
tab: {
|
|
13
11
|
display: 'flex',
|
|
14
12
|
alignItems: 'center',
|
|
15
13
|
gap: 4,
|
|
16
14
|
minWidth: 0,
|
|
17
15
|
},
|
|
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
16
|
title: {
|
|
22
17
|
flexGrow: 1,
|
|
23
18
|
marginRight: 4,
|
|
@@ -36,38 +31,20 @@ const useStyles = makeStyles()(theme => ({
|
|
|
36
31
|
flex: 1,
|
|
37
32
|
},
|
|
38
33
|
}));
|
|
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, }) {
|
|
34
|
+
export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session, onClose, }) {
|
|
61
35
|
const { classes } = useStyles();
|
|
62
36
|
const [editing, setEditing] = useState(false);
|
|
63
37
|
const [draft, setDraft] = useState('');
|
|
64
38
|
const title = tabDisplayName(tab, views, session);
|
|
39
|
+
const startEditing = () => {
|
|
40
|
+
setDraft(title);
|
|
41
|
+
setEditing(true);
|
|
42
|
+
};
|
|
43
|
+
// an empty box or the unchanged automatic name keeps the name automatic
|
|
65
44
|
const save = () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
// rename could be made and never unmade.
|
|
70
|
-
layout.renameTab(tab.id, draft.trim() || undefined);
|
|
45
|
+
const name = draft.trim();
|
|
46
|
+
const automatic = tabDisplayName({ ...tab, title: undefined }, views, session);
|
|
47
|
+
session.renameTab(tab.id, name && name !== automatic ? name : undefined);
|
|
71
48
|
setEditing(false);
|
|
72
49
|
};
|
|
73
50
|
return (_jsx("div", { className: classes.tab, children: editing ? (_jsx(InputBase, { autoFocus: true, className: classes.editInput, value: draft, onChange: e => {
|
|
@@ -75,13 +52,6 @@ export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session
|
|
|
75
52
|
}, onFocus: e => {
|
|
76
53
|
e.target.select();
|
|
77
54
|
}, 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
55
|
e.stopPropagation();
|
|
86
56
|
if (e.key === 'Enter') {
|
|
87
57
|
save();
|
|
@@ -95,11 +65,5 @@ export const WorkspaceTab = observer(function WorkspaceTab({ tab, views, session
|
|
|
95
65
|
e.stopPropagation();
|
|
96
66
|
}, onClick: e => {
|
|
97
67
|
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 })] })) }));
|
|
68
|
+
} })) : (_jsxs(_Fragment, { children: [_jsx(Typography, { className: classes.title, variant: "body2", onDoubleClick: startEditing, children: title }), _jsx(JBrowseTabMenu, { onRename: startEditing, onClose: onClose })] })) }));
|
|
105
69
|
});
|
|
@@ -1,11 +1,4 @@
|
|
|
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
|
-
*/
|
|
1
|
+
/** Drop geometry over plain rects, so it is testable without a DOM. */
|
|
9
2
|
export type DropZone = 'center' | 'left' | 'right' | 'top' | 'bottom';
|
|
10
3
|
export interface Rect {
|
|
11
4
|
left: number;
|
|
@@ -13,11 +6,7 @@ export interface Rect {
|
|
|
13
6
|
width: number;
|
|
14
7
|
height: number;
|
|
15
8
|
}
|
|
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
|
-
*/
|
|
9
|
+
/** `strip` is set on the tab strip, and gives a position in the tab order. */
|
|
21
10
|
export interface DropTarget {
|
|
22
11
|
zone: DropZone;
|
|
23
12
|
strip?: StripDrop;
|
|
@@ -30,33 +19,14 @@ export interface StripDrop {
|
|
|
30
19
|
left: number;
|
|
31
20
|
}
|
|
32
21
|
/**
|
|
33
|
-
* `center`
|
|
34
|
-
*
|
|
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%.
|
|
22
|
+
* `center` adds as a tab; an edge splits the panel. Bands are proportional and
|
|
23
|
+
* capped below half, so a narrow panel keeps a centre. A corner goes to the
|
|
24
|
+
* edge the pointer is proportionally deeper into.
|
|
47
25
|
*/
|
|
48
26
|
export declare function dropZoneAt(rect: Rect, x: number, y: number, edgeFraction?: number): DropZone;
|
|
49
27
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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.
|
|
28
|
+
* The gap between tabs nearest `x`, by tab midpoint so every x has exactly one.
|
|
29
|
+
* `x` is in the rects' coordinate space, whichever that is.
|
|
60
30
|
*/
|
|
61
31
|
export declare function stripDropAt(rects: Rect[], x: number): StripDrop;
|
|
62
32
|
/**
|
|
@@ -1,26 +1,8 @@
|
|
|
1
|
+
/** Drop geometry over plain rects, so it is testable without a DOM. */
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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
|
-
/**
|
|
10
|
-
* `center` means "add as a tab in this panel"; an edge means "split this panel
|
|
11
|
-
* and put the view in the new half".
|
|
12
|
-
*
|
|
13
|
-
* The bands are proportional rather than a fixed pixel depth so a narrow panel
|
|
14
|
-
* stays droppable — at a fixed 40px a 100px-wide panel would be all edge and
|
|
15
|
-
* have no centre to drop a tab into. `edgeFraction` is capped below 0.5 for the
|
|
16
|
-
* same reason: at 0.5 the centre vanishes entirely.
|
|
17
|
-
*
|
|
18
|
-
* Corners go to whichever edge the pointer is proportionally deeper into, so
|
|
19
|
-
* the diagonal is the tie-break and neither axis silently wins. **This is a
|
|
20
|
-
* deliberate divergence from dockview**, whose `calculateQuadrantAsPercentage`
|
|
21
|
-
* tests left, right, top, bottom in that order and returns the first hit — so
|
|
22
|
-
* in its top-left corner `left` always wins, however far into the top band the
|
|
23
|
-
* pointer is. Its band is 20% where this defaults to 25%.
|
|
3
|
+
* `center` adds as a tab; an edge splits the panel. Bands are proportional and
|
|
4
|
+
* capped below half, so a narrow panel keeps a centre. A corner goes to the
|
|
5
|
+
* edge the pointer is proportionally deeper into.
|
|
24
6
|
*/
|
|
25
7
|
export function dropZoneAt(rect, x, y, edgeFraction = 0.25) {
|
|
26
8
|
const fraction = Math.min(Math.max(edgeFraction, 0), 0.49);
|
|
@@ -43,16 +25,8 @@ export function dropZoneAt(rect, x, y, edgeFraction = 0.25) {
|
|
|
43
25
|
return depths.reduce((best, candidate) => candidate.depth > best.depth ? candidate : best).zone;
|
|
44
26
|
}
|
|
45
27
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* The test is each tab's MIDPOINT rather than its edges, so every x belongs to
|
|
50
|
-
* exactly one gap and there is no dead band between tabs where a drop would
|
|
51
|
-
* have to fall back to appending. `rects` are in strip order and `x` is in
|
|
52
|
-
* their coordinate space, whatever that is — this function does not know
|
|
53
|
-
* whether it was handed viewport or panel-relative numbers.
|
|
54
|
-
*
|
|
55
|
-
* An empty strip is index 0, which is the only sensible place for a first tab.
|
|
28
|
+
* The gap between tabs nearest `x`, by tab midpoint so every x has exactly one.
|
|
29
|
+
* `x` is in the rects' coordinate space, whichever that is.
|
|
56
30
|
*/
|
|
57
31
|
export function stripDropAt(rects, x) {
|
|
58
32
|
for (const [i, rect] of rects.entries()) {
|
|
@@ -66,18 +40,7 @@ export function stripDropAt(rects, x) {
|
|
|
66
40
|
left: last ? last.left + last.width : 0,
|
|
67
41
|
};
|
|
68
42
|
}
|
|
69
|
-
|
|
70
|
-
* What each zone means, in one table.
|
|
71
|
-
*
|
|
72
|
-
* `split` is the split a drop there asks for — which way the panel divides and
|
|
73
|
-
* whether the new half goes first; `center` is not a split. `rect` is where the
|
|
74
|
-
* indicator is drawn, as CSS percentages of the panel.
|
|
75
|
-
*
|
|
76
|
-
* One table rather than two switches because the pair has to agree: the shaded
|
|
77
|
-
* half IS the half the drop lands in, and that is the whole content of the
|
|
78
|
-
* indicator. Written apart, `right` could shade the left half and nothing would
|
|
79
|
-
* be wrong with either function on its own.
|
|
80
|
-
*/
|
|
43
|
+
// one table, so the shaded half and the half a drop lands in cannot disagree
|
|
81
44
|
const ZONES = {
|
|
82
45
|
left: {
|
|
83
46
|
split: { direction: 'row', before: true },
|