@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
package/esm/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
1
|
+
export { App, AppReadyMarker, DialogQueue } from './ui/index.js';
|
|
2
|
+
export { HistoryManagementMixin } from './HistoryManagement/index.js';
|
|
3
|
+
export { DEFAULT_SHARE_URL, JBrowseConfigF } from './JBrowseConfig/index.js';
|
|
4
|
+
export { JBrowseModelF } from './JBrowseModel/index.js';
|
|
5
|
+
export { AssembliesMixin } from './Assemblies/index.js';
|
|
6
|
+
export { AppSessionMixin } from './AppSession/index.js';
|
|
7
|
+
export { RootAppMenuMixin } from './RootMenu/index.js';
|
|
8
|
+
export { WorkspaceLayoutMixin, isSessionWithWorkspaceLayout, } from './WorkspaceLayout/index.js';
|
|
9
|
+
export { addSessionTracks, buildLgvInit, buildLgvInitFromParams, hubConnectionSpec, loadSessionSpec, parseSessionSpecUrl, readHubUrlParam, readNavParam, readTracklistParam, shortHubLabel, splitHighlights, } from './SessionSpec/index.js';
|
|
10
|
+
// URL params, for the two products that own their page (web, desktop). An
|
|
11
|
+
// embedded product must not rewrite its host's URL — see queryParams.ts
|
|
12
|
+
export { deleteQueryParams, readAllQueryParams, readQueryParams, setQueryParams, useQueryParam, } from './queryParams.js';
|
|
13
|
+
export { processMutableMenuActions, resolveMenus } from './menus.js';
|
package/esm/menus.d.ts
CHANGED
|
@@ -1,74 +1,75 @@
|
|
|
1
|
-
import type { MenuItem } from '@jbrowse/core/ui';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import type { MenuItem, MenuItemsGetter } from '@jbrowse/core/ui';
|
|
2
|
+
/**
|
|
3
|
+
* Ensure a top-level menu exists. `position` appends when omitted and counts
|
|
4
|
+
* from the end when negative. A menu whose label is already on the bar is left
|
|
5
|
+
* where it is — labels are the bar's identity (they key the React elements and
|
|
6
|
+
* they are how an item contribution names its target), so two of them is never
|
|
7
|
+
* what a caller wanted.
|
|
8
|
+
*/
|
|
9
|
+
interface AddMenuAction {
|
|
10
|
+
type: 'addMenu';
|
|
10
11
|
menuName: string;
|
|
11
|
-
|
|
12
|
-
position: number;
|
|
13
|
-
}
|
|
14
|
-
interface AppendToMenuAction {
|
|
15
|
-
type: 'appendToMenu';
|
|
16
|
-
menuName: string;
|
|
17
|
-
menuItem: MenuItem;
|
|
12
|
+
position?: number;
|
|
18
13
|
}
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Add one item at `menuPath` — the top-level menu, then any sub-menus, which
|
|
16
|
+
* are created as needed. `position` appends when omitted and counts from the
|
|
17
|
+
* end when negative.
|
|
18
|
+
*/
|
|
19
|
+
interface AddItemAction {
|
|
20
|
+
type: 'addItem';
|
|
21
21
|
menuPath: string[];
|
|
22
22
|
menuItem: MenuItem;
|
|
23
|
-
|
|
24
|
-
interface AppendMenuAction {
|
|
25
|
-
type: 'appendMenu';
|
|
26
|
-
menuName: string;
|
|
27
|
-
}
|
|
28
|
-
interface InsertMenuAction {
|
|
29
|
-
type: 'insertMenu';
|
|
30
|
-
menuName: string;
|
|
31
|
-
position: number;
|
|
23
|
+
position?: number;
|
|
32
24
|
}
|
|
33
25
|
interface SetMenusAction {
|
|
34
26
|
type: 'setMenus';
|
|
35
|
-
newMenus:
|
|
27
|
+
newMenus: MenuDefinition[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* `addMenu`/`setMenus` change which menus exist, so they resolve eagerly — the
|
|
31
|
+
* app bar renders one button per menu before anything is opened. `addItem` only
|
|
32
|
+
* changes a menu's contents, so it resolves when that menu opens, against
|
|
33
|
+
* whatever its items are at that moment.
|
|
34
|
+
*/
|
|
35
|
+
export type MenuAction = AddMenuAction | AddItemAction | SetMenusAction;
|
|
36
|
+
/**
|
|
37
|
+
* A menu as a root model authors it. Items may be a plain array, or a thunk for
|
|
38
|
+
* a menu whose contents depend on state that would otherwise be read — and so
|
|
39
|
+
* tracked — every time the app bar renders.
|
|
40
|
+
*/
|
|
41
|
+
export interface MenuDefinition {
|
|
42
|
+
label: string;
|
|
43
|
+
menuItems: MenuItemsGetter;
|
|
36
44
|
}
|
|
37
|
-
|
|
45
|
+
/**
|
|
46
|
+
* A menu as the app bar consumes it. Always a thunk returning a fresh array: a
|
|
47
|
+
* menu's items are produced when it opens and never before, so no consumer has
|
|
48
|
+
* to care which form the root model authored, and a plugin's contributions are
|
|
49
|
+
* merged in at that point rather than spliced into the definition.
|
|
50
|
+
*/
|
|
38
51
|
export interface Menu {
|
|
39
52
|
label: string;
|
|
40
|
-
menuItems: MenuItem[];
|
|
53
|
+
menuItems: () => MenuItem[];
|
|
41
54
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
export declare function appendToSubMenu({ menus, menuPath, menuItem, }: {
|
|
63
|
-
menus: Menu[];
|
|
64
|
-
menuPath: string[];
|
|
65
|
-
menuItem: MenuItem;
|
|
66
|
-
}): number;
|
|
67
|
-
export declare function insertInSubMenu({ menus, menuPath, menuItem, position, }: {
|
|
68
|
-
menus: Menu[];
|
|
69
|
-
menuPath: string[];
|
|
70
|
-
menuItem: MenuItem;
|
|
71
|
-
position: number;
|
|
72
|
-
}): number;
|
|
73
|
-
export declare function processMutableMenuActions(ret: Menu[], actions: MenuAction[]): Menu[];
|
|
55
|
+
/**
|
|
56
|
+
* Resolve a root model's menu definitions against the contributions plugins
|
|
57
|
+
* have pushed (via `RootAppMenuMixin`). Structural contributions apply now;
|
|
58
|
+
* item contributions are recorded against their target menu and applied when
|
|
59
|
+
* that menu opens, so a menu that computes its items lazily stays lazy and
|
|
60
|
+
* nothing is ever spliced into a definition.
|
|
61
|
+
*
|
|
62
|
+
* Runs on every `menus()` evaluation, so it must not mutate `base` or anything
|
|
63
|
+
* an action carries.
|
|
64
|
+
*/
|
|
65
|
+
export declare function processMutableMenuActions(base: MenuDefinition[], actions: MenuAction[]): Menu[];
|
|
66
|
+
/**
|
|
67
|
+
* Flatten resolved menus to plain data by opening every one of them. For tests
|
|
68
|
+
* and other callers that want to assert on menu contents rather than render
|
|
69
|
+
* them.
|
|
70
|
+
*/
|
|
71
|
+
export declare function resolveMenus(menus: Menu[]): {
|
|
72
|
+
label: string;
|
|
73
|
+
menuItems: MenuItem[];
|
|
74
|
+
}[];
|
|
74
75
|
export {};
|
package/esm/menus.js
CHANGED
|
@@ -1,99 +1,146 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// a contribution that threw is reported once rather than on every open — the
|
|
2
|
+
// menu re-resolves on each open and, while open, on each observer re-render
|
|
3
|
+
const reported = new WeakSet();
|
|
4
|
+
/**
|
|
5
|
+
* The action log is permanent; the menu it builds is thrown away when the menu
|
|
6
|
+
* closes. Everything a resolution walks therefore has to belong to the
|
|
7
|
+
* throwaway, because resolving a path is a **write**:
|
|
8
|
+
* `appendToSubMenu(['Add', 'My plugin'], two)` means "find the array labelled
|
|
9
|
+
* My plugin and push into it".
|
|
10
|
+
*
|
|
11
|
+
* So when an earlier action is what supplied that array —
|
|
12
|
+
* `appendToMenu('Add', { label: 'My plugin', subMenu: [one] })` — and it goes
|
|
13
|
+
* into the menu by reference, the push lands in the stored action instead. The
|
|
14
|
+
* next open replays a log that now reads `subMenu: [one, two]` and pushes
|
|
15
|
+
* again, so the sub-menu gains an item on every open, and on every observer
|
|
16
|
+
* re-render while open.
|
|
17
|
+
*
|
|
18
|
+
* Cloning is what keeps the two apart: the copy is what the path resolves to
|
|
19
|
+
* and what gets written, and it dies with the open. Leaf items are shared by
|
|
20
|
+
* reference — nothing writes into them — so this costs an allocation only for
|
|
21
|
+
* the rows that nest.
|
|
22
|
+
*/
|
|
23
|
+
function cloneMenuItem(item) {
|
|
24
|
+
return 'subMenu' in item
|
|
25
|
+
? { ...item, subMenu: cloneMenuItems(item.subMenu) }
|
|
26
|
+
: item;
|
|
3
27
|
}
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
label: menuName,
|
|
7
|
-
menuItems: [],
|
|
8
|
-
});
|
|
9
|
-
return menus.length;
|
|
28
|
+
function cloneMenuItems(items) {
|
|
29
|
+
return items.map(item => cloneMenuItem(item));
|
|
10
30
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (!menu) {
|
|
14
|
-
menus.push({ label: menuName, menuItems: [menuItem] });
|
|
15
|
-
return 1;
|
|
16
|
-
}
|
|
17
|
-
return menu.menuItems.push(menuItem);
|
|
31
|
+
function materialize(menuItems) {
|
|
32
|
+
return typeof menuItems === 'function' ? menuItems() : menuItems;
|
|
18
33
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return 1;
|
|
24
|
-
}
|
|
25
|
-
const insertPosition = position < 0 ? menu.menuItems.length + position : position;
|
|
26
|
-
menu.menuItems.splice(insertPosition, 0, menuItem);
|
|
27
|
-
return menu.menuItems.length;
|
|
34
|
+
// splice is already the "counts from the end when negative" rule, and clamps
|
|
35
|
+
// rather than wrapping past the start; the default is what makes it an append
|
|
36
|
+
function insertAt(items, item, position = items.length) {
|
|
37
|
+
items.splice(position, 0, item);
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
for (const menuName of menuPath.slice(1)) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Walk `menuPath` past its first segment (the top-level menu, already
|
|
41
|
+
* resolved), creating empty sub-menus as needed, and return the deepest
|
|
42
|
+
* sub-menu's item array. Throws if a path segment names an item that is not a
|
|
43
|
+
* sub-menu.
|
|
44
|
+
*/
|
|
45
|
+
function resolveSubMenu(items, menuPath) {
|
|
46
|
+
let level = items;
|
|
47
|
+
for (const [idx, menuName] of menuPath.slice(1).entries()) {
|
|
48
|
+
let sub = level.find((mi) => 'subMenu' in mi && mi.label === menuName);
|
|
49
|
+
if (!sub) {
|
|
50
|
+
if (level.some(mi => 'label' in mi && mi.label === menuName)) {
|
|
51
|
+
const pathSoFar = menuPath.slice(0, idx + 2).join(' > ');
|
|
52
|
+
throw new Error(`"${menuName}" in path "${pathSoFar}" is not a subMenu`);
|
|
53
|
+
}
|
|
54
|
+
sub = { label: menuName, subMenu: [] };
|
|
55
|
+
level.push(sub);
|
|
46
56
|
}
|
|
47
|
-
|
|
57
|
+
level = sub.subMenu;
|
|
48
58
|
}
|
|
49
|
-
return
|
|
59
|
+
return level;
|
|
50
60
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
let sm = subMenu.find(mi => 'label' in mi && mi.label === menuName);
|
|
62
|
-
if (!sm) {
|
|
63
|
-
const idx = subMenu.push({ label: menuName, subMenu: [] });
|
|
64
|
-
sm = subMenu[idx - 1];
|
|
61
|
+
// each contribution is applied independently: a plugin that names a bad path
|
|
62
|
+
// loses its own item rather than every other plugin's, and never the session —
|
|
63
|
+
// this runs from the app bar's click handler, where a throw goes straight
|
|
64
|
+
// through React
|
|
65
|
+
function applyItemActions(items, actions) {
|
|
66
|
+
for (const action of actions) {
|
|
67
|
+
try {
|
|
68
|
+
const target = resolveSubMenu(items, action.menuPath);
|
|
69
|
+
// cloned rather than inserted by reference — see cloneMenuItem
|
|
70
|
+
insertAt(target, cloneMenuItem(action.menuItem), action.position);
|
|
65
71
|
}
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (!reported.has(action)) {
|
|
74
|
+
reported.add(action);
|
|
75
|
+
console.error(error);
|
|
76
|
+
}
|
|
68
77
|
}
|
|
69
|
-
subMenu = sm.subMenu;
|
|
70
78
|
}
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
return items;
|
|
80
|
+
}
|
|
81
|
+
// first menu with this label, or a new empty one — an item action naming a menu
|
|
82
|
+
// that doesn't exist creates it, so a plugin can populate its own menu without
|
|
83
|
+
// declaring it first
|
|
84
|
+
function findOrCreateMenu(pending, label, position) {
|
|
85
|
+
const found = pending.find(m => m.label === label);
|
|
86
|
+
if (found) {
|
|
87
|
+
return found;
|
|
88
|
+
}
|
|
89
|
+
const menu = { label, base: [], itemActions: [] };
|
|
90
|
+
insertAt(pending, menu, position);
|
|
91
|
+
return menu;
|
|
73
92
|
}
|
|
74
|
-
|
|
93
|
+
function toMenu({ label, base, itemActions }) {
|
|
94
|
+
return {
|
|
95
|
+
label,
|
|
96
|
+
menuItems: () => applyItemActions(cloneMenuItems(materialize(base)), itemActions),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a root model's menu definitions against the contributions plugins
|
|
101
|
+
* have pushed (via `RootAppMenuMixin`). Structural contributions apply now;
|
|
102
|
+
* item contributions are recorded against their target menu and applied when
|
|
103
|
+
* that menu opens, so a menu that computes its items lazily stays lazy and
|
|
104
|
+
* nothing is ever spliced into a definition.
|
|
105
|
+
*
|
|
106
|
+
* Runs on every `menus()` evaluation, so it must not mutate `base` or anything
|
|
107
|
+
* an action carries.
|
|
108
|
+
*/
|
|
109
|
+
export function processMutableMenuActions(base, actions) {
|
|
110
|
+
const toPending = (m) => ({
|
|
111
|
+
label: m.label,
|
|
112
|
+
base: m.menuItems,
|
|
113
|
+
itemActions: [],
|
|
114
|
+
});
|
|
115
|
+
let pending = base.map(toPending);
|
|
75
116
|
for (const action of actions) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
else if (action.type === 'insertInMenu') {
|
|
95
|
-
insertInMenu({ menus: ret, ...action });
|
|
117
|
+
switch (action.type) {
|
|
118
|
+
case 'setMenus': {
|
|
119
|
+
// replaces the menu bar wholesale, so item contributions made before it
|
|
120
|
+
// are dropped along with the menus they targeted
|
|
121
|
+
pending = action.newMenus.map(toPending);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
case 'addMenu': {
|
|
125
|
+
findOrCreateMenu(pending, action.menuName, action.position);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case 'addItem': {
|
|
129
|
+
findOrCreateMenu(pending, action.menuPath[0]).itemActions.push(action);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
default: {
|
|
133
|
+
action;
|
|
134
|
+
}
|
|
96
135
|
}
|
|
97
136
|
}
|
|
98
|
-
return
|
|
137
|
+
return pending.map(m => toMenu(m));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Flatten resolved menus to plain data by opening every one of them. For tests
|
|
141
|
+
* and other callers that want to assert on menu contents rather than render
|
|
142
|
+
* them.
|
|
143
|
+
*/
|
|
144
|
+
export function resolveMenus(menus) {
|
|
145
|
+
return menus.map(m => ({ label: m.label, menuItems: m.menuItems() }));
|
|
99
146
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function readQueryParams<T extends string>(keys: T[]): Record<T, string | undefined>;
|
|
2
|
+
export declare function readAllQueryParams(): URLSearchParams;
|
|
3
|
+
export declare function deleteQueryParams(keys: readonly string[]): void;
|
|
4
|
+
export declare function setQueryParams(values: Record<string, string | undefined>): void;
|
|
5
|
+
/**
|
|
6
|
+
* Read one param and keep re-rendering as it changes, including across the
|
|
7
|
+
* `replaceState` writes above, which fire no event of their own.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useQueryParam(key: string): readonly [string | undefined, (newValue: string | undefined) => void];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { useCallback, useSyncExternalStore } from 'react';
|
|
2
|
+
// The single decision of where this URL keeps its params. Computed once per
|
|
3
|
+
// operation and threaded through read+write so the two can't disagree.
|
|
4
|
+
function paramLocation() {
|
|
5
|
+
return window.location.hash.includes('=') ? 'hash' : 'search';
|
|
6
|
+
}
|
|
7
|
+
function readParams(loc) {
|
|
8
|
+
return new URLSearchParams(loc === 'hash' ? window.location.hash.slice(1) : window.location.search);
|
|
9
|
+
}
|
|
10
|
+
// history.replaceState fires no event, so a write has to notify readers itself.
|
|
11
|
+
// popstate alone covers only back/forward, which nothing here does: desktop's
|
|
12
|
+
// Loader clears its `?config=` with the very setter defined here, and with no
|
|
13
|
+
// notification it kept reading the stale value and stayed on the loading screen
|
|
14
|
+
// (the "fall back to the start screen" error path never arrived).
|
|
15
|
+
const listeners = new Set();
|
|
16
|
+
function subscribe(callback) {
|
|
17
|
+
listeners.add(callback);
|
|
18
|
+
window.addEventListener('popstate', callback);
|
|
19
|
+
return () => {
|
|
20
|
+
listeners.delete(callback);
|
|
21
|
+
window.removeEventListener('popstate', callback);
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Writes params back into `loc`, leaving the other URL component untouched — the
|
|
25
|
+
// URL hash/search setters each replace only their own component.
|
|
26
|
+
function writeParams(loc, params) {
|
|
27
|
+
const url = new URL(window.location.href);
|
|
28
|
+
const str = params.toString();
|
|
29
|
+
if (loc === 'hash') {
|
|
30
|
+
url.hash = str;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
url.search = str;
|
|
34
|
+
}
|
|
35
|
+
window.history.replaceState(null, '', url.href);
|
|
36
|
+
// Copied, which this note has claimed while the loop read the live set. A
|
|
37
|
+
// listener is React's store-change callback, and React may flush the re-render
|
|
38
|
+
// synchronously inside it, so the set can be mutated mid-notify: an unmounted
|
|
39
|
+
// reader unsubscribes (safe either way) and a remounted one re-adds itself at
|
|
40
|
+
// the end, where iterating the live set would visit it again for a change it
|
|
41
|
+
// has already read. The snapshot makes who gets notified for one change fixed
|
|
42
|
+
// before the first call.
|
|
43
|
+
for (const listener of [...listeners]) {
|
|
44
|
+
listener();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function readQueryParams(keys) {
|
|
48
|
+
const params = readParams(paramLocation());
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const key of keys) {
|
|
51
|
+
result[key] = params.get(key) ?? undefined;
|
|
52
|
+
}
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
// Reads the full current param set from wherever this URL keeps them (hash XOR
|
|
56
|
+
// search). Producers that need every param at once (buildShareUrl) share this
|
|
57
|
+
// so the hash/search decision stays in this one module.
|
|
58
|
+
export function readAllQueryParams() {
|
|
59
|
+
return readParams(paramLocation());
|
|
60
|
+
}
|
|
61
|
+
export function deleteQueryParams(keys) {
|
|
62
|
+
const loc = paramLocation();
|
|
63
|
+
const params = readParams(loc);
|
|
64
|
+
for (const key of keys) {
|
|
65
|
+
params.delete(key);
|
|
66
|
+
}
|
|
67
|
+
writeParams(loc, params);
|
|
68
|
+
}
|
|
69
|
+
export function setQueryParams(values) {
|
|
70
|
+
const loc = paramLocation();
|
|
71
|
+
const params = readParams(loc);
|
|
72
|
+
for (const [key, value] of Object.entries(values)) {
|
|
73
|
+
if (value === undefined) {
|
|
74
|
+
params.delete(key);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
params.set(key, value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
writeParams(loc, params);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Read one param and keep re-rendering as it changes, including across the
|
|
84
|
+
* `replaceState` writes above, which fire no event of their own.
|
|
85
|
+
*/
|
|
86
|
+
export function useQueryParam(key) {
|
|
87
|
+
const value = useSyncExternalStore(subscribe, () => readParams(paramLocation()).get(key), () => null);
|
|
88
|
+
const setValue = useCallback((newValue) => {
|
|
89
|
+
setQueryParams({ [key]: newValue });
|
|
90
|
+
}, [key]);
|
|
91
|
+
return [value ?? undefined, setValue];
|
|
92
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
+
import type { BaseAssemblyConfigSchema } from '@jbrowse/core/assemblyManager/assemblyConfigSchema';
|
|
3
|
+
import type { IAnyType } from '@jbrowse/mobx-state-tree';
|
|
4
|
+
/**
|
|
5
|
+
* What each app product's root model takes as its `sessionModelFactory`
|
|
6
|
+
* argument: the root builds the assembly config schema once and hands it to the
|
|
7
|
+
* session model, so the session's assembly slots are the same type the root's
|
|
8
|
+
* are rather than a second schema built from the same plugins.
|
|
9
|
+
*
|
|
10
|
+
* One name for the three roots that take it (web, desktop, react-app). They had
|
|
11
|
+
* a copy each, and two of them spelled the schema as a local
|
|
12
|
+
* `ReturnType<typeof assemblyConfigSchemaFactory>` alias — which is what
|
|
13
|
+
* `BaseAssemblyConfigSchema` already is.
|
|
14
|
+
*/
|
|
15
|
+
export type SessionModelFactory = (args: {
|
|
16
|
+
pluginManager: PluginManager;
|
|
17
|
+
assemblyConfigSchema: BaseAssemblyConfigSchema;
|
|
18
|
+
}) => IAnyType;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/esm/ui/App/App.d.ts
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type { SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel';
|
|
3
|
-
import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util';
|
|
4
|
-
interface Menu {
|
|
5
|
-
label: string;
|
|
6
|
-
menuItems: JBMenuItem[];
|
|
7
|
-
}
|
|
1
|
+
import type { AppSession } from './types.ts';
|
|
8
2
|
interface Props {
|
|
9
3
|
HeaderButtons?: React.ReactElement;
|
|
10
|
-
session:
|
|
11
|
-
menus: () => Menu[];
|
|
12
|
-
snackbarMessages: SnackbarMessage[];
|
|
13
|
-
renameCurrentSession: (arg: string) => void;
|
|
14
|
-
popSnackbarMessage: () => unknown;
|
|
15
|
-
};
|
|
4
|
+
session: AppSession;
|
|
16
5
|
}
|
|
17
|
-
declare const App: (props: Props) => import("react
|
|
6
|
+
declare const App: (props: Props) => import("react").JSX.Element;
|
|
18
7
|
export { App };
|
package/esm/ui/App/App.js
CHANGED
|
@@ -2,42 +2,55 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Suspense, lazy } from 'react';
|
|
3
3
|
import Snackbar from '@jbrowse/core/ui/Snackbar';
|
|
4
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
+
import { ModalWidget, drawerGridTemplateColumns } from '@jbrowse/product-core';
|
|
5
6
|
import { AppBar } from '@mui/material';
|
|
6
7
|
import { observer } from 'mobx-react';
|
|
7
|
-
import AppFab from
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
8
|
+
import AppFab from './AppFab.js';
|
|
9
|
+
import AppReadyMarker from './AppReadyMarker.js';
|
|
10
|
+
import AppToolbar from './AppToolbar.js';
|
|
11
|
+
import DialogQueue from './DialogQueue.js';
|
|
12
|
+
import ViewsContainer from './ViewsContainer.js';
|
|
13
|
+
import { lazyChunk } from './lazyChunk.js';
|
|
14
|
+
// lazies
|
|
15
|
+
const DrawerWidget = lazy(lazyChunk('DrawerWidget', () => import('./DrawerWidget.js')));
|
|
12
16
|
const useStyles = makeStyles()(theme => ({
|
|
13
17
|
root: {
|
|
14
18
|
display: 'grid',
|
|
15
|
-
|
|
19
|
+
// Embedders can fit the app to its container by setting the
|
|
20
|
+
// --jbrowse-app-height CSS variable (e.g. to 100%); it defaults to the
|
|
21
|
+
// full viewport for standalone/full-window use.
|
|
22
|
+
height: 'var(--jbrowse-app-height, 100vh)',
|
|
23
|
+
// the containing block for AppFab, which floats over the app rather than
|
|
24
|
+
// over the page
|
|
25
|
+
position: 'relative',
|
|
26
|
+
// pin the single implicit row to the container height so appContainer
|
|
27
|
+
// fills it (an auto row would instead grow to content and overflow)
|
|
28
|
+
gridTemplateRows: 'minmax(0, 1fr)',
|
|
16
29
|
width: '100%',
|
|
17
30
|
colorScheme: theme.palette.mode,
|
|
18
31
|
},
|
|
19
32
|
appContainer: {
|
|
20
33
|
gridColumn: 'main',
|
|
21
34
|
display: 'grid',
|
|
22
|
-
gridTemplateRows: '[menubar] min-content [components]
|
|
23
|
-
height: '
|
|
35
|
+
gridTemplateRows: '[menubar] min-content [components] minmax(0, 1fr)',
|
|
36
|
+
height: '100%',
|
|
24
37
|
},
|
|
25
38
|
appBar: {
|
|
26
39
|
flexGrow: 1,
|
|
27
40
|
gridRow: 'menubar',
|
|
28
41
|
},
|
|
29
42
|
}));
|
|
30
|
-
const LazyDrawerWidget = observer(function LazyDrawerWidget(props) {
|
|
31
|
-
const { session } = props;
|
|
32
|
-
return (_jsx(Suspense, { fallback: null, children: _jsx(DrawerWidget, { session: session }) }));
|
|
33
|
-
});
|
|
34
43
|
const App = observer(function App(props) {
|
|
35
44
|
const { session } = props;
|
|
36
45
|
const { classes } = useStyles();
|
|
37
|
-
const {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
const { drawerVisible, drawerWidth, drawerPosition, poppedOut } = session;
|
|
47
|
+
const gridTemplateColumns = drawerGridTemplateColumns({
|
|
48
|
+
drawerVisible,
|
|
49
|
+
drawerPosition,
|
|
50
|
+
drawerWidth,
|
|
51
|
+
});
|
|
52
|
+
return (_jsxs("div", { className: classes.root, style: { gridTemplateColumns }, children: [poppedOut ? (_jsx(ModalWidget, { session: session, onClose: () => {
|
|
53
|
+
session.returnWidgetToDrawer();
|
|
54
|
+
} })) : null, _jsx(DialogQueue, { session: session }), _jsxs("div", { className: classes.appContainer, children: [_jsx(AppBar, { className: classes.appBar, position: "static", "data-testid": "app-bar", children: _jsx(AppToolbar, { ...props }) }), _jsx(ViewsContainer, { ...props })] }), _jsx(AppFab, { session: session }), _jsx(AppReadyMarker, { session: session }), drawerVisible ? (_jsx(Suspense, { fallback: null, children: _jsx(DrawerWidget, { session: session }) })) : null, _jsx(Snackbar, { session: session })] }));
|
|
42
55
|
});
|
|
43
56
|
export { App };
|
package/esm/ui/App/AppFab.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { DrawerPosition } from '@jbrowse/core/util';
|
|
2
|
+
/** All the FAB needs of a session, narrowed the way `DrawerChromeSession` is. */
|
|
3
|
+
export interface AppFabSession {
|
|
4
|
+
minimized: boolean;
|
|
5
|
+
activeWidgets: {
|
|
6
|
+
size: number;
|
|
7
|
+
};
|
|
8
|
+
drawerPosition: DrawerPosition;
|
|
9
|
+
showWidgetDrawer: () => void;
|
|
10
|
+
}
|
|
2
11
|
declare const AppFab: ({ session, }: {
|
|
3
|
-
session:
|
|
4
|
-
}) => import("react
|
|
12
|
+
session: AppFabSession;
|
|
13
|
+
}) => import("react").JSX.Element | null;
|
|
5
14
|
export default AppFab;
|