@jbrowse/plugin-linear-genome-view 2.1.2 → 2.1.4
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/ImportForm.js +37 -41
- package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +2 -1
- package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
- package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +14 -8
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js +26 -6
- package/dist/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -1
- package/dist/LinearGenomeView/components/SearchBox.js +36 -32
- package/dist/LinearGenomeView/components/SearchBox.js.map +1 -1
- package/dist/LinearGenomeView/components/SearchResultsDialog.js +7 -4
- package/dist/LinearGenomeView/components/SearchResultsDialog.js.map +1 -1
- package/dist/LinearGenomeView/components/TrackLabel.js +1 -1
- package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -1
- package/dist/LinearGenomeView/components/util.d.ts +1 -0
- package/dist/LinearGenomeView/components/util.js +14 -1
- package/dist/LinearGenomeView/components/util.js.map +1 -1
- package/dist/LinearGenomeView/index.d.ts +1 -1
- package/esm/LinearGenomeView/components/ImportForm.js +38 -42
- package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js +2 -1
- package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
- package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +14 -8
- package/esm/LinearGenomeView/components/RefNameAutocomplete.js +26 -6
- package/esm/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -1
- package/esm/LinearGenomeView/components/SearchBox.js +37 -33
- package/esm/LinearGenomeView/components/SearchBox.js.map +1 -1
- package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
- package/esm/LinearGenomeView/components/SearchResultsDialog.js.map +1 -1
- package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
- package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -1
- package/esm/LinearGenomeView/components/util.d.ts +1 -0
- package/esm/LinearGenomeView/components/util.js +12 -0
- package/esm/LinearGenomeView/components/util.js.map +1 -1
- package/esm/LinearGenomeView/index.d.ts +1 -1
- package/package.json +2 -2
- package/src/LinearGenomeView/components/ImportForm.tsx +39 -42
- package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +4 -4
- package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +27 -8
- package/src/LinearGenomeView/components/SearchBox.tsx +38 -35
- package/src/LinearGenomeView/components/SearchResultsDialog.tsx +7 -4
- package/src/LinearGenomeView/components/TrackLabel.tsx +3 -1
- package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +2 -2
- package/src/LinearGenomeView/components/util.ts +12 -0
|
@@ -9,7 +9,7 @@ import CloseIcon from '@mui/icons-material/Close'
|
|
|
9
9
|
|
|
10
10
|
// locals
|
|
11
11
|
import RefNameAutocomplete from './RefNameAutocomplete'
|
|
12
|
-
import { fetchResults } from './util'
|
|
12
|
+
import { fetchResults, splitLast } from './util'
|
|
13
13
|
import { LinearGenomeViewModel, WIDGET_HEIGHT } from '..'
|
|
14
14
|
const SearchResultsDialog = lazy(() => import('./SearchResultsDialog'))
|
|
15
15
|
|
|
@@ -34,6 +34,8 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
34
34
|
const { rankSearchResults, isSearchDialogDisplayed, error } = model
|
|
35
35
|
const [selectedAsm, setSelectedAsm] = useState(assemblyNames[0])
|
|
36
36
|
const [importError, setImportError] = useState(error)
|
|
37
|
+
const [option, setOption] = useState<BaseResult>()
|
|
38
|
+
|
|
37
39
|
const searchScope = model.searchScope(selectedAsm)
|
|
38
40
|
|
|
39
41
|
const assembly = assemblyManager.get(selectedAsm)
|
|
@@ -45,11 +47,16 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
45
47
|
const [myVal, setValue] = useState('')
|
|
46
48
|
const value = myVal || regions[0]?.refName
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
function navToOption(option: BaseResult) {
|
|
51
|
+
const location = option.getLocation()
|
|
52
|
+
const trackId = option.getTrackId()
|
|
53
|
+
if (location) {
|
|
54
|
+
model.navToLocString(location, selectedAsm)
|
|
55
|
+
if (trackId) {
|
|
56
|
+
model.showTrack(trackId)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
53
60
|
|
|
54
61
|
// gets a string as input, or use stored option results from previous query,
|
|
55
62
|
// then re-query and
|
|
@@ -57,45 +64,34 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
57
64
|
// 2) if it's a single result navigate to it
|
|
58
65
|
// 3) else assume it's a locstring and navigate to it
|
|
59
66
|
async function handleSelectedRegion(input: string) {
|
|
60
|
-
if (!option) {
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
let trackId = option.getTrackId()
|
|
64
|
-
let location = input || option.getLocation() || ''
|
|
65
|
-
const [ref, rest] = location.split(':')
|
|
66
|
-
const allRefs = assembly?.allRefNames || []
|
|
67
67
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
// - or looks like locstring
|
|
71
|
-
// then just navigate as if it were a locstring
|
|
72
|
-
if (
|
|
73
|
-
allRefs.includes(location) ||
|
|
74
|
-
(allRefs.includes(ref) &&
|
|
75
|
-
rest !== undefined &&
|
|
76
|
-
!Number.isNaN(parseInt(rest, 10)))
|
|
77
|
-
) {
|
|
78
|
-
model.navToLocString(location, selectedAsm)
|
|
68
|
+
if (option?.getDisplayString() === input && option.hasLocation()) {
|
|
69
|
+
navToOption(option)
|
|
79
70
|
} else {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
71
|
+
const [ref, rest] = splitLast(input, ':')
|
|
72
|
+
const allRefs = assembly?.allRefNamesWithLowerCase || []
|
|
73
|
+
if (
|
|
74
|
+
allRefs.includes(input) ||
|
|
75
|
+
(allRefs.includes(ref) && !Number.isNaN(parseInt(rest, 10)))
|
|
76
|
+
) {
|
|
77
|
+
model.navToLocString(input, selectedAsm)
|
|
78
|
+
} else {
|
|
79
|
+
const results = await fetchResults({
|
|
80
|
+
queryString: input,
|
|
81
|
+
searchType: 'exact',
|
|
82
|
+
searchScope,
|
|
83
|
+
rankSearchResults,
|
|
84
|
+
textSearchManager,
|
|
85
|
+
assembly,
|
|
86
|
+
})
|
|
95
87
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
88
|
+
if (results.length > 1) {
|
|
89
|
+
model.setSearchResults(results, input.toLowerCase())
|
|
90
|
+
} else if (results.length === 1) {
|
|
91
|
+
navToOption(results[0])
|
|
92
|
+
} else {
|
|
93
|
+
model.navToLocString(input, selectedAsm)
|
|
94
|
+
}
|
|
99
95
|
}
|
|
100
96
|
}
|
|
101
97
|
} catch (e) {
|
|
@@ -160,6 +156,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
|
|
|
160
156
|
// note: minWidth 270 accomodates full width of helperText
|
|
161
157
|
minWidth={270}
|
|
162
158
|
onChange={str => setValue(str)}
|
|
159
|
+
onSelect={val => setOption(val)}
|
|
163
160
|
TextFieldProps={{
|
|
164
161
|
variant: 'outlined',
|
|
165
162
|
helperText:
|
|
@@ -235,10 +235,10 @@ function SVGTracks({
|
|
|
235
235
|
const current = offset
|
|
236
236
|
const trackName =
|
|
237
237
|
getConf(track, 'name') ||
|
|
238
|
-
`Reference sequence (${
|
|
239
|
-
getParent(track.configuration),
|
|
240
|
-
'name'
|
|
241
|
-
|
|
238
|
+
`Reference sequence (${
|
|
239
|
+
readConfObject(getParent(track.configuration), 'displayName') ||
|
|
240
|
+
readConfObject(getParent(track.configuration), 'name')
|
|
241
|
+
})`
|
|
242
242
|
const display = track.displays[0]
|
|
243
243
|
offset += display.height + paddingHeight + textHeight
|
|
244
244
|
return (
|
|
@@ -19,7 +19,6 @@ import HelpIcon from '@mui/icons-material/Help'
|
|
|
19
19
|
|
|
20
20
|
// locals
|
|
21
21
|
import { LinearGenomeViewModel } from '..'
|
|
22
|
-
import { dedupe } from './util'
|
|
23
22
|
|
|
24
23
|
// lazy
|
|
25
24
|
const HelpDialog = lazy(() => import('./HelpDialog'))
|
|
@@ -74,8 +73,8 @@ function RefNameAutocomplete({
|
|
|
74
73
|
const [inputValue, setInputValue] = useState('')
|
|
75
74
|
const [searchOptions, setSearchOptions] = useState<Option[]>()
|
|
76
75
|
const debouncedSearch = useDebounce(currentSearch, 300)
|
|
77
|
-
const { coarseVisibleLocStrings, hasDisplayedRegions } = model
|
|
78
76
|
const assembly = assemblyName ? assemblyManager.get(assemblyName) : undefined
|
|
77
|
+
const { coarseVisibleLocStrings, hasDisplayedRegions } = model
|
|
79
78
|
|
|
80
79
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
80
|
const regions = assembly?.regions || []
|
|
@@ -104,11 +103,31 @@ function RefNameAutocomplete({
|
|
|
104
103
|
setLoaded(false)
|
|
105
104
|
const results = await fetchResults(debouncedSearch)
|
|
106
105
|
if (active) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
const m: { [key: string]: BaseResult[] } = {}
|
|
107
|
+
|
|
108
|
+
for (let i = 0; i < results.length; i++) {
|
|
109
|
+
const r = results[i]
|
|
110
|
+
const d = r.getDisplayString()
|
|
111
|
+
if (!m[d]) {
|
|
112
|
+
m[d] = []
|
|
113
|
+
}
|
|
114
|
+
m[d].push(r)
|
|
115
|
+
}
|
|
116
|
+
const display = Object.entries(m).map(([displayString, results]) => {
|
|
117
|
+
if (results.length === 1) {
|
|
118
|
+
return { result: results[0] }
|
|
119
|
+
} else {
|
|
120
|
+
return {
|
|
121
|
+
result: new BaseResult({
|
|
122
|
+
displayString,
|
|
123
|
+
results,
|
|
124
|
+
label: displayString,
|
|
125
|
+
}),
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
setSearchOptions(display)
|
|
112
131
|
setLoaded(true)
|
|
113
132
|
}
|
|
114
133
|
} catch (e) {
|
|
@@ -150,7 +169,7 @@ function RefNameAutocomplete({
|
|
|
150
169
|
value={inputBoxVal}
|
|
151
170
|
loading={!loaded}
|
|
152
171
|
inputValue={inputValue}
|
|
153
|
-
onInputChange={(
|
|
172
|
+
onInputChange={(_event, newInputValue) => {
|
|
154
173
|
setInputValue(newInputValue)
|
|
155
174
|
onChange?.(newInputValue)
|
|
156
175
|
}}
|
|
@@ -7,7 +7,7 @@ import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
|
|
|
7
7
|
|
|
8
8
|
// locals
|
|
9
9
|
import RefNameAutocomplete from './RefNameAutocomplete'
|
|
10
|
-
import { fetchResults } from './util'
|
|
10
|
+
import { fetchResults, splitLast } from './util'
|
|
11
11
|
import { LinearGenomeViewModel, SPACING, WIDGET_HEIGHT } from '..'
|
|
12
12
|
|
|
13
13
|
const useStyles = makeStyles()(() => ({
|
|
@@ -33,49 +33,52 @@ function SearchBox({
|
|
|
33
33
|
const assembly = assemblyManager.get(assemblyName)
|
|
34
34
|
const searchScope = model.searchScope(assemblyName)
|
|
35
35
|
|
|
36
|
+
function navToOption(option: BaseResult) {
|
|
37
|
+
const location = option.getLocation()
|
|
38
|
+
const trackId = option.getTrackId()
|
|
39
|
+
if (location) {
|
|
40
|
+
model.navToLocString(location, assemblyName)
|
|
41
|
+
if (trackId) {
|
|
42
|
+
model.showTrack(trackId)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
// gets a string as input, or use stored option results from previous query,
|
|
37
48
|
// then re-query and
|
|
38
49
|
// 1) if it has multiple results: pop a dialog
|
|
39
50
|
// 2) if it's a single result navigate to it
|
|
40
51
|
// 3) else assume it's a locstring and navigate to it
|
|
41
52
|
async function handleSelectedRegion(option: BaseResult) {
|
|
42
|
-
let trackId = option.getTrackId()
|
|
43
|
-
let location = option.getLocation()
|
|
44
|
-
const label = option.getLabel()
|
|
45
|
-
const [ref, rest] = location.split(':')
|
|
46
|
-
const allRefs = assembly?.allRefNames || []
|
|
47
53
|
try {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// - or looks like locString
|
|
51
|
-
// then just navigate as if it were a locString
|
|
52
|
-
if (
|
|
53
|
-
allRefs.includes(location) ||
|
|
54
|
-
(allRefs.includes(ref) &&
|
|
55
|
-
rest !== undefined &&
|
|
56
|
-
!Number.isNaN(parseInt(rest, 10)))
|
|
57
|
-
) {
|
|
58
|
-
model.navToLocString(location, assemblyName)
|
|
54
|
+
if (option.hasLocation()) {
|
|
55
|
+
navToOption(option)
|
|
59
56
|
} else {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
57
|
+
const input = option.getLabel()
|
|
58
|
+
const [ref, rest] = splitLast(input, ':')
|
|
59
|
+
const allRefs = assembly?.allRefNamesWithLowerCase || []
|
|
60
|
+
if (
|
|
61
|
+
allRefs.includes(input) ||
|
|
62
|
+
(allRefs.includes(ref) && !Number.isNaN(parseInt(rest, 10)))
|
|
63
|
+
) {
|
|
64
|
+
model.navToLocString(input, assemblyName)
|
|
65
|
+
} else {
|
|
66
|
+
const results = await fetchResults({
|
|
67
|
+
queryString: input,
|
|
68
|
+
searchType: 'exact',
|
|
69
|
+
searchScope,
|
|
70
|
+
rankSearchResults,
|
|
71
|
+
textSearchManager,
|
|
72
|
+
assembly,
|
|
73
|
+
})
|
|
75
74
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
if (results.length > 1) {
|
|
76
|
+
model.setSearchResults(results, input.toLowerCase())
|
|
77
|
+
} else if (results.length === 1) {
|
|
78
|
+
navToOption(results[0])
|
|
79
|
+
} else {
|
|
80
|
+
model.navToLocString(input, assemblyName)
|
|
81
|
+
}
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
} catch (e) {
|
|
@@ -150,10 +150,13 @@ export default function SearchResultsDialog({
|
|
|
150
150
|
<TableCell align="right">
|
|
151
151
|
<Button
|
|
152
152
|
onClick={() => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
const location = result.getLocation()
|
|
154
|
+
if (location) {
|
|
155
|
+
handleClick(location)
|
|
156
|
+
const resultTrackId = result.getTrackId()
|
|
157
|
+
if (resultTrackId) {
|
|
158
|
+
model.showTrack(resultTrackId)
|
|
159
|
+
}
|
|
157
160
|
}
|
|
158
161
|
handleClose()
|
|
159
162
|
}}
|
|
@@ -72,7 +72,9 @@ function getTrackName(
|
|
|
72
72
|
return (
|
|
73
73
|
trackName ||
|
|
74
74
|
(asm
|
|
75
|
-
? `Reference Sequence (${
|
|
75
|
+
? `Reference Sequence (${
|
|
76
|
+
readConfObject(asm, 'displayName') || readConfObject(asm, 'name')
|
|
77
|
+
})`
|
|
76
78
|
: 'Reference Sequence')
|
|
77
79
|
)
|
|
78
80
|
}
|
|
@@ -157,7 +157,7 @@ exports[`<LinearGenomeView /> renders one track, one region 1`] = `
|
|
|
157
157
|
/>
|
|
158
158
|
</button>
|
|
159
159
|
<div
|
|
160
|
-
class="MuiAutocomplete-root css-
|
|
160
|
+
class="MuiAutocomplete-root css-1qqsdnr-MuiAutocomplete-root"
|
|
161
161
|
data-testid="autocomplete"
|
|
162
162
|
style="width: 200px;"
|
|
163
163
|
>
|
|
@@ -722,7 +722,7 @@ exports[`<LinearGenomeView /> renders two tracks, two regions 1`] = `
|
|
|
722
722
|
/>
|
|
723
723
|
</button>
|
|
724
724
|
<div
|
|
725
|
-
class="MuiAutocomplete-root css-
|
|
725
|
+
class="MuiAutocomplete-root css-1qqsdnr-MuiAutocomplete-root"
|
|
726
726
|
data-testid="autocomplete"
|
|
727
727
|
style="width: 255.27500000000003px;"
|
|
728
728
|
>
|
|
@@ -51,3 +51,15 @@ export async function fetchResults({
|
|
|
51
51
|
elt => elt.getId(),
|
|
52
52
|
)
|
|
53
53
|
}
|
|
54
|
+
|
|
55
|
+
// splits on the last instance of a character
|
|
56
|
+
export function splitLast(str: string, split: string): [string, string] {
|
|
57
|
+
const lastIndex = str.lastIndexOf(split)
|
|
58
|
+
if (lastIndex === -1) {
|
|
59
|
+
return [str, '']
|
|
60
|
+
} else {
|
|
61
|
+
const before = str.slice(0, lastIndex)
|
|
62
|
+
const after = str.slice(lastIndex + 1)
|
|
63
|
+
return [before, after]
|
|
64
|
+
}
|
|
65
|
+
}
|