@jbrowse/plugin-linear-genome-view 1.4.4 → 1.5.3

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/BaseLinearDisplay/components/Block.d.ts +7 -10
  2. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +16 -9
  3. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -2
  4. package/dist/LinearBareDisplay/model.d.ts +8 -8
  5. package/dist/LinearBasicDisplay/model.d.ts +11 -8
  6. package/dist/LinearGenomeView/components/HelpDialog.d.ts +5 -0
  7. package/dist/LinearGenomeView/components/LinearGenomeView.d.ts +3 -5
  8. package/dist/LinearGenomeView/components/LinearGenomeViewSvg.d.ts +4 -0
  9. package/dist/LinearGenomeView/components/OverviewRubberBand.d.ts +2 -3
  10. package/dist/LinearGenomeView/components/OverviewScaleBar.d.ts +116 -2
  11. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +3 -11
  12. package/dist/LinearGenomeView/components/ScaleBar.d.ts +36 -2
  13. package/dist/LinearGenomeView/components/util.d.ts +2 -0
  14. package/dist/LinearGenomeView/index.d.ts +22 -4
  15. package/dist/index.d.ts +26 -26
  16. package/dist/plugin-linear-genome-view.cjs.development.js +3178 -2884
  17. package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
  18. package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
  19. package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
  20. package/dist/plugin-linear-genome-view.esm.js +3191 -2898
  21. package/dist/plugin-linear-genome-view.esm.js.map +1 -1
  22. package/package.json +2 -2
  23. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +3 -0
  24. package/src/BaseLinearDisplay/components/Block.tsx +20 -33
  25. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +3 -7
  26. package/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +15 -13
  27. package/src/LinearBasicDisplay/model.ts +25 -3
  28. package/src/LinearGenomeView/components/ExportSvgDialog.tsx +6 -6
  29. package/src/LinearGenomeView/components/Header.tsx +56 -78
  30. package/src/LinearGenomeView/components/HelpDialog.tsx +81 -0
  31. package/src/LinearGenomeView/components/ImportForm.tsx +139 -158
  32. package/src/LinearGenomeView/components/LinearGenomeView.test.js +6 -6
  33. package/src/LinearGenomeView/components/LinearGenomeView.tsx +30 -245
  34. package/src/LinearGenomeView/components/LinearGenomeViewSvg.tsx +317 -0
  35. package/src/LinearGenomeView/components/OverviewRubberBand.tsx +74 -34
  36. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +326 -177
  37. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +152 -157
  38. package/src/LinearGenomeView/components/SearchResultsDialog.tsx +12 -34
  39. package/src/LinearGenomeView/components/SequenceDialog.tsx +10 -9
  40. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +127 -254
  41. package/src/LinearGenomeView/components/util.ts +10 -0
  42. package/src/LinearGenomeView/index.tsx +69 -27
  43. package/src/index.ts +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-linear-genome-view",
3
- "version": "1.4.4",
3
+ "version": "1.5.3",
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": "c12a2b1cef07dbc5ea40be5c76e03b655b7f3636"
64
+ "gitHead": "cea9023ebce8bbe5c08ae9e00c4e74fe3f02a7f1"
65
65
  }
@@ -73,6 +73,9 @@ const Tooltip = observer(
73
73
  right: x,
74
74
  width: 0,
75
75
  height: 0,
76
+ x,
77
+ y,
78
+ toJSON() {},
76
79
  }
77
80
  },
78
81
  }),
@@ -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
@@ -88,7 +88,7 @@ export const BaseLinearDisplay = types
88
88
  },
89
89
 
90
90
  get TooltipComponent(): React.FC<any> {
91
- return (Tooltip as unknown) as React.FC
91
+ return Tooltip as unknown as React.FC
92
92
  },
93
93
 
