@jbrowse/plugin-linear-genome-view 3.2.0 → 3.4.0
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/dist/BaseLinearDisplay/components/LinearBlocks.js +1 -1
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -1
- package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +22 -8
- package/dist/LinearBareDisplay/model.d.ts +1 -1
- package/dist/LinearBasicDisplay/components/AddFiltersDialog.js +1 -1
- package/dist/LinearBasicDisplay/model.d.ts +1 -1
- package/dist/LinearBasicDisplay/model.js +8 -2
- package/dist/LinearGenomeView/components/Cytobands.js +18 -4
- package/dist/LinearGenomeView/components/GetSequenceDialog.js +3 -2
- package/dist/LinearGenomeView/components/HeaderZoomControls.js +8 -2
- package/dist/LinearGenomeView/components/ImportForm.js +1 -1
- package/dist/LinearGenomeView/components/OverviewRubberband.js +0 -6
- package/dist/LinearGenomeView/components/OverviewRubberbandHoverTooltip.js +1 -9
- package/dist/LinearGenomeView/components/RefNameAutocomplete/index.js +16 -10
- package/dist/LinearGenomeView/components/RubberbandSpan.js +2 -2
- package/dist/LinearGenomeView/components/SearchBox.d.ts +1 -1
- package/dist/LinearGenomeView/components/SearchBox.js +29 -16
- package/dist/LinearGenomeView/components/useWheelScroll.js +13 -1
- package/dist/LinearGenomeView/components/util.d.ts +1 -0
- package/dist/LinearGenomeView/components/util.js +7 -4
- package/dist/LinearGenomeView/model.d.ts +4 -3
- package/dist/LinearGenomeView/model.js +12 -10
- package/dist/LinearGenomeView/util.d.ts +6 -1
- package/dist/LinearGenomeView/util.js +20 -6
- package/dist/index.d.ts +1 -1
- package/dist/searchUtils.js +1 -1
- package/esm/BaseLinearDisplay/components/LinearBlocks.js +1 -1
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -1
- package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +24 -10
- package/esm/LinearBareDisplay/model.d.ts +1 -1
- package/esm/LinearBasicDisplay/components/AddFiltersDialog.js +1 -1
- package/esm/LinearBasicDisplay/model.d.ts +1 -1
- package/esm/LinearBasicDisplay/model.js +8 -2
- package/esm/LinearGenomeView/components/Cytobands.js +18 -4
- package/esm/LinearGenomeView/components/GetSequenceDialog.js +3 -2
- package/esm/LinearGenomeView/components/HeaderZoomControls.js +9 -3
- package/esm/LinearGenomeView/components/ImportForm.js +1 -1
- package/esm/LinearGenomeView/components/OverviewRubberband.js +0 -6
- package/esm/LinearGenomeView/components/OverviewRubberbandHoverTooltip.js +1 -9
- package/esm/LinearGenomeView/components/RefNameAutocomplete/index.js +16 -10
- package/esm/LinearGenomeView/components/RubberbandSpan.js +3 -3
- package/esm/LinearGenomeView/components/SearchBox.d.ts +1 -1
- package/esm/LinearGenomeView/components/SearchBox.js +29 -16
- package/esm/LinearGenomeView/components/useWheelScroll.js +13 -1
- package/esm/LinearGenomeView/components/util.d.ts +1 -0
- package/esm/LinearGenomeView/components/util.js +7 -4
- package/esm/LinearGenomeView/model.d.ts +4 -3
- package/esm/LinearGenomeView/model.js +12 -10
- package/esm/LinearGenomeView/util.d.ts +6 -1
- package/esm/LinearGenomeView/util.js +20 -6
- package/esm/index.d.ts +1 -1
- package/esm/searchUtils.js +1 -1
- package/package.json +5 -5
package/dist/searchUtils.js
CHANGED
|
@@ -14,7 +14,7 @@ async function navToOption({ option, model, assemblyName, }) {
|
|
|
14
14
|
const location = option.getLocation();
|
|
15
15
|
const trackId = option.getTrackId();
|
|
16
16
|
if (location) {
|
|
17
|
-
await model.navToLocString(location, assemblyName);
|
|
17
|
+
await model.navToLocString(location, assemblyName, 0.2);
|
|
18
18
|
if (trackId) {
|
|
19
19
|
model.showTrack(trackId);
|
|
20
20
|
}
|
|
@@ -206,7 +206,7 @@ export declare const BaseLinearDisplay: import("mobx-state-tree").IModelType<{
|
|
|
206
206
|
} & {
|
|
207
207
|
addBlock(key: string, block: BaseBlock): void;
|
|
208
208
|
deleteBlock(key: string): void;
|
|
209
|
-
selectFeature(feature: Feature): void
|
|
209
|
+
selectFeature(feature: Feature): Promise<void>;
|
|
210
210
|
navToFeature(feature: Feature): void;
|
|
211
211
|
clearFeatureSelection(): void;
|
|
212
212
|
setFeatureIdUnderMouse(feature?: string): void;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { lazy } from 'react';
|
|
2
|
-
import { ConfigurationReference } from '@jbrowse/core/configuration';
|
|
2
|
+
import { ConfigurationReference, getConf } from '@jbrowse/core/configuration';
|
|
3
3
|
import { BaseDisplay } from '@jbrowse/core/pluggableElementTypes/models';
|
|
4
4
|
import { getContainingTrack, getContainingView, getSession, isFeature, isSelectionContainer, isSessionModelWithWidgets, } from '@jbrowse/core/util';
|
|
5
5
|
import CompositeMap from '@jbrowse/core/util/compositeMap';
|
|
6
|
-
import { getParentRenderProps } from '@jbrowse/core/util/tracks';
|
|
6
|
+
import { getParentRenderProps, getRpcSessionId, } from '@jbrowse/core/util/tracks';
|
|
7
7
|
import CenterFocusStrongIcon from '@mui/icons-material/CenterFocusStrong';
|
|
8
8
|
import MenuOpenIcon from '@mui/icons-material/MenuOpen';
|
|
9
9
|
import { autorun } from 'mobx';
|
|
@@ -99,15 +99,23 @@ function stateModelFactory() {
|
|
|
99
99
|
deleteBlock(key) {
|
|
100
100
|
self.blockState.delete(key);
|
|
101
101
|
},
|
|
102
|
-
selectFeature(feature) {
|
|
102
|
+
async selectFeature(feature) {
|
|
103
103
|
const session = getSession(self);
|
|
104
104
|
if (isSessionModelWithWidgets(session)) {
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
const { rpcManager } = session;
|
|
106
|
+
const sessionId = getRpcSessionId(self);
|
|
107
|
+
const track = getContainingTrack(self);
|
|
108
|
+
const view = getContainingView(self);
|
|
109
|
+
const adapterConfig = getConf(track, 'adapter');
|
|
110
|
+
const descriptions = await rpcManager.call(sessionId, 'CoreGetMetadata', {
|
|
111
|
+
adapterConfig,
|
|
109
112
|
});
|
|
110
|
-
session.showWidget(
|
|
113
|
+
session.showWidget(session.addWidget('BaseFeatureWidget', 'baseFeature', {
|
|
114
|
+
featureData: feature.toJSON(),
|
|
115
|
+
view,
|
|
116
|
+
track,
|
|
117
|
+
descriptions,
|
|
118
|
+
}));
|
|
111
119
|
}
|
|
112
120
|
if (isSelectionContainer(session)) {
|
|
113
121
|
session.setSelection(feature);
|
|
@@ -158,7 +166,11 @@ function stateModelFactory() {
|
|
|
158
166
|
icon: MenuOpenIcon,
|
|
159
167
|
onClick: () => {
|
|
160
168
|
if (self.contextMenuFeature) {
|
|
161
|
-
self
|
|
169
|
+
self
|
|
170
|
+
.selectFeature(self.contextMenuFeature)
|
|
171
|
+
.catch((e) => {
|
|
172
|
+
getSession(e).notifyError(`${e}`, e);
|
|
173
|
+
});
|
|
162
174
|
}
|
|
163
175
|
},
|
|
164
176
|
},
|
|
@@ -189,7 +201,9 @@ function stateModelFactory() {
|
|
|
189
201
|
else {
|
|
190
202
|
const feature = self.features.get(f);
|
|
191
203
|
if (feature) {
|
|
192
|
-
self.selectFeature(feature)
|
|
204
|
+
self.selectFeature(feature).catch((e) => {
|
|
205
|
+
getSession(e).notifyError(`${e}`, e);
|
|
206
|
+
});
|
|
193
207
|
}
|
|
194
208
|
}
|
|
195
209
|
},
|
|
@@ -190,7 +190,7 @@ export declare function stateModelFactory(configSchema: AnyConfigurationSchemaTy
|
|
|
190
190
|
} & {
|
|
191
191
|
addBlock(key: string, block: import("@jbrowse/core/util/blockTypes").BaseBlock): void;
|
|
192
192
|
deleteBlock(key: string): void;
|
|
193
|
-
selectFeature(feature: import("@jbrowse/core/util").Feature): void
|
|
193
|
+
selectFeature(feature: import("@jbrowse/core/util").Feature): Promise<void>;
|
|
194
194
|
navToFeature(feature: import("@jbrowse/core/util").Feature): void;
|
|
195
195
|
clearFeatureSelection(): void;
|
|
196
196
|
setFeatureIdUnderMouse(feature?: string): void;
|
|
@@ -41,7 +41,7 @@ const AddFiltersDialog = observer(function ({ model, handleClose, }) {
|
|
|
41
41
|
setError(e);
|
|
42
42
|
}
|
|
43
43
|
}, [data]);
|
|
44
|
-
return (_jsxs(Dialog, { maxWidth: "xl", open: true, onClose: handleClose, title: "Add track filters", children: [_jsxs(DialogContent, { children: [_jsxs("div", { children: ["Add filters, in jexl format, one per line, starting with the string jexl:. Examples:", ' ', _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'name')=='BRCA1'" }), " - show only feature where the name attribute is BRCA1"] }), _jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'type')=='gene'" }), " - show only gene type features in a GFF that has many other feature types"] }), _jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'score') > 400" }), " - show only features that have a score greater than 400"] })] })] }), error ? _jsx("p", { className: classes.error, children: `${error}` }) : null, _jsx(TextField, { variant: "outlined", multiline: true, minRows: 5, maxRows: 10, className: classes.dialogContent, fullWidth: true, value: data, onChange: event => {
|
|
44
|
+
return (_jsxs(Dialog, { maxWidth: "xl", open: true, onClose: handleClose, title: "Add track filters", children: [_jsxs(DialogContent, { children: [_jsxs("div", { children: ["Add filters, in jexl format, one per line, starting with the string jexl:. Examples:", ' ', _jsxs("ul", { children: [_jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'name')=='BRCA1'" }), " - show only feature where the name attribute is BRCA1"] }), _jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'type')=='gene'" }), " - show only gene type features in a GFF that has many other feature types"] }), _jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'score') > 400" }), " - show only features that have a score greater than 400"] }), _jsxs("li", { children: [_jsx("code", { children: "jexl:get(feature,'end') - get(feature,'start') < 1000000" }), ' ', "- show only features with length less than 1Mbp"] })] })] }), error ? _jsx("p", { className: classes.error, children: `${error}` }) : null, _jsx(TextField, { variant: "outlined", multiline: true, minRows: 5, maxRows: 10, className: classes.dialogContent, fullWidth: true, value: data, onChange: event => {
|
|
45
45
|
setData(event.target.value);
|
|
46
46
|
}, slotProps: {
|
|
47
47
|
input: {
|
|
@@ -198,7 +198,7 @@ declare function stateModelFactory(configSchema: AnyConfigurationSchemaType): im
|
|
|
198
198
|
} & {
|
|
199
199
|
addBlock(key: string, block: import("@jbrowse/core/util/blockTypes").BaseBlock): void;
|
|
200
200
|
deleteBlock(key: string): void;
|
|
201
|
-
selectFeature(feature: import("@jbrowse/core/util").Feature): void
|
|
201
|
+
selectFeature(feature: import("@jbrowse/core/util").Feature): Promise<void>;
|
|
202
202
|
navToFeature(feature: import("@jbrowse/core/util").Feature): void;
|
|
203
203
|
clearFeatureSelection(): void;
|
|
204
204
|
setFeatureIdUnderMouse(feature?: string): void;
|
|
@@ -127,7 +127,10 @@ function stateModelFactory(configSchema) {
|
|
|
127
127
|
onClick: () => {
|
|
128
128
|
getSession(self).queueDialog(handleClose => [
|
|
129
129
|
SetMaxHeightDialog,
|
|
130
|
-
{
|
|
130
|
+
{
|
|
131
|
+
model: self,
|
|
132
|
+
handleClose,
|
|
133
|
+
},
|
|
131
134
|
]);
|
|
132
135
|
},
|
|
133
136
|
},
|
|
@@ -136,7 +139,10 @@ function stateModelFactory(configSchema) {
|
|
|
136
139
|
onClick: () => {
|
|
137
140
|
getSession(self).queueDialog(handleClose => [
|
|
138
141
|
AddFiltersDialog,
|
|
139
|
-
{
|
|
142
|
+
{
|
|
143
|
+
model: self,
|
|
144
|
+
handleClose,
|
|
145
|
+
},
|
|
140
146
|
]);
|
|
141
147
|
},
|
|
142
148
|
},
|
|
@@ -9,14 +9,14 @@ function rightRoundedRect(x, y, width, height, radius) {
|
|
|
9
9
|
function leftRoundedRect(x, y, width, height, radius) {
|
|
10
10
|
return `M${x + radius},${y}h${width - radius}v${height}h${radius - width}a${radius},${radius} 0 0 1 ${-radius},${-radius}v${2 * radius - height}a${radius},${radius} 0 0 1 ${radius},${-radius}z`;
|
|
11
11
|
}
|
|
12
|
-
function leftTriangle(x,
|
|
12
|
+
function leftTriangle(x, _y, width, height) {
|
|
13
13
|
return [
|
|
14
14
|
[x, 0],
|
|
15
15
|
[x + width, height / 2],
|
|
16
16
|
[x, height],
|
|
17
17
|
].toString();
|
|
18
18
|
}
|
|
19
|
-
function rightTriangle(x,
|
|
19
|
+
function rightTriangle(x, _y, width, height) {
|
|
20
20
|
return [
|
|
21
21
|
[x, height / 2],
|
|
22
22
|
[x + width, 0],
|
|
@@ -40,14 +40,28 @@ const Cytobands = observer(function ({ overview, block, assembly, }) {
|
|
|
40
40
|
const rcap = reversed ? 0 : cytobands.length - 1;
|
|
41
41
|
const h = HEADER_OVERVIEW_HEIGHT;
|
|
42
42
|
let centromereSeen = false;
|
|
43
|
+
let curr = '';
|
|
44
|
+
let idx = 0;
|
|
43
45
|
return (_jsx("g", { transform: `translate(-${offsetPx})`, children: cytobands.map((args, index) => {
|
|
44
46
|
const k = JSON.stringify(args);
|
|
45
|
-
const { refName, type, start, end } = args;
|
|
47
|
+
const { refName, name, type, start, end } = args;
|
|
46
48
|
const s = overview.bpToPx({ refName, coord: start }) || 0;
|
|
47
49
|
const e = overview.bpToPx({ refName, coord: end }) || 0;
|
|
48
50
|
const l = Math.min(s, e);
|
|
49
51
|
const w = Math.abs(e - s);
|
|
50
|
-
|
|
52
|
+
if (type === 'n/a') {
|
|
53
|
+
const match = name === null || name === void 0 ? void 0 : name.match(/^(\d+)([A-Za-z])/);
|
|
54
|
+
const ret = match[1] + match[2];
|
|
55
|
+
if (ret && ret !== curr) {
|
|
56
|
+
curr = ret;
|
|
57
|
+
idx++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const c = type === 'n/a'
|
|
61
|
+
? idx % 2
|
|
62
|
+
? 'black'
|
|
63
|
+
: '#a77'
|
|
64
|
+
: colorMap[type] || 'black';
|
|
51
65
|
if (type === 'acen' && !centromereSeen) {
|
|
52
66
|
centromereSeen = true;
|
|
53
67
|
return (_jsx("polygon", { points: reversed
|
|
@@ -28,8 +28,9 @@ async function fetchSequence(model, regions) {
|
|
|
28
28
|
if (!leftOffset || !rightOffset) {
|
|
29
29
|
throw new Error('no offsets on model to use for range');
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
const assemblyNames = new Set(regions.map(r => r.assemblyName));
|
|
32
|
+
if (assemblyNames.size > 1) {
|
|
33
|
+
throw new Error('not able to fetch sequences from multiple assemblies currently');
|
|
33
34
|
}
|
|
34
35
|
const { rpcManager, assemblyManager } = session;
|
|
35
36
|
const assemblyName = leftOffset.assemblyName || rightOffset.assemblyName || '';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { lazy, useEffect, useState } from 'react';
|
|
3
3
|
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
4
|
-
import { getSession } from '@jbrowse/core/util';
|
|
4
|
+
import { getBpDisplayStr, getSession } from '@jbrowse/core/util';
|
|
5
5
|
import MoreVert from '@mui/icons-material/MoreVert';
|
|
6
6
|
import ZoomIn from '@mui/icons-material/ZoomIn';
|
|
7
7
|
import ZoomOut from '@mui/icons-material/ZoomOut';
|
|
@@ -20,9 +20,13 @@ const useStyles = makeStyles()(theme => ({
|
|
|
20
20
|
color: theme.palette.text.secondary,
|
|
21
21
|
},
|
|
22
22
|
}));
|
|
23
|
+
function ValueLabelComponent(props) {
|
|
24
|
+
const { children, open, value } = props;
|
|
25
|
+
return (_jsx(Tooltip, { open: open, enterTouchDelay: 0, placement: "top", title: value, arrow: true, children: children }));
|
|
26
|
+
}
|
|
23
27
|
const HeaderZoomControls = observer(function ({ model, }) {
|
|
24
28
|
const { classes } = useStyles();
|
|
25
|
-
const { maxBpPerPx, minBpPerPx, bpPerPx } = model;
|
|
29
|
+
const { width, maxBpPerPx, minBpPerPx, bpPerPx } = model;
|
|
26
30
|
const [value, setValue] = useState(-Math.log2(bpPerPx) * 100);
|
|
27
31
|
useEffect(() => {
|
|
28
32
|
setValue(-Math.log2(bpPerPx) * 100);
|
|
@@ -31,7 +35,9 @@ const HeaderZoomControls = observer(function ({ model, }) {
|
|
|
31
35
|
const zoomOutDisabled = bpPerPx >= maxBpPerPx - 0.0001;
|
|
32
36
|
return (_jsxs("div", { className: classes.container, children: [_jsx(Tooltip, { title: "Zoom out 2x", children: _jsx("span", { children: _jsx(IconButton, { "data-testid": "zoom_out", disabled: zoomOutDisabled, onClick: () => {
|
|
33
37
|
model.zoom(bpPerPx * 2);
|
|
34
|
-
}, children: _jsx(ZoomOut, {}) }) }) }), _jsx(Slider, { size: "small", className: classes.slider, value: value, min: -Math.log2(maxBpPerPx) * 100, max: -Math.log2(minBpPerPx) * 100, onChangeCommitted: () => model.zoomTo(2 ** (-value / 100)),
|
|
38
|
+
}, children: _jsx(ZoomOut, {}) }) }) }), _jsx(Slider, { size: "small", className: classes.slider, value: value, min: -Math.log2(maxBpPerPx) * 100, max: -Math.log2(minBpPerPx) * 100, onChangeCommitted: () => model.zoomTo(2 ** (-value / 100)), valueLabelDisplay: "auto", valueLabelFormat: newValue => `Window size: ${getBpDisplayStr(2 ** (-newValue / 100) * width)}`, slots: {
|
|
39
|
+
valueLabel: ValueLabelComponent,
|
|
40
|
+
}, onChange: (_, val) => {
|
|
35
41
|
setValue(val);
|
|
36
42
|
} }), _jsx(Tooltip, { title: "Zoom in 2x", children: _jsx("span", { children: _jsx(IconButton, { "data-testid": "zoom_in", disabled: zoomInDisabled, onClick: () => {
|
|
37
43
|
model.zoom(model.bpPerPx / 2);
|
|
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import { AssemblySelector, ErrorMessage } from '@jbrowse/core/ui';
|
|
4
4
|
import { getSession } from '@jbrowse/core/util';
|
|
5
5
|
import CloseIcon from '@mui/icons-material/Close';
|
|
6
|
-
import { Button, CircularProgress, Container, FormControl,
|
|
6
|
+
import { Button, CircularProgress, Container, FormControl, Grid as Grid, } from '@mui/material';
|
|
7
7
|
import { observer } from 'mobx-react';
|
|
8
8
|
import { makeStyles } from 'tss-react/mui';
|
|
9
9
|
import ImportFormRefNameAutocomplete from './ImportFormRefNameAutocomplete';
|
|
@@ -4,20 +4,12 @@ import { Tooltip } from '@mui/material';
|
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
import { makeStyles } from 'tss-react/mui';
|
|
6
6
|
const useStyles = makeStyles()({
|
|
7
|
-
rubberbandControl: {
|
|
8
|
-
cursor: 'crosshair',
|
|
9
|
-
width: '100%',
|
|
10
|
-
minHeight: 8,
|
|
11
|
-
},
|
|
12
7
|
guide: {
|
|
13
8
|
pointerEvents: 'none',
|
|
14
9
|
height: '100%',
|
|
15
10
|
width: 1,
|
|
16
11
|
position: 'absolute',
|
|
17
12
|
},
|
|
18
|
-
rel: {
|
|
19
|
-
position: 'relative',
|
|
20
|
-
},
|
|
21
13
|
});
|
|
22
14
|
const OverviewRubberbandHoverTooltip = observer(function ({ model, open, guideX, overview, }) {
|
|
23
15
|
var _a;
|
|
@@ -29,6 +21,6 @@ const OverviewRubberbandHoverTooltip = observer(function ({ model, open, guideX,
|
|
|
29
21
|
const cytoband = (_a = assembly === null || assembly === void 0 ? void 0 : assembly.cytobands) === null || _a === void 0 ? void 0 : _a.find(f => px.coord > f.get('start') &&
|
|
30
22
|
px.coord < f.get('end') &&
|
|
31
23
|
px.refName === assembly.getCanonicalRefName(f.get('refName')));
|
|
32
|
-
return (_jsx(Tooltip, { open: open, placement: "top", title: [stringify(px), cytoband === null || cytoband === void 0 ? void 0 : cytoband.get('name')].join(' '), arrow: true, children: _jsx("div", { className: classes.guide, style: { left: guideX } }) }));
|
|
24
|
+
return (_jsx(Tooltip, { open: open, placement: "top", title: [stringify(px), cytoband === null || cytoband === void 0 ? void 0 : cytoband.get('name'), cytoband === null || cytoband === void 0 ? void 0 : cytoband.get('type')].join(' '), arrow: true, children: _jsx("div", { className: classes.guide, style: { left: guideX } }) }));
|
|
33
25
|
});
|
|
34
26
|
export default OverviewRubberbandHoverTooltip;
|
|
@@ -25,27 +25,31 @@ const RefNameAutocomplete = observer(function ({ model, onSelect, assemblyName,
|
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
setLoaded(false);
|
|
28
|
-
|
|
29
|
-
setLoaded(true);
|
|
30
|
-
setSearchOptions(getDeduplicatedResult(results));
|
|
28
|
+
setSearchOptions(getDeduplicatedResult(await fetchResults(debouncedSearch)));
|
|
31
29
|
}
|
|
32
30
|
catch (e) {
|
|
33
31
|
console.error(e);
|
|
34
32
|
session.notifyError(`${e}`, e);
|
|
35
33
|
}
|
|
34
|
+
finally {
|
|
35
|
+
setLoaded(true);
|
|
36
|
+
}
|
|
36
37
|
})();
|
|
37
38
|
}, [assemblyName, fetchResults, debouncedSearch, session]);
|
|
38
39
|
const inputBoxVal = coarseVisibleLocStrings || value || '';
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const regionOptions = (refNames === null || refNames === void 0 ? void 0 : refNames.map(refName => ({
|
|
40
|
+
const regions = assembly === null || assembly === void 0 ? void 0 : assembly.regions;
|
|
41
|
+
const regionOptions = (regions === null || regions === void 0 ? void 0 : regions.map(region => ({
|
|
42
42
|
result: new RefSequenceResult({
|
|
43
|
-
refName,
|
|
44
|
-
label: refName,
|
|
43
|
+
refName: region.refName,
|
|
44
|
+
label: region.refName,
|
|
45
|
+
displayString: region.refName,
|
|
45
46
|
matchedAttribute: 'refName',
|
|
46
47
|
}),
|
|
47
48
|
}))) || [];
|
|
48
|
-
return (_jsx(Autocomplete, { "data-testid": "autocomplete", disableListWrap: true, disableClearable: true, disabled: !assemblyName, freeSolo: true, includeInputInList: true, selectOnFocus: true, style: {
|
|
49
|
+
return (_jsx(Autocomplete, { "data-testid": "autocomplete", disableListWrap: true, disableClearable: true, disabled: !assemblyName, freeSolo: true, includeInputInList: true, selectOnFocus: true, style: {
|
|
50
|
+
...style,
|
|
51
|
+
width: Math.min(Math.max(measureText(inputBoxVal, 14) + 100, minWidth), maxWidth),
|
|
52
|
+
}, value: inputBoxVal, loading: !loaded, inputValue: inputValue, onInputChange: (_event, newInputValue) => {
|
|
49
53
|
setInputValue(newInputValue);
|
|
50
54
|
onChange === null || onChange === void 0 ? void 0 : onChange(newInputValue);
|
|
51
55
|
}, loadingText: "loading results", open: open, onOpen: () => {
|
|
@@ -62,7 +66,9 @@ const RefNameAutocomplete = observer(function ({ model, onSelect, assemblyName,
|
|
|
62
66
|
return;
|
|
63
67
|
}
|
|
64
68
|
if (typeof selectedOption === 'string') {
|
|
65
|
-
onSelect === null || onSelect === void 0 ? void 0 : onSelect(new BaseResult({
|
|
69
|
+
onSelect === null || onSelect === void 0 ? void 0 : onSelect(new BaseResult({
|
|
70
|
+
label: selectedOption,
|
|
71
|
+
}));
|
|
66
72
|
}
|
|
67
73
|
else {
|
|
68
74
|
onSelect === null || onSelect === void 0 ? void 0 : onSelect(selectedOption.result);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { getBpDisplayStr, stringify } from '@jbrowse/core/util';
|
|
4
4
|
import { Typography, alpha } from '@mui/material';
|
|
5
5
|
import { makeStyles } from 'tss-react/mui';
|
|
6
6
|
import RubberbandTooltip from './RubberbandTooltip';
|
|
@@ -28,10 +28,10 @@ const useStyles = makeStyles()(theme => {
|
|
|
28
28
|
export default function RubberbandSpan({ leftBpOffset, rightBpOffset, numOfBpSelected, left, width, top = 0, sticky = false, }) {
|
|
29
29
|
const { classes } = useStyles();
|
|
30
30
|
const [anchorEl, setAnchorEl] = useState(null);
|
|
31
|
-
return (_jsxs(_Fragment, { children: [anchorEl ? (_jsxs(_Fragment, { children: [_jsx(RubberbandTooltip, { side: "left", anchorEl: anchorEl, text: stringify(leftBpOffset) }), _jsx(RubberbandTooltip, { side: "right", anchorEl: anchorEl, text: stringify(rightBpOffset) })] })) : null, _jsx("div", { className: classes.rubberband, style: { left, width }, children: numOfBpSelected ? (
|
|
31
|
+
return (_jsxs(_Fragment, { children: [anchorEl ? (_jsxs(_Fragment, { children: [_jsx(RubberbandTooltip, { side: "left", anchorEl: anchorEl, text: stringify(leftBpOffset) }), _jsx(RubberbandTooltip, { side: "right", anchorEl: anchorEl, text: stringify(rightBpOffset) })] })) : null, _jsx("div", { className: classes.rubberband, style: { left, width }, children: numOfBpSelected ? (_jsx(Typography, { ref: el => {
|
|
32
32
|
setAnchorEl(el);
|
|
33
33
|
}, variant: "h6", className: classes.rubberbandText, style: {
|
|
34
34
|
top,
|
|
35
35
|
position: sticky ? 'sticky' : undefined,
|
|
36
|
-
}, children:
|
|
36
|
+
}, children: getBpDisplayStr(numOfBpSelected) })) : null })] }));
|
|
37
37
|
}
|
|
@@ -8,11 +8,33 @@ import EndAdornment from './RefNameAutocomplete/EndAdornment';
|
|
|
8
8
|
import { fetchResults } from './util';
|
|
9
9
|
import { handleSelectedRegion, navToOption } from '../../searchUtils';
|
|
10
10
|
import { SPACING, WIDGET_HEIGHT } from '../consts';
|
|
11
|
-
const useStyles = makeStyles()(
|
|
11
|
+
const useStyles = makeStyles()({
|
|
12
12
|
headerRefName: {
|
|
13
13
|
minWidth: 100,
|
|
14
14
|
},
|
|
15
|
-
})
|
|
15
|
+
});
|
|
16
|
+
async function onSelect({ option, model, assemblyName, }) {
|
|
17
|
+
var _a;
|
|
18
|
+
const { assemblyManager } = getSession(model);
|
|
19
|
+
const assembly = assemblyManager.get(assemblyName);
|
|
20
|
+
if (option.hasLocation()) {
|
|
21
|
+
await navToOption({
|
|
22
|
+
option,
|
|
23
|
+
model,
|
|
24
|
+
assemblyName,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
else if ((_a = option.results) === null || _a === void 0 ? void 0 : _a.length) {
|
|
28
|
+
model.setSearchResults(option.results, option.getLabel());
|
|
29
|
+
}
|
|
30
|
+
else if (assembly) {
|
|
31
|
+
await handleSelectedRegion({
|
|
32
|
+
input: option.getLabel(),
|
|
33
|
+
assembly,
|
|
34
|
+
model,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
16
38
|
const SearchBox = observer(function ({ model, showHelp = true, }) {
|
|
17
39
|
const { classes } = useStyles();
|
|
18
40
|
const theme = useTheme();
|
|
@@ -23,21 +45,12 @@ const SearchBox = observer(function ({ model, showHelp = true, }) {
|
|
|
23
45
|
const assembly = assemblyManager.get(assemblyName);
|
|
24
46
|
const searchScope = model.searchScope(assemblyName);
|
|
25
47
|
return (_jsx(RefNameAutocomplete, { onSelect: async (option) => {
|
|
26
|
-
var _a;
|
|
27
48
|
try {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
else if (assembly) {
|
|
35
|
-
await handleSelectedRegion({
|
|
36
|
-
input: option.getLabel(),
|
|
37
|
-
assembly,
|
|
38
|
-
model,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
49
|
+
await onSelect({
|
|
50
|
+
model,
|
|
51
|
+
assemblyName,
|
|
52
|
+
option,
|
|
53
|
+
});
|
|
41
54
|
}
|
|
42
55
|
catch (e) {
|
|
43
56
|
console.error(e);
|
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
import { useEffect, useRef } from 'react';
|
|
2
|
+
import { sum } from '@jbrowse/core/util';
|
|
2
3
|
export function useWheelScroll(ref, model) {
|
|
3
4
|
const delta = useRef(0);
|
|
4
5
|
const timeout = useRef(null);
|
|
5
6
|
const scheduled = useRef(false);
|
|
6
7
|
useEffect(() => {
|
|
8
|
+
let samples = [];
|
|
7
9
|
const curr = ref.current;
|
|
8
10
|
function onWheel(event) {
|
|
9
11
|
if (event.ctrlKey) {
|
|
10
12
|
event.preventDefault();
|
|
11
|
-
|
|
13
|
+
samples.push(event.deltaY);
|
|
14
|
+
const averageDeltaY = Math.abs(sum(samples)) / samples.length;
|
|
15
|
+
const normalizer = averageDeltaY < 6
|
|
16
|
+
? 25
|
|
17
|
+
: averageDeltaY > 30
|
|
18
|
+
? averageDeltaY > 150
|
|
19
|
+
? 500
|
|
20
|
+
: 150
|
|
21
|
+
: 75;
|
|
22
|
+
delta.current += event.deltaY / normalizer;
|
|
12
23
|
model.setScaleFactor(delta.current < 0 ? 1 - delta.current : 1 / (1 + delta.current));
|
|
13
24
|
if (timeout.current) {
|
|
14
25
|
clearTimeout(timeout.current);
|
|
@@ -19,6 +30,7 @@ export function useWheelScroll(ref, model) {
|
|
|
19
30
|
? model.bpPerPx * (1 + delta.current)
|
|
20
31
|
: model.bpPerPx / (1 - delta.current), event.clientX - ((curr === null || curr === void 0 ? void 0 : curr.getBoundingClientRect().left) || 0));
|
|
21
32
|
delta.current = 0;
|
|
33
|
+
samples = [];
|
|
22
34
|
}, 300);
|
|
23
35
|
}
|
|
24
36
|
else {
|
|
@@ -5,12 +5,14 @@ export async function fetchResults({ queryString, searchType, searchScope, rankS
|
|
|
5
5
|
if (!textSearchManager) {
|
|
6
6
|
console.warn('No text search manager');
|
|
7
7
|
}
|
|
8
|
-
const textSearchResults = await (textSearchManager === null || textSearchManager === void 0 ? void 0 : textSearchManager.search({
|
|
8
|
+
const textSearchResults = (await (textSearchManager === null || textSearchManager === void 0 ? void 0 : textSearchManager.search({
|
|
9
9
|
queryString,
|
|
10
10
|
searchType,
|
|
11
|
-
}, searchScope, rankSearchResults));
|
|
12
|
-
const refNameResults = (_a = assembly === null || assembly === void 0 ? void 0 : assembly.allRefNames) === null || _a === void 0 ? void 0 : _a.filter(ref => ref.toLowerCase().startsWith(queryString.toLowerCase())).slice(0, 10).map(r => new BaseResult({
|
|
13
|
-
|
|
11
|
+
}, searchScope, rankSearchResults))) || [];
|
|
12
|
+
const refNameResults = ((_a = assembly === null || assembly === void 0 ? void 0 : assembly.allRefNames) === null || _a === void 0 ? void 0 : _a.filter(ref => ref.toLowerCase().startsWith(queryString.toLowerCase())).slice(0, 10).map(r => new BaseResult({
|
|
13
|
+
label: r,
|
|
14
|
+
}))) || [];
|
|
15
|
+
return dedupe([...refNameResults, ...textSearchResults], elt => elt.getId());
|
|
14
16
|
}
|
|
15
17
|
export function splitLast(str, split) {
|
|
16
18
|
const lastIndex = str.lastIndexOf(split);
|
|
@@ -33,5 +35,6 @@ export function getCytobands(assembly, refName) {
|
|
|
33
35
|
start: f.get('start'),
|
|
34
36
|
end: f.get('end'),
|
|
35
37
|
type: f.get('gieStain'),
|
|
38
|
+
name: f.get('name'),
|
|
36
39
|
})).filter(f => f.refName === refName)) || []);
|
|
37
40
|
}
|
|
@@ -169,12 +169,13 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
169
169
|
setCoarseDynamicBlocks(blocks: BlockSet): void;
|
|
170
170
|
} & {
|
|
171
171
|
moveTo(start?: BpOffset, end?: BpOffset): void;
|
|
172
|
-
navToLocString(input: string, optAssemblyName?: string): Promise<void>;
|
|
172
|
+
navToLocString(input: string, optAssemblyName?: string, grow?: number): Promise<void>;
|
|
173
173
|
navToSearchString({ input, assembly, }: {
|
|
174
174
|
input: string;
|
|
175
175
|
assembly: Assembly;
|
|
176
176
|
}): Promise<void>;
|
|
177
|
-
|
|
177
|
+
navToLocation(parsedLocString: ParsedLocString, assemblyName?: string, grow?: number): Promise<void>;
|
|
178
|
+
navToLocations(regions: ParsedLocString[], assemblyName?: string, grow?: number): Promise<void>;
|
|
178
179
|
navTo(query: NavLocation): void;
|
|
179
180
|
navToMultiple(locations: NavLocation[]): void;
|
|
180
181
|
} & {
|
|
@@ -242,9 +243,9 @@ export declare function stateModelFactory(pluginManager: PluginManager): import(
|
|
|
242
243
|
displayName: string | undefined;
|
|
243
244
|
tracks: any[];
|
|
244
245
|
minimized: boolean;
|
|
245
|
-
displayedRegions: Region[];
|
|
246
246
|
offsetPx: number;
|
|
247
247
|
bpPerPx: number;
|
|
248
|
+
displayedRegions: Region[];
|
|
248
249
|
hideHeader: boolean;
|
|
249
250
|
hideHeaderOverview: boolean;
|
|
250
251
|
hideNoTracksActive: boolean;
|
|
@@ -222,12 +222,6 @@ export function stateModelFactory(pluginManager) {
|
|
|
222
222
|
return self.tracks.find(t => t.configuration.trackId === id);
|
|
223
223
|
},
|
|
224
224
|
rankSearchResults(results) {
|
|
225
|
-
const openTrackIds = new Set(self.tracks.map(track => track.configuration.trackId));
|
|
226
|
-
for (const result of results) {
|
|
227
|
-
if (openTrackIds.has(result.trackId)) {
|
|
228
|
-
result.updateScore(result.getScore() + 1);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
225
|
return results;
|
|
232
226
|
},
|
|
233
227
|
rewriteOnClicks(trackType, viewMenuActions) {
|
|
@@ -800,14 +794,14 @@ export function stateModelFactory(pluginManager) {
|
|
|
800
794
|
moveTo(start, end) {
|
|
801
795
|
moveTo(self, start, end);
|
|
802
796
|
},
|
|
803
|
-
async navToLocString(input, optAssemblyName) {
|
|
797
|
+
async navToLocString(input, optAssemblyName, grow) {
|
|
804
798
|
const { assemblyNames } = self;
|
|
805
799
|
const { assemblyManager } = getSession(self);
|
|
806
800
|
const assemblyName = optAssemblyName || assemblyNames[0];
|
|
807
801
|
if (assemblyName) {
|
|
808
802
|
await assemblyManager.waitForAssembly(assemblyName);
|
|
809
803
|
}
|
|
810
|
-
return this.navToLocations(parseLocStrings(input, assemblyName, (ref, asm) => assemblyManager.isValidRefName(ref, asm)), assemblyName);
|
|
804
|
+
return this.navToLocations(parseLocStrings(input, assemblyName, (ref, asm) => assemblyManager.isValidRefName(ref, asm)), assemblyName, grow);
|
|
811
805
|
},
|
|
812
806
|
async navToSearchString({ input, assembly, }) {
|
|
813
807
|
await handleSelectedRegion({
|
|
@@ -816,10 +810,18 @@ export function stateModelFactory(pluginManager) {
|
|
|
816
810
|
model: self,
|
|
817
811
|
});
|
|
818
812
|
},
|
|
819
|
-
async
|
|
813
|
+
async navToLocation(parsedLocString, assemblyName, grow) {
|
|
814
|
+
return this.navToLocations([parsedLocString], assemblyName, grow);
|
|
815
|
+
},
|
|
816
|
+
async navToLocations(regions, assemblyName, grow) {
|
|
820
817
|
const { assemblyManager } = getSession(self);
|
|
821
818
|
await when(() => self.volatileWidth !== undefined);
|
|
822
|
-
const locations = await generateLocations(
|
|
819
|
+
const locations = await generateLocations({
|
|
820
|
+
regions,
|
|
821
|
+
assemblyManager,
|
|
822
|
+
assemblyName,
|
|
823
|
+
grow,
|
|
824
|
+
});
|
|
823
825
|
if (locations.length === 1) {
|
|
824
826
|
const loc = locations[0];
|
|
825
827
|
const { reversed, parentRegion, start, end } = loc;
|
|
@@ -9,7 +9,12 @@ export declare function makeTicks(start: number, end: number, bpPerPx: number, e
|
|
|
9
9
|
base: number;
|
|
10
10
|
index: number;
|
|
11
11
|
}[];
|
|
12
|
-
export declare function generateLocations(regions
|
|
12
|
+
export declare function generateLocations({ regions, assemblyManager, assemblyName, grow, }: {
|
|
13
|
+
regions: ParsedLocString[];
|
|
14
|
+
assemblyManager: AssemblyManager;
|
|
15
|
+
assemblyName?: string;
|
|
16
|
+
grow?: number;
|
|
17
|
+
}): Promise<{
|
|
13
18
|
assemblyName: string;
|
|
14
19
|
parentRegion: import("@jbrowse/core/assemblyManager/assembly").BasicRegion;
|
|
15
20
|
end?: number | undefined;
|