@jbrowse/plugin-linear-genome-view 5.0.0-beta.7 → 5.0.0-beta.9
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/FeatureTrack/configSchema.d.ts +140 -48
- package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
- package/esm/LinearGenomeView/afterAttach.js +18 -24
- package/esm/LinearGenomeView/closeUps.d.ts +83 -0
- package/esm/LinearGenomeView/closeUps.js +211 -0
- package/esm/LinearGenomeView/components/AddCloseUpDialog.d.ts +19 -0
- package/esm/LinearGenomeView/components/AddCloseUpDialog.js +29 -0
- package/esm/LinearGenomeView/components/CloseUps.d.ts +5 -0
- package/esm/LinearGenomeView/components/CloseUps.js +75 -0
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +2 -1
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +6 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
- package/esm/LinearGenomeView/components/Highlight.js +2 -4
- package/esm/LinearGenomeView/components/HighlightBand.js +2 -2
- package/esm/LinearGenomeView/components/HighlightChip.d.ts +1 -2
- package/esm/LinearGenomeView/components/HighlightChip.js +5 -7
- package/esm/LinearGenomeView/components/ImportForm.js +2 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlight.js +1 -1
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.d.ts +7 -1
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +22 -7
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +6 -21
- package/esm/LinearGenomeView/components/SearchBox.js +2 -2
- package/esm/LinearGenomeView/components/TrackContainer.js +12 -5
- package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +3 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +3 -7
- package/esm/LinearGenomeView/components/TracksContainer.js +7 -15
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +1 -1
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +5 -0
- package/esm/LinearGenomeView/headerFit.d.ts +2 -1
- package/esm/LinearGenomeView/headerFit.js +7 -2
- package/esm/LinearGenomeView/index.d.ts +1 -1
- package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
- package/esm/LinearGenomeView/keyboardHandler.js +11 -0
- package/esm/LinearGenomeView/launchKeys.d.ts +1 -1
- package/esm/LinearGenomeView/launchKeys.js +15 -5
- package/esm/LinearGenomeView/lazyDialogs.d.ts +7 -0
- package/esm/LinearGenomeView/lazyDialogs.js +2 -0
- package/esm/LinearGenomeView/menuItems.d.ts +28 -16
- package/esm/LinearGenomeView/menuItems.js +213 -85
- package/esm/LinearGenomeView/model.d.ts +367 -199
- package/esm/LinearGenomeView/model.js +302 -143
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
- package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.d.ts +5 -0
- package/esm/LinearGenomeView/svgcomponents/SVGCloseUpFrame.js +8 -0
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +3 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +95 -24
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +4 -5
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +13 -4
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +52 -63
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +8 -6
- package/esm/LinearGenomeView/svgcomponents/util.js +37 -16
- package/esm/LinearGenomeView/types.d.ts +5 -4
- package/esm/LinearGenomeView/util.d.ts +20 -37
- package/esm/LinearGenomeView/util.js +58 -141
- package/esm/displayKitTests/perRegionTestEnv.d.ts +64 -45
- package/esm/displayKitTests/perRegionTestEnv.js +6 -6
- package/esm/index.d.ts +9 -67
- package/esm/index.js +6 -22
- package/esm/searchUtils.d.ts +2 -3
- package/esm/searchUtils.js +2 -2
- package/package.json +7 -7
- package/esm/lazyPluginExports.d.ts +0 -23
- package/esm/lazyPluginExports.js +0 -36
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { SubmitDialog } from '@jbrowse/core/ui';
|
|
4
|
+
import { assembleLocStrings } from '@jbrowse/core/util';
|
|
5
|
+
import { Checkbox, FormControlLabel, Typography } from '@mui/material';
|
|
6
|
+
import { observer } from 'mobx-react';
|
|
7
|
+
/**
|
|
8
|
+
* The one thing a close-up still needs deciding once a drag has named its
|
|
9
|
+
* span: whether it opens showing what this view shows, or empty for a track
|
|
10
|
+
* chosen afterwards.
|
|
11
|
+
*
|
|
12
|
+
* The span is the selection's, named here the way the same menu's "Copy range"
|
|
13
|
+
* names it, so the dialog says what it is about to open rather than describing
|
|
14
|
+
* the feature. The model's action takes the same two offsets, for a stack an
|
|
15
|
+
* agent or a spec builds outright.
|
|
16
|
+
*/
|
|
17
|
+
const AddCloseUpDialog = observer(function AddCloseUpDialog({ model, leftOffset, rightOffset, handleClose, }) {
|
|
18
|
+
const [copyTracks, setCopyTracks] = useState(true);
|
|
19
|
+
const rangeString = assembleLocStrings(model.getSelectedRegions(leftOffset, rightOffset));
|
|
20
|
+
return (_jsx(SubmitDialog, { open: true, maxWidth: "xs", fullWidth: true, title: "Add close-up view", onCancel: handleClose, submitText: "Add", onSubmit: () => {
|
|
21
|
+
model.addCloseUpForSpan(leftOffset, rightOffset, {
|
|
22
|
+
trackIds: copyTracks ? undefined : [],
|
|
23
|
+
});
|
|
24
|
+
handleClose();
|
|
25
|
+
}, children: _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 12 }, children: [_jsxs(Typography, { children: ["A closer view of ", rangeString, ", below the tracks. It stays centred on this view, which moves to the middle of that span."] }), _jsx(FormControlLabel, { label: "Copy this view's tracks", control: _jsx(Checkbox, { size: "small", checked: copyTracks, "data-testid": "close-up-copy-tracks", onChange: event => {
|
|
26
|
+
setCopyTracks(event.target.checked);
|
|
27
|
+
} }) })] }) }));
|
|
28
|
+
});
|
|
29
|
+
export default AddCloseUpDialog;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment } from 'react';
|
|
3
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
+
import { useResizeDrag } from '@jbrowse/core/util/useResizeDrag';
|
|
5
|
+
import { observer } from 'mobx-react';
|
|
6
|
+
import { CLOSE_UP_FRAME_WIDTH, closeUpColor, closeUpStackRows, } from '../closeUps.js';
|
|
7
|
+
import LinearGenomeView from './LinearGenomeView.js';
|
|
8
|
+
import OverviewScalebarPolygon from './OverviewScalebarPolygon.js';
|
|
9
|
+
const useStyles = makeStyles()(theme => ({
|
|
10
|
+
connector: {
|
|
11
|
+
position: 'relative',
|
|
12
|
+
},
|
|
13
|
+
polygon: {
|
|
14
|
+
display: 'block',
|
|
15
|
+
width: '100%',
|
|
16
|
+
height: '100%',
|
|
17
|
+
overflow: 'visible',
|
|
18
|
+
pointerEvents: 'none',
|
|
19
|
+
},
|
|
20
|
+
closeUp: {
|
|
21
|
+
position: 'relative',
|
|
22
|
+
'&::after': {
|
|
23
|
+
content: '""',
|
|
24
|
+
position: 'absolute',
|
|
25
|
+
inset: 0,
|
|
26
|
+
border: `${CLOSE_UP_FRAME_WIDTH}px solid ${closeUpColor(theme)}`,
|
|
27
|
+
pointerEvents: 'none',
|
|
28
|
+
zIndex: 1,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
// The whole band is the handle — a 4px bar under a 16px trapezoid is a
|
|
32
|
+
// smaller target than the shape it sizes — and `useResizeDrag` rather than
|
|
33
|
+
// `ResizeHandle` because that one's hover paints its whole surface, which
|
|
34
|
+
// here is the figure. A rule at the edge it drags says the same thing over
|
|
35
|
+
// the top of nothing.
|
|
36
|
+
grab: {
|
|
37
|
+
position: 'absolute',
|
|
38
|
+
inset: 0,
|
|
39
|
+
cursor: 'row-resize',
|
|
40
|
+
touchAction: 'none',
|
|
41
|
+
'&:hover': {
|
|
42
|
+
borderBottom: `2px solid ${theme.palette.action.disabled}`,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
}));
|
|
46
|
+
/**
|
|
47
|
+
* The trapezoid from the span `close-up` shows, as it sits in `context`, out to
|
|
48
|
+
* `close-up`'s full width: the header overview's "you are here", drawn between
|
|
49
|
+
* two rows of the stack instead. A close-up is scrolled, where the header
|
|
50
|
+
* overview never is, so its left edge is what its origin is shifted by.
|
|
51
|
+
*
|
|
52
|
+
* Its height belongs to the stack rather than to this pair, so a drag on any
|
|
53
|
+
* band moves all of them: the bands are a ladder the eye reads down, and one
|
|
54
|
+
* rung of its own height reads as a difference in the data. The height is worth
|
|
55
|
+
* dragging at all because the band is a picture of a ratio — a row ten times
|
|
56
|
+
* wider than the one under it narrows to a tenth of the width there — and how
|
|
57
|
+
* steep that reads is the figure's to decide, not ours.
|
|
58
|
+
*/
|
|
59
|
+
const CloseUpConnector = observer(function CloseUpConnector({ host, context, closeUp, }) {
|
|
60
|
+
const { classes } = useStyles();
|
|
61
|
+
const height = host.closeUpConnectorHeight;
|
|
62
|
+
const handleProps = useResizeDrag({
|
|
63
|
+
onDrag: delta => {
|
|
64
|
+
host.setCloseUpConnectorHeight(host.closeUpConnectorHeight + delta);
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
return context.initialized && closeUp.initialized ? (_jsxs("div", { className: classes.connector, style: { height }, children: [_jsx("svg", { className: classes.polygon, children: _jsx(OverviewScalebarPolygon, { model: closeUp, overview: context, overviewOffsetPx: -context.offsetPx, height: height, gradient: true }) }), _jsx("div", { ...handleProps, "data-testid": `close-up-connector-${closeUp.id}`, className: classes.grab })] })) : null;
|
|
68
|
+
});
|
|
69
|
+
const CloseUps = observer(function CloseUps({ model, }) {
|
|
70
|
+
const closeUps = model.closeUpViews;
|
|
71
|
+
const { classes } = useStyles();
|
|
72
|
+
const rows = closeUpStackRows(model, closeUps);
|
|
73
|
+
return rows.length ? (_jsx("div", { "data-testid": `close-ups-${model.id}`, children: rows.map(({ closeUp, context }) => (_jsxs(Fragment, { children: [_jsx(CloseUpConnector, { host: model, context: context, closeUp: closeUp }), _jsx("div", { className: classes.closeUp, children: _jsx(LinearGenomeView, { model: closeUp }) })] }, closeUp.id))) })) : null;
|
|
74
|
+
});
|
|
75
|
+
export default CloseUps;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { TrackLabelMode } from '../types.ts';
|
|
1
|
+
import type { TrackLabelMode, ViewTrackLabelMode } from '../types.ts';
|
|
2
2
|
import type { BaseExportSvgOptions } from '@jbrowse/core/ui';
|
|
3
3
|
export default function ExportSvgDialog({ model, handleClose, }: {
|
|
4
4
|
model: {
|
|
5
|
+
effectiveTrackLabels?: ViewTrackLabelMode;
|
|
5
6
|
exportSvg(opts: BaseExportSvgOptions & {
|
|
6
7
|
trackLabels: TrackLabelMode;
|
|
7
8
|
showGridlines: boolean;
|
|
@@ -4,11 +4,15 @@ import { MenuItem, TextField } from '@mui/material';
|
|
|
4
4
|
// Shared track-label + gridlines export dialog. Used by LGV, linear-synteny and
|
|
5
5
|
// breakpoint-split views (their lazyDialogs re-export this).
|
|
6
6
|
export default function ExportSvgDialog({ model, handleClose, }) {
|
|
7
|
-
|
|
7
|
+
// The view's own setting is already an export mode, so a user who has not
|
|
8
|
+
// picked one here gets the figure their screen shows. The key is `-mode`
|
|
9
|
+
// because the modes were once spelled `overlay`/`none`, and a stored one of
|
|
10
|
+
// those would select nothing in the dropdown.
|
|
11
|
+
const [trackLabels, setTrackLabels] = useExportSvgPreference('tracklabels-mode', model.effectiveTrackLabels ?? 'offset');
|
|
8
12
|
const [showGridlines, setShowGridlines] = useExportSvgPreference('gridlines', false);
|
|
9
13
|
return (_jsx(BaseExportSvgDialog, { model: model, handleClose: handleClose, exportSvg: opts => model.exportSvg({ ...opts, trackLabels, showGridlines }), checkboxes: _jsx(LabeledCheckbox, { checked: showGridlines, onChange: val => {
|
|
10
14
|
setShowGridlines(val);
|
|
11
15
|
}, label: "Show gridlines" }), children: _jsxs(TextField, { select: true, label: "Track label positioning", variant: "outlined", style: { minWidth: 200 }, value: trackLabels, onChange: event => {
|
|
12
16
|
setTrackLabels(event.target.value);
|
|
13
|
-
}, children: [_jsx(MenuItem, { value: "offset", children: "Offset" }), _jsx(MenuItem, { value: "
|
|
17
|
+
}, children: [_jsx(MenuItem, { value: "offset", children: "Offset" }), _jsx(MenuItem, { value: "overlapping", children: "Overlapping" }), _jsx(MenuItem, { value: "left", children: "Left" }), _jsx(MenuItem, { value: "hidden", children: "Hidden" })] }) }));
|
|
14
18
|
}
|
|
@@ -32,12 +32,12 @@ const GetSequenceDialog = observer(function GetSequenceDialog({ model, regions,
|
|
|
32
32
|
// a selection this dialog will happily take is a whole chromosome of
|
|
33
33
|
// sequence, so both handles are forwarded: closing the dialog stops the
|
|
34
34
|
// read rather than leaving the worker on it, and the wait is named
|
|
35
|
-
async (_name, _locs,
|
|
35
|
+
async (_name, _locs, signal, statusCallback) => {
|
|
36
36
|
if (regions.length === 0) {
|
|
37
37
|
throw new Error('Selected region is out of bounds');
|
|
38
38
|
}
|
|
39
39
|
const chunks = await fetchSequence(model, regions, {
|
|
40
|
-
|
|
40
|
+
signal,
|
|
41
41
|
statusCallback,
|
|
42
42
|
});
|
|
43
43
|
// validate here (in the async path) so a length mismatch surfaces via the
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
2
3
|
import ScrollZoomToggle from '@jbrowse/core/ui/ScrollZoomToggle';
|
|
4
|
+
import { getSession } from '@jbrowse/core/util';
|
|
3
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
6
|
import useMeasure from '@jbrowse/core/util/useMeasure';
|
|
7
|
+
import MenuIcon from '@mui/icons-material/Menu';
|
|
5
8
|
import { observer } from 'mobx-react';
|
|
6
9
|
import { HEADER_BAR_HEIGHT } from '../consts.js';
|
|
7
10
|
import { headerFit } from '../headerFit.js';
|
|
@@ -35,12 +38,14 @@ const Controls = observer(function Controls({ model, }) {
|
|
|
35
38
|
// row
|
|
36
39
|
const [ref, { width }] = useMeasure('width');
|
|
37
40
|
const highlighted = highlightedDisplays(model);
|
|
41
|
+
const viewMenu = getSession(model).viewTitleBars === false;
|
|
38
42
|
const fit = headerFit({
|
|
39
43
|
width,
|
|
40
44
|
searchBoxPx: searchBoxWidth(model.coarseVisibleLocStrings),
|
|
41
45
|
clearHighlight: highlighted.length > 0,
|
|
46
|
+
viewMenu,
|
|
42
47
|
});
|
|
43
|
-
return (_jsxs("div", { className: classes.headerBar, ref: ref, children: [_jsx(HeaderTrackSelectorButton, { model: model, indent: fit.trackSelectorIndent }), _jsx(ScrollZoomToggle, { model: model, iconOnly: !fit.scrollZoomLabel }), _jsx("div", { className: classes.spacer }), _jsx(HeaderPanControls, { model: model, compact: !fit.panButtonSpacing }), _jsx(SearchBox, { model: model }), _jsx(HeaderClearHighlightButton, { highlighted: highlighted }), fit.regionWidth ? _jsx(HeaderRegionWidth, { model: model }) : null, _jsx(HeaderZoomControls, { model: model, showSlider: fit.zoomSlider }), _jsx("div", { className: classes.spacer })] }));
|
|
48
|
+
return (_jsxs("div", { className: classes.headerBar, ref: ref, children: [viewMenu ? (_jsx(CascadingMenuButton, { menuItems: () => model.menuItems(), "data-testid": "view_menu_icon", children: _jsx(MenuIcon, {}) })) : null, _jsx(HeaderTrackSelectorButton, { model: model, indent: fit.trackSelectorIndent }), _jsx(ScrollZoomToggle, { model: model, iconOnly: !fit.scrollZoomLabel }), _jsx("div", { className: classes.spacer }), _jsx(HeaderPanControls, { model: model, compact: !fit.panButtonSpacing }), _jsx(SearchBox, { model: model }), _jsx(HeaderClearHighlightButton, { highlighted: highlighted }), fit.regionWidth ? _jsx(HeaderRegionWidth, { model: model }) : null, _jsx(HeaderZoomControls, { model: model, showSlider: fit.zoomSlider }), _jsx("div", { className: classes.spacer })] }));
|
|
44
49
|
});
|
|
45
50
|
const LinearGenomeViewHeader = observer(function LinearGenomeViewHeader({ model, }) {
|
|
46
51
|
const { hideHeader, hideHeaderOverview } = model;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useState } from 'react';
|
|
3
3
|
import { SingleSlider } from '@jbrowse/core/ui';
|
|
4
4
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
5
|
-
import { getBpDisplayStr
|
|
5
|
+
import { getBpDisplayStr } from '@jbrowse/core/util';
|
|
6
6
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
7
7
|
import MoreVert from '@mui/icons-material/MoreVert';
|
|
8
8
|
import { observer } from 'mobx-react';
|
|
9
|
-
import {
|
|
9
|
+
import { zoomMenuItems } from '../menuItems.js';
|
|
10
10
|
import ZoomButton from './ZoomButton.js';
|
|
11
|
-
const RegionWidthEditorDialog = lazy(() => import('./RegionWidthEditorDialog.js'));
|
|
12
11
|
const useStyles = makeStyles()(theme => ({
|
|
13
12
|
container: {
|
|
14
13
|
display: 'flex',
|
|
@@ -29,35 +28,6 @@ const useStyles = makeStyles()(theme => ({
|
|
|
29
28
|
},
|
|
30
29
|
},
|
|
31
30
|
}));
|
|
32
|
-
function getZoomMenuItems(model) {
|
|
33
|
-
return [
|
|
34
|
-
...[10, 50, 100].map(r => ({
|
|
35
|
-
label: `Zoom in ${r}x`,
|
|
36
|
-
onClick: () => {
|
|
37
|
-
model.zoom(model.bpPerPx / r);
|
|
38
|
-
},
|
|
39
|
-
})),
|
|
40
|
-
...[10, 50, 100].map(r => ({
|
|
41
|
-
label: `Zoom out ${r}x`,
|
|
42
|
-
onClick: () => {
|
|
43
|
-
model.zoom(model.bpPerPx * r);
|
|
44
|
-
},
|
|
45
|
-
})),
|
|
46
|
-
showAllRegionsMenuItem(model),
|
|
47
|
-
{
|
|
48
|
-
label: 'Custom zoom',
|
|
49
|
-
onClick: () => {
|
|
50
|
-
getDialogHost(model).queueDialog(handleClose => [
|
|
51
|
-
RegionWidthEditorDialog,
|
|
52
|
-
{
|
|
53
|
-
model,
|
|
54
|
-
handleClose,
|
|
55
|
-
},
|
|
56
|
-
]);
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
];
|
|
60
|
-
}
|
|
61
31
|
// The slider tracks live `bpPerPx`, which changes on every animation frame of a
|
|
62
32
|
// zoom. Isolated into its own observer so the surrounding zoom buttons/menu —
|
|
63
33
|
// whose MUI Tooltip/IconButton machinery is comparatively expensive and only
|
|
@@ -82,6 +52,6 @@ const ZoomSlider = observer(function ZoomSlider({ model, }) {
|
|
|
82
52
|
});
|
|
83
53
|
const HeaderZoomControls = observer(function HeaderZoomControls({ model, showSlider = true, }) {
|
|
84
54
|
const { classes } = useStyles();
|
|
85
|
-
return (_jsxs("div", { className: classes.container, children: [_jsx(ZoomButton, { model: model, direction: "out" }), showSlider ? _jsx(ZoomSlider, { model: model }) : null, _jsx(ZoomButton, { model: model, direction: "in" }), _jsx(CascadingMenuButton, { menuItems: () =>
|
|
55
|
+
return (_jsxs("div", { className: classes.container, children: [_jsx(ZoomButton, { model: model, direction: "out" }), showSlider ? _jsx(ZoomSlider, { model: model }) : null, _jsx(ZoomButton, { model: model, direction: "in" }), _jsx(CascadingMenuButton, { menuItems: () => zoomMenuItems(model), children: _jsx(MoreVert, {}) })] }));
|
|
86
56
|
});
|
|
87
57
|
export default HeaderZoomControls;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useHighlightChip } from '@jbrowse/core/ui/highlightChipReveal';
|
|
3
2
|
import { getSession } from '@jbrowse/core/util';
|
|
4
3
|
import CloseIcon from '@mui/icons-material/Close';
|
|
5
4
|
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
|
@@ -16,13 +15,12 @@ const Highlight = observer(function Highlight({ model, highlight, }) {
|
|
|
16
15
|
const coords = model.getHighlightCoords(highlight);
|
|
17
16
|
const bandColor = getHighlightColor(highlight, theme);
|
|
18
17
|
const label = model.labelsVisible ? highlight.label : undefined;
|
|
19
|
-
|
|
20
|
-
return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, children: chipVisible && coords.width >= CHIP_MIN_WIDTH ? (_jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', setOpen: setMenuOpen, menuItems: [
|
|
18
|
+
return coords ? (_jsx(HighlightBand, { coords: coords, background: bandColor.toRgbString(), label: label, children: coords.width >= CHIP_MIN_WIDTH ? (_jsx(HighlightChip, { color: bandColor, label: label, tooltip: highlight.label ?? 'Highlighted region', menuItems: [
|
|
21
19
|
{
|
|
22
20
|
label: 'Dismiss highlight',
|
|
23
21
|
icon: CloseIcon,
|
|
24
22
|
onClick: () => {
|
|
25
|
-
model.removeHighlight(highlight);
|
|
23
|
+
getSession(model).removeHighlight(highlight);
|
|
26
24
|
},
|
|
27
25
|
},
|
|
28
26
|
{
|
|
@@ -24,8 +24,8 @@ const useStyles = makeStyles()(theme => ({
|
|
|
24
24
|
color: theme.palette.text.primary,
|
|
25
25
|
},
|
|
26
26
|
}));
|
|
27
|
-
//
|
|
28
|
-
//
|
|
27
|
+
// OverviewHighlight stays separate (different bpToPx API). A non-interactive
|
|
28
|
+
// colored band with an
|
|
29
29
|
// optional top label; when `children` (the highlight chip) is passed it renders
|
|
30
30
|
// that instead of the plain label. Visibility is toggled from the view menu
|
|
31
31
|
export default function HighlightBand({ coords, background, label, children, }) {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { MenuItem } from '@jbrowse/core/ui';
|
|
2
2
|
import type { Colord } from '@jbrowse/core/util/colord';
|
|
3
|
-
export default function HighlightChip({ color, label, tooltip, menuItems,
|
|
3
|
+
export default function HighlightChip({ color, label, tooltip, menuItems, }: {
|
|
4
4
|
color: Colord;
|
|
5
5
|
label?: string;
|
|
6
6
|
tooltip?: string;
|
|
7
7
|
menuItems: MenuItem[];
|
|
8
|
-
setOpen?: (arg: boolean) => void;
|
|
9
8
|
}): import("react").JSX.Element;
|
|
@@ -2,14 +2,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
3
3
|
import LinkIcon from '@mui/icons-material/Link';
|
|
4
4
|
import { Box, Tooltip, Typography } from '@mui/material';
|
|
5
|
-
// Interactive chip drawn inside a highlight band
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
// chip drawn while the menu is up.
|
|
10
|
-
export default function HighlightChip({ color, label, tooltip, menuItems, setOpen, }) {
|
|
5
|
+
// Interactive chip drawn inside a highlight band: a link icon tinted to the
|
|
6
|
+
// band color plus an optional inline label, wrapped in a context menu. Sets its
|
|
7
|
+
// own pointer-events since the band is click-through.
|
|
8
|
+
export default function HighlightChip({ color, label, tooltip, menuItems, }) {
|
|
11
9
|
// hide the icon color when the band is fully transparent ("label-only"
|
|
12
10
|
// highlight); otherwise bump to 0.8 alpha so the chip stays legible
|
|
13
11
|
const chipAlpha = color.alpha() === 0 ? 0 : 0.8;
|
|
14
|
-
return (_jsx(Box, { sx: { pointerEvents: 'auto' }, children: _jsx(CascadingMenuButton, { "data-testid": "highlight-chip", menuItems: menuItems,
|
|
12
|
+
return (_jsx(Box, { sx: { pointerEvents: 'auto' }, children: _jsx(CascadingMenuButton, { "data-testid": "highlight-chip", menuItems: menuItems, children: _jsx(Tooltip, { title: tooltip, arrow: true, children: _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(LinkIcon, { fontSize: "small", sx: { color: color.alpha(chipAlpha).toRgbString() } }), label ? (_jsx(Typography, { variant: "caption", noWrap: true, children: label })) : null] }) }) }) }));
|
|
15
13
|
}
|
|
@@ -84,12 +84,12 @@ const LinearGenomeViewImportForm = observer(function LinearGenomeViewImportForm(
|
|
|
84
84
|
// reason.
|
|
85
85
|
model.setError(undefined);
|
|
86
86
|
setSelectedAssemblyName(val);
|
|
87
|
-
}, session: session, selected: selectedAsm }), selectedAsm ? (assemblyError ? (_jsx(CloseIcon, { color: "error" })) : regions ? (_jsx(RefNameAutocomplete, { fetchResults: (queryString,
|
|
87
|
+
}, session: session, selected: selectedAsm }), selectedAsm ? (assemblyError ? (_jsx(CloseIcon, { color: "error" })) : regions ? (_jsx(RefNameAutocomplete, { fetchResults: (queryString, signal) => fetchResults({
|
|
88
88
|
queryString,
|
|
89
89
|
assembly,
|
|
90
90
|
textSearchManager,
|
|
91
91
|
assemblyName: selectedAsm,
|
|
92
|
-
|
|
92
|
+
signal,
|
|
93
93
|
}), session: session, assemblyName: selectedAsm, value: value, minWidth: 270, adornmentWidth: adornmentReservePx({
|
|
94
94
|
menuItemCount: recentMenuItems.length,
|
|
95
95
|
}), onChange: v => {
|
|
@@ -6,9 +6,9 @@ import LinearGenomeViewContainer from './LinearGenomeViewContainer.js';
|
|
|
6
6
|
// lazies
|
|
7
7
|
const ImportForm = lazy(() => import('./ImportForm.js'));
|
|
8
8
|
const LinearGenomeView = observer(function LinearGenomeView({ model, }) {
|
|
9
|
-
const {
|
|
10
|
-
if (
|
|
11
|
-
return
|
|
9
|
+
const { loading, showImportForm } = model;
|
|
10
|
+
if (loading) {
|
|
11
|
+
return _jsx(ViewLoadingScreen, { ...loading });
|
|
12
12
|
}
|
|
13
13
|
else if (showImportForm) {
|
|
14
14
|
return _jsx(ImportForm, { model: model });
|
|
@@ -9,6 +9,7 @@ import Paper from '@mui/material/Paper';
|
|
|
9
9
|
import { observer } from 'mobx-react';
|
|
10
10
|
import { SCALE_BAR_HEIGHT } from '../consts.js';
|
|
11
11
|
import { stickyChromeTops } from '../stickyChrome.js';
|
|
12
|
+
import CloseUps from './CloseUps.js';
|
|
12
13
|
import Header from './Header.js';
|
|
13
14
|
import MiniControls from './MiniControls.js';
|
|
14
15
|
import NavigationAnnouncer from './NavigationAnnouncer.js';
|
|
@@ -105,7 +106,7 @@ const LinearGenomeViewContainer = observer(function LinearGenomeViewContainer({
|
|
|
105
106
|
.map(d => d.hoveredFeature)
|
|
106
107
|
.find(Boolean);
|
|
107
108
|
session.setHovered({ hoverPosition, hoverFeature });
|
|
108
|
-
}, children: [_jsx(NavigationAnnouncer, { model: model }), _jsxs("div", { ref: headerRef, className: classes.header, style: { position: stickyViewHeaders ? 'sticky' : undefined }, children: [_jsx(Header, { model: model }), hideHeader ? _jsx(MiniControls, { model: model }) : null] }), _jsx("div", { ref: tracksRef, children: model.scalebarOnly ? (_jsx(Rubberband, { model: model, ControlComponent: _jsx(Scalebar, { model: model, style: { height: SCALE_BAR_HEIGHT, boxSizing: 'border-box' } }) })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(Suspense, { fallback: null, children: _jsx(NoTracksActiveButton, { model: model }) })) : (_jsxs(_Fragment, { children: [pinnedTracks.length ? (_jsx(Paper, { elevation: 6, className: classes.pinnedTracks, style: {
|
|
109
|
+
}, children: [_jsx(NavigationAnnouncer, { model: model }), _jsxs("div", { ref: headerRef, "data-testid": `lgv-header-${model.id}`, className: classes.header, style: { position: stickyViewHeaders ? 'sticky' : undefined }, children: [_jsx(Header, { model: model }), hideHeader ? _jsx(MiniControls, { model: model }) : null] }), _jsx("div", { ref: tracksRef, children: model.scalebarOnly ? (_jsx(Rubberband, { model: model, ControlComponent: _jsx(Scalebar, { model: model, style: { height: SCALE_BAR_HEIGHT, boxSizing: 'border-box' } }) })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(Suspense, { fallback: null, children: _jsx(NoTracksActiveButton, { model: model }) })) : (_jsxs(_Fragment, { children: [pinnedTracks.length ? (_jsx(Paper, { elevation: 6, className: classes.pinnedTracks, style: {
|
|
109
110
|
top: stickyTops.pinnedTracks,
|
|
110
111
|
// the scroll port this sticks in, not the window: it is
|
|
111
112
|
// under the app bar in the classic stack, a dockview
|
|
@@ -113,6 +114,6 @@ const LinearGenomeViewContainer = observer(function LinearGenomeViewContainer({
|
|
|
113
114
|
// The fallback is what an unbounded embed gets, where
|
|
114
115
|
// the page itself is the scroller
|
|
115
116
|
maxHeight: `calc(var(${SCROLL_PORT_HEIGHT_VAR}, 100vh) - ${stickyTops.pinnedTracks})`,
|
|
116
|
-
}, children: pinnedTracks.map(track => (_jsx(TrackContainer, { model: model, track: track }, track.id))) })) : null, unpinnedTracks.map(track => (_jsx(TrackContainer, { model: model, track: track }, track.id)))] })) })) })] }) }));
|
|
117
|
+
}, children: pinnedTracks.map(track => (_jsx(TrackContainer, { model: model, track: track }, track.id))) })) : null, unpinnedTracks.map(track => (_jsx(TrackContainer, { model: model, track: track }, track.id)))] })) })) }), _jsx(CloseUps, { model: model })] }) }));
|
|
117
118
|
});
|
|
118
119
|
export default LinearGenomeViewContainer;
|
|
@@ -11,7 +11,7 @@ const OverviewHighlight = observer(function OverviewHighlight({ model, }) {
|
|
|
11
11
|
// gate on highlightsVisible to match the main-view band, scalebar band, and
|
|
12
12
|
// SVG export — otherwise "Turn off highlights" leaves the overview bands up
|
|
13
13
|
return getSession(model).highlightsVisible
|
|
14
|
-
? model.
|
|
14
|
+
? model.highlights
|
|
15
15
|
.map(highlight => {
|
|
16
16
|
const coords = model.getOverviewHighlightCoords(highlight);
|
|
17
17
|
return coords ? { coords, highlight } : undefined;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
3
|
import { Tooltip } from '@mui/material';
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// The 1px border keeps very narrow bands visible.
|
|
4
|
+
// A single absolute-positioned band over the overview scalebar. The 1px border
|
|
5
|
+
// keeps very narrow bands visible.
|
|
7
6
|
const useStyles = makeStyles()({
|
|
8
7
|
highlight: {
|
|
9
8
|
height: '100%',
|
|
@@ -12,11 +12,17 @@ import type { ViewLayout } from '@jbrowse/core/util/Base1DUtils';
|
|
|
12
12
|
* @param exportSvg - serialize explicit fill/stroke attributes (the split the
|
|
13
13
|
* `getFill/StrokeProps` helpers exist for) instead of the on-screen CSS class,
|
|
14
14
|
* which wouldn't survive into a standalone exported SVG.
|
|
15
|
+
* @param gradient - a close-up's connector: a fill fading from the narrow edge
|
|
16
|
+
* to the wide one, outlined in the colour of the frame it opens into, so the
|
|
17
|
+
* close-up below reads as an inset of the row above rather than a second view
|
|
18
|
+
* at the same zoom.
|
|
15
19
|
*/
|
|
16
|
-
declare const OverviewScalebarPolygon: ({ model, overview, overviewOffsetPx, exportSvg, }: {
|
|
20
|
+
declare const OverviewScalebarPolygon: ({ model, overview, overviewOffsetPx, height, exportSvg, gradient, }: {
|
|
17
21
|
model: LinearGenomeViewModel;
|
|
18
22
|
overview: ViewLayout;
|
|
19
23
|
overviewOffsetPx?: number;
|
|
24
|
+
height?: number;
|
|
20
25
|
exportSvg?: boolean;
|
|
26
|
+
gradient?: boolean;
|
|
21
27
|
}) => import("react").JSX.Element | null;
|
|
22
28
|
export default OverviewScalebarPolygon;
|
|
@@ -1,9 +1,11 @@
|
|
|
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
|
+
import { svgNodeId } from '@jbrowse/core/svg/svgId';
|
|
2
3
|
import { alpha } from '@jbrowse/core/ui/palette';
|
|
3
|
-
import { getFillProps, getStrokeProps } from '@jbrowse/core/util';
|
|
4
|
+
import { getFillProps, getStrokeProps, stripAlpha } from '@jbrowse/core/util';
|
|
4
5
|
import { regionBlocksPxExtent, transformPxSpan, } from '@jbrowse/core/util/Base1DUtils';
|
|
5
6
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
7
|
import { observer } from 'mobx-react';
|
|
8
|
+
import { CLOSE_UP_FRAME_WIDTH, closeUpColor } from '../closeUps.js';
|
|
7
9
|
import { HEADER_BAR_HEIGHT } from '../consts.js';
|
|
8
10
|
/**
|
|
9
11
|
* SVG points for a trapezoid with horizontal top and bottom edges, wound as a
|
|
@@ -24,10 +26,14 @@ function trapezoidPoints(top, bottom, height) {
|
|
|
24
26
|
// class computed once per theme (makeStyles memoizes) — an on-screen zoom frame
|
|
25
27
|
// then does zero color parsing. SVG export can't carry a CSS class, so that path
|
|
26
28
|
// serializes explicit fill/stroke attributes instead (see `exportSvg` below).
|
|
29
|
+
const FILL_OPACITY = 0.3;
|
|
30
|
+
const STROKE_OPACITY = 0.8;
|
|
31
|
+
const CLOSE_UP_FILL_TOP = 0.6;
|
|
32
|
+
const CLOSE_UP_FILL_BOTTOM = 0.2;
|
|
27
33
|
const useStyles = makeStyles()(theme => ({
|
|
28
34
|
polygon: {
|
|
29
|
-
fill: alpha(theme.palette.tertiary.light,
|
|
30
|
-
stroke: alpha(theme.palette.tertiary.light,
|
|
35
|
+
fill: alpha(theme.palette.tertiary.light, FILL_OPACITY),
|
|
36
|
+
stroke: alpha(theme.palette.tertiary.light, STROKE_OPACITY),
|
|
31
37
|
},
|
|
32
38
|
}));
|
|
33
39
|
/**
|
|
@@ -42,8 +48,12 @@ const useStyles = makeStyles()(theme => ({
|
|
|
42
48
|
* @param exportSvg - serialize explicit fill/stroke attributes (the split the
|
|
43
49
|
* `getFill/StrokeProps` helpers exist for) instead of the on-screen CSS class,
|
|
44
50
|
* which wouldn't survive into a standalone exported SVG.
|
|
51
|
+
* @param gradient - a close-up's connector: a fill fading from the narrow edge
|
|
52
|
+
* to the wide one, outlined in the colour of the frame it opens into, so the
|
|
53
|
+
* close-up below reads as an inset of the row above rather than a second view
|
|
54
|
+
* at the same zoom.
|
|
45
55
|
*/
|
|
46
|
-
const OverviewScalebarPolygon = observer(function OverviewScalebarPolygon({ model, overview, overviewOffsetPx = 0, exportSvg = false, }) {
|
|
56
|
+
const OverviewScalebarPolygon = observer(function OverviewScalebarPolygon({ model, overview, overviewOffsetPx = 0, height = HEADER_BAR_HEIGHT, exportSvg = false, gradient = false, }) {
|
|
47
57
|
const { classes, theme } = useStyles();
|
|
48
58
|
const { offsetPx, bpPerPx, dynamicBlocks } = model;
|
|
49
59
|
const extent = regionBlocksPxExtent(dynamicBlocks.blocks);
|
|
@@ -56,7 +66,12 @@ const OverviewScalebarPolygon = observer(function OverviewScalebarPolygon({ mode
|
|
|
56
66
|
// the scroll offset
|
|
57
67
|
const top = transformPxSpan(extent, bpPerPx / overview.bpPerPx, overviewOffsetPx);
|
|
58
68
|
const bottom = transformPxSpan(extent, 1, -offsetPx);
|
|
59
|
-
const points = trapezoidPoints(top, bottom,
|
|
60
|
-
|
|
69
|
+
const points = trapezoidPoints(top, bottom, height);
|
|
70
|
+
if (gradient) {
|
|
71
|
+
const id = `close-up-connector-${svgNodeId(model)}`;
|
|
72
|
+
const color = stripAlpha(theme.palette.tertiary.light);
|
|
73
|
+
return (_jsxs(_Fragment, { children: [_jsx("defs", { children: _jsxs("linearGradient", { id: id, x1: "0", y1: "0", x2: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: color, stopOpacity: CLOSE_UP_FILL_TOP }), _jsx("stop", { offset: "100%", stopColor: color, stopOpacity: CLOSE_UP_FILL_BOTTOM })] }) }), _jsx("polygon", { points: points, fill: `url(#${id})`, stroke: closeUpColor(theme), strokeWidth: CLOSE_UP_FRAME_WIDTH, strokeLinejoin: "round" })] }));
|
|
74
|
+
}
|
|
75
|
+
return exportSvg ? (_jsx("polygon", { points: points, ...getFillProps(alpha(theme.palette.tertiary.light, FILL_OPACITY)), ...getStrokeProps(alpha(theme.palette.tertiary.light, STROKE_OPACITY)) })) : (_jsx("polygon", { points: points, className: classes.polygon }));
|
|
61
76
|
});
|
|
62
77
|
export default OverviewScalebarPolygon;
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { getFillProps } from '@jbrowse/core/util';
|
|
3
3
|
// One full-height SVG highlight band with an optional label at the top,
|
|
4
|
-
// mirroring the on-screen HighlightBand div.
|
|
5
|
-
//
|
|
6
|
-
// label is hidden on bands too narrow to fit it.
|
|
4
|
+
// mirroring the on-screen HighlightBand div. The label is hidden on bands too
|
|
5
|
+
// narrow to fit it.
|
|
7
6
|
export default function SVGHighlightBand({ coords, height, color, label, labelColor, }) {
|
|
8
7
|
return (_jsxs(_Fragment, { children: [_jsx("rect", { x: coords.left, y: 0, width: coords.width, height: height, ...getFillProps(color) }), label && coords.width > 3 ? (_jsx("text", { x: coords.left + 3, y: 2, fontSize: 11, dominantBaseline: "hanging", ...getFillProps(labelColor), children: label })) : null] }));
|
|
9
8
|
}
|
|
@@ -31,34 +31,24 @@ const useStyles = makeStyles()(theme => ({
|
|
|
31
31
|
overflow: 'clip',
|
|
32
32
|
whiteSpace: 'nowrap',
|
|
33
33
|
},
|
|
34
|
-
//
|
|
35
|
-
// caption, and wearing the pointer and the hover tint made it look like the
|
|
36
|
-
// one thing on the row that does nothing when clicked
|
|
34
|
+
// refName labels only: the caption chip opens no menu
|
|
37
35
|
clickable: {
|
|
38
36
|
cursor: 'pointer',
|
|
39
37
|
'&:hover': {
|
|
40
|
-
// action.hover is a mode-aware translucent overlay; the old hardcoded
|
|
41
|
-
// grey[300] stayed light in dark mode, washing out the light label text
|
|
42
38
|
background: theme.palette.action.hover,
|
|
43
39
|
},
|
|
44
40
|
},
|
|
45
|
-
|
|
41
|
+
caption: {
|
|
46
42
|
zIndex: 100,
|
|
47
43
|
},
|
|
48
44
|
}));
|
|
49
45
|
const ScalebarRefNameLabels = observer(function ScalebarRefNameLabels({ model, }) {
|
|
50
46
|
const { classes, cx } = useStyles();
|
|
51
47
|
const [menuState, setMenuState] = useState();
|
|
52
|
-
// `model.scalebarRefNameLabels`, not a getScalebarRefNameLabels call of its
|
|
53
|
-
// own: a host drawing its own region names reads the same getter, so the
|
|
54
|
-
// sticky/dedup/fit rules can't be one thing here and another there. The SVG
|
|
55
|
-
// export still calls the helper directly, deliberately and with no prefix.
|
|
56
48
|
const { labels, caption } = model.scalebarRefNameLabels;
|
|
57
49
|
return (_jsxs(_Fragment, { children: [labels.map((label, i) => (_jsx(RefLabel
|
|
58
50
|
// eslint-disable-next-line @eslint-react/no-array-index-key -- position IS the identity here; keying by the run makes the list churn on zoom
|
|
59
|
-
, { model: model, label: label, onOpenMenu:
|
|
60
|
-
setMenuState(state);
|
|
61
|
-
} }, i))), caption === undefined ? null : (_jsx("span", { className: cx(classes.prefixLabel, classes.refLabel), "data-testid": "refLabel-prefix", children: caption })), menuState ? (_jsx(RefNameMenu, { model: model, menuState: menuState, onClose: () => {
|
|
51
|
+
, { model: model, label: label, onOpenMenu: setMenuState }, i))), caption === undefined ? null : (_jsx("span", { className: cx(classes.caption, classes.refLabel), "data-testid": "refLabel-prefix", children: caption })), menuState ? (_jsx(RefNameMenu, { model: model, menuState: menuState, onClose: () => {
|
|
62
52
|
model.setIsScalebarRefNameMenuOpen(false);
|
|
63
53
|
setMenuState(undefined);
|
|
64
54
|
} })) : null] }));
|
|
@@ -69,7 +59,7 @@ const ScalebarRefNameLabels = observer(function ScalebarRefNameLabels({ model, }
|
|
|
69
59
|
// this label's own onClick instead of opening the range menu.
|
|
70
60
|
function RefLabel({ model, label, onOpenMenu, }) {
|
|
71
61
|
const { classes, cx } = useStyles();
|
|
72
|
-
const { refName,
|
|
62
|
+
const { refName, transform, maxWidth, paddingLeft, text } = label;
|
|
73
63
|
return (_jsx("span", { className: cx(classes.refLabel, classes.clickable), style: {
|
|
74
64
|
transform: `translateX(${transform}px)`,
|
|
75
65
|
paddingLeft,
|
|
@@ -79,17 +69,12 @@ function RefLabel({ model, label, onOpenMenu, }) {
|
|
|
79
69
|
}, onClick: e => {
|
|
80
70
|
model.setScalebarRefNameClickPending(false);
|
|
81
71
|
model.setIsScalebarRefNameMenuOpen(true);
|
|
82
|
-
onOpenMenu({
|
|
83
|
-
anchorEl: e.currentTarget,
|
|
84
|
-
refName,
|
|
85
|
-
displayedRegionIndex,
|
|
86
|
-
lastDisplayedRegionIndex,
|
|
87
|
-
});
|
|
72
|
+
onOpenMenu({ anchorEl: e.currentTarget, label });
|
|
88
73
|
}, children: text }));
|
|
89
74
|
}
|
|
90
75
|
const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }) {
|
|
91
76
|
const { displayedRegions } = model;
|
|
92
|
-
const { refName, displayedRegionIndex: idx, lastDisplayedRegionIndex: lastIdx, } = menuState;
|
|
77
|
+
const { refName, displayedRegionIndex: idx, lastDisplayedRegionIndex: lastIdx, } = menuState.label;
|
|
93
78
|
const numRegions = displayedRegions.length;
|
|
94
79
|
const labeled = displayedRegions.slice(idx, lastIdx + 1);
|
|
95
80
|
// A label naming several regions is the collapsed-intron case: adjacent
|
|
@@ -56,12 +56,12 @@ const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth
|
|
|
56
56
|
});
|
|
57
57
|
return (_jsx(RefNameAutocomplete, { onSelect: option => {
|
|
58
58
|
navigate(option).catch(() => { });
|
|
59
|
-
}, assemblyName: assemblyName, fetchResults: (queryString,
|
|
59
|
+
}, assemblyName: assemblyName, fetchResults: (queryString, signal) => fetchResults({
|
|
60
60
|
queryString,
|
|
61
61
|
assemblyName,
|
|
62
62
|
textSearchManager,
|
|
63
63
|
assembly,
|
|
64
|
-
|
|
64
|
+
signal,
|
|
65
65
|
}), session: session, value: model.coarseVisibleLocStrings, minWidth: minWidth, maxWidth: maxWidth, adornmentWidth: adornmentReservePx({
|
|
66
66
|
showHelp,
|
|
67
67
|
menuItemCount: recentMenuItems.length,
|
|
@@ -107,7 +107,8 @@ function useTrackLabelBand(model, trackId) {
|
|
|
107
107
|
const TrackContainer = observer(function TrackContainer({ model, track, }) {
|
|
108
108
|
const { classes } = useStyles();
|
|
109
109
|
// an LGV track always holds at least one linear display (activeDisplay =
|
|
110
|
-
// displays[0])
|
|
110
|
+
// displays[0]). The one narrowing to the linear shape, handed down rather
|
|
111
|
+
// than repeated, so a track's chrome and its body can't read two displays.
|
|
111
112
|
const display = track.activeDisplay;
|
|
112
113
|
const { showTrackOutlines } = model;
|
|
113
114
|
// The Paper's 1px border, present iff outlines are on. Everything laid out in
|
|
@@ -117,13 +118,19 @@ const TrackContainer = observer(function TrackContainer({ model, track, }) {
|
|
|
117
118
|
// the data. One binding rather than three copies of the ternary, because the
|
|
118
119
|
// three agreeing is the whole point.
|
|
119
120
|
const outlineOffset = showTrackOutlines ? TRACK_OUTLINE_BORDER : 0;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
// A label in flow is a row of track height, which is most of what minimizing
|
|
122
|
+
// buys back — so a minimized track overlaps its label whatever the setting
|
|
123
|
+
// says, and there is nothing under it to print over. `prefersOffset` is the
|
|
124
|
+
// display asking for the offset row in 'overlapping' mode, because its left
|
|
125
|
+
// edge carries an axis or a sidebar the label would cover.
|
|
126
|
+
const trackLabelStyle = track.minimized ||
|
|
127
|
+
(model.effectiveTrackLabels === 'overlapping' && !display.prefersOffset)
|
|
128
|
+
? classes.trackLabelOverlap
|
|
129
|
+
: classes.trackLabelOffset;
|
|
123
130
|
const { setPaper, setContainer } = useTrackLabelBand(model, track.trackId);
|
|
124
131
|
return (_jsxs(Paper, { ref: setPaper, className: cx(classes.root, track.pinned ? null : classes.unpinnedTrack), variant: showTrackOutlines ? 'outlined' : undefined, elevation: showTrackOutlines ? undefined : 0, onDragOver: event => {
|
|
125
132
|
model.onTrackDragOver(track.id, event.clientY);
|
|
126
|
-
}, children: [track.pinned ? _jsx(Gridlines, { model: model, offset: outlineOffset }) : null, model.effectiveTrackLabels !== 'hidden' ? (_jsx(TrackLabel, { track: track, className: trackLabelStyle })) : null, _jsx(TrackOverlaySlot, { zIndex: 100, overlayStyle: { left: -outlineOffset }, children: _jsx(ErrorBoundary, { resetKeys: [display.id], FallbackComponent: e => (_jsx(ErrorBanner, { error: e.error, onReset: e.resetErrorBoundary })), children: _jsx(TrackRenderingContainer, { model: model, track: track, ref: setContainer }) }) }), _jsx(PaddingBlocks, { model: model, offset: outlineOffset }), _jsx(ResizeHandle, { style: { height: RESIZE_HANDLE_HEIGHT }, onDrag: distance => display.resizeHeight(distance),
|
|
133
|
+
}, children: [track.pinned ? _jsx(Gridlines, { model: model, offset: outlineOffset }) : null, model.effectiveTrackLabels !== 'hidden' ? (_jsx(TrackLabel, { track: track, className: trackLabelStyle })) : null, _jsx(TrackOverlaySlot, { zIndex: 100, overlayStyle: { left: -outlineOffset }, children: _jsx(ErrorBoundary, { resetKeys: [display.id], FallbackComponent: e => (_jsx(ErrorBanner, { error: e.error, onReset: e.resetErrorBoundary })), children: _jsx(TrackRenderingContainer, { model: model, track: track, display: display, ref: setContainer }) }) }), _jsx(PaddingBlocks, { model: model, offset: outlineOffset }), _jsx(ResizeHandle, { style: { height: RESIZE_HANDLE_HEIGHT }, onDrag: distance => display.resizeHeight(distance),
|
|
127
134
|
// the drag's shortcut: grow the track by whatever it is scrolled over,
|
|
128
135
|
// so a pileup showing three rows of forty opens to all forty
|
|
129
136
|
// Optional: only a display that scrolls its own content has anything
|