@jbrowse/app-core 5.0.0-beta.1 → 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/AppSessionMixin.d.ts +42 -316
- package/esm/Assemblies/AssembliesMixin.d.ts +172 -103
- package/esm/Assemblies/AssembliesMixin.js +13 -8
- package/esm/HistoryManagement/index.d.ts +3 -11
- package/esm/HistoryManagement/index.js +24 -28
- package/esm/JBrowseConfig/RootConfiguration.d.ts +80 -124
- package/esm/JBrowseConfig/RootConfiguration.js +5 -11
- package/esm/JBrowseConfig/defaultShareUrl.d.ts +1 -0
- package/esm/JBrowseConfig/defaultShareUrl.js +5 -0
- package/esm/JBrowseConfig/index.d.ts +89 -83
- package/esm/JBrowseConfig/index.js +6 -4
- package/esm/JBrowseModel/index.d.ts +93 -96
- package/esm/JBrowseModel/index.js +43 -43
- 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 +2 -11
- package/esm/RootMenu/index.js +0 -11
- package/esm/SessionSpec/index.d.ts +1 -1
- package/esm/SessionSpec/index.js +1 -1
- package/esm/SessionSpec/lgvUrlInit.js +5 -2
- package/esm/SessionSpec/loadSessionSpec.d.ts +56 -2
- package/esm/SessionSpec/loadSessionSpec.js +285 -123
- package/esm/SessionSpec/parseSessionSpecUrl.d.ts +2 -8
- package/esm/SessionSpec/parseSessionSpecUrl.js +2 -17
- package/esm/SessionSpec/types.d.ts +16 -12
- package/esm/WorkspaceLayout/WorkspaceContainer.js +4 -2
- package/esm/WorkspaceLayout/WorkspaceTab.js +2 -6
- package/esm/WorkspaceLayout/model.d.ts +24 -4
- package/esm/WorkspaceLayout/model.js +76 -17
- package/esm/WorkspaceLayout/spec.d.ts +40 -10
- package/esm/WorkspaceLayout/spec.js +81 -8
- package/esm/index.d.ts +4 -2
- package/esm/index.js +4 -1
- package/esm/menus.d.ts +15 -29
- package/esm/menus.js +76 -92
- package/esm/queryParams.d.ts +1 -1
- package/esm/queryParams.js +10 -9
- package/esm/ui/App/App.js +18 -11
- package/esm/ui/App/AppToolbar.js +23 -8
- package/esm/ui/App/ClassicViewsContainer.d.ts +2 -2
- package/esm/ui/App/DialogQueue.d.ts +2 -2
- package/esm/ui/App/DialogQueue.js +3 -3
- package/esm/ui/App/DrawerHeader.js +2 -3
- package/esm/ui/App/ThemeModeButton.d.ts +12 -0
- package/esm/ui/App/ThemeModeButton.js +34 -0
- package/esm/ui/App/ViewButtons.js +2 -2
- package/esm/ui/App/ViewContainer.js +1 -17
- package/esm/ui/App/ViewContainerTitle.js +15 -12
- package/esm/ui/App/ViewLauncher.js +1 -1
- package/esm/ui/App/ViewMenu.js +2 -2
- package/esm/ui/App/ViewStack.js +7 -1
- package/esm/ui/App/ViewsContainer.d.ts +2 -4
- package/esm/ui/App/ViewsContainer.js +3 -9
- package/esm/ui/App/index.d.ts +1 -1
- package/esm/ui/App/index.js +1 -1
- 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 +12 -7
- package/esm/ui/App/useViewVisibility.d.ts +9 -8
- package/esm/ui/App/useViewVisibility.js +11 -10
- package/esm/ui/App/viewTitle.d.ts +10 -3
- package/esm/ui/App/viewTitle.js +13 -5
- package/esm/ui/index.d.ts +1 -1
- package/esm/ui/index.js +1 -1
- package/package.json +7 -7
- package/esm/ui/App/AppReadyMarker.d.ts +0 -34
- package/esm/ui/App/AppReadyMarker.js +0 -56
- package/esm/ui/App/DrawerControls.d.ts +0 -1
- package/esm/ui/App/DrawerControls.js +0 -1
- package/esm/ui/App/copyView.d.ts +0 -1
- package/esm/ui/App/copyView.js +0 -29
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import EditableTypography from '@jbrowse/core/ui/EditableTypography';
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import { Tooltip } from '@mui/material';
|
|
5
|
+
import { Tooltip, Typography } from '@mui/material';
|
|
6
6
|
import { observer } from 'mobx-react';
|
|
7
|
-
import { viewTitle } from './viewTitle.js';
|
|
7
|
+
import { viewName, viewTitle } from './viewTitle.js';
|
|
8
8
|
const useStyles = makeStyles()(theme => ({
|
|
9
9
|
input: {
|
|
10
10
|
paddingBottom: 0,
|
|
@@ -28,18 +28,21 @@ const useStyles = makeStyles()(theme => ({
|
|
|
28
28
|
borderColor: theme.palette.primary.main,
|
|
29
29
|
backgroundColor: theme.palette.secondary.light,
|
|
30
30
|
},
|
|
31
|
+
minimized: {
|
|
32
|
+
color: theme.palette.secondary.contrastText,
|
|
33
|
+
},
|
|
31
34
|
}));
|
|
32
35
|
const ViewContainerTitle = observer(function ViewContainerTitle({ view, }) {
|
|
33
36
|
const { classes } = useStyles();
|
|
34
37
|
const { assemblyManager } = getSession(view);
|
|
35
|
-
const
|
|
36
|
-
return (_jsx(Tooltip, { title: `${
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
const getDisplayName = (r) => assemblyManager.getDisplayName(r);
|
|
39
|
+
return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { title: `${viewTitle(view, getDisplayName)} (click to rename)`, arrow: true, children: _jsx(EditableTypography, { value: viewName(view, getDisplayName), setValue: val => {
|
|
40
|
+
view.setDisplayName(val);
|
|
41
|
+
}, variant: "body2", classes: {
|
|
42
|
+
input: classes.input,
|
|
43
|
+
inputBase: classes.inputBase,
|
|
44
|
+
inputRoot: classes.inputRoot,
|
|
45
|
+
inputFocused: classes.inputFocused,
|
|
46
|
+
} }) }), view.minimized ? (_jsx(Typography, { variant: "body2", className: classes.minimized, children: "(minimized)" })) : null] }));
|
|
44
47
|
});
|
|
45
48
|
export default ViewContainerTitle;
|
|
@@ -22,7 +22,7 @@ const ViewLauncher = observer(function ViewLauncher({ session, }) {
|
|
|
22
22
|
return (_jsxs(Paper, { className: classes.selectPaper, children: [_jsx(Typography, { children: "Select a view to launch" }), _jsx(FormControl, { className: classes.m2, children: _jsx(Select, { value: value, onChange: event => {
|
|
23
23
|
setValue(event.target.value);
|
|
24
24
|
}, children: viewTypes.map(({ displayName, name }) => (_jsx(MenuItem, { value: name, children: displayName }, name))) }) }), _jsx(FormControl, { className: classes.m2, children: _jsx(Button, { onClick: () => {
|
|
25
|
-
session.
|
|
25
|
+
void session.launchView(value, {});
|
|
26
26
|
}, variant: "contained", color: "primary", children: "Launch view" }) })] }));
|
|
27
27
|
});
|
|
28
28
|
export default ViewLauncher;
|
package/esm/ui/App/ViewMenu.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { CascadingMenuButton } from '@jbrowse/core/ui';
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
|
+
import { renameIds } from '@jbrowse/core/util/types/mst';
|
|
4
5
|
import { getSnapshot } from '@jbrowse/mobx-state-tree';
|
|
5
6
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
|
6
7
|
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
|
@@ -11,7 +12,6 @@ import MenuIcon from '@mui/icons-material/Menu';
|
|
|
11
12
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
|
12
13
|
import VerticalSplitIcon from '@mui/icons-material/VerticalSplit';
|
|
13
14
|
import { observer } from 'mobx-react';
|
|
14
|
-
import { renameIds } from './copyView.js';
|
|
15
15
|
// takes the icon's class, not an SvgIconProps object: the object was built
|
|
16
16
|
// inline by ViewHeader and so was new on each of its renders, which defeated
|
|
17
17
|
// mobx-react's memo for this whole menu (a Tooltip and an IconButton) every
|
|
@@ -63,7 +63,7 @@ const ViewMenu = observer(function ViewMenu({ model, className, }) {
|
|
|
63
63
|
label: 'Copy view',
|
|
64
64
|
icon: ContentCopyIcon,
|
|
65
65
|
onClick: () => {
|
|
66
|
-
session.addView(model.type, renameIds(
|
|
66
|
+
session.addView(model.type, renameIds(getSnapshot(model)));
|
|
67
67
|
},
|
|
68
68
|
},
|
|
69
69
|
{
|
package/esm/ui/App/ViewStack.js
CHANGED
|
@@ -10,6 +10,12 @@ import ViewContainer from './ViewContainer.js';
|
|
|
10
10
|
// the amount that makes that gesture reach, and any more is room to scroll the
|
|
11
11
|
// content off its own surface.
|
|
12
12
|
//
|
|
13
|
+
// It is scrollable room rather than content, so anything measuring how tall the
|
|
14
|
+
// session is discounts it by its `view-stack-overscroll` testid — `overflow` in
|
|
15
|
+
// jbApi does, and a filmed take paid for that: counted as content, it told
|
|
16
|
+
// `jb.fitToWindow` the session was 300px taller than it was, and the dotplot
|
|
17
|
+
// absorbing the cut came out a third of the height that fit.
|
|
18
|
+
//
|
|
13
19
|
// Capped, because half a port stops being a courtesy on a tall screen. 300px is
|
|
14
20
|
// the cap the classic stack has always had and it stays the answer there — a
|
|
15
21
|
// full window is comfortably past twice it. What the cap was hiding is the
|
|
@@ -37,6 +43,6 @@ const ViewStack = observer(function ViewStack({ views, session, className, }) {
|
|
|
37
43
|
// restore / initial load), so they must not steal scroll when they mount. A
|
|
38
44
|
// view whose id isn't in this set was added later and auto-scrolls into view.
|
|
39
45
|
const [initialViewIds] = useState(() => new Set(views.map(v => v.id)));
|
|
40
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: viewsRef, className: className, children: views.map(view => (_jsx(ViewContainer, { view: view, session: session, scrollOnMount: !initialViewIds.has(view.id) }, view.id))) }), scrollable ? _jsx("div", { className: classes.spacer }) : null] }));
|
|
46
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: viewsRef, className: className, children: views.map(view => (_jsx(ViewContainer, { view: view, session: session, scrollOnMount: !initialViewIds.has(view.id) }, view.id))) }), scrollable ? (_jsx("div", { className: classes.spacer, "data-testid": "view-stack-overscroll" })) : null] }));
|
|
41
47
|
});
|
|
42
48
|
export default ViewStack;
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type { AppSession } from './types.ts';
|
|
2
|
-
|
|
3
|
-
HeaderButtons?: React.ReactElement;
|
|
2
|
+
declare const ViewsContainer: ({ session, }: {
|
|
4
3
|
session: AppSession;
|
|
5
|
-
}
|
|
6
|
-
declare const ViewsContainer: (props: Props) => import("react").JSX.Element;
|
|
4
|
+
}) => import("react").JSX.Element;
|
|
7
5
|
export default ViewsContainer;
|
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { Suspense
|
|
2
|
+
import { Suspense } from 'react';
|
|
3
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
import { isSessionWithWorkspaceLayout } from '../../WorkspaceLayout/model.js';
|
|
6
|
-
import {
|
|
7
|
-
const ClassicViewsContainer = lazy(lazyChunk('ClassicViewsContainer', () => import('./ClassicViewsContainer.js')));
|
|
8
|
-
const WorkspaceContainer = lazy(lazyChunk('WorkspaceContainer', () => import('../../WorkspaceLayout/WorkspaceContainer.js').then(m => ({
|
|
9
|
-
default: m.WorkspaceContainer,
|
|
10
|
-
}))));
|
|
11
|
-
const ViewLauncher = lazy(lazyChunk('ViewLauncher', () => import('./ViewLauncher.js')));
|
|
6
|
+
import { ClassicViewsContainer, ViewLauncher, WorkspaceContainer, } from './lazyParts.js';
|
|
12
7
|
const useStyles = makeStyles()({
|
|
13
8
|
viewsContainer: {
|
|
14
9
|
gridRow: 'components',
|
|
15
10
|
overflow: 'hidden',
|
|
16
11
|
},
|
|
17
12
|
});
|
|
18
|
-
const ViewsContainer = observer(function ViewsContainer(
|
|
19
|
-
const { session } = props;
|
|
13
|
+
const ViewsContainer = observer(function ViewsContainer({ session, }) {
|
|
20
14
|
const { views, effectiveUseWorkspaces } = session;
|
|
21
15
|
const { classes } = useStyles();
|
|
22
16
|
return (_jsx("div", { className: classes.viewsContainer, children: _jsx(Suspense, { fallback: null, children: views.length > 0 ? (effectiveUseWorkspaces && isSessionWithWorkspaceLayout(session) ? (_jsx(WorkspaceContainer, { session: session })) : (_jsx(ClassicViewsContainer, { session: session }))) : (_jsx(ViewLauncher, { session: session })) }) }));
|
package/esm/ui/App/index.d.ts
CHANGED
package/esm/ui/App/index.js
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare const DrawerWidget: import("react").LazyExoticComponent<({ session, }: {
|
|
2
|
+
session: import("@jbrowse/core/util").SessionWithFocusedViewAndDrawerWidgets;
|
|
3
|
+
}) => import("react").JSX.Element> & {
|
|
4
|
+
preload: () => Promise<{
|
|
5
|
+
default: ({ session, }: {
|
|
6
|
+
session: import("@jbrowse/core/util").SessionWithFocusedViewAndDrawerWidgets;
|
|
7
|
+
}) => import("react").JSX.Element;
|
|
8
|
+
}>;
|
|
9
|
+
};
|
|
10
|
+
export declare const ClassicViewsContainer: import("react").LazyExoticComponent<({ session, }: import("./ClassicViewsContainer.tsx").ClassicViewsContainerProps) => import("react").JSX.Element> & {
|
|
11
|
+
preload: () => Promise<{
|
|
12
|
+
default: ({ session, }: import("./ClassicViewsContainer.tsx").ClassicViewsContainerProps) => import("react").JSX.Element;
|
|
13
|
+
}>;
|
|
14
|
+
};
|
|
15
|
+
export declare const WorkspaceContainer: import("react").LazyExoticComponent<({ session, }: {
|
|
16
|
+
session: import("@jbrowse/core/util").SessionWithFocusedViewAndDrawerWidgets & import("../../index.ts").WorkspaceLayout;
|
|
17
|
+
}) => import("react").JSX.Element> & {
|
|
18
|
+
preload: () => Promise<{
|
|
19
|
+
default: ({ session, }: {
|
|
20
|
+
session: import("@jbrowse/core/util").SessionWithFocusedViewAndDrawerWidgets & import("../../index.ts").WorkspaceLayout;
|
|
21
|
+
}) => import("react").JSX.Element;
|
|
22
|
+
}>;
|
|
23
|
+
};
|
|
24
|
+
export declare const ViewLauncher: import("react").LazyExoticComponent<({ session, }: {
|
|
25
|
+
session: import("@jbrowse/core/util").SessionWithDrawerWidgets;
|
|
26
|
+
}) => import("react").JSX.Element> & {
|
|
27
|
+
preload: () => Promise<{
|
|
28
|
+
default: ({ session, }: {
|
|
29
|
+
session: import("@jbrowse/core/util").SessionWithDrawerWidgets;
|
|
30
|
+
}) => import("react").JSX.Element;
|
|
31
|
+
}>;
|
|
32
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
2
|
+
import { lazyChunk } from './lazyChunk.js';
|
|
3
|
+
export const DrawerWidget = lazyWithPreload(lazyChunk('DrawerWidget', () => import('./DrawerWidget.js')));
|
|
4
|
+
export const ClassicViewsContainer = lazyWithPreload(lazyChunk('ClassicViewsContainer', () => import('./ClassicViewsContainer.js')));
|
|
5
|
+
export const WorkspaceContainer = lazyWithPreload(lazyChunk('WorkspaceContainer', () => import('../../WorkspaceLayout/WorkspaceContainer.js').then(m => ({
|
|
6
|
+
default: m.WorkspaceContainer,
|
|
7
|
+
}))));
|
|
8
|
+
export const ViewLauncher = lazyWithPreload(lazyChunk('ViewLauncher', () => import('./ViewLauncher.js')));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AppSession } from './types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Load the views container and the drawer while the session's own view code
|
|
4
|
+
* downloads, settling when both have. An app that renders after this renders
|
|
5
|
+
* its frame in one pass; otherwise the first render suspends on them and React
|
|
6
|
+
* holds the retry for 300 ms.
|
|
7
|
+
*/
|
|
8
|
+
export declare function preloadAppFrame(): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Request the app frame's lazy parts that `session` will show (its views
|
|
11
|
+
* container, the drawer and the widget open in it) as soon as the session
|
|
12
|
+
* exists. Rendering asks for each only when it reaches it, a round trip apiece.
|
|
13
|
+
*/
|
|
14
|
+
export declare function preloadAppShell(session: AppSession): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { preloadComponent } from '@jbrowse/core/util/preloadComponent';
|
|
2
|
+
import { getEnv } from '@jbrowse/mobx-state-tree';
|
|
3
|
+
import { isSessionWithWorkspaceLayout } from '../../WorkspaceLayout/model.js';
|
|
4
|
+
import { ClassicViewsContainer, DrawerWidget, WorkspaceContainer, } from './lazyParts.js';
|
|
5
|
+
/**
|
|
6
|
+
* Load the views container and the drawer while the session's own view code
|
|
7
|
+
* downloads, settling when both have. An app that renders after this renders
|
|
8
|
+
* its frame in one pass; otherwise the first render suspends on them and React
|
|
9
|
+
* holds the retry for 300 ms.
|
|
10
|
+
*/
|
|
11
|
+
export async function preloadAppFrame() {
|
|
12
|
+
await Promise.allSettled([
|
|
13
|
+
ClassicViewsContainer.preload(),
|
|
14
|
+
DrawerWidget.preload(),
|
|
15
|
+
]);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Request the app frame's lazy parts that `session` will show (its views
|
|
19
|
+
* container, the drawer and the widget open in it) as soon as the session
|
|
20
|
+
* exists. Rendering asks for each only when it reaches it, a round trip apiece.
|
|
21
|
+
*/
|
|
22
|
+
export function preloadAppShell(session) {
|
|
23
|
+
if (session.views.length > 0) {
|
|
24
|
+
preloadComponent(session.effectiveUseWorkspaces && isSessionWithWorkspaceLayout(session)
|
|
25
|
+
? WorkspaceContainer
|
|
26
|
+
: ClassicViewsContainer);
|
|
27
|
+
}
|
|
28
|
+
const { drawerVisible, visibleWidget } = session;
|
|
29
|
+
if (drawerVisible) {
|
|
30
|
+
preloadComponent(DrawerWidget);
|
|
31
|
+
}
|
|
32
|
+
if (drawerVisible && visibleWidget) {
|
|
33
|
+
const { pluginManager } = getEnv(session);
|
|
34
|
+
try {
|
|
35
|
+
preloadComponent(pluginManager.getWidgetType(visibleWidget.type).ReactComponent);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// an unknown widget type is reported when the drawer renders it
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
package/esm/ui/App/types.d.ts
CHANGED
|
@@ -2,15 +2,20 @@ import type { Menu } from '../../menus.ts';
|
|
|
2
2
|
import type { ErrorDialogState, SnackbarMessage } from '@jbrowse/core/ui/SnackbarModel';
|
|
3
3
|
import type { SessionWithFocusedViewAndDrawerWidgets } from '@jbrowse/core/util';
|
|
4
4
|
export { type Menu } from '../../menus.ts';
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
export interface ThemeSwitchSession {
|
|
6
|
+
themeMode: 'light' | 'dark' | 'system';
|
|
7
|
+
effectiveThemeMode: 'light' | 'dark';
|
|
8
|
+
themeIsDark: boolean;
|
|
9
|
+
systemThemeOverride?: 'light' | 'dark';
|
|
10
|
+
setSystemThemeOverride: (mode?: 'light' | 'dark') => void;
|
|
11
|
+
}
|
|
12
|
+
export type WorkspaceSessionType = SessionWithFocusedViewAndDrawerWidgets;
|
|
13
|
+
export type AppSession = WorkspaceSessionType & ThemeSwitchSession & {
|
|
12
14
|
menus: () => Menu[];
|
|
13
15
|
errorDialog: ErrorDialogState | undefined;
|
|
14
16
|
setErrorDialog: (state: ErrorDialogState | undefined) => void;
|
|
15
17
|
effectiveUseWorkspaces: boolean;
|
|
18
|
+
renameCurrentSession: (arg: string) => void;
|
|
19
|
+
snackbarMessages: SnackbarMessage[];
|
|
20
|
+
popSnackbarMessage: () => unknown;
|
|
16
21
|
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* View-level lazy mounting. A view only mounts its (GPU-heavy) body
|
|
3
|
-
*
|
|
4
|
-
* spacer once scrolled far away. This bounds the number of simultaneously-live
|
|
2
|
+
* View-level lazy mounting. A view only mounts its (GPU-heavy) body while it is
|
|
3
|
+
* on screen, and collapses to a height-preserving spacer once scrolled away. This bounds the number of simultaneously-live
|
|
5
4
|
* GPU canvases/contexts — the root cause of the "workspaces freeze" with many
|
|
6
5
|
* stacked views (one WebGL context per display canvas blows past the browser's
|
|
7
6
|
* per-page cap; see agent-docs/reference/ARCHITECTURAL_LIMITS.md, "One WebGL2
|
|
@@ -10,10 +9,12 @@
|
|
|
10
9
|
* `root: null` (viewport) is container-agnostic: it reports on-screen-ness the
|
|
11
10
|
* same way whether the views scroll inside the classic container or a workspace
|
|
12
11
|
* panel, so neither container needs to know about windowing. It also means
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* **a `rootMargin` would have no effect** wherever a container scrolls — an
|
|
13
|
+
* observer clips the target against each scrolling ancestor before
|
|
14
|
+
* intersecting with the root box that the margin expands, and both containers
|
|
15
|
+
* are `overflow-y: auto`. Only an embed whose page scrolls instead would feel
|
|
16
|
+
* one, and it gets the same window. So this is a hard window with no
|
|
17
|
+
* hysteresis: a view is torn down the moment it leaves its
|
|
17
18
|
* container and rebuilt when it returns, which on a GPU backend costs a fresh
|
|
18
19
|
* WebGL2 context and a full shader recompile per display.
|
|
19
20
|
*
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
* Starts hidden so a cold load with N crammed views doesn't mount them all at
|
|
28
29
|
* once; the observer's first callback mounts only what's near the viewport.
|
|
29
30
|
*/
|
|
30
|
-
export declare function useViewVisibility(
|
|
31
|
+
export declare function useViewVisibility(): {
|
|
31
32
|
ref: import("react").RefObject<HTMLDivElement | null>;
|
|
32
33
|
visible: boolean;
|
|
33
34
|
measuredHeight: number | undefined;
|
|
@@ -5,9 +5,8 @@ function intersectionObserverAvailable() {
|
|
|
5
5
|
return typeof window !== 'undefined' && 'IntersectionObserver' in window;
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
|
-
* View-level lazy mounting. A view only mounts its (GPU-heavy) body
|
|
9
|
-
*
|
|
10
|
-
* spacer once scrolled far away. This bounds the number of simultaneously-live
|
|
8
|
+
* View-level lazy mounting. A view only mounts its (GPU-heavy) body while it is
|
|
9
|
+
* on screen, and collapses to a height-preserving spacer once scrolled away. This bounds the number of simultaneously-live
|
|
11
10
|
* GPU canvases/contexts — the root cause of the "workspaces freeze" with many
|
|
12
11
|
* stacked views (one WebGL context per display canvas blows past the browser's
|
|
13
12
|
* per-page cap; see agent-docs/reference/ARCHITECTURAL_LIMITS.md, "One WebGL2
|
|
@@ -16,10 +15,12 @@ function intersectionObserverAvailable() {
|
|
|
16
15
|
* `root: null` (viewport) is container-agnostic: it reports on-screen-ness the
|
|
17
16
|
* same way whether the views scroll inside the classic container or a workspace
|
|
18
17
|
* panel, so neither container needs to know about windowing. It also means
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* **a `rootMargin` would have no effect** wherever a container scrolls — an
|
|
19
|
+
* observer clips the target against each scrolling ancestor before
|
|
20
|
+
* intersecting with the root box that the margin expands, and both containers
|
|
21
|
+
* are `overflow-y: auto`. Only an embed whose page scrolls instead would feel
|
|
22
|
+
* one, and it gets the same window. So this is a hard window with no
|
|
23
|
+
* hysteresis: a view is torn down the moment it leaves its
|
|
23
24
|
* container and rebuilt when it returns, which on a GPU backend costs a fresh
|
|
24
25
|
* WebGL2 context and a full shader recompile per display.
|
|
25
26
|
*
|
|
@@ -33,7 +34,7 @@ function intersectionObserverAvailable() {
|
|
|
33
34
|
* Starts hidden so a cold load with N crammed views doesn't mount them all at
|
|
34
35
|
* once; the observer's first callback mounts only what's near the viewport.
|
|
35
36
|
*/
|
|
36
|
-
export function useViewVisibility(
|
|
37
|
+
export function useViewVisibility() {
|
|
37
38
|
const ref = useRef(null);
|
|
38
39
|
const [state, setState] = useState(() => ({
|
|
39
40
|
visible: !intersectionObserverAvailable(),
|
|
@@ -69,14 +70,14 @@ export function useViewVisibility(rootMargin) {
|
|
|
69
70
|
};
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
|
-
}
|
|
73
|
+
});
|
|
73
74
|
io.observe(node);
|
|
74
75
|
return () => {
|
|
75
76
|
io.disconnect();
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
return undefined;
|
|
79
|
-
}, [
|
|
80
|
+
}, []);
|
|
80
81
|
// `measuredHeight` is undefined until the body has been seen at least once.
|
|
81
82
|
// The caller supplies the estimate, and supplies it lazily. See ViewContainer
|
|
82
83
|
return {
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
interface TitledView {
|
|
2
2
|
displayName?: string;
|
|
3
3
|
assemblyNames?: string[];
|
|
4
|
-
minimized
|
|
5
|
-
}
|
|
4
|
+
minimized?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The view's own name, or its assemblies'. An emptied name counts as unset, so
|
|
8
|
+
* clearing the title in the header puts the derived one back.
|
|
9
|
+
*/
|
|
10
|
+
export declare function viewName(view: TitledView, getDisplayName: (assemblyName: string) => string): string;
|
|
11
|
+
export declare function viewTitle(view: TitledView, getDisplayName: (assemblyName: string) => string): string;
|
|
12
|
+
export {};
|
package/esm/ui/App/viewTitle.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The view's own name, or its assemblies'. An emptied name counts as unset, so
|
|
3
|
+
* clearing the title in the header puts the derived one back.
|
|
4
|
+
*/
|
|
5
|
+
export function viewName(view, getDisplayName) {
|
|
2
6
|
// assemblyNames is [] (not undefined) for a view with no displayed regions
|
|
3
7
|
// yet, so fall back on emptiness rather than nullishness
|
|
4
8
|
const assemblyNames = view.assemblyNames ?? [];
|
|
5
|
-
return (view.displayName
|
|
6
|
-
|
|
7
|
-
? assemblyNames.map(r => getDisplayName(r)).join(',')
|
|
8
|
-
: 'Untitled view'
|
|
9
|
+
return (view.displayName ||
|
|
10
|
+
(assemblyNames.length
|
|
11
|
+
? assemblyNames.map(r => getDisplayName(r)).join(', ')
|
|
12
|
+
: 'Untitled view'));
|
|
13
|
+
}
|
|
14
|
+
export function viewTitle(view, getDisplayName) {
|
|
15
|
+
const name = viewName(view, getDisplayName);
|
|
16
|
+
return view.minimized ? `${name} (minimized)` : name;
|
|
9
17
|
}
|
package/esm/ui/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { App,
|
|
1
|
+
export { App, DialogQueue, preloadAppFrame, preloadAppShell, } from './App/index.ts';
|
package/esm/ui/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { App,
|
|
1
|
+
export { App, DialogQueue, preloadAppFrame, preloadAppShell, } from './App/index.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/app-core",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 code shared between the 'full featured' apps e.g. jbrowse-web and jbrowse-desktop",
|
|
6
6
|
"keywords": [
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"esm"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jbrowse/mobx-state-tree": "^6.
|
|
28
|
-
"@mui/icons-material": "^9.
|
|
29
|
-
"@mui/material": "^9.
|
|
30
|
-
"mobx": "^7.0.
|
|
27
|
+
"@jbrowse/mobx-state-tree": "^6.6.1",
|
|
28
|
+
"@mui/icons-material": "^9.4.0",
|
|
29
|
+
"@mui/material": "^9.4.0",
|
|
30
|
+
"mobx": "^7.0.5",
|
|
31
31
|
"mobx-react": "^10.0.2",
|
|
32
|
-
"@jbrowse/product-core": "^5.0.0-beta.
|
|
33
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
32
|
+
"@jbrowse/product-core": "^5.0.0-beta.10",
|
|
33
|
+
"@jbrowse/core": "^5.0.0-beta.10"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"react": ">=19.0.0",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { AppSession } from './types.ts';
|
|
2
|
-
/**
|
|
3
|
-
* One element that says whether the whole app has finished.
|
|
4
|
-
*
|
|
5
|
-
* `[data-app-phase="ready"]` is the entire readiness contract for anything
|
|
6
|
-
* driving JBrowse from outside — a screenshot tool, a test, an agent. Wait for
|
|
7
|
-
* that selector and you are done; there is no chain to assemble and no
|
|
8
|
-
* per-display census to take.
|
|
9
|
-
*
|
|
10
|
-
* Why it exists when `data-view-phase` and `data-display-phase` already do:
|
|
11
|
-
* those are per-element and NEGATIVE. Asking them "is everything finished"
|
|
12
|
-
* means asserting that no element carries the loading value, which is equally
|
|
13
|
-
* true of an app that has not started — and the gap is real, measured at about
|
|
14
|
-
* a second on a two-track session. A positive attribute cannot be satisfied
|
|
15
|
-
* early, because something has to render it.
|
|
16
|
-
*
|
|
17
|
-
* Its own component, and `hidden`, for two reasons: an observer here subscribes
|
|
18
|
-
* to every view's phase without re-rendering the app shell around it, and a
|
|
19
|
-
* marker that is never drawn cannot be mistaken for UI or shift a layout. It is
|
|
20
|
-
* still found by `querySelector`, which is what reads it.
|
|
21
|
-
*
|
|
22
|
-
* **What it is silent about**: a display in a TERMINAL state. `error` is a
|
|
23
|
-
* finished state, so this reads `ready` over a display whose fetch failed —
|
|
24
|
-
* correctly, since nothing is still working. A capture wants more than that: it
|
|
25
|
-
* wants a picture, and an error banner is not one. `waitForDisplaysDone` after
|
|
26
|
-
* this is what draws that line, keying on the `data-display-drawn` the two
|
|
27
|
-
* comparative canvases publish from their stricter `settled` gate (see
|
|
28
|
-
* `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
|
|
29
|
-
* and says why an error separates them). Both harnesses do it in that order.
|
|
30
|
-
*/
|
|
31
|
-
declare const AppReadyMarker: ({ session, }: {
|
|
32
|
-
session: AppSession;
|
|
33
|
-
}) => import("react").JSX.Element;
|
|
34
|
-
export default AppReadyMarker;
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { observer } from 'mobx-react';
|
|
3
|
-
function containerLoading(container) {
|
|
4
|
-
return (container.tracks ?? []).some(track => (track.displays ?? []).some(d => d.displayPhase === 'loading'));
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Is anything in this session still working?
|
|
8
|
-
*
|
|
9
|
-
* The same two facts the per-element attributes publish — a view still
|
|
10
|
-
* resolving its assembly, a display still in its own fetch — reduced to one
|
|
11
|
-
* answer for the whole app. Duck-typed rather than imported: every view type
|
|
12
|
-
* would otherwise have to be a dependency of the app shell.
|
|
13
|
-
*/
|
|
14
|
-
function anythingLoading(views) {
|
|
15
|
-
return views.some(view => view.showLoading === true ||
|
|
16
|
-
view.initialized === false ||
|
|
17
|
-
containerLoading(view) ||
|
|
18
|
-
// A view whose tracks hang off something else — the synteny view's are on
|
|
19
|
-
// its levels, one list per band. `view.tracks` is empty there, so without
|
|
20
|
-
// this a stack of ribbons still fetching reads as idle.
|
|
21
|
-
(view.trackContainers ?? []).some(containerLoading) ||
|
|
22
|
-
anythingLoading(view.views ?? []));
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* One element that says whether the whole app has finished.
|
|
26
|
-
*
|
|
27
|
-
* `[data-app-phase="ready"]` is the entire readiness contract for anything
|
|
28
|
-
* driving JBrowse from outside — a screenshot tool, a test, an agent. Wait for
|
|
29
|
-
* that selector and you are done; there is no chain to assemble and no
|
|
30
|
-
* per-display census to take.
|
|
31
|
-
*
|
|
32
|
-
* Why it exists when `data-view-phase` and `data-display-phase` already do:
|
|
33
|
-
* those are per-element and NEGATIVE. Asking them "is everything finished"
|
|
34
|
-
* means asserting that no element carries the loading value, which is equally
|
|
35
|
-
* true of an app that has not started — and the gap is real, measured at about
|
|
36
|
-
* a second on a two-track session. A positive attribute cannot be satisfied
|
|
37
|
-
* early, because something has to render it.
|
|
38
|
-
*
|
|
39
|
-
* Its own component, and `hidden`, for two reasons: an observer here subscribes
|
|
40
|
-
* to every view's phase without re-rendering the app shell around it, and a
|
|
41
|
-
* marker that is never drawn cannot be mistaken for UI or shift a layout. It is
|
|
42
|
-
* still found by `querySelector`, which is what reads it.
|
|
43
|
-
*
|
|
44
|
-
* **What it is silent about**: a display in a TERMINAL state. `error` is a
|
|
45
|
-
* finished state, so this reads `ready` over a display whose fetch failed —
|
|
46
|
-
* correctly, since nothing is still working. A capture wants more than that: it
|
|
47
|
-
* wants a picture, and an error banner is not one. `waitForDisplaysDone` after
|
|
48
|
-
* this is what draws that line, keying on the `data-display-drawn` the two
|
|
49
|
-
* comparative canvases publish from their stricter `settled` gate (see
|
|
50
|
-
* `comparativeReadiness` in `@jbrowse/synteny-core`, which holds both answers
|
|
51
|
-
* and says why an error separates them). Both harnesses do it in that order.
|
|
52
|
-
*/
|
|
53
|
-
const AppReadyMarker = observer(function AppReadyMarker({ session, }) {
|
|
54
|
-
return (_jsx("span", { hidden: true, "data-testid": "app-ready-marker", "data-app-phase": anythingLoading(session.views) ? 'loading' : 'ready' }));
|
|
55
|
-
});
|
|
56
|
-
export default AppReadyMarker;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { DrawerControls as default } from '@jbrowse/product-core';
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { DrawerControls as default } from '@jbrowse/product-core';
|
package/esm/ui/App/copyView.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function renameIds(obj: Record<string, unknown>): Record<string, unknown>;
|
package/esm/ui/App/copyView.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { createElementId } from '@jbrowse/core/util/types/mst';
|
|
2
|
-
export function renameIds(obj) {
|
|
3
|
-
const idMap = new Map();
|
|
4
|
-
function transformIds(value) {
|
|
5
|
-
if (value === null || value === undefined) {
|
|
6
|
-
return value;
|
|
7
|
-
}
|
|
8
|
-
if (Array.isArray(value)) {
|
|
9
|
-
return value.map(transformIds);
|
|
10
|
-
}
|
|
11
|
-
if (typeof value === 'object') {
|
|
12
|
-
const result = {};
|
|
13
|
-
for (const [key, val] of Object.entries(value)) {
|
|
14
|
-
if (key === 'id' && typeof val === 'string') {
|
|
15
|
-
if (!idMap.has(val)) {
|
|
16
|
-
idMap.set(val, createElementId());
|
|
17
|
-
}
|
|
18
|
-
result[key] = idMap.get(val);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
result[key] = transformIds(val);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
return result;
|
|
25
|
-
}
|
|
26
|
-
return value;
|
|
27
|
-
}
|
|
28
|
-
return transformIds(obj);
|
|
29
|
-
}
|