@jbrowse/plugin-linear-genome-view 1.7.7 → 1.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.d.ts +1 -5
  2. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.js +32 -120
  3. package/dist/BaseLinearDisplay/components/Tooltip.d.ts +8 -0
  4. package/dist/BaseLinearDisplay/components/Tooltip.js +125 -0
  5. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +3 -3
  6. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.js +3 -4
  7. package/dist/LinearGenomeView/components/ExportSvgDialog.js +35 -25
  8. package/dist/LinearGenomeView/components/Header.js +5 -2
  9. package/dist/LinearGenomeView/components/HelpDialog.js +2 -3
  10. package/dist/LinearGenomeView/components/ImportForm.js +47 -47
  11. package/dist/LinearGenomeView/components/LinearGenomeView.js +6 -2
  12. package/dist/LinearGenomeView/components/LinearGenomeView.test.js +2 -2
  13. package/dist/LinearGenomeView/components/OverviewScaleBar.js +2 -2
  14. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +3 -2
  15. package/dist/LinearGenomeView/components/RefNameAutocomplete.js +7 -5
  16. package/dist/LinearGenomeView/components/ScaleBar.d.ts +8 -4
  17. package/dist/LinearGenomeView/components/ScaleBar.js +8 -3
  18. package/dist/LinearGenomeView/components/SearchBox.js +31 -22
  19. package/dist/LinearGenomeView/components/TrackLabel.js +25 -41
  20. package/dist/LinearGenomeView/index.d.ts +7 -11
  21. package/dist/LinearGenomeView/index.js +60 -33
  22. package/dist/LinearGenomeView/index.test.js +22 -5
  23. package/dist/index.js +22 -11
  24. package/package.json +3 -2
  25. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +4 -89
  26. package/src/BaseLinearDisplay/components/Tooltip.tsx +97 -0
  27. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +11 -4
  28. package/src/LinearGenomeView/components/ExportSvgDialog.tsx +24 -11
  29. package/src/LinearGenomeView/components/Header.tsx +3 -2
  30. package/src/LinearGenomeView/components/HelpDialog.tsx +5 -4
  31. package/src/LinearGenomeView/components/ImportForm.tsx +37 -32
  32. package/src/LinearGenomeView/components/LinearGenomeView.test.js +2 -2
  33. package/src/LinearGenomeView/components/LinearGenomeView.tsx +16 -10
  34. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +3 -4
  35. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +10 -5
  36. package/src/LinearGenomeView/components/ScaleBar.tsx +6 -9
  37. package/src/LinearGenomeView/components/SearchBox.tsx +20 -4
  38. package/src/LinearGenomeView/components/TrackLabel.tsx +25 -28
  39. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +4 -21
  40. package/src/LinearGenomeView/index.test.ts +20 -5
  41. package/src/LinearGenomeView/index.tsx +56 -27
  42. package/src/index.ts +35 -30
