@jbrowse/app-core 5.0.0-beta.10 → 5.0.0-beta.13
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/HistoryManagement/index.d.ts +3 -0
- package/esm/JBrowseConfig/RootConfiguration.d.ts +1 -8
- package/esm/JBrowseConfig/RootConfiguration.js +2 -6
- package/esm/JBrowseConfig/index.d.ts +1 -8
- package/esm/JBrowseModel/index.d.ts +1 -8
- package/esm/JbApi/jbApi.d.ts +2 -2
- package/esm/JbApi/jbApi.js +12 -10
- package/esm/SessionSpec/loadSessionSpec.js +2 -12
- 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 -34
- package/esm/WorkspaceLayout/WorkspacePanelActions.d.ts +1 -16
- package/esm/WorkspaceLayout/WorkspacePanelActions.js +45 -50
- package/esm/WorkspaceLayout/WorkspaceTab.d.ts +1 -16
- package/esm/WorkspaceLayout/WorkspaceTab.js +11 -47
- package/esm/WorkspaceLayout/dropZone.d.ts +7 -37
- package/esm/WorkspaceLayout/dropZone.js +7 -44
- package/esm/WorkspaceLayout/model.d.ts +42 -112
- package/esm/WorkspaceLayout/model.js +139 -268
- 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 -76
- package/esm/WorkspaceLayout/tree.js +29 -122
- package/esm/WorkspaceLayout/useLayoutDrag.d.ts +4 -22
- package/esm/WorkspaceLayout/useLayoutDrag.js +46 -174
- 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/ui/App/App.js +3 -0
- package/esm/ui/App/JBrowseTabMenu.js +1 -1
- package/esm/ui/App/ThemeModeButton.js +5 -4
- package/esm/ui/App/ViewMenu.js +47 -29
- 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/useViewVisibility.d.ts +1 -1
- package/esm/ui/App/useViewVisibility.js +1 -1
- package/package.json +5 -5
- 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,17 +1,14 @@
|
|
|
1
1
|
import type { PanelNode, TabNode } from './tree.ts';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PointerGestureHandlers } from './usePointerGesture.ts';
|
|
3
3
|
/**
|
|
4
|
-
* The app's half of a panel
|
|
5
|
-
*
|
|
6
|
-
* One object rather than five props because every one is forwarded unchanged
|
|
7
|
-
* down the whole recursion, and because one object can be memoised: it reaches
|
|
8
|
-
* every panel, so a fresh one per render re-renders the entire workspace.
|
|
4
|
+
* The app's half of a panel, as one object so it can be memoised: it reaches
|
|
5
|
+
* every panel, and a fresh one re-renders the whole workspace.
|
|
9
6
|
*/
|
|
10
7
|
export interface PanelChrome {
|
|
11
8
|
renderTabLabel: (tab: TabNode) => React.ReactNode;
|
|
12
9
|
renderTabContent: (tab: TabNode) => React.ReactNode;
|
|
13
10
|
renderPanelActions?: (panel: PanelNode) => React.ReactNode;
|
|
14
|
-
dragHandlers:
|
|
11
|
+
dragHandlers: PointerGestureHandlers;
|
|
15
12
|
/** middle-click; the caller pairs it with closing that tab's views */
|
|
16
13
|
onTabClose?: (tabId: string) => void;
|
|
17
14
|
}
|
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
// The tablist wires `aria-controls`/`aria-labelledby` with these, and the strip
|
|
2
|
-
// mints one end of each pair while the tabpanel mints the other.
|
|
3
1
|
export const tabDomId = (tabId) => `jbrowse-tab-${tabId}`;
|
|
4
2
|
export const tabPanelDomId = (panelId) => `jbrowse-tabpanel-${panelId}`;
|
|
@@ -6,15 +6,10 @@ import type { LayoutTree, NodeKind } from './tree.ts';
|
|
|
6
6
|
*/
|
|
7
7
|
export type LayoutViewRef = number | string;
|
|
8
8
|
/**
|
|
9
|
-
* The `layout` a session spec
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `horizontal`/`vertical`/`tabs` and percentage `size` rather than being
|
|
14
|
-
* renamed to match the internal tree.
|
|
15
|
-
*
|
|
16
|
-
* `resolveLayoutSpec` turns the indexes into ids; the tree builder below takes
|
|
17
|
-
* the resolved form only.
|
|
9
|
+
* The `layout` a session spec or URL param states, and the shape
|
|
10
|
+
* `applyLayoutSpec` takes. Public, documented as a URL parameter, so it keeps
|
|
11
|
+
* `horizontal`/`vertical`/`tabs` and percentage `size` rather than the
|
|
12
|
+
* internal tree's vocabulary.
|
|
18
13
|
*/
|
|
19
14
|
export interface LayoutSpecNode<View = LayoutViewRef> {
|
|
20
15
|
/** a leaf: the views to stack vertically in one tab */
|
|
@@ -28,20 +23,11 @@ export interface LayoutSpecNode<View = LayoutViewRef> {
|
|
|
28
23
|
export type ResolvedLayoutSpecNode = LayoutSpecNode<string>;
|
|
29
24
|
/**
|
|
30
25
|
* Resolve every view a spec names to an id against `allViewIds`, the list its
|
|
31
|
-
* indexes count into.
|
|
32
|
-
* received
|
|
33
|
-
*
|
|
34
|
-
* as an empty leaf and collapse the workspace into one blank tab with nothing
|
|
35
|
-
* said.
|
|
36
|
-
*
|
|
37
|
-
* A node stating no `views` and no `children` is NOT that slip — it is the
|
|
38
|
-
* empty panel `treeFromSpec` has always built, and refusing it here made the
|
|
39
|
-
* two surfaces disagree about the one shape they share. The slip is an
|
|
40
|
-
* unrecognized KEY, which is what `viewIds` actually is, and naming it beats
|
|
41
|
-
* the old message that could only say the node needed something else.
|
|
26
|
+
* indexes count into. An unknown key, index or id throws, naming what was
|
|
27
|
+
* received, so an untyped caller writing `viewIds` for `views` hears about it.
|
|
28
|
+
* A node with neither `views` nor `children` is a legal empty panel.
|
|
42
29
|
*
|
|
43
|
-
* `allViewIds` is undefined only on a model that composes no view list
|
|
44
|
-
* an id cannot be checked and an index cannot mean anything.
|
|
30
|
+
* `allViewIds` is undefined only on a model that composes no view list.
|
|
45
31
|
*/
|
|
46
32
|
export declare function resolveLayoutSpec(spec: LayoutSpecNode, allViewIds: string[] | undefined): ResolvedLayoutSpecNode;
|
|
47
33
|
/** A request to move one view relative to the others. Public plugin API. */
|
|
@@ -49,40 +35,13 @@ export interface PendingMove {
|
|
|
49
35
|
type: 'newTab' | 'splitRight';
|
|
50
36
|
viewId: string;
|
|
51
37
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Build a layout tree from a spec.
|
|
54
|
-
*
|
|
55
|
-
* The old converter had to answer "which dockview group does a nested split
|
|
56
|
-
* address?" and could not, because dockview forces orientation to alternate by
|
|
57
|
-
* depth — which is why `size` was honoured only on the top-level split, and
|
|
58
|
-
* only if every panel there carried one. Here the spec's nesting is the tree's
|
|
59
|
-
* nesting, so `size` applies wherever it is written and there is no all-or-
|
|
60
|
-
* nothing pass.
|
|
61
|
-
*/
|
|
62
38
|
export declare function treeFromSpec(spec: ResolvedLayoutSpecNode, nextId: (kind: NodeKind) => string): LayoutTree;
|
|
63
39
|
/** Every viewId a spec names, depth-first — the order it states. */
|
|
64
40
|
export declare function viewIdsInSpec(spec: ResolvedLayoutSpecNode): string[];
|
|
65
41
|
/** How "arrange everything" lays the whole session out. */
|
|
66
42
|
export type TileMode = 'tabs' | 'horizontal' | 'vertical' | 'grid';
|
|
67
43
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* The four whole-workspace commands, as a spec. dockview had these as imperative
|
|
71
|
-
* re-tiling — walk every panel and re-add it relative to the first, in an order
|
|
72
|
-
* chosen so the grid came out right — and the grid case had to compute its own
|
|
73
|
-
* row/column arithmetic against `api.panels` because there was no way to state
|
|
74
|
-
* the shape. Here the shape IS the statement, so a tiling is a spec and goes
|
|
75
|
-
* through the same `applyLayoutSpec` a session spec does.
|
|
76
|
-
*
|
|
77
|
-
* `grid` fills row-major at ceil(sqrt(n)) columns, which is what the dockview
|
|
78
|
-
* version computed; a trailing row shorter than the rest keeps its cells at full
|
|
79
|
-
* width, since `resolveSizes` reads bare siblings as an even share of their own
|
|
80
|
-
* branch rather than of the grid.
|
|
44
|
+
* One view per cell, as a spec. `grid` fills row-major at ceil(sqrt(n))
|
|
45
|
+
* columns; a short trailing row's cells stretch to full width.
|
|
81
46
|
*/
|
|
82
47
|
export declare function tileLayoutSpec(viewIds: string[], mode: TileMode): ResolvedLayoutSpecNode;
|
|
83
|
-
/**
|
|
84
|
-
* `setPendingMove` as a spec: everything else keeps its side, the named view
|
|
85
|
-
* takes the other. With nothing else on screen there is nothing to split from,
|
|
86
|
-
* so the view just takes the space.
|
|
87
|
-
*/
|
|
88
|
-
export declare function specForPendingMove(move: PendingMove, allViewIds: string[]): ResolvedLayoutSpecNode;
|
|
@@ -1,23 +1,12 @@
|
|
|
1
1
|
import { normalize } from './tree.js';
|
|
2
|
-
// `views`/`children` are pulled off by name; the rest pass through to the tree,
|
|
3
|
-
// so an unrecognized one is a key the writer expected to mean something
|
|
4
2
|
const layoutNodeKeys = new Set(['views', 'direction', 'children', 'size']);
|
|
5
3
|
/**
|
|
6
4
|
* Resolve every view a spec names to an id against `allViewIds`, the list its
|
|
7
|
-
* indexes count into.
|
|
8
|
-
* received
|
|
9
|
-
*
|
|
10
|
-
* as an empty leaf and collapse the workspace into one blank tab with nothing
|
|
11
|
-
* said.
|
|
5
|
+
* indexes count into. An unknown key, index or id throws, naming what was
|
|
6
|
+
* received, so an untyped caller writing `viewIds` for `views` hears about it.
|
|
7
|
+
* A node with neither `views` nor `children` is a legal empty panel.
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
* empty panel `treeFromSpec` has always built, and refusing it here made the
|
|
15
|
-
* two surfaces disagree about the one shape they share. The slip is an
|
|
16
|
-
* unrecognized KEY, which is what `viewIds` actually is, and naming it beats
|
|
17
|
-
* the old message that could only say the node needed something else.
|
|
18
|
-
*
|
|
19
|
-
* `allViewIds` is undefined only on a model that composes no view list, where
|
|
20
|
-
* an id cannot be checked and an index cannot mean anything.
|
|
9
|
+
* `allViewIds` is undefined only on a model that composes no view list.
|
|
21
10
|
*/
|
|
22
11
|
export function resolveLayoutSpec(spec, allViewIds) {
|
|
23
12
|
const describe = (value) => typeof value === 'string' ? `"${value}"` : String(value);
|
|
@@ -73,22 +62,9 @@ export function resolveLayoutSpec(spec, allViewIds) {
|
|
|
73
62
|
return resolved;
|
|
74
63
|
}
|
|
75
64
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* and leaves the rest bare means the rest to share what is left over: `70` and
|
|
80
|
-
* blank is 70/30. Defaulting a bare sibling to 1 instead reads a weight against
|
|
81
|
-
* a percentage, and the panel comes out at 1/71 of the width — present, about a
|
|
82
|
-
* pixel wide, and reported by nothing.
|
|
83
|
-
*
|
|
84
|
-
* The mixed case used to be unreachable: dockview forced orientation to
|
|
85
|
-
* alternate by depth, so the converter discarded any partially-sized branch
|
|
86
|
-
* wholesale and raised a notification saying so. Nesting works now, which means
|
|
87
|
-
* this has to mean something instead.
|
|
88
|
-
*
|
|
89
|
-
* All-sized and none-sized both stay plain weights — `normalize` renormalises
|
|
90
|
-
* them, so `70`/`30` and `7`/`3` are the same layout and neither has to sum to
|
|
91
|
-
* anything in particular.
|
|
65
|
+
* `size` is a percentage, so bare siblings share what the sized ones leave:
|
|
66
|
+
* `70` and blank is 70/30, not a 1/71 sliver. All-sized and none-sized stay
|
|
67
|
+
* plain weights for `normalize`.
|
|
92
68
|
*/
|
|
93
69
|
function resolveSizes(children) {
|
|
94
70
|
const stated = children.map(child => child.size);
|
|
@@ -100,21 +76,10 @@ function resolveSizes(children) {
|
|
|
100
76
|
const remainder = 100 - claimed;
|
|
101
77
|
const share = remainder > 0
|
|
102
78
|
? remainder / (children.length - named.length)
|
|
103
|
-
: //
|
|
104
|
-
// sibling takes a typical share rather than collapsing to nothing
|
|
79
|
+
: // over-subscribed: a bare sibling takes an average share
|
|
105
80
|
claimed / named.length;
|
|
106
81
|
return stated.map(size => size ?? share);
|
|
107
82
|
}
|
|
108
|
-
/**
|
|
109
|
-
* Build a layout tree from a spec.
|
|
110
|
-
*
|
|
111
|
-
* The old converter had to answer "which dockview group does a nested split
|
|
112
|
-
* address?" and could not, because dockview forces orientation to alternate by
|
|
113
|
-
* depth — which is why `size` was honoured only on the top-level split, and
|
|
114
|
-
* only if every panel there carried one. Here the spec's nesting is the tree's
|
|
115
|
-
* nesting, so `size` applies wherever it is written and there is no all-or-
|
|
116
|
-
* nothing pass.
|
|
117
|
-
*/
|
|
118
83
|
export function treeFromSpec(spec, nextId) {
|
|
119
84
|
function build(node, size) {
|
|
120
85
|
if (node.views) {
|
|
@@ -129,14 +94,8 @@ export function treeFromSpec(spec, nextId) {
|
|
|
129
94
|
if (children.length === 0) {
|
|
130
95
|
return undefined;
|
|
131
96
|
}
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
// A tab holds a FLAT stack of views, so a container child has no split to
|
|
135
|
-
// become — and the docs say containers nest arbitrarily deep, so one can be
|
|
136
|
-
// written. Its views are flattened into a single tab; they used to be
|
|
137
|
-
// dropped from the layout outright, which is invisible rather than wrong,
|
|
138
|
-
// since homing then swept them into whichever tab happened to be showing.
|
|
139
|
-
// `loadSessionSpec` reports the flattening, as it does the sizes below.
|
|
97
|
+
// every child becomes a tab in one cell; a tab holds a flat stack, so a
|
|
98
|
+
// container child's views flatten into it (`loadSessionSpec` reports this)
|
|
140
99
|
if (node.direction === 'tabs') {
|
|
141
100
|
const tabs = children.flatMap(child => {
|
|
142
101
|
const viewIds = viewIdsInSpec(child);
|
|
@@ -176,23 +135,10 @@ export function viewIdsInSpec(spec) {
|
|
|
176
135
|
];
|
|
177
136
|
}
|
|
178
137
|
/**
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* The four whole-workspace commands, as a spec. dockview had these as imperative
|
|
182
|
-
* re-tiling — walk every panel and re-add it relative to the first, in an order
|
|
183
|
-
* chosen so the grid came out right — and the grid case had to compute its own
|
|
184
|
-
* row/column arithmetic against `api.panels` because there was no way to state
|
|
185
|
-
* the shape. Here the shape IS the statement, so a tiling is a spec and goes
|
|
186
|
-
* through the same `applyLayoutSpec` a session spec does.
|
|
187
|
-
*
|
|
188
|
-
* `grid` fills row-major at ceil(sqrt(n)) columns, which is what the dockview
|
|
189
|
-
* version computed; a trailing row shorter than the rest keeps its cells at full
|
|
190
|
-
* width, since `resolveSizes` reads bare siblings as an even share of their own
|
|
191
|
-
* branch rather than of the grid.
|
|
138
|
+
* One view per cell, as a spec. `grid` fills row-major at ceil(sqrt(n))
|
|
139
|
+
* columns; a short trailing row's cells stretch to full width.
|
|
192
140
|
*/
|
|
193
141
|
export function tileLayoutSpec(viewIds, mode) {
|
|
194
|
-
// Nothing to arrange: one view is the whole workspace whatever the mode, and
|
|
195
|
-
// no views leaves the empty panel the tree already guarantees.
|
|
196
142
|
if (viewIds.length <= 1) {
|
|
197
143
|
return { views: [...viewIds] };
|
|
198
144
|
}
|
|
@@ -210,17 +156,3 @@ export function tileLayoutSpec(viewIds, mode) {
|
|
|
210
156
|
}
|
|
211
157
|
return { direction: 'vertical', children: rows };
|
|
212
158
|
}
|
|
213
|
-
/**
|
|
214
|
-
* `setPendingMove` as a spec: everything else keeps its side, the named view
|
|
215
|
-
* takes the other. With nothing else on screen there is nothing to split from,
|
|
216
|
-
* so the view just takes the space.
|
|
217
|
-
*/
|
|
218
|
-
export function specForPendingMove(move, allViewIds) {
|
|
219
|
-
const others = allViewIds.filter(id => id !== move.viewId);
|
|
220
|
-
return others.length > 0
|
|
221
|
-
? {
|
|
222
|
-
direction: move.type === 'splitRight' ? 'horizontal' : 'tabs',
|
|
223
|
-
children: [{ views: others }, { views: [move.viewId] }],
|
|
224
|
-
}
|
|
225
|
-
: { views: [move.viewId] };
|
|
226
|
-
}
|
|
@@ -1,25 +1,9 @@
|
|
|
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
1
|
export declare const MIN_PANE_PX = 100;
|
|
13
2
|
/** The space the two panes either side of the boundary before `index` share. */
|
|
14
3
|
export declare function pairSpan(sizes: number[], index: number): number;
|
|
15
4
|
/**
|
|
16
|
-
* `sizes` with that boundary moved to `position`,
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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.
|
|
5
|
+
* `sizes` with that boundary moved to `position`, in the same units; other
|
|
6
|
+
* panes hold still. `pairPx` converts `MIN_PANE_PX` to a share; 0 (jsdom, or
|
|
7
|
+
* not laid out) skips the minimum.
|
|
24
8
|
*/
|
|
25
9
|
export declare function withBoundaryAt(sizes: number[], index: number, position: number, pairPx?: number): number[];
|
|
@@ -1,33 +1,17 @@
|
|
|
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
|
-
*/
|
|
1
|
+
// a `flex-grow` share of zero is legal, so this keeps a pane from vanishing
|
|
12
2
|
export const MIN_PANE_PX = 100;
|
|
13
3
|
/** The space the two panes either side of the boundary before `index` share. */
|
|
14
4
|
export function pairSpan(sizes, index) {
|
|
15
5
|
return sizes[index - 1] + sizes[index];
|
|
16
6
|
}
|
|
17
7
|
/**
|
|
18
|
-
* `sizes` with that boundary moved to `position`,
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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.
|
|
8
|
+
* `sizes` with that boundary moved to `position`, in the same units; other
|
|
9
|
+
* panes hold still. `pairPx` converts `MIN_PANE_PX` to a share; 0 (jsdom, or
|
|
10
|
+
* not laid out) skips the minimum.
|
|
26
11
|
*/
|
|
27
12
|
export function withBoundaryAt(sizes, index, position, pairPx = 0) {
|
|
28
13
|
const pair = pairSpan(sizes, index);
|
|
29
|
-
// no room for two minimums
|
|
30
|
-
// pinning both ends past each other
|
|
14
|
+
// with no room for two minimums the boundary stops in the middle
|
|
31
15
|
const floor = pairPx > 0 ? Math.min((MIN_PANE_PX / pairPx) * pair, pair / 2) : 0;
|
|
32
16
|
const before = Math.min(Math.max(position, floor), pair - floor);
|
|
33
17
|
const next = [...sizes];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { TabNode } from './tree.ts';
|
|
2
|
+
import type { AbstractViewModel } from '@jbrowse/core/util';
|
|
3
|
+
/** A tab's name: the user's title if it has one, else derived from its views */
|
|
4
|
+
export declare function tabDisplayName(tab: TabNode, views: AbstractViewModel[], session: {
|
|
5
|
+
assemblyManager: {
|
|
6
|
+
getDisplayName: (name: string) => string;
|
|
7
|
+
};
|
|
8
|
+
}): string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { viewName } from '../ui/App/viewTitle.js';
|
|
2
|
+
/** A tab's name: the user's title if it has one, else derived from its views */
|
|
3
|
+
export function tabDisplayName(tab, views, session) {
|
|
4
|
+
if (tab.title) {
|
|
5
|
+
return tab.title;
|
|
6
|
+
}
|
|
7
|
+
if (views.length === 0) {
|
|
8
|
+
return 'Empty';
|
|
9
|
+
}
|
|
10
|
+
if (views.length === 1) {
|
|
11
|
+
return viewName(views[0], r => session.assemblyManager.getDisplayName(r));
|
|
12
|
+
}
|
|
13
|
+
return `${views.length} views`;
|
|
14
|
+
}
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The layout tree
|
|
3
|
-
*
|
|
4
|
-
*
|
|
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.
|
|
2
|
+
* The layout tree as pure functions over plain snapshots, deliberately not MST
|
|
3
|
+
* so its invariants can be tested exhaustively. Every operation returns a tree
|
|
4
|
+
* in canonical form (`normalize`).
|
|
21
5
|
*/
|
|
6
|
+
/** One tab: a vertical stack of views. */
|
|
22
7
|
export interface TabNode {
|
|
23
8
|
id: string;
|
|
24
9
|
/** membership only — `session.views` is the order (see WorkspaceLayout/CLAUDE.md) */
|
|
@@ -66,23 +51,13 @@ export declare function panelContainingView(node: LayoutTree, viewId: string): P
|
|
|
66
51
|
* it, its children's sizes scaled to preserve their share of the whole
|
|
67
52
|
* 4. sizes are renormalised so a node's siblings always sum to 1
|
|
68
53
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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.
|
|
54
|
+
* Empty panels and empty tabs stay: "new empty tab" creates one on purpose, to
|
|
55
|
+
* show the view launcher.
|
|
77
56
|
*/
|
|
78
57
|
export declare function normalize(node: LayoutTree): LayoutTree;
|
|
79
58
|
/**
|
|
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.
|
|
59
|
+
* Split `panelId`, putting `newPanel` beside it in `direction`. Always wraps
|
|
60
|
+
* the panel in a branch and lets `normalize` flatten it when directions agree.
|
|
86
61
|
*/
|
|
87
62
|
export declare function splitPanel(root: LayoutTree, panelId: string, direction: 'row' | 'column', newPanel: PanelNode, before?: boolean): LayoutTree;
|
|
88
63
|
/** Drop a panel. Its space goes back to its siblings via renormalisation. */
|
|
@@ -96,25 +71,11 @@ export declare function addTab(root: LayoutTree, panelId: string, tab: TabNode):
|
|
|
96
71
|
*/
|
|
97
72
|
export declare function removeTab(root: LayoutTree, tabId: string): LayoutTree;
|
|
98
73
|
/**
|
|
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.
|
|
74
|
+
* Move a tab into another panel, at `index` if given, or append.
|
|
104
75
|
*
|
|
105
|
-
*
|
|
106
|
-
* the
|
|
107
|
-
*
|
|
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.
|
|
76
|
+
* `index` counts the strip the user sees, before the move. Within one panel a
|
|
77
|
+
* tab left of the gap shifts it by one once removed: dragging A between B and C
|
|
78
|
+
* in `[A, B, C]` is index 2 on screen but 1 after A comes out.
|
|
118
79
|
*/
|
|
119
80
|
export declare function moveTabToPanel(root: LayoutTree, tabId: string, targetPanelId: string, index?: number): LayoutTree;
|
|
120
81
|
export declare function setActiveTab(root: LayoutTree, panelId: string, tabId: string): LayoutTree;
|
|
@@ -123,36 +84,18 @@ export declare function addViewToTab(root: LayoutTree, tabId: string, viewId: st
|
|
|
123
84
|
/** Take a view out of whatever tab holds it, leaving the tab in place. */
|
|
124
85
|
export declare function removeView(root: LayoutTree, viewId: string): LayoutTree;
|
|
125
86
|
/**
|
|
126
|
-
* Make the tree's membership agree with
|
|
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.
|
|
87
|
+
* Make the tree's membership agree with `session.views`, which owns which views
|
|
88
|
+
* exist. `viewIds` is the WHOLE set: a shorter list unhomes the rest.
|
|
134
89
|
*/
|
|
135
90
|
export declare function homeViews(root: LayoutTree, viewIds: string[], activePanelId: string | undefined, nextTabId: () => string): LayoutTree;
|
|
136
91
|
/**
|
|
137
|
-
* Drop a panel
|
|
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.
|
|
92
|
+
* Drop a panel left with no tabs, unless it is the only one. Not a
|
|
93
|
+
* normalisation rule: only the gesture that emptied the panel calls it.
|
|
144
94
|
*/
|
|
145
95
|
export declare function pruneEmptyPanel(root: LayoutTree, panelId: string): LayoutTree;
|
|
146
96
|
/**
|
|
147
|
-
* Drop a tab
|
|
148
|
-
*
|
|
149
|
-
*
|
|
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.
|
|
97
|
+
* Drop a tab a move has left with no views, unless it is the panel's last.
|
|
98
|
+
* Closing a tab's last view leaves the tab standing, as a place to open the
|
|
99
|
+
* next one; only a move out of it prunes.
|
|
157
100
|
*/
|
|
158
101
|
export declare function pruneEmptyTabIn(root: LayoutTree, panelId: string, tabId: string): LayoutTree;
|