94
94
  /**
@@ -387,12 +387,8 @@ export const BaseLinearDisplay = types
387
387
  }
388
388
  }
389
389
 
390
- const {
391
- rpcManager,
392
- renderArgs,
393
- renderProps,
394
- rendererType,
395
- } = renderBlockData(blockState, self)
390
+ const { rpcManager, renderArgs, renderProps, rendererType } =
391
+ renderBlockData(blockState, self)
396
392
 
397
393
  return rendererType.renderInClient(rpcManager, {
398
394
  ...renderArgs,
@@ -3,7 +3,10 @@ import { types, getParent, isAlive, cast, Instance } from 'mobx-state-tree'
3
3
  import { readConfObject } from '@jbrowse/core/configuration'
4
4
  import { Feature } from '@jbrowse/core/util/simpleFeature'
5
5
  import { Region } from '@jbrowse/core/util/types/mst'
6
- import { AbstractDisplayModel } from '@jbrowse/core/util/types'
6
+ import {
7
+ AbstractDisplayModel,
8
+ isRetryException,
9
+ } from '@jbrowse/core/util/types'
7
10
  import React from 'react'
8
11
 
9
12
  import {
@@ -36,7 +39,7 @@ const blockState = types
36
39
  features: undefined as Map<string, Feature> | undefined,
37
40
  layout: undefined as any,
38
41
  status: '',
39
- error: undefined as Error | undefined,
42
+ error: undefined as unknown,
40
43
  message: undefined as string | undefined,
41
44
  maxHeightReached: false,
42
45
  ReactComponent: ServerSideRenderedBlockContent,
@@ -128,7 +131,7 @@ const blockState = types
128
131
  self.renderProps = renderProps
129
132
  renderInProgress = undefined
130
133
  },
131
- setError(error: Error) {
134
+ setError(error: Error | unknown) {
132
135
  console.error(error)
133
136
  if (renderInProgress && !renderInProgress.signal.aborted) {
134
137
  renderInProgress.abort()
@@ -143,6 +146,9 @@ const blockState = types
143
146
  self.error = error
144
147
  self.renderProps = undefined
145
148
  renderInProgress = undefined
149
+ if (isRetryException(error as Error)) {
150
+ this.reload()
151
+ }
146
152
  },
147
153
  reload() {
148
154
  self.renderInProgress = undefined
@@ -286,16 +292,12 @@ async function renderBlockEffect(
286
292
  return undefined
287
293
  }
288
294
 
289
- const {
290
- reactElement,
291
- features,
292
- layout,
293
- maxHeightReached,
294
- } = await rendererType.renderInClient(rpcManager, {
295
- ...renderArgs,
296
- ...renderProps,
297
- signal,
298
- })
295
+ const { reactElement, features, layout, maxHeightReached } =
296
+ await rendererType.renderInClient(rpcManager, {
297
+ ...renderArgs,
298
+ ...renderProps,
299
+ signal,
300
+ })
299
301
  return {
300
302
  reactElement,
301
303
  features,
@@ -17,6 +17,7 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) =>
17
17
  types.model({
18
18
  type: types.literal('LinearBasicDisplay'),
19
19
  trackShowLabels: types.maybe(types.boolean),
20
+ trackShowDescriptions: types.maybe(types.boolean),
20
21
  trackDisplayMode: types.maybe(types.string),
21
22
  trackMaxHeight: types.maybe(types.number),
22
23
  configuration: ConfigurationReference(configSchema),
@@ -34,6 +35,13 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) =>
34
35
  : showLabels
35
36
  },
36
37
 
38
+ get showDescriptions() {
39
+ const showDescriptions = getConf(self, ['renderer', 'showLabels'])
40
+ return self.trackShowDescriptions !== undefined
41
+ ? self.trackShowDescriptions
42
+ : showDescriptions
43
+ },
44
+
37
45
  get maxHeight() {
38
46
  const maxHeight = getConf(self, ['renderer', 'maxHeight'])
39
47
  return self.trackMaxHeight !== undefined
@@ -54,6 +62,7 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) =>
54
62
  {
55
63
  ...configBlob,
56
64
  showLabels: this.showLabels,
65
+ showDescriptions: this.showDescriptions,
57
66
  displayMode: this.displayMode,
58
67
  maxHeight: this.maxHeight,
59
68
  },
@@ -66,6 +75,9 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) =>
66
75
  toggleShowLabels() {
67
76
  self.trackShowLabels = !self.showLabels
68
77
  },
78
+ toggleShowDescriptions() {
79
+ self.trackShowDescriptions = !self.showDescriptions
80
+ },
69
81
  setDisplayMode(val: string) {
70
82
  self.trackDisplayMode = val
71
83
  },
@@ -100,6 +112,15 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) =>
100
112
  self.toggleShowLabels()
101
113
  },
102
114
  },
115
+ {
116
+ label: 'Show descriptions',
117
+ icon: VisibilityIcon,
118
+ type: 'checkbox',
119
+ checked: self.showDescriptions,
120
+ onClick: () => {
121
+ self.toggleShowDescriptions()
122
+ },
123
+ },
103
124
  {
104
125
  label: 'Display mode',
105
126
  icon: VisibilityIcon,
@@ -118,9 +139,10 @@ const stateModelFactory = (configSchema: AnyConfigurationSchemaType) =>
118
139
  {
119
140
  label: 'Set max height',
120
141
  onClick: () => {
121
- getSession(self).setDialogComponent(SetMaxHeightDlg, {
122
- model: self,
123
- })
142
+ getSession(self).queueDialog((doneCallback: Function) => [
143
+ SetMaxHeightDlg,
144
+ { model: self, handleClose: doneCallback },
145
+ ])
124
146
  },
125
147
  },
126
148
  ]
@@ -31,11 +31,11 @@ export default function ExportSvgDlg({
31
31
  model: LGV
32
32
  handleClose: () => void
33
33
  }) {
34
- const [rasterizeLayers, setRasterizeLayers] = useState(
35
- typeof OffscreenCanvas !== 'undefined',
36
- )
34
+ // @ts-ignore
35
+ const offscreenCanvas = typeof OffscreenCanvas !== 'undefined'
36
+ const [rasterizeLayers, setRasterizeLayers] = useState(offscreenCanvas)
37
37
  const [loading, setLoading] = useState(false)
38
- const [error, setError] = useState<Error>()
38
+ const [error, setError] = useState<unknown>()
39
39
  const classes = useStyles()
40
40
  return (
41
41
  <Dialog open onClose={handleClose}>
@@ -54,8 +54,7 @@ export default function ExportSvgDlg({
54
54
  <Typography display="inline">Creating SVG</Typography>
55
55
  </div>
56
56
  ) : null}
57
-
58
- {typeof OffscreenCanvas !== 'undefined' ? (
57
+ {offscreenCanvas ? (
59
58
  <FormControlLabel
60
59
  control={
61
60
  <Checkbox
@@ -91,6 +90,7 @@ export default function ExportSvgDlg({
91
90
  await model.exportSvg({ rasterizeLayers })
92
91
  handleClose()
93
92
  } catch (e) {
93
+ console.error(e)
94
94
  setError(e)
95
95
  } finally {
96
96
  setLoading(false)
@@ -1,20 +1,28 @@
1
- import { getSession } from '@jbrowse/core/util'
2
- import Button from '@material-ui/core/Button'
3
- import { makeStyles, useTheme } from '@material-ui/core/styles'
4
- import { alpha } from '@material-ui/core/styles'
5
- import FormGroup from '@material-ui/core/FormGroup'
6
- import Typography from '@material-ui/core/Typography'
7
- import { observer } from 'mobx-react'
8
1
  import React from 'react'
2
+ import { observer } from 'mobx-react'
3
+ import { getSession } from '@jbrowse/core/util'
4
+ import {
5
+ Button,
6
+ FormGroup,
7
+ Typography,
8
+ makeStyles,
9
+ useTheme,
10
+ alpha,
11
+ } from '@material-ui/core'
9
12
  import BaseResult from '@jbrowse/core/TextSearch/BaseResults'
13
+ import { SearchType } from '@jbrowse/core/data_adapters/BaseAdapter'
10
14
 
15
+ // icons
11
16
  import { TrackSelector as TrackSelectorIcon } from '@jbrowse/core/ui/Icons'
12
17
  import ArrowForwardIcon from '@material-ui/icons/ArrowForward'
13
18
  import ArrowBackIcon from '@material-ui/icons/ArrowBack'
19
+
20
+ // locals
14
21
  import { LinearGenomeViewModel, HEADER_BAR_HEIGHT } from '..'
15
22
  import RefNameAutocomplete from './RefNameAutocomplete'
16
23
  import OverviewScaleBar from './OverviewScaleBar'
17
24
  import ZoomControls from './ZoomControls'
25
+ import { dedupe } from './util'
18
26
 
19
27
  const WIDGET_HEIGHT = 32
20
28
  const SPACING = 7
@@ -107,93 +115,63 @@ const LinearGenomeViewHeader = observer(
107
115
  const classes = useStyles()
108
116
  const theme = useTheme()
109
117
  const session = getSession(model)
110
- const { assemblyManager, textSearchManager } = session
111
- const {
112
- coarseDynamicBlocks: contentBlocks,
113
- displayedRegions,
114
- rankSearchResults,
115
- } = model
116
- const { assemblyName, refName } = contentBlocks[0] || { refName: '' }
117
- const assembly = assemblyName
118
- ? assemblyManager.get(assemblyName)
119
- : undefined
120
- const regions = assembly?.regions || []
118
+
119
+ const { textSearchManager, assemblyManager } = session
120
+ const { assemblyNames, rankSearchResults } = model
121
+ const assemblyName = assemblyNames[0]
122
+ const assembly = assemblyManager.get(assemblyName)
121
123
  const searchScope = model.searchScope(assemblyName)
122
124
 
123
- async function fetchResults(queryString: string) {
125
+ async function fetchResults(query: string, searchType?: SearchType) {
124
126
  if (!textSearchManager) {
125
127
  console.warn('No text search manager')
126
128
  }
127
- const results = await textSearchManager?.search(
129
+
130
+ const textSearchResults = await textSearchManager?.search(
128
131
  {
129
- queryString: queryString.toLowerCase(),
130
- searchType: 'exact',
132
+ queryString: query,
133
+ searchType,
131
134
  },
132
135
  searchScope,
133
136
  rankSearchResults,
134
137
  )
135
- return results?.filter(
136
- (elem, index, self) =>
137
- index === self.findIndex(t => t.getId() === elem.getId()),
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(),
138
147
  )
139
148
  }
140
149
 
141
- /**
142
- * We first check to see if the identifier/label is an appropriate region,
143
- * if it is then we set that as our displayed region
144
- * if the label was not a valid region, then
145
- * 1) we get the trackId and the location/locStr of the option we chose
146
- * 2) we then use the label to try and fetch for exact matches through our
147
- * textSearchManager
148
- * 3) if we get any hits by requerying the textSearchManager, then we either
149
- * navigate to the single hit location or pop open the the dialog with all
150
- * the results from the search
151
- * 4) if there were no hits from requerying, then we use (1) the chosen options'
152
- * trackId and locStr to navigate and show that track
153
- * 5) error handling
154
- * @param result - result chosen from dropdown
155
- */
156
- async function setDisplayedRegion(result: BaseResult) {
157
- if (result) {
158
- const label = result.getLabel()
159
- const newRegion = regions.find(region => label === region.refName)
160
- if (newRegion) {
161
- model.setDisplayedRegions([newRegion])
162
- // we use showAllRegions after setDisplayedRegions to make the entire
163
- // region visible, xref #1703
164
- model.showAllRegions()
150
+ async function handleSelectedRegion(option: BaseResult) {
151
+ let trackId = option.getTrackId()
152
+ let location = option.getLocation()
153
+ const label = option.getLabel()
154
+ try {
155
+ if (assembly?.allRefNames?.includes(location)) {
156
+ model.navToLocString(location)
165
157
  } else {
166
- let location = result.getLocation()
167
- let trackId = result.getTrackId()
168
- const results = await fetchResults(label)
158
+ const results = await fetchResults(label, 'exact')
169
159
  if (results && results.length > 1) {
170
160
  model.setSearchResults(results, label.toLowerCase())
171
- } else {
172
- if (results?.length === 1) {
173
- location = results[0].getLocation()
174
- trackId = results[0].getTrackId()
175
- }
176
- try {
177
- label !== '' && model.navToLocString(location)
178
- } catch (e) {
179
- if (`${e}` === `Error: Unknown reference sequence "${label}"`) {
180
- model.setSearchResults(results, label.toLowerCase())
181
- } else {
182
- console.warn(e)
183
- session.notify(`${e}`, 'warning')
184
- }
185
- }
186
- try {
187
- if (trackId) {
188
- model.showTrack(trackId)
189
- }
190
- } catch (e) {
191
- console.warn(
192
- `'${e}' occurred while attempting to show track: ${trackId}`,
193
- )
194
- }
161
+ return
162
+ } else if (results?.length === 1) {
163
+ location = results[0].getLocation()
164
+ trackId = results[0].getTrackId()
165
+ }
166
+
167
+ model.navToLocString(location, assemblyName)
168
+ if (trackId) {
169
+ model.showTrack(trackId)
195
170
  }
196
171
  }
172
+ } catch (e) {
173
+ console.error(e)
174
+ session.notify(`${e}`, 'warning')
197
175
  }
198
176
  }
199
177
 
@@ -204,9 +182,9 @@ const LinearGenomeViewHeader = observer(
204
182
  <FormGroup row className={classes.headerForm}>
205
183
  <PanControls model={model} />
206
184
  <RefNameAutocomplete
207
- onSelect={setDisplayedRegion}
185
+ onSelect={handleSelectedRegion}
208
186
  assemblyName={assemblyName}
209
- value={displayedRegions.length > 1 ? '' : refName}
187
+ fetchResults={fetchResults}
210
188
  model={model}
211
189
  TextFieldProps={{
212
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
+ }