@@ -0,0 +1,97 @@
1
+ import React, { useState, useMemo } from 'react'
2
+ import { getConf } from '@jbrowse/core/configuration'
3
+ import { observer } from 'mobx-react'
4
+ import { Portal, alpha, makeStyles } from '@material-ui/core'
5
+ import { usePopper } from 'react-popper'
6
+
7
+ // locals
8
+ import { BaseLinearDisplayModel } from '../models/BaseLinearDisplayModel'
9
+
10
+ function round(value: number) {
11
+ return Math.round(value * 1e5) / 1e5
12
+ }
13
+ const useStyles = makeStyles(theme => ({
14
+ // these styles come from
15
+ // https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Tooltip/Tooltip.js
16
+ tooltip: {
17
+ pointerEvents: 'none',
18
+ backgroundColor: alpha(theme.palette.grey[700], 0.9),
19
+ borderRadius: theme.shape.borderRadius,
20
+ color: theme.palette.common.white,
21
+ fontFamily: theme.typography.fontFamily,
22
+ padding: '4px 8px',
23
+ fontSize: theme.typography.pxToRem(12),
24
+ lineHeight: `${round(14 / 10)}em`,
25
+ maxWidth: 300,
26
+ wordWrap: 'break-word',
27
+ },
28
+ }))
29
+
30
+ const TooltipContents = React.forwardRef<
31
+ HTMLDivElement,
32
+ { message: React.ReactNode | string }
33
+ >(({ message }: { message: React.ReactNode | string }, ref) => {
34
+ return <div ref={ref}>{message}</div>
35
+ })
36
+
37
+ type Coord = [number, number]
38
+ const Tooltip = observer(
39
+ ({
40
+ model,
41
+ clientMouseCoord,
42
+ }: {
43
+ model: BaseLinearDisplayModel
44
+ clientMouseCoord: Coord
45
+ }) => {
46
+ const classes = useStyles()
47
+ const { featureUnderMouse } = model
48
+ const [width, setWidth] = useState(0)
49
+ const [popperElt, setPopperElt] = useState<HTMLDivElement | null>(null)
50
+
51
+ // must be memoized a la https://github.com/popperjs/react-popper/issues/391
52
+ const virtElement = useMemo(
53
+ () => ({
54
+ getBoundingClientRect: () => {
55
+ const x = clientMouseCoord[0] + width / 2 + 20
56
+ const y = clientMouseCoord[1]
57
+ return {
58
+ top: y,
59
+ left: x,
60
+ bottom: y,
61
+ right: x,
62
+ width: 0,
63
+ height: 0,
64
+ x,
65
+ y,
66
+ toJSON() {},
67
+ }
68
+ },
69
+ }),
70
+ [clientMouseCoord, width],
71
+ )
72
+ const { styles, attributes } = usePopper(virtElement, popperElt)
73
+
74
+ const contents = featureUnderMouse
75
+ ? getConf(model, 'mouseover', { feature: featureUnderMouse })
76
+ : undefined
77
+
78
+ return featureUnderMouse && contents ? (
79
+ <Portal>
80
+ <div
81
+ ref={setPopperElt}
82
+ className={classes.tooltip}
83
+ // zIndex needed to go over widget drawer
84
+ style={{ ...styles.popper, zIndex: 100000 }}
85
+ {...attributes.popper}
86
+ >
87
+ <TooltipContents
88
+ ref={elt => setWidth(elt?.getBoundingClientRect().width || 0)}
89
+ message={contents}
90
+ />
91
+ </div>
92
+ </Portal>
93
+ ) : null
94
+ },
95
+ )
96
+
97
+ export default Tooltip
@@ -7,6 +7,7 @@ import { MenuItem } from '@jbrowse/core/ui'
7
7
  import {
8
8
  isAbortException,
9
9
  getContainingView,
10
+ getContainingTrack,
10
11
  getSession,
11
12
  getViewParams,
12
13
  isSelectionContainer,
@@ -333,8 +334,13 @@ export const BaseLinearDisplay = types
333
334
  const featureWidget = session.addWidget(
334
335
  'BaseFeatureWidget',
335
336
  'baseFeature',
336
- { featureData: feature.toJSON(), view: getContainingView(self) },
337
+ {
338
+ view: getContainingView(self),
339
+ track: getContainingTrack(self),
340
+ featureData: feature.toJSON(),
341
+ },
337
342
  )
343
+
338
344
  session.showWidget(featureWidget)
339
345
  }
