@jbrowse/app-core 4.3.0 → 5.0.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/esm/AppSession/AppRootModel.d.ts +30 -0
- package/esm/AppSession/AppRootModel.js +1 -0
- package/esm/AppSession/AppSessionMixin.d.ts +1324 -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 +294 -0
- package/esm/Assemblies/AssembliesMixin.js +154 -0
- package/esm/Assemblies/index.d.ts +1 -2
- package/esm/Assemblies/index.js +1 -2
- package/esm/HistoryManagement/index.d.ts +11 -0
- package/esm/HistoryManagement/index.js +43 -10
- package/esm/JBrowseConfig/RootConfiguration.d.ts +123 -78
- package/esm/JBrowseConfig/RootConfiguration.js +79 -7
- package/esm/JBrowseConfig/defaultShareUrl.d.ts +1 -0
- package/esm/JBrowseConfig/defaultShareUrl.js +5 -0
- package/esm/JBrowseConfig/index.d.ts +175 -84
- package/esm/JBrowseConfig/index.js +64 -2
- package/esm/JBrowseModel/index.d.ts +175 -97
- package/esm/JBrowseModel/index.js +132 -31
- package/esm/JbApi/index.d.ts +2 -0
- package/esm/JbApi/index.js +1 -0
- package/esm/JbApi/jbApi.d.ts +2767 -0
- package/esm/JbApi/jbApi.js +1383 -0
- package/esm/RootMenu/index.d.ts +69 -3
- package/esm/RootMenu/index.js +96 -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 +161 -0
- package/esm/SessionSpec/loadSessionSpec.d.ts +75 -0
- package/esm/SessionSpec/loadSessionSpec.js +473 -0
- package/esm/SessionSpec/parseSessionSpecUrl.d.ts +7 -0
- package/esm/SessionSpec/parseSessionSpecUrl.js +181 -0
- package/esm/SessionSpec/types.d.ts +37 -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 +107 -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 +105 -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 +260 -0
- package/esm/WorkspaceLayout/model.js +586 -0
- package/esm/WorkspaceLayout/panelChrome.d.ts +19 -0
- package/esm/WorkspaceLayout/panelChrome.js +4 -0
- package/esm/WorkspaceLayout/spec.d.ts +88 -0
- package/esm/WorkspaceLayout/spec.js +226 -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 +18 -9
- package/esm/index.js +16 -9
- package/esm/menus.d.ts +55 -68
- package/esm/menus.js +117 -86
- package/esm/queryParams.d.ts +9 -0
- package/esm/queryParams.js +93 -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 +39 -19
- package/esm/ui/App/AppFab.d.ts +12 -3
- package/esm/ui/App/AppFab.js +15 -8
- package/esm/ui/App/AppToolbar.d.ts +2 -14
- package/esm/ui/App/AppToolbar.js +45 -31
- package/esm/ui/App/ClassicViewsContainer.d.ts +4 -10
- 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/DrawerHeader.d.ts +2 -3
- package/esm/ui/App/DrawerHeader.js +24 -11
- 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/ThemeModeButton.d.ts +12 -0
- package/esm/ui/App/ThemeModeButton.js +34 -0
- 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 +128 -24
- package/esm/ui/App/ViewContainerTitle.d.ts +1 -1
- package/esm/ui/App/ViewContainerTitle.js +21 -11
- 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 +48 -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 +4 -11
- package/esm/ui/App/ViewsContainer.js +6 -9
- 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/lazyParts.d.ts +32 -0
- package/esm/ui/App/lazyParts.js +8 -0
- package/esm/ui/App/preloadAppShell.d.ts +14 -0
- package/esm/ui/App/preloadAppShell.js +41 -0
- package/esm/ui/App/types.d.ts +17 -3
- package/esm/ui/App/types.js +1 -1
- package/esm/ui/App/useViewVisibility.d.ts +35 -0
- package/esm/ui/App/useViewVisibility.js +88 -0
- package/esm/ui/App/viewTitle.d.ts +12 -0
- package/esm/ui/App/viewTitle.js +17 -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/DrawerControls.d.ts +0 -5
- package/esm/ui/App/DrawerControls.js +0 -25
- 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/copyView.d.ts +0 -1
- package/esm/ui/App/copyView.js +0 -29
- 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,16 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
1
|
+
export { App, DialogQueue, preloadAppFrame, preloadAppShell, } 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';
|
|
14
|
+
// The helper library an agent drives the app through: desktop serves it to
|
|
15
|
+
// run_javascript over MCP, web hands it to the page as window.jb
|
|
16
|
+
export { createJbApi, ensureReExports, safeJson, sessionOf, undeliveredNotifications, waitReady, } from './JbApi/index.js';
|
package/esm/menus.d.ts
CHANGED
|
@@ -1,74 +1,61 @@
|
|
|
1
|
-
import type { MenuItem } from '@jbrowse/core/ui';
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { MenuItem } from '@jbrowse/core/ui/menuItems';
|
|
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';
|
|
11
|
+
menuName: string;
|
|
12
|
+
position?: number;
|
|
13
|
+
}
|
|
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';
|
|
4
21
|
menuPath: string[];
|
|
5
22
|
menuItem: MenuItem;
|
|
6
|
-
position
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
menuPath: string[];
|
|
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;
|
|
32
|
-
}
|
|
33
|
-
interface SetMenusAction {
|
|
34
|
-
type: 'setMenus';
|
|
35
|
-
newMenus: Menu[];
|
|
36
|
-
}
|
|
37
|
-
export type MenuAction = InsertMenuAction | AppendMenuAction | AppendToSubMenuAction | AppendToMenuAction | InsertInMenuAction | InsertInSubMenuAction | SetMenusAction;
|
|
23
|
+
position?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* `addMenu` changes which menus exist, so it resolves eagerly — the app bar
|
|
27
|
+
* renders one button per menu before anything is opened. `addItem` only changes
|
|
28
|
+
* a menu's contents, so it resolves when that menu opens, against whatever its
|
|
29
|
+
* items are at that moment.
|
|
30
|
+
*/
|
|
31
|
+
export type MenuAction = AddMenuAction | AddItemAction;
|
|
32
|
+
/**
|
|
33
|
+
* A top-level menu. `menuItems` is a thunk the app bar calls when the menu
|
|
34
|
+
* opens and never before, so a root model's reads stay out of the bar's
|
|
35
|
+
* tracking scope and a plugin's contributions are merged onto each fresh
|
|
36
|
+
* result rather than spliced into a definition.
|
|
37
|
+
*/
|
|
38
38
|
export interface Menu {
|
|
39
|
+
label: string;
|
|
40
|
+
menuItems: () => MenuItem[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a root model's menus against the contributions plugins have pushed
|
|
44
|
+
* (via `RootAppMenuMixin`). Structural contributions apply now; item
|
|
45
|
+
* contributions are grouped by path and merged when the menu they target
|
|
46
|
+
* opens.
|
|
47
|
+
*
|
|
48
|
+
* Runs on every `menus()` evaluation, so it must not mutate `base` or anything
|
|
49
|
+
* an action carries.
|
|
50
|
+
*/
|
|
51
|
+
export declare function processMutableMenuActions(base: Menu[], actions: MenuAction[]): Menu[];
|
|
52
|
+
/**
|
|
53
|
+
* Flatten resolved menus to plain data by opening every one of them. For tests
|
|
54
|
+
* and other callers that want to assert on menu contents rather than render
|
|
55
|
+
* them.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveMenus(menus: Menu[]): {
|
|
39
58
|
label: string;
|
|
40
59
|
menuItems: MenuItem[];
|
|
41
|
-
}
|
|
42
|
-
export declare function appendMenu({ menus, menuName, }: {
|
|
43
|
-
menus: Menu[];
|
|
44
|
-
menuName: string;
|
|
45
|
-
}): number;
|
|
46
|
-
export declare function insertMenu({ menus, menuName, position, }: {
|
|
47
|
-
menus: Menu[];
|
|
48
|
-
menuName: string;
|
|
49
|
-
position: number;
|
|
50
|
-
}): number;
|
|
51
|
-
export declare function appendToMenu({ menus, menuName, menuItem, }: {
|
|
52
|
-
menus: Menu[];
|
|
53
|
-
menuName: string;
|
|
54
|
-
menuItem: MenuItem;
|
|
55
|
-
}): number;
|
|
56
|
-
export declare function insertInMenu({ menus, menuName, menuItem, position, }: {
|
|
57
|
-
menus: Menu[];
|
|
58
|
-
menuName: string;
|
|
59
|
-
menuItem: MenuItem;
|
|
60
|
-
position: number;
|
|
61
|
-
}): number;
|
|
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[];
|
|
60
|
+
}[];
|
|
74
61
|
export {};
|
package/esm/menus.js
CHANGED
|
@@ -1,99 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return menus.length;
|
|
10
|
-
}
|
|
11
|
-
export function appendToMenu({ menus, menuName, menuItem, }) {
|
|
12
|
-
const menu = menus.find(m => m.label === menuName);
|
|
13
|
-
if (!menu) {
|
|
14
|
-
menus.push({ label: menuName, menuItems: [menuItem] });
|
|
15
|
-
return 1;
|
|
1
|
+
// a conflict is reported once rather than on every open — the menu re-resolves
|
|
2
|
+
// on each open and, while open, on each observer re-render — and the action is
|
|
3
|
+
// what outlives the nodes, which are rebuilt on every menus() evaluation
|
|
4
|
+
const reported = new WeakSet();
|
|
5
|
+
function register(node, action, depth) {
|
|
6
|
+
const label = action.menuPath[depth];
|
|
7
|
+
if (label === undefined) {
|
|
8
|
+
node.entries.push(action);
|
|
16
9
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
10
|
+
else {
|
|
11
|
+
let entry = node.entries.find((e) => e.type === 'subMenu' && e.label === label);
|
|
12
|
+
if (!entry) {
|
|
13
|
+
entry = {
|
|
14
|
+
type: 'subMenu',
|
|
15
|
+
label,
|
|
16
|
+
path: action.menuPath.slice(0, depth + 1),
|
|
17
|
+
node: { entries: [] },
|
|
18
|
+
first: action,
|
|
19
|
+
};
|
|
20
|
+
node.entries.push(entry);
|
|
21
|
+
}
|
|
22
|
+
register(entry.node, action, depth + 1);
|
|
24
23
|
}
|
|
25
|
-
const insertPosition = position < 0 ? menu.menuItems.length + position : position;
|
|
26
|
-
menu.menuItems.splice(insertPosition, 0, menuItem);
|
|
27
|
-
return menu.menuItems.length;
|
|
28
24
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
25
|
+
// splice is already the "counts from the end when negative" rule, and clamps
|
|
26
|
+
// rather than wrapping past the start; the default is what makes it an append
|
|
27
|
+
function insertAt(items, item, position = items.length) {
|
|
28
|
+
items.splice(position, 0, item);
|
|
29
|
+
}
|
|
30
|
+
// the author's form is kept: an array sub-menu resolves now, a thunk sub-menu
|
|
31
|
+
// resolves when its panel opens, with the contributions merged in either way
|
|
32
|
+
function withContributions(item, node) {
|
|
33
|
+
const { subMenu } = item;
|
|
34
|
+
return {
|
|
35
|
+
...item,
|
|
36
|
+
subMenu: typeof subMenu === 'function'
|
|
37
|
+
? () => apply(subMenu(), node)
|
|
38
|
+
: apply(subMenu, node),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Return a copy of `items` with a node's contributions merged in. Nothing is
|
|
43
|
+
* written into `items`, into any sub-menu it holds, or into a contribution's
|
|
44
|
+
* own payload: a sub-menu that gains contributions is replaced by a copy whose
|
|
45
|
+
* `subMenu` resolves them, so the arrays behind a definition or an action are
|
|
46
|
+
* the same on every open.
|
|
47
|
+
*
|
|
48
|
+
* A path segment naming a row that is not a sub-menu is a plugin bug. It
|
|
49
|
+
* costs that subtree of contributions, reported once, and nothing else — this
|
|
50
|
+
* runs from the app bar's click handler, where a throw goes straight through
|
|
51
|
+
* React.
|
|
52
|
+
*/
|
|
53
|
+
function apply(items, node) {
|
|
54
|
+
const result = [...items];
|
|
55
|
+
for (const entry of node.entries) {
|
|
56
|
+
if (entry.type === 'addItem') {
|
|
57
|
+
insertAt(result, entry.menuItem, entry.position);
|
|
43
58
|
}
|
|
44
|
-
|
|
45
|
-
|
|
59
|
+
else {
|
|
60
|
+
const idx = result.findIndex(mi => 'label' in mi && mi.label === entry.label);
|
|
61
|
+
const found = result[idx];
|
|
62
|
+
if (found === undefined) {
|
|
63
|
+
result.push({ label: entry.label, subMenu: apply([], entry.node) });
|
|
64
|
+
}
|
|
65
|
+
else if ('subMenu' in found) {
|
|
66
|
+
result[idx] = withContributions(found, entry.node);
|
|
67
|
+
}
|
|
68
|
+
else if (!reported.has(entry.first)) {
|
|
69
|
+
reported.add(entry.first);
|
|
70
|
+
console.error(new Error(`"${entry.label}" in path "${entry.path.join(' > ')}" is not a subMenu`));
|
|
71
|
+
}
|
|
46
72
|
}
|
|
47
|
-
subMenu = sm.subMenu;
|
|
48
73
|
}
|
|
49
|
-
return
|
|
74
|
+
return result;
|
|
50
75
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
// first menu with this label, or a new empty one — an item action naming a menu
|
|
77
|
+
// that doesn't exist creates it, so a plugin can populate its own menu without
|
|
78
|
+
// declaring it first
|
|
79
|
+
function findOrCreateMenu(pending, label, position) {
|
|
80
|
+
const found = pending.find(m => m.label === label);
|
|
81
|
+
if (found) {
|
|
82
|
+
return found;
|
|
56
83
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
sm = subMenu[idx - 1];
|
|
65
|
-
}
|
|
66
|
-
if (!('subMenu' in sm)) {
|
|
67
|
-
throw new Error(`"${menuName}" in path "${pathSoFar}" is not a subMenu`);
|
|
68
|
-
}
|
|
69
|
-
subMenu = sm.subMenu;
|
|
70
|
-
}
|
|
71
|
-
subMenu.splice(position, 0, menuItem);
|
|
72
|
-
return subMenu.length;
|
|
84
|
+
const menu = {
|
|
85
|
+
label,
|
|
86
|
+
menuItems: () => [],
|
|
87
|
+
node: { entries: [] },
|
|
88
|
+
};
|
|
89
|
+
insertAt(pending, menu, position);
|
|
90
|
+
return menu;
|
|
73
91
|
}
|
|
74
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Resolve a root model's menus against the contributions plugins have pushed
|
|
94
|
+
* (via `RootAppMenuMixin`). Structural contributions apply now; item
|
|
95
|
+
* contributions are grouped by path and merged when the menu they target
|
|
96
|
+
* opens.
|
|
97
|
+
*
|
|
98
|
+
* Runs on every `menus()` evaluation, so it must not mutate `base` or anything
|
|
99
|
+
* an action carries.
|
|
100
|
+
*/
|
|
101
|
+
export function processMutableMenuActions(base, actions) {
|
|
102
|
+
const pending = base.map(m => ({ ...m, node: { entries: [] } }));
|
|
75
103
|
for (const action of actions) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
else if (action.type === 'appendToSubMenu') {
|
|
89
|
-
appendToSubMenu({ menus: ret, ...action });
|
|
90
|
-
}
|
|
91
|
-
else if (action.type === 'appendToMenu') {
|
|
92
|
-
appendToMenu({ menus: ret, ...action });
|
|
93
|
-
}
|
|
94
|
-
else if (action.type === 'insertInMenu') {
|
|
95
|
-
insertInMenu({ menus: ret, ...action });
|
|
104
|
+
switch (action.type) {
|
|
105
|
+
case 'addMenu': {
|
|
106
|
+
findOrCreateMenu(pending, action.menuName, action.position);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'addItem': {
|
|
110
|
+
register(findOrCreateMenu(pending, action.menuPath[0]).node, action, 1);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
default: {
|
|
114
|
+
action;
|
|
115
|
+
}
|
|
96
116
|
}
|
|
97
117
|
}
|
|
98
|
-
return
|
|
118
|
+
return pending.map(({ label, menuItems, node }) => ({
|
|
119
|
+
label,
|
|
120
|
+
menuItems: () => apply(menuItems(), node),
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Flatten resolved menus to plain data by opening every one of them. For tests
|
|
125
|
+
* and other callers that want to assert on menu contents rather than render
|
|
126
|
+
* them.
|
|
127
|
+
*/
|
|
128
|
+
export function resolveMenus(menus) {
|
|
129
|
+
return menus.map(m => ({ label: m.label, menuItems: m.menuItems() }));
|
|
99
130
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function readQueryParams<T extends string>(keys: T[]): Record<T, string | undefined>;
|
|
2
|
+
export declare function readAllQueryParams(url?: URL | Location): 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,93 @@
|
|
|
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(url = window.location) {
|
|
5
|
+
return url.hash.includes('=') ? 'hash' : 'search';
|
|
6
|
+
}
|
|
7
|
+
function readParams(loc, url = window.location) {
|
|
8
|
+
return new URLSearchParams(loc === 'hash' ? url.hash.slice(1) : url.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 param set from wherever a URL keeps them (hash XOR search),
|
|
56
|
+
// the page's own by default. Producers that need every param at once
|
|
57
|
+
// (buildShareUrl) share this so the hash/search decision stays in this one
|
|
58
|
+
// module.
|
|
59
|
+
export function readAllQueryParams(url = window.location) {
|
|
60
|
+
return readParams(paramLocation(url), url);
|
|
61
|
+
}
|
|
62
|
+
export function deleteQueryParams(keys) {
|
|
63
|
+
const loc = paramLocation();
|
|
64
|
+
const params = readParams(loc);
|
|
65
|
+
for (const key of keys) {
|
|
66
|
+
params.delete(key);
|
|
67
|
+
}
|
|
68
|
+
writeParams(loc, params);
|
|
69
|
+
}
|
|
70
|
+
export function setQueryParams(values) {
|
|
71
|
+
const loc = paramLocation();
|
|
72
|
+
const params = readParams(loc);
|
|
73
|
+
for (const [key, value] of Object.entries(values)) {
|
|
74
|
+
if (value === undefined) {
|
|
75
|
+
params.delete(key);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
params.set(key, value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
writeParams(loc, params);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Read one param and keep re-rendering as it changes, including across the
|
|
85
|
+
* `replaceState` writes above, which fire no event of their own.
|
|
86
|
+
*/
|
|
87
|
+
export function useQueryParam(key) {
|
|
88
|
+
const value = useSyncExternalStore(subscribe, () => readParams(paramLocation()).get(key), () => null);
|
|
89
|
+
const setValue = useCallback((newValue) => {
|
|
90
|
+
setQueryParams({ [key]: newValue });
|
|
91
|
+
}, [key]);
|
|
92
|
+
return [value ?? undefined, setValue];
|
|
93
|
+
}
|
|
@@ -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
|
@@ -1,43 +1,63 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense,
|
|
2
|
+
import { Suspense, useEffect } from 'react';
|
|
3
3
|
import Snackbar from '@jbrowse/core/ui/Snackbar';
|
|
4
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import {
|
|
5
|
+
import { AppReadyMarker, ModalWidget, drawerGridTemplateColumns, } from '@jbrowse/product-core';
|
|
6
|
+
import { AppBar, useMediaQuery, useTheme } from '@mui/material';
|
|
6
7
|
import { observer } from 'mobx-react';
|
|
7
|
-
import AppFab from
|
|
8
|
-
import AppToolbar from
|
|
9
|
-
import DialogQueue from
|
|
10
|
-
import ViewsContainer from
|
|
11
|
-
|
|
8
|
+
import AppFab from './AppFab.js';
|
|
9
|
+
import AppToolbar from './AppToolbar.js';
|
|
10
|
+
import DialogQueue from './DialogQueue.js';
|
|
11
|
+
import ViewsContainer from './ViewsContainer.js';
|
|
12
|
+
import { DrawerWidget } from './lazyParts.js';
|
|
12
13
|
const useStyles = makeStyles()(theme => ({
|
|
13
14
|
root: {
|
|
14
15
|
display: 'grid',
|
|
15
|
-
|
|
16
|
+
// Embedders can fit the app to its container by setting the
|
|
17
|
+
// --jbrowse-app-height CSS variable (e.g. to 100%); it defaults to the
|
|
18
|
+
// full viewport for standalone/full-window use.
|
|
19
|
+
height: 'var(--jbrowse-app-height, 100vh)',
|
|
20
|
+
// the containing block for AppFab, which floats over the app rather than
|
|
21
|
+
// over the page
|
|
22
|
+
position: 'relative',
|
|
23
|
+
// pin the single implicit row to the container height so appContainer
|
|
24
|
+
// fills it (an auto row would instead grow to content and overflow)
|
|
25
|
+
gridTemplateRows: 'minmax(0, 1fr)',
|
|
16
26
|
width: '100%',
|
|
17
27
|
colorScheme: theme.palette.mode,
|
|
18
28
|
},
|
|
19
29
|
appContainer: {
|
|
20
30
|
gridColumn: 'main',
|
|
21
31
|
display: 'grid',
|
|
22
|
-
gridTemplateRows: '[menubar] min-content [components]
|
|
23
|
-
|
|
32
|
+
gridTemplateRows: '[menubar] min-content [components] minmax(0, 1fr)',
|
|
33
|
+
// an implicit column floors at the toolbar's min-content, which widened
|
|
34
|
+
// the page past a phone's screen rather than ellipsizing the session name
|
|
35
|
+
gridTemplateColumns: '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
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
const { drawerVisible, drawerWidth, drawerPosition, modalWidgetVisible } = session;
|
|
47
|
+
// a phone has no room for a drawer column beside the views
|
|
48
|
+
const narrow = useMediaQuery(useTheme().breakpoints.down('sm'), {
|
|
49
|
+
noSsr: true,
|
|
50
|
+
});
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
session.setModalWidgets(narrow);
|
|
53
|
+
}, [session, narrow]);
|
|
54
|
+
const gridTemplateColumns = drawerGridTemplateColumns({
|
|
55
|
+
drawerVisible,
|
|
56
|
+
drawerPosition,
|
|
57
|
+
drawerWidth,
|
|
58
|
+
});
|
|
59
|
+
return (_jsxs("div", { className: classes.root, style: { gridTemplateColumns }, children: [modalWidgetVisible ? (_jsx(ModalWidget, { session: session, fullScreen: narrow, onClose: () => {
|
|
60
|
+
session.closeModalWidget();
|
|
61
|
+
} })) : 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, { session: session })] }), _jsx(AppFab, { session: session }), _jsx(AppReadyMarker, { session: session }), drawerVisible ? (_jsx(Suspense, { fallback: null, children: _jsx(DrawerWidget, { session: session }) })) : null, _jsx(Snackbar, { session: session })] }));
|
|
42
62
|
});
|
|
43
63
|
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;
|