@jbrowse/plugin-linear-genome-view 5.0.0-beta.17 → 5.0.0-beta.19
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/LinearGenomeView/afterAttach.js +6 -4
- package/esm/LinearGenomeView/components/ImportForm.js +2 -4
- package/esm/LinearGenomeView/components/LinearGenomeViewContainer.js +4 -2
- package/esm/LinearGenomeView/components/SearchBox.js +1 -9
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +1 -1
- package/esm/LinearGenomeView/components/SearchResultsTable.d.ts +2 -1
- package/esm/LinearGenomeView/components/SearchResultsTable.js +23 -5
- package/esm/LinearGenomeView/components/SequenceFeatureHoverHighlight.js +2 -3
- package/esm/LinearGenomeView/components/TrackContainer.d.ts +2 -3
- package/esm/LinearGenomeView/components/TrackContainer.js +3 -4
- package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.d.ts +3 -4
- package/esm/LinearGenomeView/components/TrackLabelRefNameWarning.js +23 -10
- package/esm/LinearGenomeView/components/TrackRenderingContainer.d.ts +2 -3
- package/esm/LinearGenomeView/components/TrackRenderingContainer.js +1 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.d.ts +1 -1
- package/esm/LinearGenomeView/components/useSearchBoxPrefs.js +1 -1
- package/esm/LinearGenomeView/index.d.ts +1 -1
- package/esm/LinearGenomeView/index.js +2 -0
- package/esm/LinearGenomeView/keyboardHandler.d.ts +8 -9
- package/esm/LinearGenomeView/keyboardHandler.js +58 -34
- package/esm/LinearGenomeView/menuItems.js +3 -2
- package/esm/LinearGenomeView/model.d.ts +489 -52
- package/esm/LinearGenomeView/model.js +125 -45
- package/esm/LinearGenomeView/rExport/exportLinearView.js +4 -5
- package/esm/LinearGenomeView/rExport/rScript.d.ts +26 -1
- package/esm/LinearGenomeView/rExport/rScript.js +27 -6
- package/esm/LinearGenomeView/sharedScaleContainer.js +2 -12
- package/esm/LinearGenomeView/showFiles.d.ts +2 -0
- package/esm/LinearGenomeView/showFiles.js +19 -0
- package/esm/LinearGenomeView/showRegionsWithUndo.d.ts +1 -1
- package/esm/LinearGenomeView/showRegionsWithUndo.js +1 -1
- package/esm/LinearGenomeView/svgcomponents/SVGLinearGenomeView.js +3 -1
- package/esm/LinearGenomeView/util.d.ts +1 -1
- package/esm/LinearGenomeView/util.js +1 -1
- package/esm/TrackModifiers/index.d.ts +80 -0
- package/esm/TrackModifiers/index.js +212 -0
- package/esm/displayKitTests/perRegionTestEnv.d.ts +10 -4
- package/esm/index.d.ts +3 -1
- package/esm/index.js +1 -2
- package/esm/searchUtils.d.ts +14 -2
- package/esm/searchUtils.js +56 -36
- package/package.json +10 -9
- package/esm/FeatureTrack/configSchema.d.ts +0 -217
- package/esm/FeatureTrack/configSchema.js +0 -55
- package/esm/FeatureTrack/index.d.ts +0 -2
- package/esm/FeatureTrack/index.js +0 -13
|
@@ -2,8 +2,10 @@ import { getSession, isSessionModelWithWidgets, resolveNamedRegions, } from '@jb
|
|
|
2
2
|
import { coerceHighlight } from '@jbrowse/core/util/highlights';
|
|
3
3
|
import { installInitAutorun } from '@jbrowse/core/util/installInitAutorun';
|
|
4
4
|
import { normalizeTrackInit, warmTrackDisplayGeneric, } from '@jbrowse/core/util/tracks';
|
|
5
|
+
import { requestedDisplayType } from '@jbrowse/launch-args/displaySettings';
|
|
5
6
|
import { addDisposer } from '@jbrowse/mobx-state-tree';
|
|
6
7
|
import { autorun, untracked, when } from 'mobx';
|
|
8
|
+
import { launchTrackInit } from '../TrackModifiers/index.js';
|
|
7
9
|
import { SearchResultsNotFoundError } from '../searchUtils.js';
|
|
8
10
|
// `init` is a frozen blob that often comes from hand-authored JSON, where a
|
|
9
11
|
// lone entry gets written bare (`tracks: 'genes'`). A string is iterable, so
|
|
@@ -127,8 +129,7 @@ async function navigateInit(self, session, init) {
|
|
|
127
129
|
// display's state model first when it is registered lazily
|
|
128
130
|
async function showInitTracks(self, init) {
|
|
129
131
|
for (const t of asArray(init.tracks)) {
|
|
130
|
-
|
|
131
|
-
await self.launchTrack(trackId, trackSnapshot, displaySnapshot);
|
|
132
|
+
await launchTrackInit(self, t);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
134
135
|
/**
|
|
@@ -219,8 +220,9 @@ function setupInitWarmup(self) {
|
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
for (const t of tracks) {
|
|
222
|
-
const { trackId, displaySnapshot } = normalizeTrackInit(t);
|
|
223
|
-
|
|
223
|
+
const { trackId, displaySnapshot, modifiers } = normalizeTrackInit(t);
|
|
224
|
+
const type = requestedDisplayType(modifiers);
|
|
225
|
+
warmTrackDisplayGeneric(self, trackId, type ? { ...displaySnapshot, type } : displaySnapshot, assembly);
|
|
224
226
|
}
|
|
225
227
|
});
|
|
226
228
|
}, { name: 'LGVInitWarmup' }));
|
|
@@ -20,9 +20,8 @@ const useStyles = makeStyles()(theme => ({
|
|
|
20
20
|
const LinearGenomeViewImportForm = observer(function LinearGenomeViewImportForm({ model, }) {
|
|
21
21
|
const { classes } = useStyles();
|
|
22
22
|
const session = getSession(model);
|
|
23
|
-
const { textSearchManager } = session;
|
|
24
23
|
const { error: viewError } = model;
|
|
25
|
-
const { selectedAssemblyName: selectedAsm, setSelectedAssemblyName,
|
|
24
|
+
const { selectedAssemblyName: selectedAsm, setSelectedAssemblyName, assemblyError, regions, } = useAssemblySelection(session, 'lgv');
|
|
26
25
|
const { recentLocations, addRecentLocation, clearRecentLocations } = useRecentLocations(selectedAsm);
|
|
27
26
|
// The location the form will open, tagged with the assembly it was entered
|
|
28
27
|
// for, and read back only while that tag still matches. Switching assembly
|
|
@@ -85,9 +84,8 @@ const LinearGenomeViewImportForm = observer(function LinearGenomeViewImportForm(
|
|
|
85
84
|
model.setError(undefined);
|
|
86
85
|
setSelectedAssemblyName(val);
|
|
87
86
|
}, session: session, selected: selectedAsm }), selectedAsm ? (assemblyError ? (_jsx(CloseIcon, { color: "error" })) : regions ? (_jsx(RefNameAutocomplete, { fetchResults: (queryString, signal) => fetchResults({
|
|
87
|
+
session,
|
|
88
88
|
queryString,
|
|
89
|
-
assembly,
|
|
90
|
-
textSearchManager,
|
|
91
89
|
assemblyName: selectedAsm,
|
|
92
90
|
signal,
|
|
93
91
|
}), session: session, assemblyName: selectedAsm, value: value, minWidth: 270, adornmentWidth: adornmentReservePx({
|
|
@@ -99,10 +99,12 @@ const LinearGenomeViewContainer = observer(function LinearGenomeViewContainer({
|
|
|
99
99
|
// alone. At most one display can be under the pointer, so the first
|
|
100
100
|
// non-empty answer is the answer.
|
|
101
101
|
const hoverFeature = tracks
|
|
102
|
-
.flatMap(t => t.displays)
|
|
102
|
+
.flatMap((t) => t.displays)
|
|
103
103
|
.map(d => d.hoveredFeature)
|
|
104
104
|
.find(Boolean);
|
|
105
|
-
|
|
105
|
+
// `viewId` lets a view paired with one linear view ignore another
|
|
106
|
+
// showing the same locus
|
|
107
|
+
session.setHovered({ hoverPosition, hoverFeature, viewId: model.id });
|
|
106
108
|
}, 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 })) : (_jsx(TracksContainer, { model: model, children: !tracks.length ? (_jsx(NoTracksActiveButton, { model: model })) : (_jsxs(_Fragment, { children: [pinnedTracks.length ? (_jsx(Paper, { elevation: 2, "data-testid": "pinned-tracks", className: classes.pinnedTracks, style: {
|
|
107
109
|
top: stickyTops.pinnedTracks,
|
|
108
110
|
// the scroll port this sticks in, not the window: a
|
|
@@ -34,10 +34,8 @@ export function searchBoxWidth(value) {
|
|
|
34
34
|
const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth = MIN_WIDTH, maxWidth = MAX_WIDTH, style = defaultStyle, }) {
|
|
35
35
|
const palette = usePalette();
|
|
36
36
|
const session = getSession(model);
|
|
37
|
-
const { textSearchManager, assemblyManager } = session;
|
|
38
37
|
const { assemblyNames } = model;
|
|
39
38
|
const assemblyName = assemblyNames[0];
|
|
40
|
-
const assembly = assemblyManager.get(assemblyName);
|
|
41
39
|
const { recentLocations, addRecentLocation, clearRecentLocations } = useRecentLocations(assemblyName);
|
|
42
40
|
async function navigate(option) {
|
|
43
41
|
try {
|
|
@@ -57,13 +55,7 @@ const SearchBox = observer(function SearchBox({ model, showHelp = true, minWidth
|
|
|
57
55
|
});
|
|
58
56
|
return (_jsx(RefNameAutocomplete, { onSelect: option => {
|
|
59
57
|
void navigate(option);
|
|
60
|
-
}, assemblyName: assemblyName, fetchResults: (queryString, signal) => fetchResults({
|
|
61
|
-
queryString,
|
|
62
|
-
assemblyName,
|
|
63
|
-
textSearchManager,
|
|
64
|
-
assembly,
|
|
65
|
-
signal,
|
|
66
|
-
}), session: session, value: model.coarseVisibleLocStrings, minWidth: minWidth, maxWidth: maxWidth, adornmentWidth: adornmentReservePx({
|
|
58
|
+
}, assemblyName: assemblyName, fetchResults: (queryString, signal) => fetchResults({ session, queryString, assemblyName, signal }), session: session, value: model.coarseVisibleLocStrings, minWidth: minWidth, maxWidth: maxWidth, adornmentWidth: adornmentReservePx({
|
|
67
59
|
showHelp,
|
|
68
60
|
menuItemCount: recentMenuItems.length,
|
|
69
61
|
}), style: style, endAdornment: _jsx(RefNameAutocompleteEndAdornment, { showHelp: showHelp, menuItems: recentMenuItems }), inputStyle: {
|
|
@@ -5,6 +5,6 @@ import { observer } from 'mobx-react';
|
|
|
5
5
|
import SearchResultsTable from './SearchResultsTable.js';
|
|
6
6
|
const SearchResultsDialog = observer(function SearchResultsDialog({ model, }) {
|
|
7
7
|
const { searchPicker } = model;
|
|
8
|
-
return searchPicker ? (_jsxs(InfoDialog, { open: true, maxWidth: "xl", onClose: model.closeSearchPicker, title: "Search results", children: [_jsxs(Typography, { children: ["
|
|
8
|
+
return searchPicker ? (_jsxs(InfoDialog, { open: true, maxWidth: "xl", onClose: model.closeSearchPicker, title: "Search results", children: [_jsxs(Typography, { children: [searchPicker.rows.length, " matches for ", _jsx("b", { children: searchPicker.query })] }), _jsx(SearchResultsTable, { picker: searchPicker })] })) : null;
|
|
9
9
|
});
|
|
10
10
|
export default SearchResultsDialog;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { SearchPickerState } from '../model.ts';
|
|
1
|
+
import type { SearchPickerRow, SearchPickerState } from '../model.ts';
|
|
2
|
+
export declare function filterRows(rows: SearchPickerRow[], text: string): SearchPickerRow[];
|
|
2
3
|
declare const SearchResultsTable: ({ picker, }: {
|
|
3
4
|
picker: SearchPickerState;
|
|
4
5
|
}) => import("react").JSX.Element;
|
|
@@ -1,10 +1,28 @@
|
|
|
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
|
+
import { useState } from 'react';
|
|
2
3
|
import { SanitizedHTML } from '@jbrowse/core/ui';
|
|
3
|
-
import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, } from '@mui/material';
|
|
4
|
+
import { Button, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, } from '@mui/material';
|
|
4
5
|
import { observer } from 'mobx-react';
|
|
6
|
+
// past this many rows a list is easier to narrow than to read; gene names
|
|
7
|
+
// sort too irregularly (scn1ab, HOXA10-AS) for an order to stand in for it
|
|
8
|
+
const FILTER_AT = 8;
|
|
9
|
+
export function filterRows(rows, text) {
|
|
10
|
+
const needle = text.trim().toLowerCase();
|
|
11
|
+
return needle
|
|
12
|
+
? rows.filter(row => [row.label, row.location, row.trackName].some(field => field.toLowerCase().includes(needle)))
|
|
13
|
+
: rows;
|
|
14
|
+
}
|
|
5
15
|
const SearchResultsTable = observer(function SearchResultsTable({ picker, }) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
16
|
+
const [filter, setFilter] = useState('');
|
|
17
|
+
const rows = filterRows(picker.rows, filter);
|
|
18
|
+
return (_jsxs(_Fragment, { children: [picker.rows.length > FILTER_AT ? (_jsx(TextField, { autoFocus: true, fullWidth: true, size: "small", margin: "dense", placeholder: "Filter by name, location or track", value: filter, onChange: event => {
|
|
19
|
+
setFilter(event.target.value);
|
|
20
|
+
}, onKeyDown: event => {
|
|
21
|
+
if (event.key === 'Enter' && rows.length === 1) {
|
|
22
|
+
void picker.pick(rows[0].id);
|
|
23
|
+
}
|
|
24
|
+
}, helperText: filter ? `${rows.length} of ${picker.rows.length}` : undefined })) : null, _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: rows.map(row => (_jsxs(TableRow, { children: [_jsx(TableCell, { component: "th", scope: "row", children: row.label }), _jsx(TableCell, { align: "right", children: row.location }), _jsx(TableCell, { align: "right", children: _jsx(SanitizedHTML, { html: row.trackName || 'N/A' }) }), _jsx(TableCell, { align: "right", children: _jsx(Button, { onClick: () => {
|
|
25
|
+
void picker.pick(row.id);
|
|
26
|
+
}, color: "primary", variant: "contained", children: "Go" }) })] }, row.id))) })] }) })] }));
|
|
9
27
|
});
|
|
10
28
|
export default SearchResultsTable;
|
|
@@ -15,8 +15,7 @@ function isFeatureWidgetForView(w, viewId) {
|
|
|
15
15
|
// is owned by the display rather than by a widget — so the view's own displays
|
|
16
16
|
// publish hover positions too.
|
|
17
17
|
function displayHoverPosition(display) {
|
|
18
|
-
|
|
19
|
-
return candidate?.sequenceHoverPosition;
|
|
18
|
+
return display.sequenceHoverPosition;
|
|
20
19
|
}
|
|
21
20
|
// Draws a crosshair on the LGV at the base a connected feature-detail sequence
|
|
22
21
|
// panel is hovering, so mousing over the sequence readout points at the genome.
|
|
@@ -24,7 +23,7 @@ const SequenceFeatureHoverHighlight = observer(function SequenceFeatureHoverHigh
|
|
|
24
23
|
return (_jsxs(_Fragment, { children: [_jsx(ConnectedHoverHighlight, { model: model, getPosition: (widget, viewId) => isFeatureWidgetForView(widget, viewId)
|
|
25
24
|
? widget.sequenceHoverPosition
|
|
26
25
|
: undefined }), model.tracks
|
|
27
|
-
.flatMap(track => track.displays)
|
|
26
|
+
.flatMap((track) => track.displays)
|
|
28
27
|
.flatMap(display => {
|
|
29
28
|
const position = displayHoverPosition(display);
|
|
30
29
|
return position
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { LinearGenomeViewModel } from '../index.ts';
|
|
2
|
-
import type { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
1
|
+
import type { LinearGenomeViewModel, LinearGenomeViewTrack } from '../index.ts';
|
|
3
2
|
type LGV = LinearGenomeViewModel;
|
|
4
3
|
declare const TrackContainer: ({ model, track, }: {
|
|
5
4
|
model: LGV;
|
|
6
|
-
track:
|
|
5
|
+
track: LinearGenomeViewTrack;
|
|
7
6
|
}) => import("react").JSX.Element;
|
|
8
7
|
export default TrackContainer;
|
|
@@ -111,9 +111,8 @@ function useTrackLabelBand(model, trackId) {
|
|
|
111
111
|
}
|
|
112
112
|
const TrackContainer = observer(function TrackContainer({ model, track, }) {
|
|
113
113
|
const { classes } = useStyles();
|
|
114
|
-
//
|
|
115
|
-
// displays
|
|
116
|
-
// than repeated, so a track's chrome and its body can't read two displays.
|
|
114
|
+
// read once and handed down, so a track's chrome and its body can't read two
|
|
115
|
+
// displays
|
|
117
116
|
const display = track.activeDisplay;
|
|
118
117
|
const { showTrackOutlines } = model;
|
|
119
118
|
// The Paper's 1px border, present iff outlines are on. Everything laid out in
|
|
@@ -135,7 +134,7 @@ const TrackContainer = observer(function TrackContainer({ model, track, }) {
|
|
|
135
134
|
const { setPaper, setContainer } = useTrackLabelBand(model, track.trackId);
|
|
136
135
|
return (_jsxs(Paper, { ref: setPaper, className: cx(classes.root, track.pinned ? null : classes.unpinnedTrack), variant: showTrackOutlines ? 'outlined' : undefined, elevation: showTrackOutlines ? undefined : 0, onDragOver: event => {
|
|
137
136
|
model.onTrackDragOver(track.id, event.clientY);
|
|
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, "data-gesture-owner": "true", 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),
|
|
137
|
+
}, 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 }, labelBand: model.trackLabelBand(track), children: _jsx(ErrorBoundary, { FallbackComponent: e => (_jsx("div", { className: classes.crashBanner, "data-gesture-owner": "true", 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),
|
|
139
138
|
// the drag's shortcut: grow the track by whatever it is scrolled over,
|
|
140
139
|
// so a pileup showing three rows of forty opens to all forty
|
|
141
140
|
// Optional: only a display that scrolls its own content has anything
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
2
2
|
/**
|
|
3
|
-
* The track label's warning for a file
|
|
4
|
-
* common with the assembly's
|
|
5
|
-
*
|
|
6
|
-
* exactly like a track with no features in view.
|
|
3
|
+
* The track label's warning for a file that does not fit its assembly: its
|
|
4
|
+
* reference names have nothing in common with the assembly's, so the track
|
|
5
|
+
* draws nothing, or its contig lengths say it was made against another build.
|
|
7
6
|
*
|
|
8
7
|
* The label rather than the display chrome, deliberately. The chrome's states
|
|
9
8
|
* are `displayPhase`'s, which are mutually exclusive and two of which replace
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { refNameMismatchMessage } from '@jbrowse/core/assemblyManager/assembly';
|
|
3
|
+
import { contigLengthMismatchMessage, refNameMismatchMessage, } from '@jbrowse/core/assemblyManager/assembly';
|
|
4
4
|
import { InfoDialog } from '@jbrowse/core/ui';
|
|
5
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
6
|
import WarningIcon from '@mui/icons-material/Warning';
|
|
@@ -12,11 +12,24 @@ const useStyles = makeStyles()(theme => ({
|
|
|
12
12
|
color: theme.palette.warning.main,
|
|
13
13
|
},
|
|
14
14
|
}));
|
|
15
|
+
function warningFor(track) {
|
|
16
|
+
const { refNameMismatch, contigLengthMismatch } = track;
|
|
17
|
+
return refNameMismatch
|
|
18
|
+
? {
|
|
19
|
+
title: 'Reference names do not match the assembly',
|
|
20
|
+
message: refNameMismatchMessage(refNameMismatch),
|
|
21
|
+
}
|
|
22
|
+
: contigLengthMismatch
|
|
23
|
+
? {
|
|
24
|
+
title: 'Reference sequence lengths do not match the assembly',
|
|
25
|
+
message: contigLengthMismatchMessage(contigLengthMismatch),
|
|
26
|
+
}
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
15
29
|
/**
|
|
16
|
-
* The track label's warning for a file
|
|
17
|
-
* common with the assembly's
|
|
18
|
-
*
|
|
19
|
-
* exactly like a track with no features in view.
|
|
30
|
+
* The track label's warning for a file that does not fit its assembly: its
|
|
31
|
+
* reference names have nothing in common with the assembly's, so the track
|
|
32
|
+
* draws nothing, or its contig lengths say it was made against another build.
|
|
20
33
|
*
|
|
21
34
|
* The label rather than the display chrome, deliberately. The chrome's states
|
|
22
35
|
* are `displayPhase`'s, which are mutually exclusive and two of which replace
|
|
@@ -32,15 +45,15 @@ const useStyles = makeStyles()(theme => ({
|
|
|
32
45
|
const TrackLabelRefNameWarning = observer(function TrackLabelRefNameWarning({ track, }) {
|
|
33
46
|
const { classes } = useStyles();
|
|
34
47
|
const [open, setOpen] = useState(false);
|
|
35
|
-
const
|
|
36
|
-
if (!
|
|
48
|
+
const warning = warningFor(track);
|
|
49
|
+
if (!warning) {
|
|
37
50
|
return null;
|
|
38
51
|
}
|
|
39
|
-
const message =
|
|
52
|
+
const { title, message } = warning;
|
|
40
53
|
return (_jsxs(_Fragment, { children: [_jsx(IconButton, { onClick: () => {
|
|
41
54
|
setOpen(true);
|
|
42
|
-
}, className: classes.iconButton, title: message, "data-testid": `track-refname-warning-${track.trackId}`, children: _jsx(WarningIcon, { fontSize: "small" }) }), open ? (_jsxs(InfoDialog, { open: true, title:
|
|
55
|
+
}, className: classes.iconButton, title: message, "data-testid": `track-refname-warning-${track.trackId}`, children: _jsx(WarningIcon, { fontSize: "small" }) }), open ? (_jsxs(InfoDialog, { open: true, title: title, onClose: () => {
|
|
43
56
|
setOpen(false);
|
|
44
|
-
}, children: [_jsx(Typography, { children: message }), _jsx(Typography, { variant: "body2", style: { marginTop: 8 }, children: "The track menu's \"About track\" shows every reference name the file uses." })] })) : null] }));
|
|
57
|
+
}, children: [_jsx(Typography, { children: message }), _jsx(Typography, { variant: "body2", style: { marginTop: 8 }, children: "The track menu's \"About track\" shows every reference name the file uses, with the lengths it states." })] })) : null] }));
|
|
45
58
|
});
|
|
46
59
|
export default TrackLabelRefNameWarning;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import type { LinearGenomeViewModel } from '../index.ts';
|
|
2
|
-
import type { BaseTrackModel } from '@jbrowse/core/pluggableElementTypes/models';
|
|
1
|
+
import type { LinearGenomeViewModel, LinearGenomeViewTrack } from '../index.ts';
|
|
3
2
|
import type { LinearDisplayModel } from '@jbrowse/display-kit/types';
|
|
4
3
|
type LGV = LinearGenomeViewModel;
|
|
5
4
|
declare const TrackRenderingContainer: ({ model, track, display, ref, }: {
|
|
6
5
|
model: LGV;
|
|
7
|
-
track:
|
|
6
|
+
track: LinearGenomeViewTrack;
|
|
8
7
|
display: LinearDisplayModel;
|
|
9
8
|
ref?: React.Ref<HTMLDivElement>;
|
|
10
9
|
}) => import("react").JSX.Element;
|
|
@@ -18,7 +18,7 @@ const useStyles = makeStyles()({
|
|
|
18
18
|
width: '100%',
|
|
19
19
|
},
|
|
20
20
|
// Tracks never scroll natively on this outer container — every display owns
|
|
21
|
-
// its own vertical scroll:
|
|
21
|
+
// its own vertical scroll: feature displays scroll an inner sticky-canvas
|
|
22
22
|
// container (FeatureComponent), while alignments and variants draw a custom
|
|
23
23
|
// VerticalScrollbar overlay and redraw the canvas at `scrollTop`. A native
|
|
24
24
|
// scroll port here only produced a *second*, spurious scrollbar whenever a
|
|
@@ -22,7 +22,7 @@ export type SearchBoxPrefs = ReturnType<typeof useSearchBoxPrefs>;
|
|
|
22
22
|
/**
|
|
23
23
|
* The menu rows that drive those prefs. "Stacked" rather than "Vertical": the
|
|
24
24
|
* two headers name the same state and had drifted apart on it once already.
|
|
25
|
-
* A caller spreading the rows into a "Show
|
|
25
|
+
* A caller spreading the rows into a "Show" submenu passes `'Search boxes'`
|
|
26
26
|
* as the toggle's label.
|
|
27
27
|
*/
|
|
28
28
|
export declare function searchBoxMenuItems({ showSearchBoxes, setShowSearchBoxes, sideBySide, setSideBySide, }: SearchBoxPrefs, toggleLabel?: string): MenuItem[];
|
|
@@ -22,7 +22,7 @@ export function useSearchBoxPrefs(prefix, numViews) {
|
|
|
22
22
|
/**
|
|
23
23
|
* The menu rows that drive those prefs. "Stacked" rather than "Vertical": the
|
|
24
24
|
* two headers name the same state and had drifted apart on it once already.
|
|
25
|
-
* A caller spreading the rows into a "Show
|
|
25
|
+
* A caller spreading the rows into a "Show" submenu passes `'Search boxes'`
|
|
26
26
|
* as the toggle's label.
|
|
27
27
|
*/
|
|
28
28
|
export function searchBoxMenuItems({ showSearchBoxes, setShowSearchBoxes, sideBySide, setSideBySide, }, toggleLabel = 'Show search boxes') {
|
|
@@ -25,6 +25,6 @@ declare module '@jbrowse/core/PluginManager' {
|
|
|
25
25
|
}
|
|
26
26
|
export default function LinearGenomeViewF(pluginManager: PluginManager): void;
|
|
27
27
|
export { stateModelFactory } from './model.ts';
|
|
28
|
-
export type { LinearGenomeViewModel, LinearGenomeViewStateModel, } from './model.ts';
|
|
28
|
+
export type { LinearGenomeViewModel, LinearGenomeViewStateModel, LinearGenomeViewTrack, } from './model.ts';
|
|
29
29
|
export { installLinkedViewSync } from './linkedViewSync.ts';
|
|
30
30
|
export { default as HighlightChip } from './components/HighlightChip.tsx';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ViewType } from '@jbrowse/core/pluggableElementTypes';
|
|
2
2
|
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
3
|
+
import { lgvKeyboardShortcuts } from './keyboardHandler.js';
|
|
3
4
|
import { lgvLaunchKeys } from './launchKeys.js';
|
|
4
5
|
import { stateModelFactory } from './model.js';
|
|
5
6
|
export default function LinearGenomeViewF(pluginManager) {
|
|
@@ -13,6 +14,7 @@ export default function LinearGenomeViewF(pluginManager) {
|
|
|
13
14
|
displayName: 'Linear genome view',
|
|
14
15
|
stateModel,
|
|
15
16
|
launchKeys: lgvLaunchKeys,
|
|
17
|
+
keyboardShortcuts: lgvKeyboardShortcuts,
|
|
16
18
|
ReactComponent: lazyWithPreload(() => import('./components/LinearGenomeView.js')),
|
|
17
19
|
});
|
|
18
20
|
});
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { LinearGenomeViewModel } from './model.ts';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* - Ctrl/Cmd + Shift + M: go to the newest highlight
|
|
10
|
-
*/
|
|
2
|
+
import type { KeyboardShortcut } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
interface LgvShortcut extends KeyboardShortcut {
|
|
4
|
+
code: string;
|
|
5
|
+
shift?: boolean;
|
|
6
|
+
run: (self: LinearGenomeViewModel) => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const lgvKeyboardShortcuts: readonly LgvShortcut[];
|
|
11
9
|
export declare function setupKeyboardHandler(self: LinearGenomeViewModel): void;
|
|
10
|
+
export {};
|
|
@@ -1,48 +1,72 @@
|
|
|
1
1
|
import { getSession } from '@jbrowse/core/util';
|
|
2
2
|
import { isTextEntryFocused } from '@jbrowse/core/util/isTextEntryFocused';
|
|
3
3
|
import { addDisposer } from '@jbrowse/mobx-state-tree';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
// every entry also needs Ctrl or Cmd; an arrow fires with or without Shift
|
|
5
|
+
export const lgvKeyboardShortcuts = [
|
|
6
|
+
{
|
|
7
|
+
keys: 'Ctrl/Cmd + ←',
|
|
8
|
+
description: 'Pan left',
|
|
9
|
+
code: 'ArrowLeft',
|
|
10
|
+
run: self => {
|
|
11
|
+
self.slide(-0.9);
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
keys: 'Ctrl/Cmd + →',
|
|
16
|
+
description: 'Pan right',
|
|
17
|
+
code: 'ArrowRight',
|
|
18
|
+
run: self => {
|
|
19
|
+
self.slide(0.9);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
keys: 'Ctrl/Cmd + ↑',
|
|
24
|
+
description: 'Zoom in',
|
|
25
|
+
code: 'ArrowUp',
|
|
26
|
+
run: self => {
|
|
27
|
+
self.zoom(self.bpPerPx / 2);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
keys: 'Ctrl/Cmd + ↓',
|
|
32
|
+
description: 'Zoom out',
|
|
33
|
+
code: 'ArrowDown',
|
|
34
|
+
run: self => {
|
|
35
|
+
self.zoom(self.bpPerPx * 2);
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
keys: 'Ctrl/Cmd + Shift + D',
|
|
40
|
+
description: 'Highlight the region in view',
|
|
41
|
+
code: 'KeyD',
|
|
42
|
+
shift: true,
|
|
43
|
+
run: self => {
|
|
44
|
+
self.highlightCurrentRegion();
|
|
45
|
+
getSession(self).notify('Region highlighted.', 'success');
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
keys: 'Ctrl/Cmd + Shift + M',
|
|
50
|
+
description: 'Go to the newest highlight',
|
|
51
|
+
code: 'KeyM',
|
|
52
|
+
shift: true,
|
|
53
|
+
run: self => {
|
|
54
|
+
void self.navigateNewestHighlight();
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
];
|
|
13
58
|
export function setupKeyboardHandler(self) {
|
|
14
59
|
if (typeof document === 'undefined') {
|
|
15
60
|
return;
|
|
16
61
|
}
|
|
17
62
|
function handler(e) {
|
|
18
|
-
|
|
19
|
-
if (session.focusedViewId === self.id &&
|
|
63
|
+
if (getSession(self).focusedViewId === self.id &&
|
|
20
64
|
(e.ctrlKey || e.metaKey) &&
|
|
21
65
|
!isTextEntryFocused()) {
|
|
22
|
-
|
|
66
|
+
const shortcut = lgvKeyboardShortcuts.find(s => s.code === e.code && (!s.shift || e.shiftKey));
|
|
67
|
+
if (shortcut) {
|
|
23
68
|
e.preventDefault();
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
else if (e.code === 'ArrowRight') {
|
|
27
|
-
e.preventDefault();
|
|
28
|
-
self.slide(0.9);
|
|
29
|
-
}
|
|
30
|
-
else if (e.code === 'ArrowUp') {
|
|
31
|
-
e.preventDefault();
|
|
32
|
-
self.zoom(self.bpPerPx / 2);
|
|
33
|
-
}
|
|
34
|
-
else if (e.code === 'ArrowDown') {
|
|
35
|
-
e.preventDefault();
|
|
36
|
-
self.zoom(self.bpPerPx * 2);
|
|
37
|
-
}
|
|
38
|
-
else if (e.shiftKey && e.code === 'KeyD') {
|
|
39
|
-
e.preventDefault();
|
|
40
|
-
self.highlightCurrentRegion();
|
|
41
|
-
session.notify('Region highlighted.', 'success');
|
|
42
|
-
}
|
|
43
|
-
else if (e.shiftKey && e.code === 'KeyM') {
|
|
44
|
-
e.preventDefault();
|
|
45
|
-
void self.navigateNewestHighlight();
|
|
69
|
+
shortcut.run(self);
|
|
46
70
|
}
|
|
47
71
|
}
|
|
48
72
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Highlighter, TrackSelector as TrackSelectorIcon, } from '@jbrowse/core/ui/Icons';
|
|
2
|
-
import { radioItems, subMenuIfAny } from '@jbrowse/core/ui/menuItems';
|
|
2
|
+
import { highlightListMenuItems, radioItems, subMenuIfAny, } from '@jbrowse/core/ui/menuItems';
|
|
3
3
|
import { makeShowSubMenu } from '@jbrowse/core/ui/showSubMenu';
|
|
4
|
-
import { assembleLocStrings, getDialogHost, getSession, isSessionWithAddSessionTrack, toLocale, } from '@jbrowse/core/util';
|
|
4
|
+
import { assembleLocStrings, getDialogHost, getEnv, getSession, isSessionWithAddSessionTrack, toLocale, } from '@jbrowse/core/util';
|
|
5
5
|
import { basePaintedAt, computeMoveToLayout, } from '@jbrowse/core/util/Base1DUtils';
|
|
6
6
|
import { copyText } from '@jbrowse/core/util/copyText';
|
|
7
7
|
import { renameIds } from '@jbrowse/core/util/types/mst';
|
|
@@ -78,6 +78,7 @@ export function buildHighlightsSubMenuItems(self) {
|
|
|
78
78
|
session.setHighlightLabelsVisible(!session.highlightLabelsVisible);
|
|
79
79
|
},
|
|
80
80
|
},
|
|
81
|
+
...highlightListMenuItems(session, getEnv(self).pluginManager),
|
|
81
82
|
];
|
|
82
83
|
}
|
|
83
84
|
/**
|