@jbrowse/plugin-linear-genome-view 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/FeatureTrack/configSchema.d.ts +142 -50
- package/esm/LaunchLinearGenomeView/index.js +2 -13
- package/esm/LinearGenomeView/afterAttach.d.ts +5 -5
- package/esm/LinearGenomeView/afterAttach.js +73 -59
- package/esm/LinearGenomeView/components/CenterLine.js +5 -3
- package/esm/LinearGenomeView/components/CrisprGuidePanel.js +3 -5
- package/esm/LinearGenomeView/components/ExportSvgDialog.d.ts +3 -2
- package/esm/LinearGenomeView/components/ExportSvgDialog.js +6 -2
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +2 -2
- package/esm/LinearGenomeView/components/Header.js +11 -1
- package/esm/LinearGenomeView/components/HeaderClearHighlightButton.js +2 -2
- package/esm/LinearGenomeView/components/HeaderPanControls.js +2 -1
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +4 -34
- package/esm/LinearGenomeView/components/Highlight.js +7 -7
- 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/HoverPositionHighlight.js +3 -3
- package/esm/LinearGenomeView/components/ImportForm.js +2 -2
- package/esm/LinearGenomeView/components/LinearGenomeView.js +3 -3
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +3 -4
- package/esm/LinearGenomeView/components/MiniControls.js +3 -2
- package/esm/LinearGenomeView/components/OverviewHighlight.js +3 -3
- package/esm/LinearGenomeView/components/OverviewHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/OverviewRubberband.js +20 -45
- package/esm/LinearGenomeView/components/OverviewScalebar.js +4 -5
- package/esm/LinearGenomeView/components/OverviewScalebarPolygon.js +5 -3
- package/esm/LinearGenomeView/components/PaddingBlocks.d.ts +1 -1
- package/esm/LinearGenomeView/components/PaddingBlocks.js +14 -9
- package/esm/LinearGenomeView/components/RangeSelectOverlay.js +5 -6
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.d.ts +5 -0
- package/esm/LinearGenomeView/components/ResizeAllTracksHandle.js +71 -0
- package/esm/LinearGenomeView/components/Rubberband.d.ts +1 -2
- package/esm/LinearGenomeView/components/Rubberband.js +3 -2
- package/esm/LinearGenomeView/components/SVGHighlightBand.js +2 -3
- package/esm/LinearGenomeView/components/Scalebar.d.ts +2 -6
- package/esm/LinearGenomeView/components/Scalebar.js +8 -5
- package/esm/LinearGenomeView/components/ScalebarRefNameLabels.js +49 -34
- package/esm/LinearGenomeView/components/SearchBox.js +8 -7
- package/esm/LinearGenomeView/components/SearchResultsDialog.d.ts +2 -1
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +2 -2
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
- package/esm/LinearGenomeView/components/SearchResultsTable.js +12 -29
- package/esm/LinearGenomeView/components/SequenceSearchDialog.d.ts +2 -4
- package/esm/LinearGenomeView/components/TrackContainer.js +63 -9
- package/esm/LinearGenomeView/components/TrackLabel.js +10 -3
- package/esm/LinearGenomeView/components/TrackLabelButtons.js +2 -2
- package/esm/LinearGenomeView/components/TrackLabelDragHandle.js +2 -2
- package/esm/LinearGenomeView/components/TrackLabelMenu.js +3 -2
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +4 -1
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +11 -23
- package/esm/LinearGenomeView/components/TracksContainer.js +10 -22
- package/esm/LinearGenomeView/components/VerticalGuide.d.ts +1 -2
- package/esm/LinearGenomeView/components/VerticalGuide.js +3 -16
- package/esm/LinearGenomeView/components/ZoomButton.js +6 -1
- package/esm/LinearGenomeView/components/ZoomTransform.js +4 -0
- package/esm/LinearGenomeView/components/fetchSequence.d.ts +1 -2
- package/esm/LinearGenomeView/components/recentLocationsMenu.js +10 -0
- package/esm/LinearGenomeView/components/searchModes.d.ts +1 -1
- package/esm/LinearGenomeView/components/searchModes.js +1 -1
- package/esm/LinearGenomeView/components/useRangeSelect.d.ts +0 -3
- package/esm/LinearGenomeView/components/useRangeSelect.js +20 -59
- package/esm/LinearGenomeView/components/useSideScroll.d.ts +16 -2
- package/esm/LinearGenomeView/components/useSideScroll.js +153 -103
- package/esm/LinearGenomeView/components/util.d.ts +4 -0
- package/esm/LinearGenomeView/components/util.js +4 -2
- package/esm/LinearGenomeView/consts.d.ts +2 -0
- package/esm/LinearGenomeView/consts.js +3 -1
- package/esm/LinearGenomeView/containingLgv.d.ts +21 -0
- package/esm/LinearGenomeView/containingLgv.js +23 -0
- package/esm/LinearGenomeView/headerFit.d.ts +4 -2
- package/esm/LinearGenomeView/headerFit.js +20 -7
- package/esm/LinearGenomeView/index.d.ts +1 -5
- package/esm/LinearGenomeView/index.js +10 -7
- package/esm/LinearGenomeView/keyboardHandler.d.ts +2 -0
- package/esm/LinearGenomeView/keyboardHandler.js +15 -1
- package/esm/LinearGenomeView/launchKeys.d.ts +2 -0
- package/esm/LinearGenomeView/launchKeys.js +27 -0
- package/esm/LinearGenomeView/lazyDialogs.d.ts +2 -4
- package/esm/LinearGenomeView/lazyDialogs.js +1 -0
- package/esm/LinearGenomeView/linkedViewSync.d.ts +3 -11
- package/esm/LinearGenomeView/linkedViewSync.js +27 -43
- package/esm/LinearGenomeView/menuItems.d.ts +16 -18
- package/esm/LinearGenomeView/menuItems.js +242 -151
- package/esm/LinearGenomeView/model.d.ts +2009 -200
- package/esm/LinearGenomeView/model.js +545 -395
- package/esm/LinearGenomeView/sharedScaleContainer.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +7 -1
- package/esm/LinearGenomeView/stickyChrome.d.ts +5 -9
- package/esm/LinearGenomeView/stickyChrome.js +5 -9
- package/esm/LinearGenomeView/svgcomponents/SVGGridlines.js +4 -4
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.d.ts +4 -1
- package/esm/LinearGenomeView/svgcomponents/SVGHeader.js +8 -21
- package/esm/LinearGenomeView/svgcomponents/SVGHighlights.js +8 -8
- package/esm/LinearGenomeView/svgcomponents/SVGHighlightsOverlay.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +7 -11
- package/esm/LinearGenomeView/svgcomponents/SVGRegionSeparators.js +2 -5
- package/esm/LinearGenomeView/svgcomponents/SVGRowHeader.js +5 -6
- package/esm/LinearGenomeView/svgcomponents/SVGRuler.js +9 -10
- package/esm/LinearGenomeView/svgcomponents/SVGScalebar.js +3 -3
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.d.ts +27 -0
- package/esm/LinearGenomeView/svgcomponents/SVGStackedRow.js +12 -0
- package/esm/LinearGenomeView/svgcomponents/SVGTrackLabel.js +15 -6
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.d.ts +4 -4
- package/esm/LinearGenomeView/svgcomponents/SVGTracks.js +6 -5
- package/esm/LinearGenomeView/svgcomponents/SVGView.d.ts +1 -1
- package/esm/LinearGenomeView/svgcomponents/SVGView.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/figureContract.js +14 -14
- package/esm/LinearGenomeView/svgcomponents/renderViewTracks.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/useViewSvgFigure.js +50 -68
- package/esm/LinearGenomeView/svgcomponents/util.d.ts +9 -5
- package/esm/LinearGenomeView/svgcomponents/util.js +26 -21
- package/esm/LinearGenomeView/types.d.ts +8 -11
- package/esm/LinearGenomeView/util.d.ts +60 -45
- package/esm/LinearGenomeView/util.js +131 -160
- package/esm/MultiLevelRubberband/MultiLevelRubberband.js +7 -4
- package/esm/MultiLevelRubberband/index.d.ts +2 -0
- package/esm/MultiLevelRubberband/index.js +2 -0
- package/esm/MultiLevelRubberband/menuItems.d.ts +34 -0
- package/esm/MultiLevelRubberband/menuItems.js +52 -0
- package/esm/MultiLevelRubberband/rowLabel.d.ts +22 -0
- package/esm/MultiLevelRubberband/rowLabel.js +27 -0
- package/esm/MultiLevelRubberband/types.d.ts +1 -0
- package/esm/MultiLevelRubberband/useRangeSelect.d.ts +2 -1
- package/esm/MultiLevelRubberband/useRangeSelect.js +31 -47
- package/esm/displayKitTests/perRegionTestEnv.d.ts +214 -86
- package/esm/displayKitTests/perRegionTestEnv.js +30 -29
- package/esm/index.d.ts +15 -85
- package/esm/index.js +13 -36
- package/esm/searchUtils.d.ts +18 -9
- package/esm/searchUtils.js +85 -69
- package/esm/shared/RubberbandSpan.js +2 -1
- package/esm/shared/coordLabels.d.ts +7 -0
- package/esm/shared/coordLabels.js +15 -1
- package/esm/shared/useWindowDrag.d.ts +18 -0
- package/esm/shared/useWindowDrag.js +45 -0
- package/package.json +10 -10
- package/esm/LinearGenomeView/FloatingSvgOverlay.d.ts +0 -5
- package/esm/LinearGenomeView/FloatingSvgOverlay.js +0 -25
- package/esm/LinearGenomeView/initKeys.d.ts +0 -13
- package/esm/LinearGenomeView/initKeys.js +0 -104
- package/esm/MultiLevelRubberband/VerticalGuide.d.ts +0 -6
- package/esm/MultiLevelRubberband/VerticalGuide.js +0 -21
- package/esm/lazyPluginExports.d.ts +0 -23
- package/esm/lazyPluginExports.js +0 -36
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { ResizeHandle } from '@jbrowse/core/ui';
|
|
4
|
+
import { scrollPortOf } from '@jbrowse/core/util/hooks';
|
|
5
|
+
import { isAlive } from '@jbrowse/mobx-state-tree';
|
|
6
|
+
import { observer } from 'mobx-react';
|
|
7
|
+
import { RESIZE_ALL_HANDLE_HEIGHT } from '../consts.js';
|
|
8
|
+
function useScrollPortExcess(ref, model, enabled) {
|
|
9
|
+
useLayoutEffect(() => {
|
|
10
|
+
const box = ref.current?.closest('[data-testid^="view-container-"]') ??
|
|
11
|
+
ref.current?.closest('[data-testid^="linear-genome-view-"]');
|
|
12
|
+
if (!enabled || !box || !('ResizeObserver' in window)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const port = scrollPortOf(box);
|
|
16
|
+
const measure = () => {
|
|
17
|
+
const { marginTop, marginBottom } = getComputedStyle(box);
|
|
18
|
+
const outer = box.getBoundingClientRect().height +
|
|
19
|
+
Number.parseFloat(marginTop) +
|
|
20
|
+
Number.parseFloat(marginBottom);
|
|
21
|
+
model.setScrollPortExcess(Math.round(outer - (port?.clientHeight ?? window.innerHeight)));
|
|
22
|
+
};
|
|
23
|
+
measure();
|
|
24
|
+
const observer = new ResizeObserver(measure);
|
|
25
|
+
observer.observe(box);
|
|
26
|
+
if (port) {
|
|
27
|
+
observer.observe(port);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
window.addEventListener('resize', measure);
|
|
31
|
+
}
|
|
32
|
+
return () => {
|
|
33
|
+
observer.disconnect();
|
|
34
|
+
window.removeEventListener('resize', measure);
|
|
35
|
+
if (isAlive(model)) {
|
|
36
|
+
model.setScrollPortExcess(undefined);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}, [ref, model, enabled]);
|
|
40
|
+
}
|
|
41
|
+
const ResizeAllTracksHandle = observer(function ResizeAllTracksHandle({ model, }) {
|
|
42
|
+
const ref = useRef(null);
|
|
43
|
+
const drag = useRef({ from: new Map(), moved: 0 });
|
|
44
|
+
const { isTopLevelView } = model;
|
|
45
|
+
useScrollPortExcess(ref, model, isTopLevelView);
|
|
46
|
+
return (_jsx("div", { ref: ref, style: isTopLevelView
|
|
47
|
+
? { position: 'sticky', bottom: 0, zIndex: 300 }
|
|
48
|
+
: { position: 'relative', height: 0, zIndex: 300 }, children: _jsx(ResizeHandle, { grip: isTopLevelView || 'hover', "data-testid": "resize-all-tracks", title: isTopLevelView
|
|
49
|
+
? 'Drag to resize all tracks, double-click to fit them to the window'
|
|
50
|
+
: 'Drag to resize all tracks', style: isTopLevelView
|
|
51
|
+
? { height: RESIZE_ALL_HANDLE_HEIGHT }
|
|
52
|
+
: {
|
|
53
|
+
height: RESIZE_ALL_HANDLE_HEIGHT,
|
|
54
|
+
position: 'absolute',
|
|
55
|
+
bottom: 0,
|
|
56
|
+
}, onDragStart: () => {
|
|
57
|
+
drag.current = { from: model.resizableTrackHeights, moved: 0 };
|
|
58
|
+
for (const track of model.tracks) {
|
|
59
|
+
track.setResizing(true);
|
|
60
|
+
}
|
|
61
|
+
}, onDrag: distance => {
|
|
62
|
+
drag.current.moved = model.resizeTracks(drag.current.moved + distance, drag.current.from);
|
|
63
|
+
}, onDragEnd: () => {
|
|
64
|
+
for (const track of model.tracks) {
|
|
65
|
+
track.setResizing(false);
|
|
66
|
+
}
|
|
67
|
+
}, onDoubleClick: () => {
|
|
68
|
+
model.fitTracksToWindow();
|
|
69
|
+
} }) }));
|
|
70
|
+
});
|
|
71
|
+
export default ResizeAllTracksHandle;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { LinearGenomeViewModel } from '../index.ts';
|
|
2
2
|
type LGV = LinearGenomeViewModel;
|
|
3
|
-
declare const Rubberband: ({ model
|
|
3
|
+
declare const Rubberband: ({ model }: {
|
|
4
4
|
model: LGV;
|
|
5
|
-
ControlComponent?: React.ReactElement;
|
|
6
5
|
}) => import("react").JSX.Element;
|
|
7
6
|
export default Rubberband;
|
|
@@ -4,6 +4,7 @@ import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
import { stickyChromeTops } from '../stickyChrome.js';
|
|
6
6
|
import RangeSelectOverlay from './RangeSelectOverlay.js';
|
|
7
|
+
import Scalebar from './Scalebar.js';
|
|
7
8
|
import { useRangeSelect } from './useRangeSelect.js';
|
|
8
9
|
const useStyles = makeStyles()({
|
|
9
10
|
rubberbandControl: {
|
|
@@ -13,7 +14,7 @@ const useStyles = makeStyles()({
|
|
|
13
14
|
zIndex: 825,
|
|
14
15
|
},
|
|
15
16
|
});
|
|
16
|
-
const Rubberband = observer(function Rubberband({ model
|
|
17
|
+
const Rubberband = observer(function Rubberband({ model }) {
|
|
17
18
|
const ref = useRef(null);
|
|
18
19
|
const { classes } = useStyles();
|
|
19
20
|
const { stickyViewHeaders, headerHeight } = model;
|
|
@@ -24,6 +25,6 @@ const Rubberband = observer(function Rubberband({ model, ControlComponent = _jsx
|
|
|
24
25
|
// (TracksContainer): mouseout bubbles from every child, so crossing a
|
|
25
26
|
// refName or coordinate label inside the scalebar cleared the guide and
|
|
26
27
|
// the next mousemove put it straight back
|
|
27
|
-
onMouseLeave: range.mouseOut, children:
|
|
28
|
+
onMouseLeave: range.mouseOut, children: _jsx(Scalebar, { model: model }) })] }));
|
|
28
29
|
});
|
|
29
30
|
export default Rubberband;
|
|
@@ -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
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import type { LinearGenomeViewModel } from '../index.ts';
|
|
2
|
-
import type React from 'react';
|
|
3
2
|
type LGV = LinearGenomeViewModel;
|
|
4
|
-
|
|
3
|
+
declare const Scalebar: ({ model }: {
|
|
5
4
|
model: LGV;
|
|
6
|
-
|
|
7
|
-
className?: string;
|
|
8
|
-
}
|
|
9
|
-
declare const Scalebar: ({ model, style, className, }: ScalebarProps) => React.JSX.Element;
|
|
5
|
+
}) => import("react").JSX.Element;
|
|
10
6
|
export default Scalebar;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
3
|
import { Paper } from '@mui/material';
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
|
+
import { SCALE_BAR_HEIGHT } from '../consts.js';
|
|
5
6
|
import Gridlines from './Gridlines.js';
|
|
6
7
|
import PaddingBlocks from './PaddingBlocks.js';
|
|
7
8
|
import ScalebarCoordinateLabels from './ScalebarCoordinateLabels.js';
|
|
@@ -14,15 +15,17 @@ const useStyles = makeStyles()({
|
|
|
14
15
|
container: {
|
|
15
16
|
overflow: 'clip',
|
|
16
17
|
position: 'relative',
|
|
18
|
+
height: SCALE_BAR_HEIGHT,
|
|
19
|
+
boxSizing: 'border-box',
|
|
17
20
|
},
|
|
18
21
|
});
|
|
19
|
-
const Scalebar = observer(function Scalebar({ model
|
|
22
|
+
const Scalebar = observer(function Scalebar({ model }) {
|
|
20
23
|
const { classes } = useStyles();
|
|
21
24
|
return (_jsxs(Paper
|
|
22
|
-
// The rubberband owns presses on the scalebar (it wraps this
|
|
23
|
-
//
|
|
25
|
+
// The rubberband owns presses on the scalebar (it wraps this), so the
|
|
26
|
+
// view's click-drag pan must not also start one
|
|
24
27
|
// on the paths where the rubberband doesn't stopPropagation — a press with
|
|
25
28
|
// the refName menu already open. See ResizeHandle for the marker contract.
|
|
26
|
-
, { "data-gesture-owner": "true", className:
|
|
29
|
+
, { "data-gesture-owner": "true", className: classes.container, variant: "outlined", children: [_jsx(Gridlines, { model: model, offset: 1 }), _jsx(PaddingBlocks, { model: model, offset: 1 }), _jsx(ScalebarCoordinateLabels, { model: model }), _jsx(ScalebarRefNameLabels, { model: model })] }));
|
|
27
30
|
});
|
|
28
31
|
export default Scalebar;
|
|
@@ -5,7 +5,8 @@ import { assembleLocString, assembleLocStrings } from '@jbrowse/core/util';
|
|
|
5
5
|
import { copyText } from '@jbrowse/core/util/copyText';
|
|
6
6
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
7
7
|
import { observer } from 'mobx-react';
|
|
8
|
-
import { REF_NAME_LABEL_FONT_SIZE, regionMoveActions, withRegionMoved, withRegionRemoved, withRegionReversed, } from '../util.js';
|
|
8
|
+
import { REF_NAME_LABEL_FONT_SIZE, regionMoveActions, regionRunBounds, withRegionMoved, withRegionRemoved, withRegionReversed, withRegionsKept, } from '../util.js';
|
|
9
|
+
import { scalebarRefLabelProps } from './util.js';
|
|
9
10
|
const useStyles = makeStyles()(theme => ({
|
|
10
11
|
refLabel: {
|
|
11
12
|
// the width the fit test in getScalebarRefNameLabels measures against
|
|
@@ -31,65 +32,45 @@ const useStyles = makeStyles()(theme => ({
|
|
|
31
32
|
overflow: 'clip',
|
|
32
33
|
whiteSpace: 'nowrap',
|
|
33
34
|
},
|
|
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
|
|
35
|
+
// refName labels only: the caption chip opens no menu
|
|
37
36
|
clickable: {
|
|
38
37
|
cursor: 'pointer',
|
|
39
38
|
'&: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
39
|
background: theme.palette.action.hover,
|
|
43
40
|
},
|
|
44
41
|
},
|
|
45
|
-
|
|
42
|
+
caption: {
|
|
46
43
|
zIndex: 100,
|
|
47
44
|
},
|
|
48
45
|
}));
|
|
49
46
|
const ScalebarRefNameLabels = observer(function ScalebarRefNameLabels({ model, }) {
|
|
50
47
|
const { classes, cx } = useStyles();
|
|
51
48
|
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
49
|
const { labels, caption } = model.scalebarRefNameLabels;
|
|
57
50
|
return (_jsxs(_Fragment, { children: [labels.map((label, i) => (_jsx(RefLabel
|
|
58
51
|
// 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: () => {
|
|
52
|
+
, { 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
53
|
model.setIsScalebarRefNameMenuOpen(false);
|
|
63
54
|
setMenuState(undefined);
|
|
64
55
|
} })) : null] }));
|
|
65
56
|
});
|
|
66
|
-
//
|
|
67
|
-
// the
|
|
68
|
-
// click as starting on a label so the scalebar's rubberband hands the click to
|
|
69
|
-
// this label's own onClick instead of opening the range menu.
|
|
57
|
+
// Not an observer: `label` is plain data from the parent. The attribute tells
|
|
58
|
+
// the scalebar's rubberband to leave a click here to this label's onClick.
|
|
70
59
|
function RefLabel({ model, label, onOpenMenu, }) {
|
|
71
60
|
const { classes, cx } = useStyles();
|
|
72
|
-
const { refName,
|
|
61
|
+
const { refName, transform, maxWidth, paddingLeft, text } = label;
|
|
73
62
|
return (_jsx("span", { className: cx(classes.refLabel, classes.clickable), style: {
|
|
74
63
|
transform: `translateX(${transform}px)`,
|
|
75
64
|
paddingLeft,
|
|
76
65
|
maxWidth,
|
|
77
|
-
}, "data-testid": `refLabel-${refName}`,
|
|
78
|
-
model.setScalebarRefNameClickPending(true);
|
|
79
|
-
}, onClick: e => {
|
|
80
|
-
model.setScalebarRefNameClickPending(false);
|
|
66
|
+
}, "data-testid": `refLabel-${refName}`, ...scalebarRefLabelProps, onClick: e => {
|
|
81
67
|
model.setIsScalebarRefNameMenuOpen(true);
|
|
82
|
-
onOpenMenu({
|
|
83
|
-
anchorEl: e.currentTarget,
|
|
84
|
-
refName,
|
|
85
|
-
displayedRegionIndex,
|
|
86
|
-
lastDisplayedRegionIndex,
|
|
87
|
-
});
|
|
68
|
+
onOpenMenu({ anchorEl: e.currentTarget, label });
|
|
88
69
|
}, children: text }));
|
|
89
70
|
}
|
|
90
71
|
const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }) {
|
|
91
72
|
const { displayedRegions } = model;
|
|
92
|
-
const { refName, displayedRegionIndex: idx, lastDisplayedRegionIndex: lastIdx, } = menuState;
|
|
73
|
+
const { refName, displayedRegionIndex: idx, lastDisplayedRegionIndex: lastIdx, } = menuState.label;
|
|
93
74
|
const numRegions = displayedRegions.length;
|
|
94
75
|
const labeled = displayedRegions.slice(idx, lastIdx + 1);
|
|
95
76
|
// A label naming several regions is the collapsed-intron case: adjacent
|
|
@@ -104,6 +85,16 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
|
|
|
104
85
|
const locString = oneRegion
|
|
105
86
|
? assembleLocString(displayedRegions[idx])
|
|
106
87
|
: assembleLocStrings(labeled);
|
|
88
|
+
// The region this label's blocks were cut out of. Taken over the WHOLE run
|
|
89
|
+
// rather than `labeled`, which the viewport clips: a collapsed-introns view
|
|
90
|
+
// zoomed into two exons would otherwise offer a span that is not the gene's.
|
|
91
|
+
const { first: runFirst, last: runLast } = regionRunBounds(displayedRegions, idx, lastIdx);
|
|
92
|
+
const run = displayedRegions.slice(runFirst, runLast + 1);
|
|
93
|
+
const wholeSpan = {
|
|
94
|
+
...run[0],
|
|
95
|
+
start: Math.min(...run.map(r => r.start)),
|
|
96
|
+
end: Math.max(...run.map(r => r.end)),
|
|
97
|
+
};
|
|
107
98
|
return (_jsx(Menu, { anchorEl: menuState.anchorEl, open: true, onClose: onClose, onMenuItemClick: callback => {
|
|
108
99
|
callback();
|
|
109
100
|
onClose();
|
|
@@ -126,7 +117,31 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
|
|
|
126
117
|
? 'Show only this region'
|
|
127
118
|
: `Show only ${refName}`,
|
|
128
119
|
onClick: () => {
|
|
129
|
-
model.
|
|
120
|
+
model.editDisplayedRegions(withRegionsKept(displayedRegions, idx, lastIdx));
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
]
|
|
124
|
+
: []),
|
|
125
|
+
...(runLast > runFirst
|
|
126
|
+
? [
|
|
127
|
+
{
|
|
128
|
+
// Names its subject the way the two rows above it do; the span
|
|
129
|
+
// it lands on is not spelled into the label, which would move
|
|
130
|
+
// under the reader as they navigate.
|
|
131
|
+
label: `Show the whole span of ${refName}`,
|
|
132
|
+
// Focused rather than centre-kept: the rejoined region is one
|
|
133
|
+
// the old viewport never saw, so there is no centre to hold,
|
|
134
|
+
// and showing the span is the whole point of the row.
|
|
135
|
+
onClick: () => {
|
|
136
|
+
model.setDisplayedRegions([
|
|
137
|
+
...displayedRegions.slice(0, runFirst),
|
|
138
|
+
wholeSpan,
|
|
139
|
+
...displayedRegions.slice(runLast + 1),
|
|
140
|
+
]);
|
|
141
|
+
model.moveTo({ index: runFirst, offset: 0 }, {
|
|
142
|
+
index: runFirst,
|
|
143
|
+
offset: wholeSpan.end - wholeSpan.start,
|
|
144
|
+
});
|
|
130
145
|
},
|
|
131
146
|
},
|
|
132
147
|
]
|
|
@@ -156,13 +171,13 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
|
|
|
156
171
|
{
|
|
157
172
|
label: 'Reverse region',
|
|
158
173
|
onClick: () => {
|
|
159
|
-
model.
|
|
174
|
+
model.editDisplayedRegions(withRegionReversed(displayedRegions, idx));
|
|
160
175
|
},
|
|
161
176
|
},
|
|
162
177
|
...regionMoveActions(idx, numRegions).map(({ label, to }) => ({
|
|
163
178
|
label,
|
|
164
179
|
onClick: () => {
|
|
165
|
-
model.
|
|
180
|
+
model.editDisplayedRegions(withRegionMoved(displayedRegions, idx, to));
|
|
166
181
|
},
|
|
167
182
|
})),
|
|
168
183
|
...(numRegions > 1
|
|
@@ -170,7 +185,7 @@ const RefNameMenu = observer(function RefNameMenu({ model, menuState, onClose, }
|
|
|
170
185
|
{
|
|
171
186
|
label: 'Remove this region from view',
|
|
172
187
|
onClick: () => {
|
|
173
|
-
model.
|
|
188
|
+
model.editDisplayedRegions(withRegionRemoved(displayedRegions, idx));
|
|
174
189
|
},
|
|
175
190
|
},
|
|
176
191
|
]
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { RefNameAutocomplete, RefNameAutocompleteEndAdornment, adornmentReservePx, getInputWidth, useRecentLocations, } from '@jbrowse/core/ui';
|
|
3
|
+
import { usePalette } from '@jbrowse/core/ui/PaletteContext';
|
|
4
|
+
import { alpha } from '@jbrowse/core/ui/palette';
|
|
3
5
|
import { getSession } from '@jbrowse/core/util';
|
|
4
|
-
import { alpha, useTheme } from '@mui/material';
|
|
5
6
|
import { observer } from 'mobx-react';
|
|
6
7
|
import { fetchResults, navigateToSelectedOption, notifySearchFailure, } from '../../searchUtils.js';
|
|
7
8
|
import { SPACING, WIDGET_HEIGHT } from '../consts.js';
|
|
@@ -31,7 +32,7 @@ export function searchBoxWidth(value) {
|
|
|
31
32
|
2 * SPACING);
|
|
32
33
|
}
|
|
33
34
|
const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth = MIN_WIDTH, maxWidth = MAX_WIDTH, style = defaultStyle, }) {
|
|
34
|
-
const
|
|
35
|
+
const palette = usePalette();
|
|
35
36
|
const session = getSession(model);
|
|
36
37
|
const { textSearchManager, assemblyManager } = session;
|
|
37
38
|
const { assemblyNames } = model;
|
|
@@ -50,25 +51,25 @@ const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth
|
|
|
50
51
|
const recentMenuItems = recentLocationsMenu({
|
|
51
52
|
recentLocations,
|
|
52
53
|
onNavigate: option => {
|
|
53
|
-
navigate(option)
|
|
54
|
+
void navigate(option);
|
|
54
55
|
},
|
|
55
56
|
onClear: clearRecentLocations,
|
|
56
57
|
});
|
|
57
58
|
return (_jsx(RefNameAutocomplete, { onSelect: option => {
|
|
58
|
-
navigate(option)
|
|
59
|
-
}, assemblyName: assemblyName, fetchResults: (queryString,
|
|
59
|
+
void navigate(option);
|
|
60
|
+
}, assemblyName: assemblyName, fetchResults: (queryString, signal) => fetchResults({
|
|
60
61
|
queryString,
|
|
61
62
|
assemblyName,
|
|
62
63
|
textSearchManager,
|
|
63
64
|
assembly,
|
|
64
|
-
|
|
65
|
+
signal,
|
|
65
66
|
}), session: session, value: model.coarseVisibleLocStrings, minWidth: minWidth, maxWidth: maxWidth, adornmentWidth: adornmentReservePx({
|
|
66
67
|
showHelp,
|
|
67
68
|
menuItemCount: recentMenuItems.length,
|
|
68
69
|
}), style: style, endAdornment: _jsx(RefNameAutocompleteEndAdornment, { showHelp: showHelp, menuItems: recentMenuItems }), inputStyle: {
|
|
69
70
|
padding: 0,
|
|
70
71
|
height: WIDGET_HEIGHT,
|
|
71
|
-
background: alpha(
|
|
72
|
+
background: alpha(palette.background.paper, 0.8),
|
|
72
73
|
} }));
|
|
73
74
|
});
|
|
74
75
|
export default SearchBox;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { LinearGenomeViewModel } from '../../index.ts';
|
|
2
2
|
import type BaseResult from '@jbrowse/core/TextSearch/BaseResults';
|
|
3
|
-
export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, }: {
|
|
3
|
+
export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, onPick, }: {
|
|
4
4
|
model: LinearGenomeViewModel;
|
|
5
5
|
assemblyName: string;
|
|
6
6
|
searchQuery: string;
|
|
7
7
|
searchResults: BaseResult[];
|
|
8
8
|
handleClose: () => void;
|
|
9
|
+
onPick: (result: BaseResult) => Promise<unknown>;
|
|
9
10
|
}): import("react").JSX.Element;
|
|
@@ -2,6 +2,6 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { InfoDialog } from '@jbrowse/core/ui';
|
|
3
3
|
import { Typography } from '@mui/material';
|
|
4
4
|
import SearchResultsTable from './SearchResultsTable.js';
|
|
5
|
-
export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, }) {
|
|
6
|
-
return (_jsx(InfoDialog, { open: true, maxWidth: "xl", onClose: handleClose, title: "Search results", children: !searchResults.length ? (_jsxs(Typography, { children: ["No results found for ", _jsx("b", { children: searchQuery })] })) : (_jsxs(_Fragment, { children: [_jsxs(Typography, { children: ["Showing results for ", _jsx("b", { children: searchQuery })] }), _jsx(SearchResultsTable, { model: model, handleClose: handleClose, assemblyName: assemblyName, searchResults: searchResults })] })) }));
|
|
5
|
+
export default function SearchResultsDialog({ model, assemblyName, searchQuery, searchResults, handleClose, onPick, }) {
|
|
6
|
+
return (_jsx(InfoDialog, { open: true, maxWidth: "xl", onClose: handleClose, title: "Search results", children: !searchResults.length ? (_jsxs(Typography, { children: ["No results found for ", _jsx("b", { children: searchQuery })] })) : (_jsxs(_Fragment, { children: [_jsxs(Typography, { children: ["Showing results for ", _jsx("b", { children: searchQuery })] }), _jsx(SearchResultsTable, { model: model, handleClose: handleClose, assemblyName: assemblyName, searchResults: searchResults, onPick: onPick })] })) }));
|
|
7
7
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { LinearGenomeViewModel } from '../../index.ts';
|
|
2
2
|
import type BaseResult from '@jbrowse/core/TextSearch/BaseResults';
|
|
3
|
-
declare const SearchResultsTable: ({ searchResults, assemblyName, model, handleClose, }: {
|
|
3
|
+
declare const SearchResultsTable: ({ searchResults, assemblyName, model, handleClose, onPick, }: {
|
|
4
4
|
searchResults: BaseResult[];
|
|
5
5
|
assemblyName: string;
|
|
6
6
|
model: LinearGenomeViewModel;
|
|
7
7
|
handleClose: () => void;
|
|
8
|
+
onPick: (result: BaseResult) => Promise<unknown>;
|
|
8
9
|
}) => import("react").JSX.Element;
|
|
9
10
|
export default SearchResultsTable;
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { canonicalLocString } from '@jbrowse/core/TextSearch/places';
|
|
2
3
|
import { readConfObject } from '@jbrowse/core/configuration';
|
|
3
|
-
import {
|
|
4
|
+
import { SanitizedHTML } from '@jbrowse/core/ui';
|
|
5
|
+
import { getSession } from '@jbrowse/core/util';
|
|
4
6
|
import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, } from '@mui/material';
|
|
5
7
|
import { observer } from 'mobx-react';
|
|
6
|
-
import { isOpenInView
|
|
7
|
-
const SearchResultsTable = observer(function SearchResultsTable({ searchResults, assemblyName, model, handleClose, }) {
|
|
8
|
+
import { isOpenInView } from '../../searchUtils.js';
|
|
9
|
+
const SearchResultsTable = observer(function SearchResultsTable({ searchResults, assemblyName, model, handleClose, onPick, }) {
|
|
8
10
|
const session = getSession(model);
|
|
9
11
|
const { assemblyManager } = session;
|
|
10
12
|
const assembly = assemblyManager.get(assemblyName);
|
|
@@ -12,39 +14,20 @@ const SearchResultsTable = observer(function SearchResultsTable({ searchResults,
|
|
|
12
14
|
const conf = trackId !== undefined ? session.getTrackById(trackId) : undefined;
|
|
13
15
|
return conf ? readConfObject(conf, 'name') : '';
|
|
14
16
|
}
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// resolve is still listed as-is rather than blowing up the dialog: the Go
|
|
18
|
-
// button navigates through navToOption, which reports its own failure
|
|
17
|
+
// A location the assembly can't resolve is listed as-is: Go navigates
|
|
18
|
+
// through navToOption, which reports its own failure
|
|
19
19
|
function formatLocation(locString) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return assembleLocString({
|
|
24
|
-
...loc,
|
|
25
|
-
refName: assembly.getCanonicalRefName2(loc.refName),
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
catch (e) {
|
|
29
|
-
console.warn('failed to parse location string', locString, e);
|
|
30
|
-
return locString;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
return locString;
|
|
35
|
-
}
|
|
20
|
+
return assembly && locString
|
|
21
|
+
? canonicalLocString(locString, assembly)
|
|
22
|
+
: locString;
|
|
36
23
|
}
|
|
37
24
|
// A hit in a track that is already on screen is usually the one meant, so it
|
|
38
25
|
// is listed first rather than given a control that says so. sort is stable,
|
|
39
26
|
// so everything else keeps the ranked order it arrived in.
|
|
40
27
|
const ordered = [...searchResults].sort((a, b) => Number(isOpenInView(b, model)) - Number(isOpenInView(a, model)));
|
|
41
|
-
return (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Name" }), _jsx(TableCell, { align: "right", children: "Location" }), _jsx(TableCell, { align: "right", children: "Track" }), _jsx(TableCell, { align: "right" })] }) }), _jsx(TableBody, { children: ordered.map(result => (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: result.getLabel() }), _jsx(TableCell, { align: "right", children: formatLocation(result.getLocation()) }), _jsx(TableCell, { align: "right", children: getTrackName(result.getTrackId()) || 'N/A' }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: async () => {
|
|
28
|
+
return (_jsx(TableContainer, { component: Paper, children: _jsxs(Table, { children: [_jsx(TableHead, { children: _jsxs(TableRow, { children: [_jsx(TableCell, { children: "Name" }), _jsx(TableCell, { align: "right", children: "Location" }), _jsx(TableCell, { align: "right", children: "Track" }), _jsx(TableCell, { align: "right" })] }) }), _jsx(TableBody, { children: ordered.map(result => (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: result.getLabel() }), _jsx(TableCell, { align: "right", children: formatLocation(result.getLocation()) }), _jsx(TableCell, { align: "right", children: _jsx(SanitizedHTML, { html: getTrackName(result.getTrackId()) || 'N/A' }) }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: async () => {
|
|
42
29
|
try {
|
|
43
|
-
await
|
|
44
|
-
option: result,
|
|
45
|
-
model,
|
|
46
|
-
assemblyName,
|
|
47
|
-
});
|
|
30
|
+
await onPick(result);
|
|
48
31
|
}
|
|
49
32
|
catch (e) {
|
|
50
33
|
console.error(e);
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
+
import type { SequenceSearchModel } from './searchModes.ts';
|
|
1
2
|
declare const SequenceSearchDialog: ({ model, handleClose, }: {
|
|
2
|
-
model:
|
|
3
|
-
assemblyNames: string[];
|
|
4
|
-
showTrack: (trackId: string) => void;
|
|
5
|
-
};
|
|
3
|
+
model: SequenceSearchModel;
|
|
6
4
|
handleClose: () => void;
|
|
7
5
|
}) => import("react").JSX.Element;
|
|
8
6
|
export default SequenceSearchDialog;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
2
3
|
import { ErrorBanner, ResizeHandle } from '@jbrowse/core/ui';
|
|
3
4
|
import { ErrorBoundary } from '@jbrowse/core/ui/ErrorBoundary';
|
|
4
5
|
import { cx, makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
6
|
import { TrackOverlaySlot } from '@jbrowse/display-ui';
|
|
7
|
+
import { isAlive } from '@jbrowse/mobx-state-tree';
|
|
6
8
|
import { Paper } from '@mui/material';
|
|
7
9
|
import { observer } from 'mobx-react';
|
|
8
10
|
import { RESIZE_HANDLE_HEIGHT, TRACK_OUTLINE_BORDER, TRACK_TOP_GAP, } from '../consts.js';
|
|
@@ -16,8 +18,8 @@ const useStyles = makeStyles()({
|
|
|
16
18
|
// described in TrackRenderingContainer. flow-root keeps the block formatting
|
|
17
19
|
// context that `overflow: hidden` used to establish as a side effect (neither
|
|
18
20
|
// `clip` nor no overflow does), so child margins still can't collapse through
|
|
19
|
-
// and shift the track — which
|
|
20
|
-
//
|
|
21
|
+
// and shift the track — which would put the pixels off the model's offset
|
|
22
|
+
// arithmetic, which the breakpoint split view draws its connectors from.
|
|
21
23
|
root: {
|
|
22
24
|
marginTop: TRACK_TOP_GAP,
|
|
23
25
|
position: 'relative',
|
|
@@ -56,17 +58,62 @@ const useStyles = makeStyles()({
|
|
|
56
58
|
// spends the other 4 on clearance.
|
|
57
59
|
trackLabelOffset: {
|
|
58
60
|
position: 'relative',
|
|
59
|
-
display: 'inline-block',
|
|
60
61
|
marginBottom: 8,
|
|
61
62
|
},
|
|
62
63
|
trackLabelOverlap: {
|
|
63
64
|
position: 'absolute',
|
|
64
65
|
},
|
|
66
|
+
// in flow, so the track keeps the banner's height, and raised over the
|
|
67
|
+
// `PaddingBlocks` painted after it
|
|
68
|
+
crashBanner: {
|
|
69
|
+
position: 'relative',
|
|
70
|
+
zIndex: 1,
|
|
71
|
+
},
|
|
65
72
|
});
|
|
73
|
+
// Measures the in-flow label band, the distance from the Paper's content top to
|
|
74
|
+
// the rendering container, into the model. Observes the Paper: it grows and
|
|
75
|
+
// shrinks with the label, the label setting, and the display, and each of those
|
|
76
|
+
// can move the container. Published after a requestAnimationFrame like
|
|
77
|
+
// useWidthSetter, and cleared on unmount so a removed track leaves no band.
|
|
78
|
+
function useTrackLabelBand(model, trackId) {
|
|
79
|
+
const [paper, setPaper] = useState(null);
|
|
80
|
+
const [container, setContainer] = useState(null);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (paper && container && 'ResizeObserver' in window) {
|
|
83
|
+
let token;
|
|
84
|
+
const publish = () => {
|
|
85
|
+
token = requestAnimationFrame(() => {
|
|
86
|
+
if (isAlive(model)) {
|
|
87
|
+
const paperRect = paper.getBoundingClientRect();
|
|
88
|
+
const band = container.getBoundingClientRect().top -
|
|
89
|
+
paperRect.top -
|
|
90
|
+
paper.clientTop;
|
|
91
|
+
model.setTrackLabelBand(trackId, band);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
publish();
|
|
96
|
+
const observer = new ResizeObserver(publish);
|
|
97
|
+
observer.observe(paper);
|
|
98
|
+
return () => {
|
|
99
|
+
observer.disconnect();
|
|
100
|
+
if (token !== undefined) {
|
|
101
|
+
cancelAnimationFrame(token);
|
|
102
|
+
}
|
|
103
|
+
if (isAlive(model)) {
|
|
104
|
+
model.setTrackLabelBand(trackId, 0);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}, [paper, container, model, trackId]);
|
|
110
|
+
return { setPaper, setContainer };
|
|
111
|
+
}
|
|
66
112
|
const TrackContainer = observer(function TrackContainer({ model, track, }) {
|
|
67
113
|
const { classes } = useStyles();
|
|
68
114
|
// an LGV track always holds at least one linear display (activeDisplay =
|
|
69
|
-
// displays[0])
|
|
115
|
+
// displays[0]). The one narrowing to the linear shape, handed down rather
|
|
116
|
+
// than repeated, so a track's chrome and its body can't read two displays.
|
|
70
117
|
const display = track.activeDisplay;
|
|
71
118
|
const { showTrackOutlines } = model;
|
|
72
119
|
// The Paper's 1px border, present iff outlines are on. Everything laid out in
|
|
@@ -76,12 +123,19 @@ const TrackContainer = observer(function TrackContainer({ model, track, }) {
|
|
|
76
123
|
// the data. One binding rather than three copies of the ternary, because the
|
|
77
124
|
// three agreeing is the whole point.
|
|
78
125
|
const outlineOffset = showTrackOutlines ? TRACK_OUTLINE_BORDER : 0;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
126
|
+
// A label in flow is a row of track height, which is most of what minimizing
|
|
127
|
+
// buys back — so a minimized track overlaps its label whatever the setting
|
|
128
|
+
// says, and there is nothing under it to print over. `prefersOffset` is the
|
|
129
|
+
// display asking for the offset row in 'overlapping' mode, because its left
|
|
130
|
+
// edge carries an axis or a sidebar the label would cover.
|
|
131
|
+
const trackLabelStyle = track.minimized ||
|
|
132
|
+
(model.effectiveTrackLabels === 'overlapping' && !display.prefersOffset)
|
|
133
|
+
? classes.trackLabelOverlap
|
|
134
|
+
: classes.trackLabelOffset;
|
|
135
|
+
const { setPaper, setContainer } = useTrackLabelBand(model, track.trackId);
|
|
136
|
+
return (_jsxs(Paper, { ref: setPaper, className: cx(classes.root, track.pinned ? null : classes.unpinnedTrack), variant: showTrackOutlines ? 'outlined' : undefined, elevation: showTrackOutlines ? undefined : 0, onDragOver: event => {
|
|
83
137
|
model.onTrackDragOver(track.id, event.clientY);
|
|
84
|
-
}, 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, {
|
|
138
|
+
}, 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, { FallbackComponent: e => (_jsx("div", { className: classes.crashBanner, children: _jsx(ErrorBanner, { error: e.error, onReset: e.resetErrorBoundary }) })), children: _jsx(TrackRenderingContainer, { model: model, track: track, display: display, ref: setContainer }) }, display.id) }), _jsx(PaddingBlocks, { model: model, offset: outlineOffset }), _jsx(ResizeHandle, { style: { height: RESIZE_HANDLE_HEIGHT }, onDrag: distance => display.resizeHeight(distance),
|
|
85
139
|
// the drag's shortcut: grow the track by whatever it is scrolled over,
|
|
86
140
|
// so a pileup showing three rows of forty opens to all forty
|
|
87
141
|
// Optional: only a display that scrolls its own content has anything
|
|
@@ -14,16 +14,23 @@ const useStyles = makeStyles()(theme => ({
|
|
|
14
14
|
// above breakpoint split view
|
|
15
15
|
zIndex: 200,
|
|
16
16
|
background: theme.palette.background.paper,
|
|
17
|
+
display: 'inline-flex',
|
|
18
|
+
alignItems: 'center',
|
|
19
|
+
maxWidth: 'calc(100% - 16px)',
|
|
17
20
|
},
|
|
21
|
+
// one line: a long name wrapping over a narrow view covered the data below
|
|
18
22
|
trackName: {
|
|
19
|
-
margin:
|
|
20
|
-
|
|
23
|
+
margin: theme.spacing(0, 0.5),
|
|
24
|
+
minWidth: 0,
|
|
25
|
+
overflow: 'hidden',
|
|
26
|
+
textOverflow: 'ellipsis',
|
|
27
|
+
whiteSpace: 'nowrap',
|
|
21
28
|
pointerEvents: 'none',
|
|
22
29
|
},
|
|
23
30
|
}));
|
|
24
31
|
const TrackLabel = observer(function TrackLabel({ track, className, }) {
|
|
25
32
|
const { classes } = useStyles();
|
|
26
33
|
const trackName = getTrackName(track.configuration, getSession(track));
|
|
27
|
-
return (_jsxs(Paper, { className: cx(className, classes.root), children: [_jsx(TrackLabelDragHandle, { track: track }), _jsx(TrackLabelCloseButton, { track: track }), _jsx(TrackLabelMinimizeButton, { track: track }), _jsx(Typography, { variant: "body2", component: "span", className: classes.trackName, children: _jsx(SanitizedHTML, { html: trackName }) }), _jsx(TrackLabelRefNameWarning, { track: track }), _jsx(TrackLabelMenu, { track: track })] }));
|
|
34
|
+
return (_jsxs(Paper, { className: cx(className, classes.root), "data-testid": `trackLabel-${track.trackId}`, children: [_jsx(TrackLabelDragHandle, { track: track }), _jsx(TrackLabelCloseButton, { track: track }), _jsx(TrackLabelMinimizeButton, { track: track }), _jsx(Typography, { variant: "body2", component: "span", className: classes.trackName, children: _jsx(SanitizedHTML, { html: trackName }) }), _jsx(TrackLabelRefNameWarning, { track: track }), _jsx(TrackLabelMenu, { track: track })] }));
|
|
28
35
|
});
|
|
29
36
|
export default TrackLabel;
|