@jbrowse/display-ui 5.0.0-beta.11 → 5.0.0-beta.14
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/README.md +70 -76
- package/esm/AxisCaption.d.ts +2 -2
- package/esm/AxisCaption.js +6 -4
- package/esm/AxisGutter.d.ts +2 -2
- package/esm/AxisGutter.js +5 -4
- package/esm/CrossHatches.d.ts +3 -2
- package/esm/CrossHatches.js +5 -2
- package/esm/DisplayUIProvider.d.ts +3 -2
- package/esm/DisplayUIProvider.js +4 -2
- package/esm/FloatingLegend.js +7 -3
- package/esm/FloatingText.d.ts +9 -8
- package/esm/FloatingText.js +9 -7
- package/esm/ScoreDomainCaption.d.ts +2 -2
- package/esm/ScoreDomainCaption.js +3 -2
- package/esm/ScoreRules.d.ts +3 -2
- package/esm/ScoreRules.js +5 -2
- package/esm/YScaleBar.js +1 -8
- package/esm/YScaleBarOverlay.d.ts +3 -2
- package/esm/YScaleBarOverlay.js +4 -2
- package/esm/axisPlacement.d.ts +16 -9
- package/esm/axisPlacement.js +22 -14
- package/esm/bottomRightCorner.d.ts +19 -47
- package/esm/bottomRightCorner.js +19 -47
- package/esm/chromeOverlayContext.js +10 -25
- package/esm/chromeOverlays.d.ts +73 -67
- package/esm/embed/engineBoundary.d.ts +34 -0
- package/esm/embed/engineBoundary.js +29 -0
- package/esm/embed/index.d.ts +6 -0
- package/esm/embed/index.js +18 -3
- package/esm/embed/legend.js +2 -2
- package/esm/embed/location.d.ts +2 -1
- package/esm/embed/location.js +18 -17
- package/esm/embed/notifications.d.ts +29 -0
- package/esm/embed/notifications.js +38 -0
- package/esm/embed/provider.d.ts +5 -3
- package/esm/embed/provider.js +6 -4
- package/esm/embed/regions.d.ts +3 -2
- package/esm/embed/regions.js +9 -7
- package/esm/embed/searchPicker.d.ts +24 -0
- package/esm/embed/searchPicker.js +14 -0
- package/esm/embed/toolbar.d.ts +16 -0
- package/esm/embed/toolbar.js +22 -0
- package/esm/index.d.ts +1 -1
- package/esm/isLiveModel.d.ts +10 -15
- package/esm/isLiveModel.js +10 -15
- package/esm/plainChromeOverlays.d.ts +2 -2
- package/esm/plainChromeOverlays.js +17 -39
- package/esm/tooltip/Tooltip.d.ts +3 -3
- package/esm/tooltip/Tooltip.js +4 -2
- package/esm/trackControl/plainTrackControl.d.ts +2 -1
- package/esm/trackControl/plainTrackControl.js +8 -6
- package/esm/trackOverlay/TrackOverlaySlot.d.ts +3 -3
- package/esm/trackOverlay/TrackOverlaySlot.js +4 -3
- package/esm/valueScale.d.ts +5 -5
- package/package.json +4 -4
package/esm/embed/index.d.ts
CHANGED
|
@@ -2,10 +2,15 @@ import type { PanZoomView } from '@jbrowse/core/util/usePanZoom';
|
|
|
2
2
|
import type { ViewStatus as ViewStatusValue } from '@jbrowse/core/util/viewStatus';
|
|
3
3
|
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
4
4
|
import type React from 'react';
|
|
5
|
+
export { EngineBoundary } from './engineBoundary.tsx';
|
|
6
|
+
export type { EngineFallbackProps } from './engineBoundary.tsx';
|
|
5
7
|
export { Legend } from './legend.tsx';
|
|
6
8
|
export { LocationBox, useLocationBox } from './location.tsx';
|
|
9
|
+
export { Notifications } from './notifications.tsx';
|
|
7
10
|
export { EmbedProvider } from './provider.tsx';
|
|
8
11
|
export { Highlights, RegionSeams, Scalebar } from './regions.tsx';
|
|
12
|
+
export { SearchPicker } from './searchPicker.tsx';
|
|
13
|
+
export { NavButton, Toolbar } from './toolbar.tsx';
|
|
9
14
|
export interface EmbedDisplay {
|
|
10
15
|
height: number;
|
|
11
16
|
RenderingComponent: React.ComponentType<{
|
|
@@ -30,6 +35,7 @@ export declare const Track: ({ view, trackId, }: {
|
|
|
30
35
|
trackId: string;
|
|
31
36
|
}) => React.JSX.Element | null;
|
|
32
37
|
export interface ResizableDisplay {
|
|
38
|
+
height: number;
|
|
33
39
|
setResizing: (resizing: boolean) => void;
|
|
34
40
|
resizeHeight: (distance: number) => unknown;
|
|
35
41
|
}
|
package/esm/embed/index.js
CHANGED
|
@@ -7,10 +7,14 @@ import { usePanZoom } from '@jbrowse/core/util/usePanZoom';
|
|
|
7
7
|
import { useResizeDrag } from '@jbrowse/core/util/useResizeDrag';
|
|
8
8
|
import { observer } from 'mobx-react';
|
|
9
9
|
import { TrackOverlaySlot } from '../trackOverlay/TrackOverlaySlot.js';
|
|
10
|
+
export { EngineBoundary } from './engineBoundary.js';
|
|
10
11
|
export { Legend } from './legend.js';
|
|
11
12
|
export { LocationBox, useLocationBox } from './location.js';
|
|
13
|
+
export { Notifications } from './notifications.js';
|
|
12
14
|
export { EmbedProvider } from './provider.js';
|
|
13
15
|
export { Highlights, RegionSeams, Scalebar } from './regions.js';
|
|
16
|
+
export { SearchPicker } from './searchPicker.js';
|
|
17
|
+
export { NavButton, Toolbar } from './toolbar.js';
|
|
14
18
|
export const Track = observer(function Track({ view, trackId, }) {
|
|
15
19
|
const display = view.getTrack(trackId)?.activeDisplay;
|
|
16
20
|
if (!display) {
|
|
@@ -19,6 +23,7 @@ export const Track = observer(function Track({ view, trackId, }) {
|
|
|
19
23
|
const { RenderingComponent } = display;
|
|
20
24
|
return (_jsx(TrackOverlaySlot, { zIndex: 3, style: { height: display.height }, children: _jsx("div", { style: { position: 'absolute', inset: 0, contain: 'strict' }, children: _jsx(Suspense, { fallback: null, children: _jsx(RenderingComponent, { model: display, onHorizontalScroll: view.horizontalScroll }) }) }) }));
|
|
21
25
|
});
|
|
26
|
+
const KEYBOARD_RESIZE_STEP = 10;
|
|
22
27
|
export const ResizeHandle = observer(function ResizeHandle({ view, trackId, style, }) {
|
|
23
28
|
const display = view.getTrack(trackId)?.activeDisplay;
|
|
24
29
|
const props = useResizeDrag({
|
|
@@ -32,7 +37,17 @@ export const ResizeHandle = observer(function ResizeHandle({ view, trackId, styl
|
|
|
32
37
|
display?.setResizing(false);
|
|
33
38
|
},
|
|
34
39
|
});
|
|
35
|
-
return display ? (_jsx("div", { ...props, "data-gesture-owner": "true", "aria-label": `Resize ${trackId}`,
|
|
40
|
+
return display ? (_jsx("div", { ...props, "data-gesture-owner": "true", role: "separator", tabIndex: 0, "aria-orientation": "horizontal", "aria-label": `Resize ${trackId}`, "aria-valuenow": Math.round(display.height), onKeyDown: event => {
|
|
41
|
+
const step = event.key === 'ArrowDown'
|
|
42
|
+
? KEYBOARD_RESIZE_STEP
|
|
43
|
+
: event.key === 'ArrowUp'
|
|
44
|
+
? -KEYBOARD_RESIZE_STEP
|
|
45
|
+
: 0;
|
|
46
|
+
if (step) {
|
|
47
|
+
event.preventDefault();
|
|
48
|
+
display.resizeHeight(step);
|
|
49
|
+
}
|
|
50
|
+
}, style: {
|
|
36
51
|
height: 4,
|
|
37
52
|
cursor: 'row-resize',
|
|
38
53
|
touchAction: 'none',
|
|
@@ -45,11 +60,11 @@ export const TrackToggle = observer(function TrackToggle({ view, trackId, style,
|
|
|
45
60
|
void view.launchToggleTrack(trackId);
|
|
46
61
|
} }), children] }));
|
|
47
62
|
});
|
|
48
|
-
function ViewLoading({ message, progress, source }) {
|
|
63
|
+
const ViewLoading = observer(function ViewLoading({ message, progress, source, }) {
|
|
49
64
|
const slow = useSlowLoad();
|
|
50
65
|
const stalled = useStalled(`${message}|${progress}|${source}`);
|
|
51
66
|
return (_jsxs(_Fragment, { children: [slow ? message : 'Loading', !slow || progress === undefined ? null : (_jsx("progress", { value: progress, max: 1, style: { display: 'block', width: 300, maxWidth: '100%' } })), stalled && source ? (_jsxs("div", { style: { overflowWrap: 'anywhere' }, children: ["still waiting on ", source] })) : null] }));
|
|
52
|
-
}
|
|
67
|
+
});
|
|
53
68
|
export const ViewStatus = observer(function ViewStatus({ view, style, }) {
|
|
54
69
|
const { status } = view;
|
|
55
70
|
return status.type === 'ready' ? null : (_jsx("div", { role: status.type === 'error' ? 'alert' : 'status', style: {
|
package/esm/embed/legend.js
CHANGED
|
@@ -8,7 +8,7 @@ function gradientCss({ stops }) {
|
|
|
8
8
|
.map(({ color, opacity = 1, offset }) => `color-mix(in srgb, ${color} ${Math.round(opacity * 100)}%, transparent) ${offset * 100}%`)
|
|
9
9
|
.join(', ')})`;
|
|
10
10
|
}
|
|
11
|
-
function Row({ item }) {
|
|
11
|
+
const Row = observer(function Row({ item }) {
|
|
12
12
|
const { gradient, label, hidden } = item;
|
|
13
13
|
return gradient ? (_jsxs("span", { style: { display: 'inline-flex', alignItems: 'center', gap: 4 }, children: [label, _jsx("span", { children: gradient.minLabel }), _jsx("span", { style: {
|
|
14
14
|
width: 80,
|
|
@@ -21,7 +21,7 @@ function Row({ item }) {
|
|
|
21
21
|
opacity: hidden ? 0.5 : undefined,
|
|
22
22
|
textDecoration: hidden ? 'line-through' : undefined,
|
|
23
23
|
}, children: [legendSwatches(item).map(swatch => (_jsx("svg", { "aria-hidden": true, width: SWATCH, height: SWATCH, children: _jsx(LegendSwatchGlyph, { swatch: swatch, size: SWATCH }) }, swatch.color))), label] }));
|
|
24
|
-
}
|
|
24
|
+
});
|
|
25
25
|
function legendSpecIn(display) {
|
|
26
26
|
return display && typeof display === 'object' && 'legendSpec' in display
|
|
27
27
|
? display.legendSpec
|
package/esm/embed/location.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SearchPickerView } from './searchPicker.tsx';
|
|
1
2
|
import type React from 'react';
|
|
2
3
|
export interface LocationView {
|
|
3
4
|
coarseVisibleLocStrings: string;
|
|
@@ -12,6 +13,6 @@ export declare function useLocationBox(view: LocationView): {
|
|
|
12
13
|
go(input?: string): void;
|
|
13
14
|
};
|
|
14
15
|
export declare const LocationBox: ({ view, style, }: {
|
|
15
|
-
view: LocationView;
|
|
16
|
+
view: LocationView & SearchPickerView;
|
|
16
17
|
style?: React.CSSProperties;
|
|
17
18
|
}) => React.JSX.Element;
|
package/esm/embed/location.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useRef, useState } from 'react';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
|
+
import { SearchPicker } from './searchPicker.js';
|
|
4
5
|
export function useLocationBox(view) {
|
|
5
6
|
const [draft, setDraft] = useState();
|
|
6
7
|
const [pending, setPending] = useState(false);
|
|
@@ -46,20 +47,20 @@ export function useLocationBox(view) {
|
|
|
46
47
|
}
|
|
47
48
|
export const LocationBox = observer(function LocationBox({ view, style, }) {
|
|
48
49
|
const box = useLocationBox(view);
|
|
49
|
-
return (_jsxs("form", { style: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
return (_jsxs(_Fragment, { children: [_jsxs("form", { style: {
|
|
51
|
+
display: 'flex',
|
|
52
|
+
flexWrap: 'wrap',
|
|
53
|
+
alignItems: 'center',
|
|
54
|
+
gap: 4,
|
|
55
|
+
...style,
|
|
56
|
+
}, onSubmit: event => {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
box.go();
|
|
59
|
+
}, children: [_jsx("input", { "aria-label": "Location", value: box.value, size: 38, style: { font: 'inherit', padding: '2px 4px' }, onChange: event => {
|
|
60
|
+
box.edit(event.target.value);
|
|
61
|
+
}, onKeyDown: event => {
|
|
62
|
+
if (event.key === 'Escape') {
|
|
63
|
+
box.reset();
|
|
64
|
+
}
|
|
65
|
+
} }), _jsx("button", { type: "submit", disabled: box.pending, style: { font: 'inherit' }, children: "Go" }), box.error ? (_jsx("span", { role: "alert", children: box.error instanceof Error ? box.error.message : String(box.error) })) : null] }), _jsx(SearchPicker, { view: view })] }));
|
|
65
66
|
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface NotificationsSession {
|
|
3
|
+
snackbarMessages: readonly {
|
|
4
|
+
message: string;
|
|
5
|
+
level?: string;
|
|
6
|
+
actions?: readonly {
|
|
7
|
+
name: React.ReactNode;
|
|
8
|
+
onClick: () => void;
|
|
9
|
+
}[];
|
|
10
|
+
}[];
|
|
11
|
+
popSnackbarMessage: () => unknown;
|
|
12
|
+
queueOfDialogs: readonly unknown[];
|
|
13
|
+
removeActiveDialog: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Draws the two session channels a host with its own UI otherwise drops: the
|
|
17
|
+
* latest of `session.snackbarMessages`, where a failed navigation, track launch
|
|
18
|
+
* or config lands, and a line for each dialog JBrowse queued, which only a
|
|
19
|
+
* Material host can open. A name search's picker is not one: it is
|
|
20
|
+
* `view.searchPicker`, drawn by `SearchPicker`.
|
|
21
|
+
*
|
|
22
|
+
* `EmbedProvider` mounts one below its children. Pass it
|
|
23
|
+
* `notifications={false}` to place this elsewhere or to draw the two queues
|
|
24
|
+
* yourself.
|
|
25
|
+
*/
|
|
26
|
+
export declare const Notifications: ({ session, style, }: {
|
|
27
|
+
session: NotificationsSession;
|
|
28
|
+
style?: React.CSSProperties;
|
|
29
|
+
}) => React.JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { usePalette } from '@jbrowse/core/ui/PaletteContext';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
/**
|
|
5
|
+
* Draws the two session channels a host with its own UI otherwise drops: the
|
|
6
|
+
* latest of `session.snackbarMessages`, where a failed navigation, track launch
|
|
7
|
+
* or config lands, and a line for each dialog JBrowse queued, which only a
|
|
8
|
+
* Material host can open. A name search's picker is not one: it is
|
|
9
|
+
* `view.searchPicker`, drawn by `SearchPicker`.
|
|
10
|
+
*
|
|
11
|
+
* `EmbedProvider` mounts one below its children. Pass it
|
|
12
|
+
* `notifications={false}` to place this elsewhere or to draw the two queues
|
|
13
|
+
* yourself.
|
|
14
|
+
*/
|
|
15
|
+
export const Notifications = observer(function Notifications({ session, style, }) {
|
|
16
|
+
const palette = usePalette();
|
|
17
|
+
const latest = session.snackbarMessages.at(-1);
|
|
18
|
+
const queued = session.queueOfDialogs.length;
|
|
19
|
+
const row = {
|
|
20
|
+
display: 'flex',
|
|
21
|
+
alignItems: 'center',
|
|
22
|
+
gap: 8,
|
|
23
|
+
marginTop: 8,
|
|
24
|
+
padding: '4px 8px',
|
|
25
|
+
fontSize: '0.8rem',
|
|
26
|
+
colorScheme: palette.mode,
|
|
27
|
+
color: palette.text.primary,
|
|
28
|
+
background: `color-mix(in srgb, ${palette.text.primary} 8%, ${palette.background.paper})`,
|
|
29
|
+
};
|
|
30
|
+
return (_jsxs(_Fragment, { children: [latest ? (_jsxs("div", { role: latest.level === 'error' ? 'alert' : 'status', "data-testid": "embed-notification", style: { ...row, ...style }, children: [_jsx("span", { style: { flex: 1, overflowWrap: 'anywhere' }, children: latest.message }), latest.actions
|
|
31
|
+
// 'report' opens the stack-trace dialog, which is Material
|
|
32
|
+
?.filter(action => action.name !== 'report')
|
|
33
|
+
.map((action, i) => (_jsx("button", { type: "button", onClick: action.onClick, children: action.name }, i))), _jsx("button", { type: "button", "aria-label": "Dismiss notification", onClick: () => {
|
|
34
|
+
session.popSnackbarMessage();
|
|
35
|
+
}, children: "\u2715" })] })) : null, queued ? (_jsxs("div", { role: "status", "data-testid": "queued-dialog-notice", style: { ...row, ...style }, children: [_jsxs("span", { style: { flex: 1 }, children: ["JBrowse queued ", queued, " dialog", queued > 1 ? 's' : '', ", and this page draws none."] }), _jsx("button", { type: "button", onClick: () => {
|
|
36
|
+
session.removeActiveDialog();
|
|
37
|
+
}, children: "Dismiss" })] })) : null] }));
|
|
38
|
+
});
|
package/esm/embed/provider.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { DisplayChromeOverlays } from '../chromeOverlays.ts';
|
|
2
2
|
import type { TrackControlComponent } from '../trackControl/types.ts';
|
|
3
|
+
import type { NotificationsSession } from './notifications.tsx';
|
|
3
4
|
import type { ThemeModeSession } from '@jbrowse/core/ui/PaletteContext';
|
|
4
5
|
import type React from 'react';
|
|
5
|
-
export declare
|
|
6
|
-
session: ThemeModeSession;
|
|
6
|
+
export declare const EmbedProvider: ({ session, mode, overlays, trackControl, notifications, children, }: {
|
|
7
|
+
session: ThemeModeSession & NotificationsSession;
|
|
7
8
|
mode?: 'light' | 'dark';
|
|
8
9
|
overlays?: Partial<DisplayChromeOverlays>;
|
|
9
10
|
trackControl?: TrackControlComponent;
|
|
11
|
+
notifications?: boolean;
|
|
10
12
|
children: React.ReactNode;
|
|
11
|
-
})
|
|
13
|
+
}) => React.JSX.Element;
|
package/esm/embed/provider.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { SessionPaletteProvider } from '@jbrowse/core/ui/PaletteContext';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
3
4
|
import DisplayUIProvider from '../DisplayUIProvider.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
5
|
+
import { Notifications } from './notifications.js';
|
|
6
|
+
export const EmbedProvider = observer(function EmbedProvider({ session, mode, overlays, trackControl, notifications = true, children, }) {
|
|
7
|
+
return (_jsx(SessionPaletteProvider, { session: session, mode: mode, children: _jsxs(DisplayUIProvider, { overlays: overlays, trackControl: trackControl, children: [children, notifications ? _jsx(Notifications, { session: session }) : null] }) }));
|
|
8
|
+
});
|
package/esm/embed/regions.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
1
2
|
import type React from 'react';
|
|
2
3
|
export interface RegionSeamsView {
|
|
3
4
|
staticBlocksTranslateX: number;
|
|
@@ -28,7 +29,7 @@ export interface ScalebarView {
|
|
|
28
29
|
caption?: string;
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
|
-
export interface HighlightsView {
|
|
32
|
+
export interface HighlightsView extends IStateTreeNode {
|
|
32
33
|
highlights: readonly {
|
|
33
34
|
assemblyName?: string;
|
|
34
35
|
refName: string;
|
|
@@ -47,7 +48,7 @@ export declare const RegionSeams: ({ view, }: {
|
|
|
47
48
|
}) => React.JSX.Element;
|
|
48
49
|
export declare const Highlights: ({ view, }: {
|
|
49
50
|
view: HighlightsView;
|
|
50
|
-
}) => (React.JSX.Element | null)[];
|
|
51
|
+
}) => (React.JSX.Element | null)[] | null;
|
|
51
52
|
export declare const Scalebar: ({ view, style, ...props }: {
|
|
52
53
|
view: ScalebarView;
|
|
53
54
|
} & React.ComponentProps<'div'>) => React.JSX.Element;
|
package/esm/embed/regions.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { usePalette } from '@jbrowse/core/ui/PaletteContext';
|
|
3
3
|
import { highlightKey } from '@jbrowse/core/util/highlights';
|
|
4
|
+
import { getSession } from '@jbrowse/core/util/mstUtils';
|
|
4
5
|
import { observer } from 'mobx-react';
|
|
5
|
-
const
|
|
6
|
-
seam: 'color-mix(in srgb, CanvasText 45%, Canvas)',
|
|
7
|
-
boundary: 'color-mix(in srgb, CanvasText 12%, Canvas)',
|
|
8
|
-
elided: 'color-mix(in srgb, CanvasText 30%, Canvas)',
|
|
9
|
-
};
|
|
6
|
+
const spanInk = { seam: 45, boundary: 12, elided: 30 };
|
|
10
7
|
export const RegionSeams = observer(function RegionSeams({ view, }) {
|
|
8
|
+
const { text, background } = usePalette();
|
|
11
9
|
return (_jsx("div", { "aria-hidden": true, "data-region-seams": true, style: {
|
|
12
10
|
position: 'absolute',
|
|
13
11
|
top: 0,
|
|
@@ -22,11 +20,15 @@ export const RegionSeams = observer(function RegionSeams({ view, }) {
|
|
|
22
20
|
bottom: 0,
|
|
23
21
|
left: x,
|
|
24
22
|
width,
|
|
25
|
-
background:
|
|
23
|
+
background: `color-mix(in srgb, ${text.primary} ${spanInk[kind]}%, ${background.default})`,
|
|
26
24
|
} }, key))) }));
|
|
27
25
|
});
|
|
28
26
|
export const Highlights = observer(function Highlights({ view, }) {
|
|
29
27
|
const palette = usePalette();
|
|
28
|
+
const { highlightsVisible, highlightLabelsVisible } = getSession(view);
|
|
29
|
+
if (!highlightsVisible) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
30
32
|
return view.highlights.map((highlight, i) => {
|
|
31
33
|
const coords = view.getHighlightCoords(highlight);
|
|
32
34
|
return coords ? (_jsx("div", { "data-testid": "highlight-band", style: {
|
|
@@ -40,7 +42,7 @@ export const Highlights = observer(function Highlights({ view, }) {
|
|
|
40
42
|
pointerEvents: 'none',
|
|
41
43
|
background: highlight.color ??
|
|
42
44
|
`color-mix(in srgb, ${palette.highlight.main} 20%, transparent)`,
|
|
43
|
-
}, children: highlight.label ? (_jsx("span", { style: {
|
|
45
|
+
}, children: highlight.label && highlightLabelsVisible ? (_jsx("span", { style: {
|
|
44
46
|
position: 'absolute',
|
|
45
47
|
top: 0,
|
|
46
48
|
left: 3,
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
export interface SearchPickerView {
|
|
3
|
+
searchPicker?: {
|
|
4
|
+
query: string;
|
|
5
|
+
rows: readonly {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
location: string;
|
|
9
|
+
trackName: string;
|
|
10
|
+
}[];
|
|
11
|
+
pick: (id: string) => Promise<unknown>;
|
|
12
|
+
};
|
|
13
|
+
closeSearchPicker: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Lists the places a name search points at when it matches several and none
|
|
17
|
+
* exactly, which `view.navToLocString` leaves to the host instead of
|
|
18
|
+
* navigating. Draws nothing until a search sets `view.searchPicker`. A
|
|
19
|
+
* `LocationBox` mounts one under itself.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SearchPicker: ({ view, style, }: {
|
|
22
|
+
view: SearchPickerView;
|
|
23
|
+
style?: React.CSSProperties;
|
|
24
|
+
}) => React.JSX.Element | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { observer } from 'mobx-react';
|
|
3
|
+
/**
|
|
4
|
+
* Lists the places a name search points at when it matches several and none
|
|
5
|
+
* exactly, which `view.navToLocString` leaves to the host instead of
|
|
6
|
+
* navigating. Draws nothing until a search sets `view.searchPicker`. A
|
|
7
|
+
* `LocationBox` mounts one under itself.
|
|
8
|
+
*/
|
|
9
|
+
export const SearchPicker = observer(function SearchPicker({ view, style, }) {
|
|
10
|
+
const { searchPicker } = view;
|
|
11
|
+
return searchPicker ? (_jsxs("div", { role: "dialog", "aria-label": "Search results", "data-testid": "search-picker", style: { fontSize: '0.8rem', paddingBottom: 8, ...style }, children: [_jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsxs("strong", { style: { flex: 1 }, children: ["\u201C", searchPicker.query, "\u201D matches ", searchPicker.rows.length, " places"] }), _jsx("button", { type: "button", "aria-label": "Close search results", onClick: view.closeSearchPicker, children: "\u2715" })] }), _jsx("ul", { style: { listStyle: 'none', margin: 0, padding: 0 }, children: searchPicker.rows.map(row => (_jsx("li", { children: _jsxs("button", { type: "button", style: { width: '100%', textAlign: 'left' }, onClick: () => {
|
|
12
|
+
void searchPicker.pick(row.id);
|
|
13
|
+
}, children: [_jsx("strong", { children: row.label }), " ", row.location, " ", row.trackName] }) }, row.id))) })] })) : null;
|
|
14
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
export declare const Toolbar: ({ style, children, }: {
|
|
4
|
+
style?: React.CSSProperties;
|
|
5
|
+
children?: React.ReactNode;
|
|
6
|
+
}) => React.JSX.Element;
|
|
7
|
+
export interface NavView extends IStateTreeNode {
|
|
8
|
+
navToLocString: (input: string) => Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
export declare const NavButton: ({ view, loc, onClick, style, children, }: {
|
|
11
|
+
view: NavView;
|
|
12
|
+
loc: string;
|
|
13
|
+
onClick?: () => void;
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
children?: React.ReactNode;
|
|
16
|
+
}) => React.JSX.Element;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getSession } from '@jbrowse/core/util/mstUtils';
|
|
3
|
+
import { observer } from 'mobx-react';
|
|
4
|
+
export const Toolbar = observer(function Toolbar({ style, children, }) {
|
|
5
|
+
return (_jsx("div", { style: {
|
|
6
|
+
display: 'flex',
|
|
7
|
+
flexWrap: 'wrap',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
gap: 12,
|
|
10
|
+
paddingBottom: 8,
|
|
11
|
+
fontSize: '0.85rem',
|
|
12
|
+
...style,
|
|
13
|
+
}, children: children }));
|
|
14
|
+
});
|
|
15
|
+
export const NavButton = observer(function NavButton({ view, loc, onClick, style, children, }) {
|
|
16
|
+
return (_jsx("button", { type: "button", style: style, onClick: () => {
|
|
17
|
+
onClick?.();
|
|
18
|
+
view.navToLocString(loc).catch((e) => {
|
|
19
|
+
getSession(view).notifyError(`${e}`, e);
|
|
20
|
+
});
|
|
21
|
+
}, children: children ?? loc }));
|
|
22
|
+
});
|
package/esm/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default as DisplayUIProvider, resolveOverlays, } from './DisplayUIProvider.tsx';
|
|
2
2
|
export { DisplayChromeOverlayProvider, useChromeOverlayOverride, } from './chromeOverlayContext.ts';
|
|
3
3
|
export { default as plainChromeOverlays } from './plainChromeOverlays.tsx';
|
|
4
|
-
export type {
|
|
4
|
+
export type { BackgroundProgressProps, DisplayChromeOverlays, ErrorBarProps, LoadingProps, RenderErrorProps, TooLargeProps, } from './chromeOverlays.ts';
|
|
5
5
|
export { TrackControlProvider, useTrackControlOverride, } from './trackControl/trackControlContext.ts';
|
|
6
6
|
export { default as plainTrackControl } from './trackControl/plainTrackControl.tsx';
|
|
7
7
|
export { useTrackControlMenu } from './trackControl/useTrackControlMenu.tsx';
|
package/esm/isLiveModel.d.ts
CHANGED
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* #api
|
|
3
|
-
* Whether
|
|
3
|
+
* Whether a display model can still be called into.
|
|
4
4
|
*
|
|
5
|
-
* The
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The chrome's overlay slots guard every button with it: the terminal states
|
|
6
|
+
* unmount the canvas, so a click landing after the track was closed would
|
|
7
|
+
* otherwise run an action on a destroyed node.
|
|
8
8
|
*
|
|
9
|
-
* **`isAlive` alone is wrong here, and it throws rather than answering.**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* button looking live and doing nothing, which is the exact state
|
|
16
|
-
* `DisplayChromeOverlays.TooLarge` documents itself as existing to prevent.
|
|
17
|
-
*
|
|
18
|
-
* A plain object is never destroyed, so it is always callable. The liveness
|
|
19
|
-
* question only exists for an MST node.
|
|
9
|
+
* **`isAlive` alone is wrong here, and it throws rather than answering.** A
|
|
10
|
+
* host writing their own display over `DisplayChromeBase` may hand the chrome a
|
|
11
|
+
* plain object, and `isAlive` runs `assertIsStateTreeNode`, which throws inside
|
|
12
|
+
* the click handler, where React logs it and moves on: a Force load button that
|
|
13
|
+
* looks live and does nothing. A plain object is never destroyed, so it is
|
|
14
|
+
* always callable.
|
|
20
15
|
*/
|
|
21
16
|
export declare function isLiveModel(model: unknown): boolean;
|
package/esm/isLiveModel.js
CHANGED
|
@@ -1,23 +1,18 @@
|
|
|
1
1
|
import { isAlive, isStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
/**
|
|
3
3
|
* #api
|
|
4
|
-
* Whether
|
|
4
|
+
* Whether a display model can still be called into.
|
|
5
5
|
*
|
|
6
|
-
* The
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* The chrome's overlay slots guard every button with it: the terminal states
|
|
7
|
+
* unmount the canvas, so a click landing after the track was closed would
|
|
8
|
+
* otherwise run an action on a destroyed node.
|
|
9
9
|
*
|
|
10
|
-
* **`isAlive` alone is wrong here, and it throws rather than answering.**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* button looking live and doing nothing, which is the exact state
|
|
17
|
-
* `DisplayChromeOverlays.TooLarge` documents itself as existing to prevent.
|
|
18
|
-
*
|
|
19
|
-
* A plain object is never destroyed, so it is always callable. The liveness
|
|
20
|
-
* question only exists for an MST node.
|
|
10
|
+
* **`isAlive` alone is wrong here, and it throws rather than answering.** A
|
|
11
|
+
* host writing their own display over `DisplayChromeBase` may hand the chrome a
|
|
12
|
+
* plain object, and `isAlive` runs `assertIsStateTreeNode`, which throws inside
|
|
13
|
+
* the click handler, where React logs it and moves on: a Force load button that
|
|
14
|
+
* looks live and does nothing. A plain object is never destroyed, so it is
|
|
15
|
+
* always callable.
|
|
21
16
|
*/
|
|
22
17
|
export function isLiveModel(model) {
|
|
23
18
|
return !isStateTreeNode(model) || isAlive(model);
|
|
@@ -7,8 +7,8 @@ import type { DisplayChromeOverlays } from './chromeOverlays.ts';
|
|
|
7
7
|
*
|
|
8
8
|
* `DisplayUIProvider` installs this by default, so mount that rather than
|
|
9
9
|
* naming this — reach for it directly only to wrap a state or to build a
|
|
10
|
-
* context value by hand.
|
|
11
|
-
*
|
|
10
|
+
* context value by hand. Colors come from `currentColor` and the CSS system
|
|
11
|
+
* colors, so the host's own cascade drives them in both light and dark.
|
|
12
12
|
*
|
|
13
13
|
* The `data-testid` values it renders are a contract four of JBrowse's test
|
|
14
14
|
* systems key on, so a replacement set that keeps them can be driven by those
|
|
@@ -4,8 +4,6 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
4
4
|
import { GPU_FALLBACK_LABEL, GPU_FALLBACK_TOOLTIP, disableGpuRendering, shouldOfferGpuFallback, } from '@jbrowse/core/ui/gpuFallback';
|
|
5
5
|
import { progressLabel } from '@jbrowse/core/util/progress';
|
|
6
6
|
import { observer } from 'mobx-react';
|
|
7
|
-
import { isLiveModel } from './isLiveModel.js';
|
|
8
|
-
import { tooLargeBannerText } from './tooLargeBannerText.js';
|
|
9
7
|
import Tooltip from './tooltip/Tooltip.js';
|
|
10
8
|
// A dependency-free `DisplayChromeOverlays` for embedders who don't want MUI:
|
|
11
9
|
// no theme provider to mount, no emotion injected into the host page, and
|
|
@@ -37,9 +35,9 @@ const overlayBox = {
|
|
|
37
35
|
pointerEvents: 'none',
|
|
38
36
|
zIndex: 1,
|
|
39
37
|
};
|
|
40
|
-
// Nearly opaque on purpose. These sit over a canvas whose
|
|
38
|
+
// Nearly opaque on purpose. These sit over a canvas whose colors are not known
|
|
41
39
|
// here, and a translucent chip over dense data was measurably hard to read.
|
|
42
|
-
// `Canvas`/`CanvasText` are the CSS system
|
|
40
|
+
// `Canvas`/`CanvasText` are the CSS system colors, so this tracks the host's
|
|
43
41
|
// light/dark mode without needing a theme object.
|
|
44
42
|
const chip = {
|
|
45
43
|
pointerEvents: 'auto',
|
|
@@ -98,7 +96,7 @@ function errorText(error) {
|
|
|
98
96
|
// toolkit-free, but it paints the palette's `accent`; every rule in this
|
|
99
97
|
// file reads the host's own cascade instead, so the bar is drawn here rather
|
|
100
98
|
// than imported.
|
|
101
|
-
function ProgressBar({ fraction }) {
|
|
99
|
+
const ProgressBar = observer(function ProgressBar({ fraction, }) {
|
|
102
100
|
const filled = Math.min(1, Math.max(0, fraction));
|
|
103
101
|
return (_jsx("div", { role: "progressbar", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.round(filled * 100), style: {
|
|
104
102
|
overflow: 'hidden',
|
|
@@ -116,53 +114,33 @@ function ProgressBar({ fraction }) {
|
|
|
116
114
|
transform: `scaleX(${filled})`,
|
|
117
115
|
transition: 'transform 200ms linear',
|
|
118
116
|
} }) }));
|
|
119
|
-
}
|
|
117
|
+
});
|
|
120
118
|
const PlainRenderError = observer(function PlainRenderError({ error, onRetry, height, }) {
|
|
121
119
|
return (_jsxs("div", { style: banner(height), role: "alert", children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(error) }), _jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: onRetry, children: "Retry" }), shouldOfferGpuFallback(error) ? (_jsx(Tooltip, { title: GPU_FALLBACK_TOOLTIP, children: _jsx("button", { type: "button", style: button, "data-testid": "use_canvas2d_button", onClick: () => {
|
|
122
120
|
disableGpuRendering();
|
|
123
121
|
onRetry();
|
|
124
122
|
}, children: GPU_FALLBACK_LABEL }) })) : null] }));
|
|
125
123
|
});
|
|
126
|
-
const PlainTooLarge = observer(function PlainTooLarge({
|
|
127
|
-
return (_jsxs("div", { style: banner(), role: "status", children: [_jsx("span", { style: { wordBreak: 'break-word' }, children:
|
|
128
|
-
zoomCanRelease: model.zoomCanReleaseGate,
|
|
129
|
-
}) }), _jsx("button", { type: "button", style: button, onClick: () => {
|
|
130
|
-
// the banner unmounts the canvas, so a click landing after the track
|
|
131
|
-
// was closed would otherwise call an action on a dead node. Not bare
|
|
132
|
-
// `isAlive` — this model may be the plain object the contract invites,
|
|
133
|
-
// which that throws on. See isLiveModel.ts.
|
|
134
|
-
if (isLiveModel(model)) {
|
|
135
|
-
model.forceLoad();
|
|
136
|
-
}
|
|
137
|
-
}, children: "Force load" })] }));
|
|
124
|
+
const PlainTooLarge = observer(function PlainTooLarge({ message, onForceLoad, }) {
|
|
125
|
+
return (_jsxs("div", { style: banner(), role: "status", children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: message }), _jsx("button", { type: "button", style: button, onClick: onForceLoad, children: "Force load" })] }));
|
|
138
126
|
});
|
|
139
|
-
const PlainErrorBar = observer(function PlainErrorBar({
|
|
140
|
-
return
|
|
141
|
-
model.reload();
|
|
142
|
-
}, children: "Retry" })] }) })) : null;
|
|
127
|
+
const PlainErrorBar = observer(function PlainErrorBar({ error, onRetry, }) {
|
|
128
|
+
return (_jsx("div", { style: overlayBox, role: onRetry ? 'alert' : 'status', children: _jsxs("div", { style: chip, children: [_jsx("span", { style: { wordBreak: 'break-word' }, children: errorText(error) }), onRetry ? (_jsx("button", { type: "button", style: button, "data-testid": "reload_button", onClick: onRetry, children: "Retry" })) : null] }) }));
|
|
143
129
|
});
|
|
144
130
|
// No anti-flash delay here, unlike the MUI set: that timing is a product
|
|
145
131
|
// judgment JBrowse made for its own UI, and an embedder pacing their own
|
|
146
132
|
// loading affordances shouldn't inherit it invisibly. Wrap this if you want it.
|
|
147
|
-
const PlainLoading = observer(function PlainLoading({
|
|
133
|
+
const PlainLoading = observer(function PlainLoading({ visible, message, progress, canceled, onCancel, onRetry, }) {
|
|
148
134
|
if (!visible) {
|
|
149
135
|
return null;
|
|
150
136
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}, children: "Retry" })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: statusProgress === undefined
|
|
155
|
-
? `${statusMessage || 'Loading'}...`
|
|
156
|
-
: progressLabel(statusMessage || 'Loading', statusProgress) }), statusProgress === undefined ? null : (_jsx(ProgressBar, { fraction: statusProgress })), model.cancelFetchByUser ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-cancel", onClick: () => {
|
|
157
|
-
model.cancelFetchByUser?.();
|
|
158
|
-
}, children: "Cancel" })) : null] })) }) }));
|
|
137
|
+
return (_jsx("div", { style: overlayBox, "data-testid": canceled ? 'loading-overlay-canceled' : 'loading-overlay', children: _jsx("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", children: canceled ? (_jsxs(_Fragment, { children: [_jsx("span", { children: "Loading canceled" }), onRetry ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-retry", onClick: onRetry, children: "Retry" })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: progress === undefined
|
|
138
|
+
? `${message || 'Loading'}...`
|
|
139
|
+
: progressLabel(message || 'Loading', progress) }), progress === undefined ? null : (_jsx(ProgressBar, { fraction: progress })), onCancel ? (_jsx("button", { type: "button", style: button, "data-testid": "loading-overlay-cancel", onClick: onCancel, children: "Cancel" })) : null] })) }) }));
|
|
159
140
|
});
|
|
160
|
-
const PlainBackgroundProgress = observer(function PlainBackgroundProgress({
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
// row, so this is laid out there. Claiming `inset: 0` here would collapse
|
|
164
|
-
// against that box, and pinning to the corner would sit under the row.
|
|
165
|
-
return visible && model.statusMessage ? (_jsxs("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", "data-testid": "progress-chip", children: [_jsx("span", { children: progressLabel(model.statusMessage, model.statusProgress) }), model.statusProgress === undefined ? null : (_jsx(ProgressBar, { fraction: model.statusProgress }))] })) : null;
|
|
141
|
+
const PlainBackgroundProgress = observer(function PlainBackgroundProgress({ message, progress, }) {
|
|
142
|
+
// in-flow: the chrome anchors the corner it shares with the control row
|
|
143
|
+
return (_jsxs("div", { style: { ...chip, display: 'flex', alignItems: 'center' }, role: "status", "data-testid": "progress-chip", children: [_jsx("span", { children: progressLabel(message || 'Loading', progress) }), progress === undefined ? null : _jsx(ProgressBar, { fraction: progress })] }));
|
|
166
144
|
});
|
|
167
145
|
/**
|
|
168
146
|
* #api
|
|
@@ -172,8 +150,8 @@ const PlainBackgroundProgress = observer(function PlainBackgroundProgress({ mode
|
|
|
172
150
|
*
|
|
173
151
|
* `DisplayUIProvider` installs this by default, so mount that rather than
|
|
174
152
|
* naming this — reach for it directly only to wrap a state or to build a
|
|
175
|
-
* context value by hand.
|
|
176
|
-
*
|
|
153
|
+
* context value by hand. Colors come from `currentColor` and the CSS system
|
|
154
|
+
* colors, so the host's own cascade drives them in both light and dark.
|
|
177
155
|
*
|
|
178
156
|
* The `data-testid` values it renders are a contract four of JBrowse's test
|
|
179
157
|
* systems key on, so a replacement set that keeps them can be driven by those
|