340
346
  if (isSelectionContainer(session)) {
@@ -551,7 +557,7 @@ export const BaseLinearDisplay = types
551
557
  self.currBpPerPx !== view.bpPerPx || !self.estimatedRegionStats,
552
558
  rpcDriverName: self.rpcDriverName,
553
559
  displayModel: self,
554
- onFeatureClick(_: unknown, featureId: string | undefined) {
560
+ onFeatureClick(_: unknown, featureId?: string) {
555
561
  const f = featureId || self.featureIdUnderMouse
556
562
  if (!f) {
557
563
  self.clearFeatureSelection()
@@ -566,7 +572,7 @@ export const BaseLinearDisplay = types
566
572
  self.clearFeatureSelection()
567
573
  },
568
574
  // similar to click but opens a menu with further options
569
- onFeatureContextMenu(_: unknown, featureId: string | undefined) {
575
+ onFeatureContextMenu(_: unknown, featureId?: string) {
570
576
  const f = featureId || self.featureIdUnderMouse
571
577
  if (!f) {
572
578
  self.clearFeatureSelection()
@@ -576,7 +582,7 @@ export const BaseLinearDisplay = types
576
582
  }
577
583
  },
578
584
 
579
- onMouseMove(_: unknown, featureId: string | undefined) {
585
+ onMouseMove(_: unknown, featureId?: string) {
580
586
  self.setFeatureIdUnderMouse(featureId)
581
587
  },
582
588
 
@@ -642,6 +648,7 @@ export const BaseLinearDisplay = types
642
648
  const { offsetPx } = roundedDynamicBlocks[index]
643
649
  const offset = offsetPx - viewOffsetPx
644
650
  const clipid = getId(id, index)
651
+
645
652
  return (
646
653
  <React.Fragment key={`frag-${index}`}>
647
654
  <defs>
@@ -1,17 +1,19 @@
1
1
  import React, { useState } from 'react'
2
- import { makeStyles } from '@material-ui/core/styles'
3
2
  import {
4
3
  Button,
4
+ Checkbox,
5
+ CircularProgress,
5
6
  Dialog,
6
7
  DialogActions,
7
8
  DialogContent,
8
9
  DialogTitle,
9
- IconButton,
10
- Checkbox,
11
10
  FormControlLabel,
12
- CircularProgress,
11
+ IconButton,
12
+ TextField,
13
13
  Typography,
14
+ makeStyles,
14
15
  } from '@material-ui/core'
16
+ import { ErrorMessage } from '@jbrowse/core/ui'
15
17
  import CloseIcon from '@material-ui/icons/Close'
16
18
  import { LinearGenomeViewModel as LGV } from '..'
17
19
 
@@ -24,6 +26,15 @@ const useStyles = makeStyles(theme => ({
24
26
  },
25
27
  }))
26
28
 
29
+ function LoadingMessage() {
30
+ return (
31
+ <div>
32
+ <CircularProgress size={20} style={{ marginRight: 20 }} />
33
+ <Typography display="inline">Creating SVG</Typography>
34
+ </div>
35
+ )
36
+ }
37
+
27
38
  export default function ExportSvgDlg({
28
39
  model,
29
40
  handleClose,
@@ -35,6 +46,7 @@ export default function ExportSvgDlg({
35
46
  const offscreenCanvas = typeof OffscreenCanvas !== 'undefined'
36
47
  const [rasterizeLayers, setRasterizeLayers] = useState(offscreenCanvas)
37
48
  const [loading, setLoading] = useState(false)
49
+ const [filename, setFilename] = useState('jbrowse.svg')
38
50
  const [error, setError] = useState<unknown>()
39
51
  const classes = useStyles()
40
52
  return (
@@ -47,13 +59,15 @@ export default function ExportSvgDlg({
47
59
  </DialogTitle>
48
60
  <DialogContent>
49
61
  {error ? (
50
- <div style={{ color: 'red' }}>{`${error}`}</div>
62
+ <ErrorMessage error={error} />
51
63
  ) : loading ? (
52
- <div>
53
- <CircularProgress size={20} style={{ marginRight: 20 }} />
54
- <Typography display="inline">Creating SVG</Typography>
55
- </div>
64
+ <LoadingMessage />
56
65
  ) : null}
66
+ <TextField
67
+ helperText="filename"
68
+ value={filename}
69
+ onChange={event => setFilename(event.target.value)}
70
+ />
57
71
  {offscreenCanvas ? (
58
72
  <FormControlLabel
59
73
  control={
@@ -87,12 +101,11 @@ export default function ExportSvgDlg({
87
101
  setLoading(true)
88
102
  setError(undefined)
89
103
  try {
90
- await model.exportSvg({ rasterizeLayers })
104
+ await model.exportSvg({ rasterizeLayers, filename })
91
105
  handleClose()
92
106
  } catch (e) {
93
107
  console.error(e)
94
108
  setError(e)
95
- } finally {
96
109
  setLoading(false)
97
110
  }
98
111
  }}
@@ -7,6 +7,7 @@ import {
7
7
  makeStyles,
8
8
  alpha,
9
9
  } from '@material-ui/core'
10
+ import { getTickDisplayStr2 } from '@jbrowse/core/util'
10
11
  import SearchBox from './SearchBox'
11
12
 
12
13
  // icons
@@ -97,10 +98,10 @@ function PanControls({ model }: { model: LGV }) {
97
98
 
98
99
  const RegionWidth = observer(({ model }: { model: LGV }) => {
99
100
  const classes = useStyles()
100
- const { coarseTotalBp } = model
101
+ const { coarseTotalBp, bpPerPx } = model
101
102
  return (
102
103
  <Typography variant="body2" color="textSecondary" className={classes.bp}>
103
- {Math.round(coarseTotalBp).toLocaleString('en-US')} bp
104
+ {getTickDisplayStr2(coarseTotalBp, bpPerPx)}
104
105
  </Typography>
105
106
  )
106
107
  })
@@ -32,11 +32,8 @@ export default function HelpDialog({
32
32
  Using the search box
33
33
  {handleClose ? (
34
34
  <IconButton
35
- data-testid="close-resultsDialog"
36
35
  className={classes.closeButton}
37
- onClick={() => {
38
- handleClose()
39
- }}
36
+ onClick={() => handleClose()}
40
37
  >
41
38
  <CloseIcon />
42
39
  </IconButton>
@@ -81,6 +78,10 @@ export default function HelpDialog({
81
78
  <code>chr1:1-100[rev] chr2:1-100</code> - open up the first region
82
79
  in the horizontally flipped orientation
83
80
  </li>
81
+ <li>
82
+ <code>chr1 100 200</code> - use whitespace separated refname, start,
83
+ end
84
+ </li>
84
85
  </ul>
85
86
  </DialogContent>
86
87
  <Divider />
@@ -37,13 +37,9 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
37
37
  const classes = useStyles()
38
38
  const session = getSession(model)
39
39
  const { assemblyNames, assemblyManager, textSearchManager } = session
40
- const {
41
- rankSearchResults,
42
- isSearchDialogDisplayed,
43
- error: modelError,
44
- } = model
40
+ const { rankSearchResults, isSearchDialogDisplayed, error } = model
45
41
  const [selectedAsm, setSelectedAsm] = useState(assemblyNames[0])
46
- const [error, setError] = useState<typeof modelError | undefined>(modelError)
42
+ const [importError, setImportError] = useState(error)
47
43
  const searchScope = model.searchScope(selectedAsm)
48
44
 
49
45
  const assembly = assemblyManager.get(selectedAsm)
@@ -51,19 +47,15 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
51
47
  ? assembly?.error
52
48
  : 'No configured assemblies'
53
49
  const regions = assembly?.regions || []
54
- const err = assemblyError || error
55
-
56
- const [myOption, setOption] = useState<BaseResult>()
50
+ const err = assemblyError || importError
51
+ const [myVal, setValue] = useState('')
52
+ const value = myVal || regions[0]?.refName
57
53
 
58
54
  // use this instead of useState initializer because the useState initializer
59
55
  // won't update in response to an observable
60
- const option =
61
- myOption ||
62
- new BaseResult({
63
- label: regions[0]?.refName,
64
- })
65
-
66
- const selectedRegion = option?.getLocation()
56
+ const option = new BaseResult({
57
+ label: value,
58
+ })
67
59
 
68
60
  async function fetchResults(query: string, searchType?: SearchType) {
69
61
  if (!textSearchManager) {
@@ -98,15 +90,26 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
98
90
  }
99
91
  let trackId = option.getTrackId()
100
92
  let location = input || option.getLocation() || ''
93
+ const [ref, rest] = location.split(':')
94
+ const allRefs = assembly?.allRefNames || []
101
95
  try {
102
- if (assembly?.allRefNames?.includes(location)) {
96
+ // instead of querying text-index, first:
97
+ // - check if input matches a refname directly
98
+ // - or looks like locstring
99
+ // then just navigate as if it were a locstring
100
+ if (
101
+ allRefs.includes(location) ||
102
+ (allRefs.includes(ref) &&
103
+ rest !== undefined &&
104
+ !Number.isNaN(parseInt(rest, 10)))
105
+ ) {
103
106
  model.navToLocString(location, selectedAsm)
104
107
  } else {
105
108
  const results = await fetchResults(input, 'exact')
106
- if (results && results.length > 1) {
109
+ if (results.length > 1) {
107
110
  model.setSearchResults(results, input.toLowerCase())
108
111
  return
109
- } else if (results?.length === 1) {
112
+ } else if (results.length === 1) {
110
113
  location = results[0].getLocation()
111
114
  trackId = results[0].getTrackId()
112
115
  }
@@ -130,7 +133,15 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
130
133
  <div className={classes.container}>
131
134
  {err ? <ErrorMessage error={err} /> : null}
132
135
  <Container className={classes.importFormContainer}>
133
- <form onSubmit={event => event.preventDefault()}>
136
+ <form
137
+ onSubmit={event => {
138
+ event.preventDefault()
139
+ model.setError(undefined)
140
+ if (value) {
141
+ handleSelectedRegion(value)
142
+ }
143
+ }}
144
+ >
134
145
  <Grid
135
146
  container
136
147
  spacing={1}
@@ -140,7 +151,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
140
151
  <Grid item>
141
152
  <AssemblySelector
142
153
  onChange={val => {
143
- setError(undefined)
154
+ setImportError('')
144
155
  setSelectedAsm(val)
145
156
  }}
146
157
  session={session}
@@ -152,15 +163,15 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
152
163
  {selectedAsm ? (
153
164
  err ? (
154
165
  <CloseIcon style={{ color: 'red' }} />
155
- ) : selectedRegion ? (
166
+ ) : value ? (
156
167
  <RefNameAutocomplete
157
168
  fetchResults={fetchResults}
158
169
  model={model}
159
170
  assemblyName={assemblyError ? undefined : selectedAsm}
160
- value={selectedRegion}
171
+ value={value}
161
172
  // note: minWidth 270 accomodates full width of helperText
162
173
  minWidth={270}
163
- onSelect={option => setOption(option)}
174
+ onChange={str => setValue(str)}
164
175
  TextFieldProps={{
165
176
  variant: 'outlined',
166
177
  helperText:
@@ -181,21 +192,15 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
181
192
  <Grid item>
182
193
  <Button
183
194
  type="submit"
184
- disabled={!selectedRegion}
195
+ disabled={!value}
185
196
  className={classes.button}
186
- onClick={() => {
187
- model.setError(undefined)
188
- if (selectedRegion) {
189
- handleSelectedRegion(selectedRegion)
190
- }
191
- }}
192
197
  variant="contained"
193
198
  color="primary"
194
199
  >
195
200
  Open
196
201
  </Button>
197
202
  <Button
198
- disabled={!selectedRegion}
203
+ disabled={!value}
199
204
  className={classes.button}
200
205
  onClick={() => {
201
206
  model.setError(undefined)
@@ -79,7 +79,7 @@ describe('<LinearGenomeView />', () => {
79
79
  await findByText('Foo Track')
80
80
  // test needs to wait until it's updated to display 100 bp in the header to
81
81
  // make snapshot pass
82
- await findByText('100 bp')
82
+ await findByText('100bp')
83
83
  expect(container.firstChild).toMatchSnapshot()
84
84
  })
85
85
  it('renders two tracks, two regions', async () => {
@@ -145,7 +145,7 @@ describe('<LinearGenomeView />', () => {
145
145
  <LinearGenomeView model={model} />,
146
146
  )
147
147
  await findByText('Foo Track')
148
- await findByText('798 bp')
148
+ await findByText('798bp')
149
149
  await findAllByTestId('svgfeatures')
150
150
 
151
151
  expect(container.firstChild).toMatchSnapshot()
@@ -92,16 +92,22 @@ const LinearGenomeView = observer(({ model }: { model: LGV }) => {
92
92
  <TracksContainer model={model}>
93
93
  {!tracks.length ? (
94
94
  <Paper variant="outlined" className={classes.note}>
95
- <Typography>No tracks active.</Typography>
96
- <Button
97
- variant="contained"
98
- color="primary"
99
- onClick={model.activateTrackSelector}
100
- style={{ zIndex: 1000 }}
101
- startIcon={<TrackSelectorIcon />}
102
- >
103
- Open track selector
104
- </Button>
95
+ {!model.hideNoTracksActive ? (
96
+ <>
97
+ <Typography>No tracks active.</Typography>
98
+ <Button
99
+ variant="contained"
100
+ color="primary"
101
+ onClick={model.activateTrackSelector}
102
+ style={{ zIndex: 1000 }}
103
+ startIcon={<TrackSelectorIcon />}
104
+ >
105
+ Open track selector
106
+ </Button>
107
+ </>
108
+ ) : (
109
+ <div style={{ height: '48px' }}></div>
110
+ )}
105
111
  </Paper>
106
112
  ) : (
107
113
  tracks.map(track => (
@@ -5,7 +5,7 @@ import { Instance } from 'mobx-state-tree'
5
5
  import clsx from 'clsx'
6
6
 
7
7
  import Base1DView, { Base1DViewModel } from '@jbrowse/core/util/Base1DViewModel'
8
- import { getSession } from '@jbrowse/core/util'
8
+ import { getSession, getTickDisplayStr } from '@jbrowse/core/util'
9
9
  import { ContentBlock } from '@jbrowse/core/util/blockTypes'
10
10
  import { Assembly } from '@jbrowse/core/assemblyManager/assembly'
11
11
 
@@ -51,7 +51,6 @@ const useStyles = makeStyles(theme => {
51
51
  },
52
52
  scaleBarLabel: {
53
53
  height: HEADER_OVERVIEW_HEIGHT,
54
- width: 1,
55
54
  position: 'absolute',
56
55
  display: 'flex',
57
56
  justifyContent: 'center',
@@ -301,7 +300,7 @@ const OverviewBox = observer(
301
300
  overview: Base1DViewModel
302
301
  }) => {
303
302
  const classes = useStyles()
304
- const { cytobandOffset, showCytobands } = model
303
+ const { cytobandOffset, bpPerPx, showCytobands } = model
305
304
  const { start, end, reversed, refName, assemblyName } = block
306
305
  const { majorPitch } = chooseGridPitch(scale, 120, 15)
307
306
  const { assemblyManager } = getSession(model)
@@ -354,7 +353,7 @@ const OverviewBox = observer(
354
353
  color: refNameColor,
355
354
  }}
356
355
  >
357
- {tickLabel.toLocaleString('en-US')}
356
+ {getTickDisplayStr(tickLabel, bpPerPx)}
358
357
  </Typography>
359
358
  ))
360
359
  : null}
@@ -66,17 +66,19 @@ const MyPopper = function (
66
66
 
67
67
  function RefNameAutocomplete({
68
68
  model,
69
- showHelp = true,
70
69
  onSelect,
71
70
  assemblyName,
72
71
  style,
73
72
  fetchResults,
73
+ onChange,
74
74
  value,
75
+ showHelp = true,
75
76
  minWidth = 200,
76
77
  TextFieldProps = {},
77
78
  }: {
78
79
  model: LinearGenomeViewModel
79
- onSelect: (region: BaseResult) => void
80
+ onSelect?: (region: BaseResult) => void
81
+ onChange?: (val: string) => void
80
82
  assemblyName?: string
81
83
  value?: string
82
84
  fetchResults: (query: string) => Promise<BaseResult[]>
@@ -171,7 +173,10 @@ function RefNameAutocomplete({
171
173
  value={inputBoxVal}
172
174
  loading={!loaded}
173
175
  inputValue={inputValue}
174
- onInputChange={(event, newInputValue) => setInputValue(newInputValue)}
176
+ onInputChange={(event, newInputValue) => {
177
+ setInputValue(newInputValue)
178
+ onChange?.(newInputValue)
179
+ }}
175
180
  loadingText="loading results"
176
181
  open={open}
177
182
  onOpen={() => setOpen(true)}
@@ -190,9 +195,9 @@ function RefNameAutocomplete({
190
195
 
191
196
  if (typeof selectedOption === 'string') {
192
197
  // handles string inputs on keyPress enter
193
- onSelect(new BaseResult({ label: selectedOption }))
198
+ onSelect?.(new BaseResult({ label: selectedOption }))
194
199
  } else {
195
- onSelect(selectedOption.result)
200
+ onSelect?.(selectedOption.result)
196
201
  }
197
202
  setInputValue(inputBoxVal)
198
203
  }}
@@ -14,6 +14,7 @@ import {
14
14
  InterRegionPaddingBlock as InterRegionPaddingBlockComponent,
15
15
  } from '../../BaseLinearDisplay/components/Block'
16
16
  import { makeTicks } from '../util'
17
+ import { getTickDisplayStr } from '@jbrowse/core/util'
17
18
 
18
19
  type LGV = LinearGenomeViewModel
19
20
 
@@ -95,18 +96,14 @@ const RenderedRefNameLabels = observer(({ model }: { model: LGV }) => {
95
96
 
96
97
  const RenderedScaleBarLabels = observer(({ model }: { model: LGV }) => {
97
98
  const classes = useStyles()
99
+ const { bpPerPx } = model
98
100
 
99
101
  return (
100
102
  <>
101
103
  {model.staticBlocks.map((block, index) => {
102
104
  if (block instanceof ContentBlock) {
103
- const ticks = makeTicks(
104
- block.start,
105
- block.end,
106
- model.bpPerPx,
107
- true,
108
- false,
109
- )
105
+ const { start, end } = block
106
+ const ticks = makeTicks(start, end, bpPerPx, true, false)
110
107
 
111
108
  return (
112
109
  <ContentBlockComponent key={`${block.key}-${index}`} block={block}>
@@ -116,7 +113,7 @@ const RenderedScaleBarLabels = observer(({ model }: { model: LGV }) => {
116
113
  (block.reversed
117
114
  ? block.end - tick.base
118
115
  : tick.base - block.start) / model.bpPerPx
119
- const baseNumber = (tick.base + 1).toLocaleString('en-US')
116
+ const baseNumber = tick.base + 1
120
117
  return (
121
118
  <div
122
119
  key={tick.base}
@@ -125,7 +122,7 @@ const RenderedScaleBarLabels = observer(({ model }: { model: LGV }) => {
125
122
  >
126
123
  {baseNumber ? (
127
124
  <Typography className={classes.majorTickLabel}>
128
- {baseNumber}
125
+ {getTickDisplayStr(baseNumber, bpPerPx)}
129
126
  </Typography>
130
127
  ) : null}
131
128
  </div>
@@ -58,19 +58,35 @@ function SearchBox({
58
58
  )
59
59
  }
60
60
 
61
+ // gets a string as input, or use stored option results from previous query,
62
+ // then re-query and
63
+ // 1) if it has multiple results: pop a dialog
64
+ // 2) if it's a single result navigate to it
65
+ // 3) else assume it's a locstring and navigate to it
61
66
  async function handleSelectedRegion(option: BaseResult) {
62
67
  let trackId = option.getTrackId()
63
68
  let location = option.getLocation()
64
69
  const label = option.getLabel()
70
+ const [ref, rest] = location.split(':')
71
+ const allRefs = assembly?.allRefNames || []
65
72
  try {
66
- if (assembly?.allRefNames?.includes(location)) {
67
- model.navToLocString(location)
73
+ // instead of querying text-index, first:
74
+ // - check if input matches a refName directly
75
+ // - or looks like locString
76
+ // then just navigate as if it were a locString
77
+ if (
78
+ allRefs.includes(location) ||
79
+ (allRefs.includes(ref) &&
80
+ rest !== undefined &&
81
+ !Number.isNaN(parseInt(rest, 10)))
82
+ ) {
83
+ model.navToLocString(location, assemblyName)
68
84
  } else {
69
85
  const results = await fetchResults(label, 'exact')
70
- if (results && results.length > 1) {
86
+ if (results.length > 1) {
71
87
  model.setSearchResults(results, label.toLowerCase())
72
88
  return
73
- } else if (results?.length === 1) {
89
+ } else if (results.length === 1) {
74
90
  location = results[0].getLocation()
75
91
  trackId = results[0].getTrackId()
76
92
  }