@jbrowse/plugin-linear-genome-view 1.5.0 → 1.5.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 (33) hide show
  1. package/dist/BaseLinearDisplay/components/Block.d.ts +7 -10
  2. package/dist/LinearGenomeView/components/HelpDialog.d.ts +5 -0
  3. package/dist/LinearGenomeView/components/LinearGenomeView.d.ts +3 -5
  4. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +4 -0
  5. package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +2 -3
  6. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +116 -2
  7. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +3 -1
  8. package/dist/LinearGenomeView/components/ScaleBar.d.ts +14 -0
  9. package/dist/LinearGenomeView/components/util.d.ts +1 -1
  10. package/dist/LinearGenomeView/index.d.ts +9 -2
  11. package/dist/plugin-linear-genome-view.cjs.development.js +3163 -2886
  12. package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
  13. package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
  14. package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
  15. package/dist/plugin-linear-genome-view.esm.js +3122 -2845
  16. package/dist/plugin-linear-genome-view.esm.js.map +1 -1
  17. package/package.json +4 -4
  18. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +0 -1
  19. package/src/BaseLinearDisplay/components/Block.tsx +20 -33
  20. package/src/LinearGenomeView/components/Header.tsx +19 -7
  21. package/src/LinearGenomeView/components/HelpDialog.tsx +81 -0
  22. package/src/LinearGenomeView/components/ImportForm.tsx +42 -51
  23. package/src/LinearGenomeView/components/LinearGenomeView.tsx +30 -245
  24. package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +317 -0
  25. package/src/LinearGenomeView/components/OverviewRubberBand.tsx +74 -34
  26. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +326 -177
  27. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +145 -145
  28. package/src/LinearGenomeView/components/SearchResultsDialog.tsx +12 -34
  29. package/src/LinearGenomeView/components/SequenceDialog.tsx +9 -8
  30. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +127 -78
  31. package/src/LinearGenomeView/components/util.ts +6 -4
  32. package/src/LinearGenomeView/index.tsx +55 -14
  33. package/src/declare.d.ts +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "1.5.0",
3
+ "version": "1.5.4",
4
4
  "description": "JBrowse 2 linear genome view",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@material-ui/icons": "^4.9.1",
39
- "@popperjs/core": "^2.9.3",
39
+ "@popperjs/core": "^2.11.0",
40
40
  "clone": "^2.1.2",
41
41
  "clsx": "^1.0.4",
42
42
  "copy-to-clipboard": "^3.3.1",
@@ -45,7 +45,7 @@
45
45
  "json-stable-stringify": "^1.0.1",
46
46
  "normalize-wheel": "^1.0.1",
47
47
  "react-popper": "^2.0.0",
48
- "react-sizeme": "^2.6.7"
48
+ "react-sizeme": "^3.0.2"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "@jbrowse/core": "^1.0.0",
@@ -61,5 +61,5 @@
61
61
  "publishConfig": {
62
62
  "access": "public"
63
63
  },
64
- "gitHead": "542025578a39bd170c8a166f2568ee7edbd54072"
64
+ "gitHead": "0c398214590969168694b4ed8e20b595178b9efd"
65
65
  }
@@ -35,7 +35,6 @@ const useStyles = makeStyles(theme => ({
35
35
  lineHeight: `${round(14 / 10)}em`,
36
36
  maxWidth: 300,
37
37
  wordWrap: 'break-word',
38
- fontWeight: theme.typography.fontWeightMedium,
39
38
  },
40
39
  }))
41
40
 
@@ -1,7 +1,7 @@
1
+ import React from 'react'
1
2
  import { BaseBlock } from '@jbrowse/core/util/blockTypes'
2
3
  import { makeStyles } from '@material-ui/core/styles'
3
4
  import { observer } from 'mobx-react'
4
- import React from 'react'
5
5
 
