@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.
Files changed (53) hide show
  1. package/dist/BaseLinearDisplay/components/LinearBlocks.js +1 -1
  2. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -1
  3. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +22 -8
  4. package/dist/LinearBareDisplay/model.d.ts +1 -1
  5. package/dist/LinearBasicDisplay/components/AddFiltersDialog.js +1 -1
  6. package/dist/LinearBasicDisplay/model.d.ts +1 -1
  7. package/dist/LinearBasicDisplay/model.js +8 -2
  8. package/dist/LinearGenomeView/components/Cytobands.js +18 -4
  9. package/dist/LinearGenomeView/components/GetSequenceDialog.js +3 -2
  10. package/dist/LinearGenomeView/components/HeaderZoomControls.js +8 -2
  11. package/dist/LinearGenomeView/components/ImportForm.js +1 -1
  12. package/dist/LinearGenomeView/components/OverviewRubberband.js +0 -6
  13. package/dist/LinearGenomeView/components/OverviewRubberbandHoverTooltip.js +1 -9
  14. package/dist/LinearGenomeView/components/RefNameAutocomplete/index.js +16 -10
  15. package/dist/LinearGenomeView/components/RubberbandSpan.js +2 -2
  16. package/dist/LinearGenomeView/components/SearchBox.d.ts +1 -1
  17. package/dist/LinearGenomeView/components/SearchBox.js +29 -16
  18. package/dist/LinearGenomeView/components/useWheelScroll.js +13 -1
  19. package/dist/LinearGenomeView/components/util.d.ts +1 -0
  20. package/dist/LinearGenomeView/components/util.js +7 -4
  21. package/dist/LinearGenomeView/model.d.ts +4 -3
  22. package/dist/LinearGenomeView/model.js +12 -10
  23. package/dist/LinearGenomeView/util.d.ts +6 -1
  24. package/dist/LinearGenomeView/util.js +20 -6
  25. package/dist/index.d.ts +1 -1
  26. package/dist/searchUtils.js +1 -1
  27. package/esm/BaseLinearDisplay/components/LinearBlocks.js +1 -1
  28. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +1 -1
  29. package/esm/BaseLinearDisplay/models/BaseLinearDisplayModel.js +24 -10
  30. package/esm/LinearBareDisplay/model.d.ts +1 -1
  31. package/esm/LinearBasicDisplay/components/AddFiltersDialog.js +1 -1
  32. package/esm/LinearBasicDisplay/model.d.ts +1 -1
  33. package/esm/LinearBasicDisplay/model.js +8 -2
  34. package/esm/LinearGenomeView/components/Cytobands.js +18 -4
  35. package/esm/LinearGenomeView/components/GetSequenceDialog.js +3 -2
  36. package/esm/LinearGenomeView/components/HeaderZoomControls.js +9 -3
  37. package/esm/LinearGenomeView/components/ImportForm.js +1 -1
  38. package/esm/LinearGenomeView/components/OverviewRubberband.js +0 -6
  39. package/esm/LinearGenomeView/components/OverviewRubberbandHoverTooltip.js +1 -9
  40. package/esm/LinearGenomeView/components/RefNameAutocomplete/index.js +16 -10
  41. package/esm/LinearGenomeView/components/RubberbandSpan.js +3 -3
  42. package/esm/LinearGenomeView/components/SearchBox.d.ts +1 -1
  43. package/esm/LinearGenomeView/components/SearchBox.js +29 -16
  44. package/esm/LinearGenomeView/components/useWheelScroll.js +13 -1
  45. package/esm/LinearGenomeView/components/util.d.ts +1 -0
  46. package/esm/LinearGenomeView/components/util.js +7 -4
  47. package/esm/LinearGenomeView/model.d.ts +4 -3
  48. package/esm/LinearGenomeView/model.js +12 -10
  49. package/esm/LinearGenomeView/util.d.ts +6 -1
  50. package/esm/LinearGenomeView/util.js +20 -6
  51. package/esm/index.d.ts +1 -1
  52. package/esm/searchUtils.js +1 -1
  53. package/package.json +5 -5
@@ -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
  }
@@ -20,7 +20,7 @@ const useStyles = makeStyles()({
20
20
  width: '100%',
21
21
  fontWeight: 'bold',
22
22
  textAlign: 'center',
23
- zIndex: 2000,
23
+ zIndex: 999,
24
24
  boxSizing: 'border-box',
25
25
  },
26
26
  });
@@ -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 featureWidget = session.addWidget('BaseFeatureWidget', 'baseFeature', {
106
- view: getContainingView(self),
107
- track: getContainingTrack(self),
108
- featureData: feature.toJSON(),
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(featureWidget);
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.selectFeature(self.contextMenuFeature);
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
- { model: self, handleClose },
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
- { model: self, handleClose },
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, y, width, height) {
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, y, width, height) {
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
- const c = colorMap[type] || 'black';
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
- if (leftOffset.assemblyName !== rightOffset.assemblyName) {
32
- throw new Error('not able to fetch sequences from multiple assemblies');
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)), onChange: (_, val) => {
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, Grid2 as Grid, } from '@mui/material';
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';
@@ -12,12 +12,6 @@ const useStyles = makeStyles()({
12
12
  width: '100%',
13
13
  minHeight: 8,
14
14
  },
15
- guide: {
16
- pointerEvents: 'none',
17
- height: '100%',
18
- width: 1,
19
- position: 'absolute',
20
- },
21
15
  rel: {
22
16
  position: 'relative',
23
17
  },
@@ -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
- const results = await fetchResults(debouncedSearch);
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 width = Math.min(Math.max(measureText(inputBoxVal, 14) + 100, minWidth), maxWidth);
40
- const refNames = assembly === null || assembly === void 0 ? void 0 : assembly.refNames;
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: { ...style, width }, value: inputBoxVal, loading: !loaded, inputValue: inputValue, onInputChange: (_event, newInputValue) => {
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({ label: selectedOption }));
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 { stringify, toLocale } from '@jbrowse/core/util';
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 ? (_jsxs(Typography, { ref: el => {
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: [toLocale(numOfBpSelected), " bp"] })) : null })] }));
36
+ }, children: getBpDisplayStr(numOfBpSelected) })) : null })] }));
37
37
  }
@@ -1,4 +1,4 @@
1
- import type { LinearGenomeViewModel } from '..';
1
+ import type { LinearGenomeViewModel } from '../model';
2
2
  declare const SearchBox: ({ model, showHelp, }: {
3
3
  showHelp?: boolean;
4
4
  model: LinearGenomeViewModel;
@@ -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
- if (option.hasLocation()) {
29
- await navToOption({ option, model, assemblyName });
30
- }
31
- else if ((_a = option.results) === null || _a === void 0 ? void 0 : _a.length) {
32
- model.setSearchResults(option.results, option.getLabel());
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
- delta.current += event.deltaY / 500;
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 {
@@ -21,4 +21,5 @@ export declare function getCytobands(assembly: Assembly | undefined, refName: st
21
21
  start: number;
22
22
  end: number;
23
23
  type: string;
24
+ name: any;
24
25
  }[];
@@ -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({ label: r }));
13
- return dedupe([...(refNameResults || []), ...(textSearchResults || [])], elt => elt.getId());
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
- navToLocations(parsedLocStrings: ParsedLocString[], assemblyName?: string): Promise<void>;
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 navToLocations(parsedLocStrings, assemblyName) {
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(parsedLocStrings, assemblyManager, assemblyName);
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: ParsedLocString[], assemblyManager: AssemblyManager, assemblyName?: string): Promise<{
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;