@jbrowse/app-core 4.3.0 → 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.
- package/esm/AppSession/AppRootModel.d.ts +30 -0
- package/esm/AppSession/AppRootModel.js +1 -0
- package/esm/AppSession/AppSessionMixin.d.ts +1598 -0
- package/esm/AppSession/AppSessionMixin.js +62 -0
- package/esm/AppSession/index.d.ts +2 -0
- package/esm/AppSession/index.js +1 -0
- package/esm/Assemblies/AssembliesMixin.d.ts +225 -0
- package/esm/Assemblies/AssembliesMixin.js +149 -0
- package/esm/Assemblies/index.d.ts +1 -2
- package/esm/Assemblies/index.js +1 -2
- package/esm/HistoryManagement/index.d.ts +19 -0
- package/esm/HistoryManagement/index.js +42 -5
- package/esm/JBrowseConfig/RootConfiguration.d.ts +167 -78
- package/esm/JBrowseConfig/RootConfiguration.js +85 -7
- package/esm/JBrowseConfig/index.d.ts +169 -84
- package/esm/JBrowseConfig/index.js +62 -2
- package/esm/JBrowseModel/index.d.ts +176 -95
- package/esm/JBrowseModel/index.js +122 -21
- package/esm/RootMenu/index.d.ts +77 -2
- package/esm/RootMenu/index.js +107 -61
- package/esm/SessionSpec/index.d.ts +6 -0
- package/esm/SessionSpec/index.js +3 -0
- package/esm/SessionSpec/lgvUrlInit.d.ts +79 -0
- package/esm/SessionSpec/lgvUrlInit.js +158 -0
- package/esm/SessionSpec/loadSessionSpec.d.ts +21 -0
- package/esm/SessionSpec/loadSessionSpec.js +311 -0
- package/esm/SessionSpec/parseSessionSpecUrl.d.ts +13 -0
- package/esm/SessionSpec/parseSessionSpecUrl.js +196 -0
- package/esm/SessionSpec/types.d.ts +33 -0
- package/esm/SessionSpec/types.js +1 -0
- package/esm/WorkspaceLayout/LayoutRenderer.d.ts +24 -0
- package/esm/WorkspaceLayout/LayoutRenderer.js +185 -0
- package/esm/WorkspaceLayout/PanelView.d.ts +13 -0
- package/esm/WorkspaceLayout/PanelView.js +99 -0
- package/esm/WorkspaceLayout/TabStrip.d.ts +14 -0
- package/esm/WorkspaceLayout/TabStrip.js +226 -0
- package/esm/WorkspaceLayout/WorkspaceContainer.d.ts +16 -0
- package/esm/WorkspaceLayout/WorkspaceContainer.js +105 -0
- package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +24 -0
- package/esm/WorkspaceLayout/WorkspacePanelActions.js +120 -0
- package/esm/WorkspaceLayout/WorkspaceTab.d.ts +27 -0
- package/esm/WorkspaceLayout/WorkspaceTab.js +109 -0
- package/esm/WorkspaceLayout/dockviewTheme.d.ts +69 -0
- package/esm/WorkspaceLayout/dockviewTheme.js +82 -0
- package/esm/WorkspaceLayout/dropZone.d.ts +105 -0
- package/esm/WorkspaceLayout/dropZone.js +113 -0
- package/esm/WorkspaceLayout/index.d.ts +8 -0
- package/esm/WorkspaceLayout/index.js +4 -0
- package/esm/WorkspaceLayout/model.d.ts +240 -0
- package/esm/WorkspaceLayout/model.js +527 -0
- package/esm/WorkspaceLayout/panelChrome.d.ts +19 -0
- package/esm/WorkspaceLayout/panelChrome.js +4 -0
- package/esm/WorkspaceLayout/spec.d.ts +58 -0
- package/esm/WorkspaceLayout/spec.js +153 -0
- package/esm/WorkspaceLayout/splitter.d.ts +25 -0
- package/esm/WorkspaceLayout/splitter.js +37 -0
- package/esm/WorkspaceLayout/tree.d.ts +158 -0
- package/esm/WorkspaceLayout/tree.js +379 -0
- package/esm/WorkspaceLayout/useLayoutDrag.d.ts +34 -0
- package/esm/WorkspaceLayout/useLayoutDrag.js +232 -0
- package/esm/index.d.ts +16 -9
- package/esm/index.js +13 -9
- package/esm/menus.d.ts +63 -62
- package/esm/menus.js +131 -84
- package/esm/queryParams.d.ts +9 -0
- package/esm/queryParams.js +92 -0
- package/esm/sessionModelFactory.d.ts +18 -0
- package/esm/sessionModelFactory.js +1 -0
- package/esm/ui/App/App.d.ts +3 -14
- package/esm/ui/App/App.js +30 -17
- package/esm/ui/App/AppFab.d.ts +12 -3
- package/esm/ui/App/AppFab.js +15 -8
- package/esm/ui/App/AppReadyMarker.d.ts +34 -0
- package/esm/ui/App/AppReadyMarker.js +56 -0
- package/esm/ui/App/AppToolbar.d.ts +2 -14
- package/esm/ui/App/AppToolbar.js +23 -24
- package/esm/ui/App/ClassicViewsContainer.d.ts +3 -9
- package/esm/ui/App/ClassicViewsContainer.js +9 -8
- package/esm/ui/App/DialogQueue.d.ts +17 -1
- package/esm/ui/App/DialogQueue.js +30 -1
- package/esm/ui/App/Drawer.d.ts +1 -1
- package/esm/ui/App/Drawer.js +10 -39
- package/esm/ui/App/DrawerControls.d.ts +1 -5
- package/esm/ui/App/DrawerControls.js +1 -25
- package/esm/ui/App/DrawerHeader.d.ts +2 -3
- package/esm/ui/App/DrawerHeader.js +24 -10
- package/esm/ui/App/DrawerHeaderHelpButton.d.ts +1 -1
- package/esm/ui/App/DrawerHeaderHelpButton.js +3 -3
- package/esm/ui/App/DrawerHeaderHelpDialog.d.ts +2 -2
- package/esm/ui/App/DrawerHeaderHelpDialog.js +3 -4
- package/esm/ui/App/DrawerWidget.d.ts +1 -1
- package/esm/ui/App/DrawerWidget.js +9 -21
- package/esm/ui/App/JBrowseTabMenu.d.ts +2 -3
- package/esm/ui/App/JBrowseTabMenu.js +24 -5
- package/esm/ui/App/ViewButtons.d.ts +2 -4
- package/esm/ui/App/ViewButtons.js +15 -3
- package/esm/ui/App/ViewContainer.d.ts +3 -2
- package/esm/ui/App/ViewContainer.js +144 -24
- package/esm/ui/App/ViewContainerTitle.d.ts +1 -1
- package/esm/ui/App/ViewContainerTitle.js +9 -2
- package/esm/ui/App/ViewErrorFallback.d.ts +18 -0
- package/esm/ui/App/ViewErrorFallback.js +42 -0
- package/esm/ui/App/ViewHeader.d.ts +3 -4
- package/esm/ui/App/ViewHeader.js +28 -17
- package/esm/ui/App/ViewLauncher.d.ts +2 -8
- package/esm/ui/App/ViewLauncher.js +7 -5
- package/esm/ui/App/ViewMenu.d.ts +3 -5
- package/esm/ui/App/ViewMenu.js +87 -92
- package/esm/ui/App/ViewStack.d.ts +7 -0
- package/esm/ui/App/ViewStack.js +42 -0
- package/esm/ui/App/ViewWrapper.d.ts +15 -1
- package/esm/ui/App/ViewWrapper.js +26 -10
- package/esm/ui/App/ViewsContainer.d.ts +3 -8
- package/esm/ui/App/ViewsContainer.js +9 -6
- package/esm/ui/App/copyView.js +1 -1
- package/esm/ui/App/index.d.ts +2 -1
- package/esm/ui/App/index.js +3 -2
- package/esm/ui/App/lazyChunk.d.ts +13 -0
- package/esm/ui/App/lazyChunk.js +91 -0
- package/esm/ui/App/types.d.ts +12 -3
- package/esm/ui/App/types.js +1 -1
- package/esm/ui/App/useViewVisibility.d.ts +34 -0
- package/esm/ui/App/useViewVisibility.js +87 -0
- package/esm/ui/App/viewTitle.d.ts +5 -0
- package/esm/ui/App/viewTitle.js +9 -0
- package/esm/ui/index.d.ts +1 -1
- package/esm/ui/index.js +1 -1
- package/package.json +12 -17
- package/esm/AppFocus/index.d.ts +0 -5
- package/esm/AppFocus/index.js +0 -12
- package/esm/Assemblies/SessionAssembliesMixin.d.ts +0 -92
- package/esm/Assemblies/SessionAssembliesMixin.js +0 -53
- package/esm/Assemblies/TemporaryAssembliesMixin.d.ts +0 -10
- package/esm/Assemblies/TemporaryAssembliesMixin.js +0 -37
- package/esm/DockviewLayout/index.d.ts +0 -35
- package/esm/DockviewLayout/index.js +0 -111
- package/esm/ui/App/DockviewContext.d.ts +0 -22
- package/esm/ui/App/DockviewContext.js +0 -29
- package/esm/ui/App/DockviewLeftHeaderActions.d.ts +0 -2
- package/esm/ui/App/DockviewLeftHeaderActions.js +0 -58
- package/esm/ui/App/DockviewRightHeaderActions.d.ts +0 -2
- package/esm/ui/App/DockviewRightHeaderActions.js +0 -100
- package/esm/ui/App/DrawerWidgetSelector.d.ts +0 -5
- package/esm/ui/App/DrawerWidgetSelector.js +0 -47
- package/esm/ui/App/JBrowseViewPanel.d.ts +0 -8
- package/esm/ui/App/JBrowseViewPanel.js +0 -40
- package/esm/ui/App/JBrowseViewTab.d.ts +0 -8
- package/esm/ui/App/JBrowseViewTab.js +0 -97
- package/esm/ui/App/ModalWidget.d.ts +0 -6
- package/esm/ui/App/ModalWidget.js +0 -48
- package/esm/ui/App/TiledViewsContainer.d.ts +0 -6
- package/esm/ui/App/TiledViewsContainer.js +0 -366
- package/esm/ui/App/dockviewUtils.d.ts +0 -46
- package/esm/ui/App/dockviewUtils.js +0 -55
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { normalize } from './tree.js';
|
|
2
|
+
/**
|
|
3
|
+
* The effective size of each child of one branch.
|
|
4
|
+
*
|
|
5
|
+
* `size` is documented as a **percentage**, so a spec that sizes some siblings
|
|
6
|
+
* and leaves the rest bare means the rest to share what is left over: `70` and
|
|
7
|
+
* blank is 70/30. Defaulting a bare sibling to 1 instead reads a weight against
|
|
8
|
+
* a percentage, and the panel comes out at 1/71 of the width — present, about a
|
|
9
|
+
* pixel wide, and reported by nothing.
|
|
10
|
+
*
|
|
11
|
+
* The mixed case used to be unreachable: dockview forced orientation to
|
|
12
|
+
* alternate by depth, so the converter discarded any partially-sized branch
|
|
13
|
+
* wholesale and raised a notification saying so. Nesting works now, which means
|
|
14
|
+
* this has to mean something instead.
|
|
15
|
+
*
|
|
16
|
+
* All-sized and none-sized both stay plain weights — `normalize` renormalises
|
|
17
|
+
* them, so `70`/`30` and `7`/`3` are the same layout and neither has to sum to
|
|
18
|
+
* anything in particular.
|
|
19
|
+
*/
|
|
20
|
+
function resolveSizes(children) {
|
|
21
|
+
const stated = children.map(child => child.size);
|
|
22
|
+
const named = stated.filter(size => size !== undefined);
|
|
23
|
+
if (named.length === 0 || named.length === children.length) {
|
|
24
|
+
return stated.map(size => size ?? 1);
|
|
25
|
+
}
|
|
26
|
+
const claimed = named.reduce((a, b) => a + b, 0);
|
|
27
|
+
const remainder = 100 - claimed;
|
|
28
|
+
const share = remainder > 0
|
|
29
|
+
? remainder / (children.length - named.length)
|
|
30
|
+
: // an over-subscribed branch has nothing left to hand out, so a bare
|
|
31
|
+
// sibling takes a typical share rather than collapsing to nothing
|
|
32
|
+
claimed / named.length;
|
|
33
|
+
return stated.map(size => size ?? share);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a layout tree from a spec.
|
|
37
|
+
*
|
|
38
|
+
* The old converter had to answer "which dockview group does a nested split
|
|
39
|
+
* address?" and could not, because dockview forces orientation to alternate by
|
|
40
|
+
* depth — which is why `size` was honoured only on the top-level split, and
|
|
41
|
+
* only if every panel there carried one. Here the spec's nesting is the tree's
|
|
42
|
+
* nesting, so `size` applies wherever it is written and there is no all-or-
|
|
43
|
+
* nothing pass.
|
|
44
|
+
*/
|
|
45
|
+
export function treeFromSpec(spec, nextId) {
|
|
46
|
+
function build(node, size) {
|
|
47
|
+
if (node.viewIds) {
|
|
48
|
+
return {
|
|
49
|
+
id: nextId('panel'),
|
|
50
|
+
size,
|
|
51
|
+
tabs: [{ id: nextId('tab'), viewIds: [...node.viewIds] }],
|
|
52
|
+
activeTabId: undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const children = node.children ?? [];
|
|
56
|
+
if (children.length === 0) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
// `tabs` is not a split: every child's views become a tab in one cell.
|
|
60
|
+
//
|
|
61
|
+
// A tab holds a FLAT stack of views, so a container child has no split to
|
|
62
|
+
// become — and the docs say containers nest arbitrarily deep, so one can be
|
|
63
|
+
// written. Its views are flattened into a single tab; they used to be
|
|
64
|
+
// dropped from the layout outright, which is invisible rather than wrong,
|
|
65
|
+
// since homing then swept them into whichever tab happened to be showing.
|
|
66
|
+
// `loadSessionSpec` reports the flattening, as it does the sizes below.
|
|
67
|
+
if (node.direction === 'tabs') {
|
|
68
|
+
const tabs = children.flatMap(child => {
|
|
69
|
+
const viewIds = viewIdsInSpec(child);
|
|
70
|
+
return child.viewIds === undefined && viewIds.length === 0
|
|
71
|
+
? []
|
|
72
|
+
: [{ id: nextId('tab'), viewIds }];
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
id: nextId('panel'),
|
|
76
|
+
size,
|
|
77
|
+
tabs,
|
|
78
|
+
activeTabId: tabs[0]?.id,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const sizes = resolveSizes(children);
|
|
82
|
+
const built = children.flatMap((child, i) => {
|
|
83
|
+
const subtree = build(child, sizes[i]);
|
|
84
|
+
return subtree ? [subtree] : [];
|
|
85
|
+
});
|
|
86
|
+
return built.length === 0
|
|
87
|
+
? undefined
|
|
88
|
+
: {
|
|
89
|
+
id: nextId('branch'),
|
|
90
|
+
size,
|
|
91
|
+
direction: node.direction === 'vertical' ? 'column' : 'row',
|
|
92
|
+
children: built,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const root = build(spec, spec.size ?? 1);
|
|
96
|
+
return root ? normalize(root) : { id: nextId('panel'), size: 1, tabs: [] };
|
|
97
|
+
}
|
|
98
|
+
/** Every viewId a spec names, depth-first — the order it states. */
|
|
99
|
+
export function viewIdsInSpec(spec) {
|
|
100
|
+
return [
|
|
101
|
+
...(spec.viewIds ?? []),
|
|
102
|
+
...(spec.children ?? []).flatMap(viewIdsInSpec),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Re-arrange every view at once, one view per cell.
|
|
107
|
+
*
|
|
108
|
+
* The four whole-workspace commands, as a spec. dockview had these as imperative
|
|
109
|
+
* re-tiling — walk every panel and re-add it relative to the first, in an order
|
|
110
|
+
* chosen so the grid came out right — and the grid case had to compute its own
|
|
111
|
+
* row/column arithmetic against `api.panels` because there was no way to state
|
|
112
|
+
* the shape. Here the shape IS the statement, so a tiling is a spec and goes
|
|
113
|
+
* through the same `applyLayoutSpec` a session spec does.
|
|
114
|
+
*
|
|
115
|
+
* `grid` fills row-major at ceil(sqrt(n)) columns, which is what the dockview
|
|
116
|
+
* version computed; a trailing row shorter than the rest keeps its cells at full
|
|
117
|
+
* width, since `resolveSizes` reads bare siblings as an even share of their own
|
|
118
|
+
* branch rather than of the grid.
|
|
119
|
+
*/
|
|
120
|
+
export function tileLayoutSpec(viewIds, mode) {
|
|
121
|
+
// Nothing to arrange: one view is the whole workspace whatever the mode, and
|
|
122
|
+
// no views leaves the empty panel the tree already guarantees.
|
|
123
|
+
if (viewIds.length <= 1) {
|
|
124
|
+
return { viewIds: [...viewIds] };
|
|
125
|
+
}
|
|
126
|
+
const cell = (id) => ({ viewIds: [id] });
|
|
127
|
+
if (mode !== 'grid') {
|
|
128
|
+
return { direction: mode, children: viewIds.map(cell) };
|
|
129
|
+
}
|
|
130
|
+
const cols = Math.ceil(Math.sqrt(viewIds.length));
|
|
131
|
+
const rows = [];
|
|
132
|
+
for (let i = 0; i < viewIds.length; i += cols) {
|
|
133
|
+
rows.push({
|
|
134
|
+
direction: 'horizontal',
|
|
135
|
+
children: viewIds.slice(i, i + cols).map(cell),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { direction: 'vertical', children: rows };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* `setPendingMove` as a spec: everything else keeps its side, the named view
|
|
142
|
+
* takes the other. With nothing else on screen there is nothing to split from,
|
|
143
|
+
* so the view just takes the space.
|
|
144
|
+
*/
|
|
145
|
+
export function specForPendingMove(move, allViewIds) {
|
|
146
|
+
const others = allViewIds.filter(id => id !== move.viewId);
|
|
147
|
+
return others.length > 0
|
|
148
|
+
? {
|
|
149
|
+
direction: move.type === 'splitRight' ? 'horizontal' : 'tabs',
|
|
150
|
+
children: [{ viewIds: others }, { viewIds: [move.viewId] }],
|
|
151
|
+
}
|
|
152
|
+
: { viewIds: [move.viewId] };
|
|
153
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moving the boundary between two panes: pure, so it is checkable without a DOM
|
|
3
|
+
* or a synthetic pointer. The handle in `LayoutRenderer.tsx` only measures.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A pane never shrinks below this many pixels.
|
|
7
|
+
*
|
|
8
|
+
* dockview's `MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH`/`_HEIGHT`. A `flex-grow`
|
|
9
|
+
* share of zero is legal, so without it a pane can be dragged — or `Home`'d —
|
|
10
|
+
* to nothing, taking its tab strip and views with it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const MIN_PANE_PX = 100;
|
|
13
|
+
/** The space the two panes either side of the boundary before `index` share. */
|
|
14
|
+
export declare function pairSpan(sizes: number[], index: number): number;
|
|
15
|
+
/**
|
|
16
|
+
* `sizes` with that boundary moved to `position`, measured in the same units.
|
|
17
|
+
*
|
|
18
|
+
* The move stays inside the pair, so every other pane holds still. The pointer
|
|
19
|
+
* and the arrow keys are the same gesture at two resolutions and both land here.
|
|
20
|
+
*
|
|
21
|
+
* `pairPx` is the pair's current pixel span, needed only to convert
|
|
22
|
+
* `MIN_PANE_PX` into a share; 0 means nothing measurable (jsdom, or a pane not
|
|
23
|
+
* laid out yet) and gives the unconstrained clamp.
|
|
24
|
+
*/
|
|
25
|
+
export declare function withBoundaryAt(sizes: number[], index: number, position: number, pairPx?: number): number[];
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Moving the boundary between two panes: pure, so it is checkable without a DOM
|
|
3
|
+
* or a synthetic pointer. The handle in `LayoutRenderer.tsx` only measures.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* A pane never shrinks below this many pixels.
|
|
7
|
+
*
|
|
8
|
+
* dockview's `MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH`/`_HEIGHT`. A `flex-grow`
|
|
9
|
+
* share of zero is legal, so without it a pane can be dragged — or `Home`'d —
|
|
10
|
+
* to nothing, taking its tab strip and views with it.
|
|
11
|
+
*/
|
|
12
|
+
export const MIN_PANE_PX = 100;
|
|
13
|
+
/** The space the two panes either side of the boundary before `index` share. */
|
|
14
|
+
export function pairSpan(sizes, index) {
|
|
15
|
+
return sizes[index - 1] + sizes[index];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `sizes` with that boundary moved to `position`, measured in the same units.
|
|
19
|
+
*
|
|
20
|
+
* The move stays inside the pair, so every other pane holds still. The pointer
|
|
21
|
+
* and the arrow keys are the same gesture at two resolutions and both land here.
|
|
22
|
+
*
|
|
23
|
+
* `pairPx` is the pair's current pixel span, needed only to convert
|
|
24
|
+
* `MIN_PANE_PX` into a share; 0 means nothing measurable (jsdom, or a pane not
|
|
25
|
+
* laid out yet) and gives the unconstrained clamp.
|
|
26
|
+
*/
|
|
27
|
+
export function withBoundaryAt(sizes, index, position, pairPx = 0) {
|
|
28
|
+
const pair = pairSpan(sizes, index);
|
|
29
|
+
// no room for two minimums: the boundary stops in the middle rather than
|
|
30
|
+
// pinning both ends past each other
|
|
31
|
+
const floor = pairPx > 0 ? Math.min((MIN_PANE_PX / pairPx) * pair, pair / 2) : 0;
|
|
32
|
+
const before = Math.min(Math.max(position, floor), pair - floor);
|
|
33
|
+
const next = [...sizes];
|
|
34
|
+
next[index - 1] = before;
|
|
35
|
+
next[index] = pair - before;
|
|
36
|
+
return next;
|
|
37
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The layout tree, as pure functions over plain snapshots.
|
|
3
|
+
*
|
|
4
|
+
* This is the half of an MST-native workspace that carries the risk, so it is
|
|
5
|
+
* deliberately not MST: no nodes, no parents, no lifecycle, nothing to be alive
|
|
6
|
+
* or dead. Every operation is `tree in -> tree out`, which means the invariants
|
|
7
|
+
* below can be tested exhaustively instead of observed to hold.
|
|
8
|
+
*
|
|
9
|
+
* The trade being made is worth naming, because it is not "complexity for no
|
|
10
|
+
* complexity". Reconciling with dockview is replaced by **normalisation**: a
|
|
11
|
+
* tree that has just had a panel removed, or a branch split, is usually not in
|
|
12
|
+
* canonical form, and every operation has to put it back. That is real work and
|
|
13
|
+
* it is where this design's bugs would live. What it is not is timing: there is
|
|
14
|
+
* no event, no re-entrancy, no window during which the tree is half-updated,
|
|
15
|
+
* and no second owner to disagree with.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* One tab. Its content is a **vertical stack of views**, which is JBrowse's own
|
|
19
|
+
* concept and the reason a generic window manager never fits cleanly: dockview
|
|
20
|
+
* has a group holding tabs, and we need a third level under that.
|
|
21
|
+
*/
|
|
22
|
+
export interface TabNode {
|
|
23
|
+
id: string;
|
|
24
|
+
/** membership only — `session.views` is the order (see WorkspaceLayout/CLAUDE.md) */
|
|
25
|
+
viewIds: string[];
|
|
26
|
+
/** set only when the user renames it; otherwise derived from the views */
|
|
27
|
+
title?: string;
|
|
28
|
+
}
|
|
29
|
+
/** A cell of the grid: the space a tab strip and one visible tab live in. */
|
|
30
|
+
export interface PanelNode {
|
|
31
|
+
id: string;
|
|
32
|
+
/** share of the parent branch's space, relative to its siblings */
|
|
33
|
+
size: number;
|
|
34
|
+
tabs: TabNode[];
|
|
35
|
+
activeTabId?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface BranchNode {
|
|
38
|
+
id: string;
|
|
39
|
+
size: number;
|
|
40
|
+
direction: 'row' | 'column';
|
|
41
|
+
children: LayoutTree[];
|
|
42
|
+
}
|
|
43
|
+
export type LayoutTree = PanelNode | BranchNode;
|
|
44
|
+
/** What an id is being minted for. Ids are prefixed with it, so they read. */
|
|
45
|
+
export type NodeKind = 'panel' | 'tab' | 'branch';
|
|
46
|
+
export declare function isBranch(node: LayoutTree): node is BranchNode;
|
|
47
|
+
export declare function panels(node: LayoutTree): PanelNode[];
|
|
48
|
+
export declare function tabs(node: LayoutTree): TabNode[];
|
|
49
|
+
/** A tab and the panel holding it — there are no parent pointers to walk. */
|
|
50
|
+
export interface TabHome {
|
|
51
|
+
panel: PanelNode;
|
|
52
|
+
tab: TabNode;
|
|
53
|
+
}
|
|
54
|
+
export declare function findTab(node: LayoutTree, tabId: string): TabHome | undefined;
|
|
55
|
+
/** The tab a panel is showing: the one it names, or its first. */
|
|
56
|
+
export declare function activeTabIn(panel: PanelNode): TabNode | undefined;
|
|
57
|
+
export declare function tabContainingView(node: LayoutTree, viewId: string): TabHome | undefined;
|
|
58
|
+
export declare function panelContainingView(node: LayoutTree, viewId: string): PanelNode | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Put a tree back in canonical form. Four rules, applied bottom-up:
|
|
61
|
+
*
|
|
62
|
+
* 1. a branch with no children is dropped by its parent
|
|
63
|
+
* 2. a branch with one child is replaced by that child, which inherits the
|
|
64
|
+
* branch's size — so the space the branch occupied does not move
|
|
65
|
+
* 3. a branch directly inside a branch of the same direction is flattened into
|
|
66
|
+
* it, its children's sizes scaled to preserve their share of the whole
|
|
67
|
+
* 4. sizes are renormalised so a node's siblings always sum to 1
|
|
68
|
+
*
|
|
69
|
+
* Rule 3 is the one dockview cannot express: it forces orientation to alternate
|
|
70
|
+
* by depth, so `row` inside `row` is not representable and a nested split gets
|
|
71
|
+
* silently reparented. Here it is representable AND canonicalised, which is
|
|
72
|
+
* what makes `size` work at any depth rather than only on the top-level split.
|
|
73
|
+
*
|
|
74
|
+
* Empty panels are NOT dropped, and neither are empty tabs. A tab with no views
|
|
75
|
+
* is exactly what "new empty tab" creates — it shows the view launcher — so
|
|
76
|
+
* pruning empties wholesale would delete the thing the user just asked for.
|
|
77
|
+
*/
|
|
78
|
+
export declare function normalize(node: LayoutTree): LayoutTree;
|
|
79
|
+
/**
|
|
80
|
+
* Split `panelId`, putting `newPanel` beside it in `direction`.
|
|
81
|
+
*
|
|
82
|
+
* Always builds a branch around the panel and lets `normalize` flatten it into
|
|
83
|
+
* the parent when the directions agree. Trying to decide up front whether to
|
|
84
|
+
* nest or to insert as a sibling is the same decision rule twice, and the two
|
|
85
|
+
* copies drift.
|
|
86
|
+
*/
|
|
87
|
+
export declare function splitPanel(root: LayoutTree, panelId: string, direction: 'row' | 'column', newPanel: PanelNode, before?: boolean): LayoutTree;
|
|
88
|
+
/** Drop a panel. Its space goes back to its siblings via renormalisation. */
|
|
89
|
+
export declare function removePanel(root: LayoutTree, panelId: string): LayoutTree;
|
|
90
|
+
/** Set the sizes of one branch's children, e.g. from a splitter drag. */
|
|
91
|
+
export declare function setSizes(root: LayoutTree, branchId: string, sizes: number[]): LayoutTree;
|
|
92
|
+
export declare function addTab(root: LayoutTree, panelId: string, tab: TabNode): LayoutTree;
|
|
93
|
+
/**
|
|
94
|
+
* Close a tab. The panel stays — an empty panel shows the view launcher, which
|
|
95
|
+
* is a state the user can reach deliberately.
|
|
96
|
+
*/
|
|
97
|
+
export declare function removeTab(root: LayoutTree, tabId: string): LayoutTree;
|
|
98
|
+
/**
|
|
99
|
+
* Move a tab into another panel, at `index` if given.
|
|
100
|
+
*
|
|
101
|
+
* One function returning one tree, so there is no instant at which the tab is
|
|
102
|
+
* in both panels or neither — the state the imperative bridge had to wrap an
|
|
103
|
+
* explicit `runInAction` around to hide from its own reconcile autorun.
|
|
104
|
+
*
|
|
105
|
+
* **`index` counts the tabs as they are ordered NOW**, before the move. That is
|
|
106
|
+
* the only reading a caller can supply, because it is the strip the user is
|
|
107
|
+
* looking at — and it differs from the post-removal ordering exactly when the
|
|
108
|
+
* tab is moving within its own panel and is currently to the LEFT of the gap it
|
|
109
|
+
* was dropped in. Dragging A to the gap between B and C in `[A, B, C]` is index
|
|
110
|
+
* 2 on screen; taking A out first makes that gap index 1, and inserting at 2
|
|
111
|
+
* lands it after C instead. Adjusting here rather than in the caller keeps the
|
|
112
|
+
* whole "remove then insert" mechanic inside the one function that does it.
|
|
113
|
+
*
|
|
114
|
+
* dockview reaches the same rule from the other end and it is worth knowing
|
|
115
|
+
* they agree, since this is the fiddliest arithmetic in the file: its tabs
|
|
116
|
+
* container computes `insertionIndex - (sourceIndex !== -1 && sourceIndex <
|
|
117
|
+
* insertionIndex ? 1 : 0)`, which is the condition below with the same bounds.
|
|
118
|
+
*/
|
|
119
|
+
export declare function moveTabToPanel(root: LayoutTree, tabId: string, targetPanelId: string, index?: number): LayoutTree;
|
|
120
|
+
export declare function setActiveTab(root: LayoutTree, panelId: string, tabId: string): LayoutTree;
|
|
121
|
+
export declare function renameTab(root: LayoutTree, tabId: string, title: string | undefined): LayoutTree;
|
|
122
|
+
export declare function addViewToTab(root: LayoutTree, tabId: string, viewId: string): LayoutTree;
|
|
123
|
+
/** Take a view out of whatever tab holds it, leaving the tab in place. */
|
|
124
|
+
export declare function removeView(root: LayoutTree, viewId: string): LayoutTree;
|
|
125
|
+
/**
|
|
126
|
+
* Make the tree's membership agree with the session's list of views.
|
|
127
|
+
*
|
|
128
|
+
* The only reconciliation left, and one-directional: `session.views` owns which
|
|
129
|
+
* views exist, and nothing reads back.
|
|
130
|
+
*
|
|
131
|
+
* **`viewIds` is the session's WHOLE set.** A shorter list does not leave the
|
|
132
|
+
* rest alone — it unhomes them, and the next homing pass sweeps them into one
|
|
133
|
+
* tab.
|
|
134
|
+
*/
|
|
135
|
+
export declare function homeViews(root: LayoutTree, viewIds: string[], activePanelId: string | undefined, nextTabId: () => string): LayoutTree;
|
|
136
|
+
/**
|
|
137
|
+
* Drop a panel that has been left with no tabs, unless it is the only one.
|
|
138
|
+
*
|
|
139
|
+
* Deliberately NOT a normalisation rule. A panel with no tabs is reachable on
|
|
140
|
+
* purpose, so pruning empties wholesale would delete a space the user asked
|
|
141
|
+
* for. It is a step the *drag* gesture takes about its own source panel,
|
|
142
|
+
* because dragging the last tab out of a split and leaving a blank half is the
|
|
143
|
+
* one place an empty panel is clearly not what was meant.
|
|
144
|
+
*/
|
|
145
|
+
export declare function pruneEmptyPanel(root: LayoutTree, panelId: string): LayoutTree;
|
|
146
|
+
/**
|
|
147
|
+
* Drop a tab that a move has left with no views, unless it is the panel's last.
|
|
148
|
+
*
|
|
149
|
+
* The mirror of `pruneEmptyPanel`, and deliberately as narrow: an empty tab is
|
|
150
|
+
* legitimate (it shows the view launcher), so this only ever runs on the tab a
|
|
151
|
+
* move just emptied, never as a rule over the tree.
|
|
152
|
+
*
|
|
153
|
+
* Which is why closing a tab's last VIEW leaves the tab standing where a move
|
|
154
|
+
* out of it does not: the move acted somewhere else and left this behind, and
|
|
155
|
+
* the close acted here, where an empty tab is a usable place to open the next
|
|
156
|
+
* thing. A cell's last tab prunes because a blank cell is not usable.
|
|
157
|
+
*/
|
|
158
|
+
export declare function pruneEmptyTabIn(root: LayoutTree, panelId: string, tabId: string): LayoutTree;
|