6
6
  const useStyles = makeStyles(theme => ({
7
7
  contentBlock: {
@@ -28,51 +28,38 @@ const useStyles = makeStyles(theme => ({
28
28
  },
29
29
  }))
30
30
 
31
- interface ContentBlockProps {
32
- block: BaseBlock
33
- children: React.ReactNode
34
- }
35
-
36
- interface ElidedBlockProps {
37
- width: number
38
- }
39
-
40
- interface InterRegionPaddingBlockProps {
41
- boundary: boolean
42
- width: number
43
- style?: React.CSSProperties
44
- }
45
-
46
- const ContentBlock = observer(({ block, children }: ContentBlockProps) => {
47
- const classes = useStyles()
48
- return (
49
- <div
50
- style={{
51
- width: `${block.widthPx}px`,
52
- }}
53
- className={classes.contentBlock}
54
- >
55
- {children}
56
- </div>
57
- )
58
- })
31
+ const ContentBlock = observer(
32
+ ({ block, children }: { block: BaseBlock; children: React.ReactNode }) => {
33
+ const classes = useStyles()
34
+ const { widthPx } = block
35
+ return (
36
+ <div style={{ width: widthPx }} className={classes.contentBlock}>
37
+ {children}
38
+ </div>
39
+ )
40
+ },
41
+ )
59
42
 
60
- function ElidedBlock({ width }: ElidedBlockProps) {
43
+ function ElidedBlock({ width }: { width: number }) {
61
44
  const classes = useStyles()
62
- return <div className={classes.elidedBlock} style={{ width: `${width}px` }} />
45
+ return <div className={classes.elidedBlock} style={{ width }} />
63
46
  }
64
47
 
65
48
  function InterRegionPaddingBlock({
66
49
  boundary,
67
50
  width,
68
51
  style = {},
69
- }: InterRegionPaddingBlockProps) {
52
+ }: {
53
+ boundary: boolean
54
+ width: number
55
+ style?: React.CSSProperties
56
+ }) {
70
57
  const classes = useStyles()
71
58
  return (
72
59
  <div
73
60
  style={{
74
61
  ...style,
75
- width: `${width}px`,
62
+ width,
76
63
  }}
77
64
  className={
78
65
  boundary
@@ -10,6 +10,7 @@ import {
10
10
  alpha,
11
11
  } from '@material-ui/core'
12
12
  import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
13
+ import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
13
14
 
14
15
  // icons
15
16
  import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
@@ -121,19 +122,29 @@ const LinearGenomeViewHeader = observer(
121
122
  const assembly = assemblyManager.get(assemblyName)
122
123
  const searchScope = model.searchScope(assemblyName)
123
124
 
124
- async function fetchResults(queryString: string) {
125
+ async function fetchResults(query: string, searchType?: SearchType) {
125
126
  if (!textSearchManager) {
126
127
  console.warn('No text search manager')
127
128
  }
128
- const results = await textSearchManager?.search(
129
+
130
+ const textSearchResults = await textSearchManager?.search(
129
131
  {
130
- queryString: queryString.toLowerCase(),
131
- searchType: 'exact',
132
+ queryString: query,
133
+ searchType,
132
134
  },
133
135
  searchScope,
134
136
  rankSearchResults,
135
137
  )
136
- return dedupe(results)
138
+
139
+ const refNameResults = assembly?.allRefNames
140
+ ?.filter(refName => refName.startsWith(query))
141
+ .map(r => new BaseResult({ label: r }))
142
+ .slice(0, 10)
143
+
144
+ return dedupe(
145
+ [...(refNameResults || []), ...(textSearchResults || [])],
146
+ elt => elt.getId(),
147
+ )
137
148
  }
138
149
 
139
150
  async function handleSelectedRegion(option: BaseResult) {
@@ -141,10 +152,10 @@ const LinearGenomeViewHeader = observer(
141
152
  let location = option.getLocation()
142
153
  const label = option.getLabel()
143
154
  try {
144
- if (assembly?.refNames?.includes(location)) {
155
+ if (assembly?.allRefNames?.includes(location)) {
145
156
  model.navToLocString(location)
146
157
  } else {
147
- const results = await fetchResults(label)
158
+ const results = await fetchResults(label, 'exact')
148
159
  if (results && results.length > 1) {
149
160
  model.setSearchResults(results, label.toLowerCase())
150
161
  return
@@ -173,6 +184,7 @@ const LinearGenomeViewHeader = observer(
173
184
  <RefNameAutocomplete
174
185
  onSelect={handleSelectedRegion}
175
186
  assemblyName={assemblyName}
187
+ fetchResults={fetchResults}
176
188
  model={model}
177
189
  TextFieldProps={{
178
190
  variant: 'outlined',
@@ -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,23 +1,23 @@
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
- import BaseResult, {
5
- RefSequenceResult,
6
- } from '@jbrowse/core/TextSearch/BaseResults'
7
- import AssemblySelector from '@jbrowse/core/ui/AssemblySelector'
8
4
  import {
9
5
  Button,
10
6
  CircularProgress,
11
7
  Container,
12
8
  Grid,
13
- Typography,
14
9
  makeStyles,
15
10
  } from '@material-ui/core'
16
- // other
11
+ import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
12
+ import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
13
+ import AssemblySelector from '@jbrowse/core/ui/AssemblySelector'
14
+ import ErrorMessage from '@jbrowse/core/ui/ErrorMessage'
15
+ import CloseIcon from '@material-ui/icons/Close'
16
+
17
+ // locals
17
18
  import RefNameAutocomplete from './RefNameAutocomplete'
18
- import SearchResultsDialog from './SearchResultsDialog'
19
19
  import { LinearGenomeViewModel } from '..'
20
- import { dedupe } from './util'
20
+ const SearchResultsDialog = lazy(() => import('./SearchResultsDialog'))
21
21
 
22
22
  const useStyles = makeStyles(theme => ({
23
23
  importFormContainer: {
@@ -30,14 +30,6 @@ const useStyles = makeStyles(theme => ({
30
30
 
31
31
  type LGV = LinearGenomeViewModel
32
32
 
33
- const ErrorDisplay = observer(({ error }: { error?: Error | string }) => {
34
- return (
35
- <Typography variant="h6" color="error">
36
- {`${error}`}
37
- </Typography>
38
- )
39
- })
40
-
41
33
  const ImportForm = observer(({ model }: { model: LGV }) => {
42
34
  const classes = useStyles()
43
35
  const session = getSession(model)
@@ -49,7 +41,6 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
49
41
  } = model
50
42
  const [selectedAsm, setSelectedAsm] = useState(assemblyNames[0])
51
43
  const [error, setError] = useState<typeof modelError | undefined>(modelError)
52
- const message = !assemblyNames.length ? 'No configured assemblies' : ''
53
44
  const searchScope = model.searchScope(selectedAsm)
54
45
 
55
46
  const assembly = assemblyManager.get(selectedAsm)
@@ -59,42 +50,45 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
59
50
  const regions = assembly?.regions || []
60
51
  const err = assemblyError || error
61
52
 
62
- const [myOption, setOption] = useState<BaseResult | undefined>()
53
+ const [myOption, setOption] = useState<BaseResult>()
63
54
 
64
55
  // use this instead of useState initializer because the useState initializer
65
56
  // won't update in response to an observable
66
57
  const option =
67
58
  myOption ||
68
- new RefSequenceResult({
69
- refName: regions[0]?.refName,
59
+ new BaseResult({
70
60
  label: regions[0]?.refName,
71
61
  })
72
62
 
73
63
  const selectedRegion = option?.getLocation()
74
64
 
75
- async function fetchResults(queryString: string) {
65
+ async function fetchResults(query: string, searchType?: SearchType) {
76
66
  if (!textSearchManager) {
77
67
  console.warn('No text search manager')
78
68
  }
79
- const results = await textSearchManager?.search(
69
+
70
+ const textSearchResults = await textSearchManager?.search(
80
71
  {
81
- queryString: queryString.toLowerCase(),
82
- searchType: 'exact',
72
+ queryString: query,
73
+ searchType,
83
74
  },
84
75
  searchScope,
85
76
  rankSearchResults,
86
77
  )
87
78
 
88
- return dedupe(results)
79
+ const refNameResults = assembly?.allRefNames
80
+ ?.filter(refName => refName.startsWith(query))
81
+ .map(r => new BaseResult({ label: r }))
82
+ .slice(0, 10)
83
+
84
+ return [...(refNameResults || []), ...(textSearchResults || [])]
89
85
  }
90
86
 
91
- /**
92
- * gets a string as input, or use stored option results from previous query,
93
- * then re-query and
94
- * 1) if it has multiple results: pop a dialog
95
- * 2) if it's a single result navigate to it
96
- * 3) else assume it's a locstring and navigate to it
97
- */
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
98
92
  async function handleSelectedRegion(input: string) {
99
93
  if (!option) {
100
94
  return
@@ -102,10 +96,10 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
102
96
  let trackId = option.getTrackId()
103
97
  let location = input || option.getLocation() || ''
104
98
  try {
105
- if (assembly?.refNames?.includes(location)) {
99
+ if (assembly?.allRefNames?.includes(location)) {
106
100
  model.navToLocString(location, selectedAsm)
107
101
  } else {
108
- const results = await fetchResults(input)
102
+ const results = await fetchResults(input, 'exact')
109
103
  if (results && results.length > 1) {
110
104
  model.setSearchResults(results, input.toLowerCase())
111
105
  return
@@ -129,13 +123,9 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
129
123
  // having this wrapped in a form allows intuitive use of enter key to submit
130
124
  return (
131
125
  <div>
132
- {err ? <ErrorDisplay error={err} /> : null}
126
+ {err ? <ErrorMessage error={err} /> : null}
133
127
  <Container className={classes.importFormContainer}>
134
- <form
135
- onSubmit={event => {
136
- event.preventDefault()
137
- }}
138
- >
128
+ <form onSubmit={event => event.preventDefault()}>
139
129
  <Grid
140
130
  container
141
131
  spacing={1}
@@ -155,19 +145,21 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
155
145
  <Grid item>
156
146
  {selectedAsm ? (
157
147
  err ? (
158
- <Typography color="error">X</Typography>
159
- ) : selectedRegion && model.volatileWidth ? (
148
+ <CloseIcon style={{ color: 'red' }} />
149
+ ) : selectedRegion ? (
160
150
  <RefNameAutocomplete
151
+ fetchResults={fetchResults}
161
152
  model={model}
162
- assemblyName={message ? undefined : selectedAsm}
153
+ assemblyName={assemblyError ? undefined : selectedAsm}
163
154
  value={selectedRegion}
164
- onSelect={option => {
165
- setOption(option)
166
- }}
155
+ // note: minWidth 270 accomodates full width of helperText
156
+ minWidth={270}
157
+ onSelect={option => setOption(option)}
167
158
  TextFieldProps={{
168
159
  margin: 'normal',
169
160
  variant: 'outlined',
170
- helperText: 'Enter a sequence or location',
161
+ helperText:
162
+ 'Enter sequence name, feature name, or location',
171
163
  }}
172
164
  />
173
165
  ) : (
@@ -179,6 +171,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
179
171
  )
180
172
  ) : null}
181
173
  </Grid>
174
+ <Grid item></Grid>
182
175
  <Grid item>
183
176
  <Button
184
177
  type="submit"
@@ -215,9 +208,7 @@ const ImportForm = observer(({ model }: { model: LGV }) => {
215
208
  <SearchResultsDialog
216
209
  model={model}
217
210
  optAssemblyName={selectedAsm}
218
- handleClose={() => {
219
- model.setSearchResults(undefined, undefined)
220
- }}
211
+ handleClose={() => model.setSearchResults(undefined, undefined)}
221
212
  />
222
213
  ) : null}
223
214
  </div>