@jbrowse/plugin-linear-genome-view 1.5.1 → 1.5.2
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/LinearGenomeView/components/HelpDialog.d.ts +5 -0
- package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +2 -1
- package/dist/plugin-linear-genome-view.cjs.development.js +254 -198
- package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
- package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
- package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
- package/dist/plugin-linear-genome-view.esm.js +255 -199
- package/dist/plugin-linear-genome-view.esm.js.map +1 -1
- package/package.json +2 -2
- package/src/LinearGenomeView/components/HelpDialog.tsx +81 -0
- package/src/LinearGenomeView/components/ImportForm.tsx +18 -27
- package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +1 -1
- package/src/LinearGenomeView/components/OverviewRubberBand.tsx +1 -1
- package/src/LinearGenomeView/components/OverviewScaleBar.tsx +6 -2
- package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +132 -114
- package/src/LinearGenomeView/components/SearchResultsDialog.tsx +12 -34
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +49 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-linear-genome-view",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.2",
|
|
4
4
|
"description": "JBrowse 2 linear genome view",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jbrowse",
|
|
@@ -61,5 +61,5 @@
|
|
|
61
61
|
"publishConfig": {
|
|
62
62
|
"access": "public"
|
|
63
63
|
},
|
|
64
|
-
"gitHead": "
|
|
64
|
+
"gitHead": "94fdfbc34787ab8f12a87e00038da74b247b42fa"
|
|
65
65
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
Dialog,
|
|
5
|
+
DialogActions,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
Divider,
|
|
9
|
+
IconButton,
|
|
10
|
+
makeStyles,
|
|
11
|
+
} from '@material-ui/core'
|
|
12
|
+
import CloseIcon from '@material-ui/icons/Close'
|
|
13
|
+
|
|
14
|
+
export const useStyles = makeStyles(theme => ({
|
|
15
|
+
closeButton: {
|
|
16
|
+
position: 'absolute',
|
|
17
|
+
right: theme.spacing(1),
|
|
18
|
+
top: theme.spacing(1),
|
|
19
|
+
color: theme.palette.grey[500],
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
export default function HelpDialog({
|
|
24
|
+
handleClose,
|
|
25
|
+
}: {
|
|
26
|
+
handleClose: () => void
|
|
27
|
+
}) {
|
|
28
|
+
const classes = useStyles()
|
|
29
|
+
return (
|
|
30
|
+
<Dialog open maxWidth="xl" onClose={handleClose}>
|
|
31
|
+
<DialogTitle>
|
|
32
|
+
Using the search box
|
|
33
|
+
{handleClose ? (
|
|
34
|
+
<IconButton
|
|
35
|
+
data-testid="close-resultsDialog"
|
|
36
|
+
className={classes.closeButton}
|
|
37
|
+
onClick={() => {
|
|
38
|
+
handleClose()
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<CloseIcon />
|
|
42
|
+
</IconButton>
|
|
43
|
+
) : null}
|
|
44
|
+
</DialogTitle>
|
|
45
|
+
<Divider />
|
|
46
|
+
<DialogContent>
|
|
47
|
+
<h3>Searching</h3>
|
|
48
|
+
<ul>
|
|
49
|
+
<li>
|
|
50
|
+
Jump to a feature or reference sequence by typing its name in the
|
|
51
|
+
location box and pressing Enter.
|
|
52
|
+
</li>
|
|
53
|
+
<li>
|
|
54
|
+
Jump to a specific region by typing the region into the location box
|
|
55
|
+
as: <code>ref:start..end</code> or <code>ref:start-end</code>.
|
|
56
|
+
Commas are allowed in the start and end coordinates.
|
|
57
|
+
</li>
|
|
58
|
+
</ul>
|
|
59
|
+
<h3>Example Searches</h3>
|
|
60
|
+
<ul>
|
|
61
|
+
<li>
|
|
62
|
+
<code>BRCA</code> - searches for the feature named BRCA
|
|
63
|
+
</li>
|
|
64
|
+
<li>
|
|
65
|
+
<code>chr4</code> - jumps to chromosome 4
|
|
66
|
+
</li>
|
|
67
|
+
<li>
|
|
68
|
+
<code>chr4:79,500,000..80,000,000</code> - jumps the region on
|
|
69
|
+
chromosome 4 between 79.5Mb and 80Mb.
|
|
70
|
+
</li>
|
|
71
|
+
</ul>
|
|
72
|
+
</DialogContent>
|
|
73
|
+
<Divider />
|
|
74
|
+
<DialogActions>
|
|
75
|
+
<Button onClick={() => handleClose()} color="primary">
|
|
76
|
+
Close
|
|
77
|
+
</Button>
|
|
78
|
+
</DialogActions>
|
|
79
|
+
</Dialog>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
1
|
+
import React, { useState, lazy } from 'react'
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
3
|
import { getSession } from '@jbrowse/core/util'
|
|
4
4
|
import {
|
|
@@ -6,17 +6,18 @@ import {
|
|
|
6
6
|
CircularProgress,
|
|
7
7
|
Container,
|
|
8
8
|
Grid,
|
|
9
|
-
Typography,
|
|
10
9
|
makeStyles,
|
|
11
10
|
} from '@material-ui/core'
|
|
12
11
|
import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
|
|
13
12
|
import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
|
|
14
13
|
import AssemblySelector from '@jbrowse/core/ui/AssemblySelector'
|
|
14
|
+
import ErrorMessage from '@jbrowse/core/ui/ErrorMessage'
|
|
15
|
+
import CloseIcon from '@material-ui/icons/Close'
|
|
15
16
|
|
|
16
17
|
// locals
|
|
17
18
|
import RefNameAutocomplete from './RefNameAutocomplete'
|
|
18
|
-
import SearchResultsDialog from './SearchResultsDialog'
|
|
19
19
|
import { LinearGenomeViewModel } from '..'
|
|
20
|
+
const SearchResultsDialog = lazy(() => import('./SearchResultsDialog'))
|
|
20
21
|
|
|
21
22
|
const useStyles = makeStyles(theme => ({
|
|
22
23
|
importFormContainer: {
|
|
@@ -29,14 +30,6 @@ const useStyles = makeStyles(theme => ({
|
|
|
29
30
|
|
|
30
31
|
type LGV = LinearGenomeViewModel
|
|
31
32
|
|
|
32
|
-
const ErrorDisplay = observer(({ error }: { error?: Error | string }) => {
|
|
33
|
-
return (
|
|
34
|
-
<Typography variant="h6" color="error">
|
|
35
|
-
{`${error}`}
|
|
36
|
-
</Typography>
|
|
37
|
-
)
|
|
38
|
-
})
|
|
39
|
-
|
|
40
33
|
const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
41
34
|
const classes = useStyles()
|
|
42
35
|
const session = getSession(model)
|
|
@@ -91,13 +84,11 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
91
84
|
return [...(refNameResults || []), ...(textSearchResults || [])]
|
|
92
85
|
}
|
|
93
86
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
* 3) else assume it's a locstring and navigate to it
|
|
100
|
-
*/
|
|
87
|
+
// gets a string as input, or use stored option results from previous query,
|
|
88
|
+
// then re-query and
|
|
89
|
+
// 1) if it has multiple results: pop a dialog
|
|
90
|
+
// 2) if it's a single result navigate to it
|
|
91
|
+
// 3) else assume it's a locstring and navigate to it
|
|
101
92
|
async function handleSelectedRegion(input: string) {
|
|
102
93
|
if (!option) {
|
|
103
94
|
return
|
|
@@ -132,13 +123,9 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
132
123
|
// having this wrapped in a form allows intuitive use of enter key to submit
|
|
133
124
|
return (
|
|
134
125
|
<div>
|
|
135
|
-
{err ? <
|
|
126
|
+
{err ? <ErrorMessage error={err} /> : null}
|
|
136
127
|
<Container className={classes.importFormContainer}>
|
|
137
|
-
<form
|
|
138
|
-
onSubmit={event => {
|
|
139
|
-
event.preventDefault()
|
|
140
|
-
}}
|
|
141
|
-
>
|
|
128
|
+
<form onSubmit={event => event.preventDefault()}>
|
|
142
129
|
<Grid
|
|
143
130
|
container
|
|
144
131
|
spacing={1}
|
|
@@ -158,18 +145,21 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
158
145
|
<Grid item>
|
|
159
146
|
{selectedAsm ? (
|
|
160
147
|
err ? (
|
|
161
|
-
<
|
|
162
|
-
) : selectedRegion
|
|
148
|
+
<CloseIcon style={{ color: 'red' }} />
|
|
149
|
+
) : selectedRegion ? (
|
|
163
150
|
<RefNameAutocomplete
|
|
164
151
|
fetchResults={fetchResults}
|
|
165
152
|
model={model}
|
|
166
153
|
assemblyName={assemblyError ? undefined : selectedAsm}
|
|
167
154
|
value={selectedRegion}
|
|
155
|
+
// note: minWidth 270 accomodates full width of helperText
|
|
156
|
+
minWidth={270}
|
|
168
157
|
onSelect={option => setOption(option)}
|
|
169
158
|
TextFieldProps={{
|
|
170
159
|
margin: 'normal',
|
|
171
160
|
variant: 'outlined',
|
|
172
|
-
helperText:
|
|
161
|
+
helperText:
|
|
162
|
+
'Enter sequence name, feature name, or location',
|
|
173
163
|
}}
|
|
174
164
|
/>
|
|
175
165
|
) : (
|
|
@@ -181,6 +171,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
181
171
|
)
|
|
182
172
|
) : null}
|
|
183
173
|
</Grid>
|
|
174
|
+
<Grid item></Grid>
|
|
184
175
|
<Grid item>
|
|
185
176
|
<Button
|
|
186
177
|
type="submit"
|
|
@@ -177,7 +177,7 @@ const SVGHeader = ({ model }: { model: LGV }) => {
|
|
|
177
177
|
<Cytobands overview={overview} assembly={assembly} block={block} />
|
|
178
178
|
<rect
|
|
179
179
|
stroke="red"
|
|
180
|
-
fill="
|
|
180
|
+
fill="rgb(255,0,0,0.1)"
|
|
181
181
|
width={Math.max(lastOverviewPx - firstOverviewPx, 0.5)}
|
|
182
182
|
height={HEADER_OVERVIEW_HEIGHT - 1}
|
|
183
183
|
x={firstOverviewPx}
|
|
@@ -143,7 +143,7 @@ function OverviewRubberBand({
|
|
|
143
143
|
startX !== undefined &&
|
|
144
144
|
currentX === undefined
|
|
145
145
|
) {
|
|
146
|
-
const clickedAt = overview.pxToBp(startX)
|
|
146
|
+
const clickedAt = overview.pxToBp(startX - cytobandOffset)
|
|
147
147
|
model.centerAt(
|
|
148
148
|
Math.round(clickedAt.coord),
|
|
149
149
|
clickedAt.refName,
|
|
@@ -62,7 +62,7 @@ const useStyles = makeStyles(theme => {
|
|
|
62
62
|
height: HEADER_OVERVIEW_HEIGHT,
|
|
63
63
|
pointerEvents: 'none',
|
|
64
64
|
zIndex: 100,
|
|
65
|
-
border: '1px solid
|
|
65
|
+
border: '1px solid',
|
|
66
66
|
},
|
|
67
67
|
overview: {
|
|
68
68
|
height: HEADER_BAR_HEIGHT,
|
|
@@ -405,6 +405,9 @@ const ScaleBar = observer(
|
|
|
405
405
|
coord: last.reversed ? last.start : last.end,
|
|
406
406
|
}) || 0
|
|
407
407
|
|
|
408
|
+
const color = showCytobands ? '#f00' : scaleBarColor
|
|
409
|
+
const transparency = showCytobands ? 0.1 : 0.3
|
|
410
|
+
|
|
408
411
|
return (
|
|
409
412
|
<div className={classes.scaleBar}>
|
|
410
413
|
<div
|
|
@@ -412,7 +415,8 @@ const ScaleBar = observer(
|
|
|
412
415
|
style={{
|
|
413
416
|
width: lastOverviewPx - firstOverviewPx,
|
|
414
417
|
left: firstOverviewPx + cytobandOffset,
|
|
415
|
-
background:
|
|
418
|
+
background: alpha(color, transparency),
|
|
419
|
+
borderColor: color,
|
|
416
420
|
}}
|
|
417
421
|
/>
|
|
418
422
|
{/* this is the entire scale bar */}
|
|
@@ -1,31 +1,32 @@
|
|
|
1
|
-
import React, { useMemo, useEffect, useState } from 'react'
|
|
1
|
+
import React, { lazy, useMemo, useEffect, useState } from 'react'
|
|
2
2
|
import { observer } from 'mobx-react'
|
|
3
|
-
|
|
4
|
-
// jbrowse core
|
|
5
3
|
import { getSession, useDebounce, measureText } from '@jbrowse/core/util'
|
|
6
4
|
import BaseResult, {
|
|
7
5
|
RefSequenceResult,
|
|
8
6
|
} from '@jbrowse/core/TextSearch/BaseResults'
|
|
9
|
-
|
|
10
|
-
// material ui
|
|
11
7
|
import {
|
|
12
8
|
CircularProgress,
|
|
9
|
+
IconButton,
|
|
13
10
|
InputAdornment,
|
|
14
11
|
Popper,
|
|
12
|
+
PopperProps,
|
|
15
13
|
TextField,
|
|
16
14
|
TextFieldProps as TFP,
|
|
17
|
-
PopperProps,
|
|
18
15
|
Typography,
|
|
19
16
|
} from '@material-ui/core'
|
|
20
17
|
|
|
21
18
|
// icons
|
|
22
19
|
import SearchIcon from '@material-ui/icons/Search'
|
|
23
20
|
import Autocomplete from '@material-ui/lab/Autocomplete'
|
|
21
|
+
import HelpIcon from '@material-ui/icons/Help'
|
|
24
22
|
|
|
25
23
|
// locals
|
|
26
24
|
import { LinearGenomeViewModel } from '..'
|
|
27
25
|
import { dedupe } from './util'
|
|
28
26
|
|
|
27
|
+
// lazy
|
|
28
|
+
const HelpDialog = lazy(() => import('./HelpDialog'))
|
|
29
|
+
|
|
29
30
|
export interface Option {
|
|
30
31
|
group?: string
|
|
31
32
|
result: BaseResult
|
|
@@ -70,6 +71,7 @@ function RefNameAutocomplete({
|
|
|
70
71
|
style,
|
|
71
72
|
fetchResults,
|
|
72
73
|
value,
|
|
74
|
+
minWidth = 200,
|
|
73
75
|
TextFieldProps = {},
|
|
74
76
|
}: {
|
|
75
77
|
model: LinearGenomeViewModel
|
|
@@ -78,12 +80,14 @@ function RefNameAutocomplete({
|
|
|
78
80
|
value?: string
|
|
79
81
|
fetchResults: (query: string) => Promise<BaseResult[]>
|
|
80
82
|
style?: React.CSSProperties
|
|
83
|
+
minWidth?: number
|
|
81
84
|
TextFieldProps?: TFP
|
|
82
85
|
}) {
|
|
83
86
|
const session = getSession(model)
|
|
84
87
|
const { assemblyManager } = session
|
|
85
88
|
const [open, setOpen] = useState(false)
|
|
86
89
|
const [loaded, setLoaded] = useState(true)
|
|
90
|
+
const [isHelpDialogDisplayed, setHelpDialogDisplayed] = useState(false)
|
|
87
91
|
const [currentSearch, setCurrentSearch] = useState('')
|
|
88
92
|
const [inputValue, setInputValue] = useState('')
|
|
89
93
|
const [searchOptions, setSearchOptions] = useState<Option[]>()
|
|
@@ -140,121 +144,135 @@ function RefNameAutocomplete({
|
|
|
140
144
|
|
|
141
145
|
const inputBoxVal = coarseVisibleLocStrings || value || ''
|
|
142
146
|
|
|
143
|
-
// heuristic, text width + icon width
|
|
144
|
-
|
|
147
|
+
// heuristic, text width + icon width
|
|
148
|
+
// + 45 accomodates help icon and search icon
|
|
149
|
+
const width = Math.min(
|
|
150
|
+
Math.max(measureText(inputBoxVal, 16) + 45, minWidth),
|
|
151
|
+
550,
|
|
152
|
+
)
|
|
145
153
|
|
|
146
154
|
// notes on implementation:
|
|
147
155
|
// The selectOnFocus setting helps highlight the field when clicked
|
|
148
156
|
return (
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
157
|
+
<>
|
|
158
|
+
<Autocomplete
|
|
159
|
+
id={`refNameAutocomplete-${model.id}`}
|
|
160
|
+
data-testid="autocomplete"
|
|
161
|
+
disableListWrap
|
|
162
|
+
disableClearable
|
|
163
|
+
PopperComponent={MyPopper}
|
|
164
|
+
disabled={!assemblyName}
|
|
165
|
+
freeSolo
|
|
166
|
+
includeInputInList
|
|
167
|
+
selectOnFocus
|
|
168
|
+
style={{ ...style, width }}
|
|
169
|
+
value={inputBoxVal}
|
|
170
|
+
loading={!loaded}
|
|
171
|
+
inputValue={inputValue}
|
|
172
|
+
onInputChange={(event, newInputValue) => setInputValue(newInputValue)}
|
|
173
|
+
loadingText="loading results"
|
|
174
|
+
open={open}
|
|
175
|
+
onOpen={() => setOpen(true)}
|
|
176
|
+
onClose={() => {
|
|
177
|
+
setOpen(false)
|
|
178
|
+
setLoaded(true)
|
|
179
|
+
if (hasDisplayedRegions) {
|
|
180
|
+
setCurrentSearch('')
|
|
181
|
+
setSearchOptions(undefined)
|
|
182
|
+
}
|
|
183
|
+
}}
|
|
184
|
+
onChange={(_event, selectedOption) => {
|
|
185
|
+
if (!selectedOption || !assemblyName) {
|
|
186
|
+
return
|
|
187
|
+
}
|
|
179
188
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
189
|
+
if (typeof selectedOption === 'string') {
|
|
190
|
+
// handles string inputs on keyPress enter
|
|
191
|
+
onSelect(new BaseResult({ label: selectedOption }))
|
|
192
|
+
} else {
|
|
193
|
+
onSelect(selectedOption.result)
|
|
194
|
+
}
|
|
195
|
+
setInputValue(inputBoxVal)
|
|
196
|
+
}}
|
|
197
|
+
options={!searchOptions?.length ? options : searchOptions}
|
|
198
|
+
getOptionDisabled={option => option?.group === 'limitOption'}
|
|
199
|
+
filterOptions={(options, params) => {
|
|
200
|
+
const filtered = filterOptions(
|
|
201
|
+
options,
|
|
202
|
+
params.inputValue.toLocaleLowerCase(),
|
|
203
|
+
)
|
|
204
|
+
return [
|
|
205
|
+
...filtered.slice(0, 100),
|
|
206
|
+
...(filtered.length > 100
|
|
207
|
+
? [
|
|
208
|
+
{
|
|
209
|
+
group: 'limitOption',
|
|
210
|
+
result: new BaseResult({
|
|
211
|
+
label: 'keep typing for more results',
|
|
212
|
+
}),
|
|
213
|
+
},
|
|
214
|
+
]
|
|
215
|
+
: []),
|
|
216
|
+
]
|
|
217
|
+
}}
|
|
218
|
+
renderInput={params => {
|
|
219
|
+
const { helperText, InputProps = {} } = TextFieldProps
|
|
220
|
+
return (
|
|
221
|
+
<TextField
|
|
222
|
+
onBlur={() => {
|
|
223
|
+
// this is used to restore a refName or the non-user-typed input
|
|
224
|
+
// to the box on blurring
|
|
225
|
+
setInputValue(inputBoxVal)
|
|
226
|
+
}}
|
|
227
|
+
{...params}
|
|
228
|
+
{...TextFieldProps}
|
|
229
|
+
helperText={helperText}
|
|
230
|
+
InputProps={{
|
|
231
|
+
...params.InputProps,
|
|
232
|
+
...InputProps,
|
|
224
233
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
234
|
+
endAdornment: (
|
|
235
|
+
<>
|
|
236
|
+
{regions.length === 0 ? (
|
|
237
|
+
<CircularProgress color="inherit" size={20} />
|
|
238
|
+
) : (
|
|
239
|
+
<InputAdornment position="end" style={{ marginRight: 7 }}>
|
|
240
|
+
<SearchIcon />
|
|
241
|
+
<IconButton
|
|
242
|
+
onClick={() => setHelpDialogDisplayed(true)}
|
|
243
|
+
>
|
|
244
|
+
<HelpIcon />
|
|
245
|
+
</IconButton>
|
|
246
|
+
</InputAdornment>
|
|
247
|
+
)}
|
|
248
|
+
{params.InputProps.endAdornment}
|
|
249
|
+
</>
|
|
250
|
+
),
|
|
251
|
+
}}
|
|
252
|
+
placeholder="Search for location"
|
|
253
|
+
onChange={e => {
|
|
254
|
+
setCurrentSearch(e.target.value)
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
)
|
|
258
|
+
}}
|
|
259
|
+
renderOption={option => {
|
|
260
|
+
const { result } = option
|
|
261
|
+
const component = result.getRenderingComponent()
|
|
262
|
+
if (component && React.isValidElement(component)) {
|
|
263
|
+
return component
|
|
264
|
+
}
|
|
251
265
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
266
|
+
return <Typography noWrap>{result.getDisplayString()}</Typography>
|
|
267
|
+
}}
|
|
268
|
+
getOptionLabel={option =>
|
|
269
|
+
(typeof option === 'string' ? option : option.result.getLabel()) || ''
|
|
270
|
+
}
|
|
271
|
+
/>
|
|
272
|
+
{isHelpDialogDisplayed ? (
|
|
273
|
+
<HelpDialog handleClose={() => setHelpDialogDisplayed(false)} />
|
|
274
|
+
) : null}
|
|
275
|
+
</>
|
|
258
276
|
)
|
|
259
277
|
}
|
|
260
278
|
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
Dialog,
|
|
7
7
|
DialogActions,
|
|
8
8
|
DialogContent,
|
|
9
|
-
DialogContentText,
|
|
10
9
|
DialogTitle,
|
|
11
10
|
Divider,
|
|
12
11
|
IconButton,
|
|
@@ -18,10 +17,9 @@ import {
|
|
|
18
17
|
TableRow,
|
|
19
18
|
Typography,
|
|
20
19
|
Paper,
|
|
20
|
+
makeStyles,
|
|
21
21
|
} from '@material-ui/core'
|
|
22
22
|
import CloseIcon from '@material-ui/icons/Close'
|
|
23
|
-
import { makeStyles } from '@material-ui/core/styles'
|
|
24
|
-
import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
|
|
25
23
|
import { LinearGenomeViewModel } from '../..'
|
|
26
24
|
|
|
27
25
|
export const useStyles = makeStyles(theme => ({
|
|
@@ -101,8 +99,8 @@ export default function SearchResultsDialog({
|
|
|
101
99
|
|
|
102
100
|
return (
|
|
103
101
|
<Dialog open maxWidth="xl" onClose={handleClose}>
|
|
104
|
-
<DialogTitle
|
|
105
|
-
Search
|
|
102
|
+
<DialogTitle>
|
|
103
|
+
Search results
|
|
106
104
|
{handleClose ? (
|
|
107
105
|
<IconButton
|
|
108
106
|
data-testid="close-resultsDialog"
|
|
@@ -117,18 +115,15 @@ export default function SearchResultsDialog({
|
|
|
117
115
|
</DialogTitle>
|
|
118
116
|
<Divider />
|
|
119
117
|
<DialogContent>
|
|
120
|
-
{model.searchResults?.length
|
|
121
|
-
model.searchResults === undefined ? (
|
|
118
|
+
{!model.searchResults?.length ? (
|
|
122
119
|
<Typography>
|
|
123
|
-
|
|
124
|
-
<b>{model.searchQuery}</b>
|
|
120
|
+
No results found for <b>{model.searchQuery}</b>
|
|
125
121
|
</Typography>
|
|
126
122
|
) : (
|
|
127
123
|
<>
|
|
128
|
-
<
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
</DialogContentText>
|
|
124
|
+
<Typography>
|
|
125
|
+
Showing results for <b>{model.searchQuery}</b>
|
|
126
|
+
</Typography>
|
|
132
127
|
<TableContainer component={Paper}>
|
|
133
128
|
<Table>
|
|
134
129
|
<TableHead>
|
|
@@ -140,8 +135,8 @@ export default function SearchResultsDialog({
|
|
|
140
135
|
</TableRow>
|
|
141
136
|
</TableHead>
|
|
142
137
|
<TableBody>
|
|
143
|
-
{model.searchResults.map(
|
|
144
|
-
<TableRow key={`${result.
|
|
138
|
+
{model.searchResults.map(result => (
|
|
139
|
+
<TableRow key={`${result.getId()}`}>
|
|
145
140
|
<TableCell component="th" scope="row">
|
|
146
141
|
{result.getLabel()}
|
|
147
142
|
</TableCell>
|
|
@@ -151,18 +146,6 @@ export default function SearchResultsDialog({
|
|
|
151
146
|
<TableCell align="right">
|
|
152
147
|
{getTrackName(result.getTrackId()) || 'N/A'}
|
|
153
148
|
</TableCell>
|
|
154
|
-
<TableCell align="right">
|
|
155
|
-
<Button
|
|
156
|
-
onClick={() => {
|
|
157
|
-
handleClick(result.getLocation())
|
|
158
|
-
handleClose()
|
|
159
|
-
}}
|
|
160
|
-
color="primary"
|
|
161
|
-
variant="contained"
|
|
162
|
-
>
|
|
163
|
-
Go to location
|
|
164
|
-
</Button>
|
|
165
|
-
</TableCell>
|
|
166
149
|
<TableCell align="right">
|
|
167
150
|
<Button
|
|
168
151
|
onClick={() => {
|
|
@@ -177,7 +160,7 @@ export default function SearchResultsDialog({
|
|
|
177
160
|
color="primary"
|
|
178
161
|
variant="contained"
|
|
179
162
|
>
|
|
180
|
-
|
|
163
|
+
Go
|
|
181
164
|
</Button>
|
|
182
165
|
</TableCell>
|
|
183
166
|
</TableRow>
|
|
@@ -190,12 +173,7 @@ export default function SearchResultsDialog({
|
|
|
190
173
|
</DialogContent>
|
|
191
174
|
<Divider />
|
|
192
175
|
<DialogActions>
|
|
193
|
-
<Button
|
|
194
|
-
onClick={() => {
|
|
195
|
-
handleClose()
|
|
196
|
-
}}
|
|
197
|
-
color="primary"
|
|
198
|
-
>
|
|
176
|
+
<Button onClick={() => handleClose()} color="primary">
|
|
199
177
|
Cancel
|
|
200
178
|
</Button>
|
|
201
179
|
</DialogActions>
|