@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.
Files changed (43) hide show
  1. package/dist/LinearGenomeView/components/ImportForm.js +37 -41
  2. package/dist/LinearGenomeView/components/ImportForm.js.map +1 -1
  3. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js +2 -1
  4. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
  5. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +14 -8
  6. package/dist/LinearGenomeView/components/RefNameAutocomplete.js +26 -6
  7. package/dist/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -1
  8. package/dist/LinearGenomeView/components/SearchBox.js +36 -32
  9. package/dist/LinearGenomeView/components/SearchBox.js.map +1 -1
  10. package/dist/LinearGenomeView/components/SearchResultsDialog.js +7 -4
  11. package/dist/LinearGenomeView/components/SearchResultsDialog.js.map +1 -1
  12. package/dist/LinearGenomeView/components/TrackLabel.js +1 -1
  13. package/dist/LinearGenomeView/components/TrackLabel.js.map +1 -1
  14. package/dist/LinearGenomeView/components/util.d.ts +1 -0
  15. package/dist/LinearGenomeView/components/util.js +14 -1
  16. package/dist/LinearGenomeView/components/util.js.map +1 -1
  17. package/dist/LinearGenomeView/index.d.ts +1 -1
  18. package/esm/LinearGenomeView/components/ImportForm.js +38 -42
  19. package/esm/LinearGenomeView/components/ImportForm.js.map +1 -1
  20. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js +2 -1
  21. package/esm/LinearGenomeView/components/LinearGenomeViewSvg.js.map +1 -1
  22. package/esm/LinearGenomeView/components/OverviewScaleBar.d.ts +14 -8
  23. package/esm/LinearGenomeView/components/RefNameAutocomplete.js +26 -6
  24. package/esm/LinearGenomeView/components/RefNameAutocomplete.js.map +1 -1
  25. package/esm/LinearGenomeView/components/SearchBox.js +37 -33
  26. package/esm/LinearGenomeView/components/SearchBox.js.map +1 -1
  27. package/esm/LinearGenomeView/components/SearchResultsDialog.js +7 -4
  28. package/esm/LinearGenomeView/components/SearchResultsDialog.js.map +1 -1
  29. package/esm/LinearGenomeView/components/TrackLabel.js +1 -1
  30. package/esm/LinearGenomeView/components/TrackLabel.js.map +1 -1
  31. package/esm/LinearGenomeView/components/util.d.ts +1 -0
  32. package/esm/LinearGenomeView/components/util.js +12 -0
  33. package/esm/LinearGenomeView/components/util.js.map +1 -1
  34. package/esm/LinearGenomeView/index.d.ts +1 -1
  35. package/package.json +2 -2
  36. package/src/LinearGenomeView/components/ImportForm.tsx +39 -42
  37. package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +4 -4
  38. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +27 -8
  39. package/src/LinearGenomeView/components/SearchBox.tsx +38 -35
  40. package/src/LinearGenomeView/components/SearchResultsDialog.tsx +7 -4
  41. package/src/LinearGenomeView/components/TrackLabel.tsx +3 -1
  42. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +2 -2
  43. 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
- // use this instead of useState initializer because the useState initializer
49
- // won't update in response to an observable
50
- const option = new BaseResult({
51
- label: value,
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
- // instead of querying text-index, first:
69
- // - check if input matches a refname directly
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 results = await fetchResults({
81
- queryString: input,
82
- searchType: 'exact',
83
- searchScope,
84
- rankSearchResults,
85
- textSearchManager,
86
- assembly,
87
- })
88
- if (results.length > 1) {
89
- model.setSearchResults(results, input.toLowerCase())
90
- return
91
- } else if (results.length === 1) {
92
- location = results[0].getLocation()
93
- trackId = results[0].getTrackId()
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
- model.navToLocString(location, selectedAsm)
97
- if (trackId) {
98
- model.showTrack(trackId)
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 (${readConfObject(
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
- setSearchOptions(
108
- dedupe(results, r => r.getDisplayString()).map(result => ({
109
- result,
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={(event, newInputValue) => {
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
- // instead of querying text-index, first:
49
- // - check if input matches a refName directly
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 results = await fetchResults({
61
- queryString: label,
62
- searchType: 'exact',
63
- searchScope,
64
- rankSearchResults,
65
- textSearchManager,
66
- assembly,
67
- })
68
- if (results.length > 1) {
69
- model.setSearchResults(results, label.toLowerCase())
70
- return
71
- } else if (results.length === 1) {
72
- location = results[0].getLocation()
73
- trackId = results[0].getTrackId()
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
- model.navToLocString(location, assemblyName)
77
- if (trackId) {
78
- model.showTrack(trackId)
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
- handleClick(result.getLocation())
154
- const resultTrackId = result.getTrackId()
155
- if (resultTrackId) {
156
- model.showTrack(resultTrackId)
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 (${readConfObject(asm, 'name')})`
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-16awh2u-MuiAutocomplete-root"
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-16awh2u-MuiAutocomplete-root"
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
+ }