@jbrowse/plugin-linear-genome-view 1.3.4 → 1.5.0

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 (34) hide show
  1. package/dist/BaseLinearDisplay/components/BaseLinearDisplay.d.ts +3 -2
  2. package/dist/BaseLinearDisplay/models/BaseLinearDisplayModel.d.ts +20 -10
  3. package/dist/BaseLinearDisplay/models/serverSideRenderedBlock.d.ts +2 -2
  4. package/dist/LinearBareDisplay/model.d.ts +10 -9
  5. package/dist/LinearBasicDisplay/model.d.ts +13 -9
  6. package/dist/LinearGenomeView/components/Header.d.ts +2 -4
  7. package/dist/LinearGenomeView/components/RefNameAutocomplete.d.ts +1 -11
  8. package/dist/LinearGenomeView/components/ScaleBar.d.ts +46 -14
  9. package/dist/LinearGenomeView/components/util.d.ts +2 -0
  10. package/dist/LinearGenomeView/index.d.ts +13 -2
  11. package/dist/index.d.ts +47 -56
  12. package/dist/plugin-linear-genome-view.cjs.development.js +642 -423
  13. package/dist/plugin-linear-genome-view.cjs.development.js.map +1 -1
  14. package/dist/plugin-linear-genome-view.cjs.production.min.js +1 -1
  15. package/dist/plugin-linear-genome-view.cjs.production.min.js.map +1 -1
  16. package/dist/plugin-linear-genome-view.esm.js +652 -433
  17. package/dist/plugin-linear-genome-view.esm.js.map +1 -1
  18. package/package.json +4 -2
  19. package/src/BaseLinearDisplay/components/BaseLinearDisplay.tsx +100 -21
  20. package/src/BaseLinearDisplay/models/BaseLinearDisplayModel.tsx +10 -10
  21. package/src/BaseLinearDisplay/models/serverSideRenderedBlock.ts +15 -13
  22. package/src/LinearBasicDisplay/model.ts +25 -3
  23. package/src/LinearGenomeView/components/ExportSvgDialog.tsx +17 -8
  24. package/src/LinearGenomeView/components/Header.tsx +101 -104
  25. package/src/LinearGenomeView/components/ImportForm.tsx +146 -113
  26. package/src/LinearGenomeView/components/LinearGenomeView.test.js +6 -6
  27. package/src/LinearGenomeView/components/OverviewScaleBar.tsx +4 -1
  28. package/src/LinearGenomeView/components/RefNameAutocomplete.tsx +196 -169
  29. package/src/LinearGenomeView/components/SearchResultsDialog.tsx +1 -16
  30. package/src/LinearGenomeView/components/SequenceDialog.tsx +59 -58
  31. package/src/LinearGenomeView/components/__snapshots__/LinearGenomeView.test.js.snap +5 -177
  32. package/src/LinearGenomeView/components/util.ts +8 -0
  33. package/src/LinearGenomeView/index.tsx +39 -28
  34. 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.3.4",
3
+ "version": "1.5.0",
4
4
  "description": "JBrowse 2 linear genome view",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -36,6 +36,7 @@
36
36
  },
37
37
  "dependencies": {
38
38
  "@material-ui/icons": "^4.9.1",
39
+ "@popperjs/core": "^2.9.3",
39
40
  "clone": "^2.1.2",
40
41
  "clsx": "^1.0.4",
41
42
  "copy-to-clipboard": "^3.3.1",
@@ -43,6 +44,7 @@
43
44
  "is-object": "^1.0.1",
44
45
  "json-stable-stringify": "^1.0.1",
45
46
  "normalize-wheel": "^1.0.1",
47
+ "react-popper": "^2.0.0",
46
48
  "react-sizeme": "^2.6.7"
47
49
  },
48
50
  "peerDependencies": {
@@ -59,5 +61,5 @@
59
61
  "publishConfig": {
60
62
  "access": "public"
61
63
  },
62
- "gitHead": "4e2fcbbfd682edc3132f372fb72bffc513248d63"
64
+ "gitHead": "542025578a39bd170c8a166f2568ee7edbd54072"
63
65
  }
@@ -1,13 +1,19 @@
1
+ import React, { useState, useRef, useMemo } from 'react'
2
+ import { Portal, alpha, useTheme, makeStyles } from '@material-ui/core'
1
3
  import { getConf } from '@jbrowse/core/configuration'
2
4
  import { Menu } from '@jbrowse/core/ui'
3
- import { useTheme, makeStyles } from '@material-ui/core/styles'
4
5
  import { observer } from 'mobx-react'
5
- import React, { useState, useRef } from 'react'
6
- import MUITooltip from '@material-ui/core/Tooltip'
6
+ import { usePopper } from 'react-popper'
7
+
8
+ // locals
7
9
  import LinearBlocks from './LinearBlocks'
8
10
  import { BaseLinearDisplayModel } from '../models/BaseLinearDisplayModel'
9
11
 
10
- const useStyles = makeStyles({
12
+ function round(value: number) {
13
+ return Math.round(value * 1e5) / 1e5
14
+ }
15
+
16
+ const useStyles = makeStyles(theme => ({
11
17
  display: {
12
18
  position: 'relative',
13
19
  whiteSpace: 'nowrap',
@@ -15,26 +21,89 @@ const useStyles = makeStyles({
15
21
  width: '100%',
16
22
  minHeight: '100%',
17
23
  },
24
+
25
+ // these styles come from
26
+ // https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Tooltip/Tooltip.js
27
+ tooltip: {
28
+ pointerEvents: 'none',
29
+ backgroundColor: alpha(theme.palette.grey[700], 0.9),
30
+ borderRadius: theme.shape.borderRadius,
31
+ color: theme.palette.common.white,
32
+ fontFamily: theme.typography.fontFamily,
33
+ padding: '4px 8px',
34
+ fontSize: theme.typography.pxToRem(10),
35
+ lineHeight: `${round(14 / 10)}em`,
36
+ maxWidth: 300,
37
+ wordWrap: 'break-word',
38
+ fontWeight: theme.typography.fontWeightMedium,
39
+ },
40
+ }))
41
+
42
+ const TooltipContents = React.forwardRef<
43
+ HTMLDivElement,
44
+ { message: React.ReactNode | string }
45
+ >(({ message }: { message: React.ReactNode | string }, ref) => {
46
+ return <div ref={ref}>{message}</div>
18
47
  })
48
+
19
49
  const Tooltip = observer(
20
- (props: { model: BaseLinearDisplayModel; mouseCoord: [number, number] }) => {
21
- const { model, mouseCoord } = props
50
+ ({
51
+ model,
52
+ clientMouseCoord,
53
+ }: {
54
+ model: BaseLinearDisplayModel
55
+ clientMouseCoord: Coord
56
+ }) => {
57
+ const classes = useStyles()
22
58
  const { featureUnderMouse } = model
23
- const mouseover = featureUnderMouse
59
+ const [width, setWidth] = useState(0)
60
+
61
+ const [popperElt, setPopperElt] = useState<HTMLDivElement | null>(null)
62
+
63
+ // must be memoized a la https://github.com/popperjs/react-popper/issues/391
64
+ const virtElement = useMemo(
65
+ () => ({
66
+ getBoundingClientRect: () => {
67
+ const x = clientMouseCoord[0] + width / 2 + 20
68
+ const y = clientMouseCoord[1]
69
+ return {
70
+ top: y,
71
+ left: x,
72
+ bottom: y,
73
+ right: x,
74
+ width: 0,
75
+ height: 0,
76
+ x,
77
+ y,
78
+ toJSON() {},
79
+ }
80
+ },
81
+ }),
82
+ [clientMouseCoord, width],
83
+ )
84
+ const { styles, attributes } = usePopper(virtElement, popperElt)
85
+
86
+ const contents = featureUnderMouse
24
87
  ? getConf(model, 'mouseover', { feature: featureUnderMouse })
25
88
  : undefined
26
- return mouseover ? (
27
- <MUITooltip title={mouseover} open placement="right">
89
+
90
+ return featureUnderMouse && contents ? (
91
+ <Portal>
28
92
  <div
29
- style={{
30
- position: 'absolute',
31
- left: mouseCoord[0],
32
- top: mouseCoord[1],
33
- }}
93
+ ref={setPopperElt}
94
+ className={classes.tooltip}
95
+ // zIndex needed to go over widget drawer
96
+ style={{ ...styles.popper, zIndex: 100000 }}
97
+ {...attributes.popper}
34
98
  >
35
- {' '}
99
+ <TooltipContents
100
+ ref={(elt: HTMLDivElement) =>
101
+ setWidth(elt?.getBoundingClientRect().width || 0)
102
+ }
103
+ message={contents}
104
+ />
36
105
  </div>
37
- </MUITooltip>
106
+ </Portal>
38
107
  ) : null
39
108
  },
40
109
  )
@@ -44,10 +113,11 @@ const BaseLinearDisplay = observer(
44
113
  (props: { model: BaseLinearDisplayModel; children?: React.ReactNode }) => {
45
114
  const classes = useStyles()
46
115
  const theme = useTheme()
47
-
48
- const [mouseCoord, setMouseCoord] = useState<Coord>([0, 0])
49
- const [contextCoord, setContextCoord] = useState<Coord>()
50
116
  const ref = useRef<HTMLDivElement>(null)
117
+ const [clientRect, setClientRect] = useState<ClientRect>()
118
+ const [offsetMouseCoord, setOffsetMouseCoord] = useState<Coord>([0, 0])
119
+ const [clientMouseCoord, setClientMouseCoord] = useState<Coord>([0, 0])
120
+ const [contextCoord, setContextCoord] = useState<Coord>()
51
121
  const { model, children } = props
52
122
  const {
53
123
  TooltipComponent,
@@ -74,7 +144,12 @@ const BaseLinearDisplay = observer(
74
144
  onMouseMove={event => {
75
145
  if (ref.current) {
76
146
  const rect = ref.current.getBoundingClientRect()
77
- setMouseCoord([event.clientX - rect.left, event.clientY - rect.top])
147
+ setOffsetMouseCoord([
148
+ event.clientX - rect.left,
149
+ event.clientY - rect.top,
150
+ ])
151
+ setClientMouseCoord([event.clientX, event.clientY])
152
+ setClientRect(rect)
78
153
  }
79
154
  }}
80
155
  role="presentation"
@@ -85,10 +160,14 @@ const BaseLinearDisplay = observer(
85
160
  <LinearBlocks {...props} />
86
161
  )}
87
162
  {children}
163
+
88
164
  <TooltipComponent
89
165
  model={model}
90
166
  height={height}
91
- mouseCoord={mouseCoord}
167
+ offsetMouseCoord={offsetMouseCoord}
168
+ clientMouseCoord={clientMouseCoord}
169
+ clientRect={clientRect}
170
+ mouseCoord={offsetMouseCoord}
92
171
  />
93
172
 
94
173
  <Menu
@@ -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
  /**
@@ -97,8 +97,7 @@ export const BaseLinearDisplay = types
97
97
  */
98
98
  get selectedFeatureId() {
99
99
  if (isAlive(self)) {
100
- const session = getSession(self)
101
- const { selection } = session
100
+ const { selection } = getSession(self)
102
101
  // does it quack like a feature?
103
102
  if (isFeature(selection)) {
104
103
  return selection.id()
@@ -140,7 +139,12 @@ export const BaseLinearDisplay = types
140
139
  return self.blockState.get(blockKey)?.layout?.getByCoord(x, y)
141
140
  },
142
141
 
143
- getFeatureByID(id: string): [number, number, number, number] | undefined {
142
+ getFeatureByID(blockKey: string, id: string): LayoutRecord | undefined {
143
+ return self.blockState.get(blockKey)?.layout?.getByID(id)
144
+ },
145
+
146
+ // if block key is not supplied, can look at all blocks
147
+ searchFeatureByID(id: string): LayoutRecord | undefined {
144
148
  let ret
145
149
  self.blockState.forEach(block => {
146
150
  const val = block?.layout?.getByID(id)
@@ -383,12 +387,8 @@ export const BaseLinearDisplay = types
383
387
  }
384
388
  }
385
389
 
386
- const {
387
- rpcManager,
388
- renderArgs,
389
- renderProps,
390
- rendererType,
391
- } = renderBlockData(blockState, self)
390
+ const { rpcManager, renderArgs, renderProps, rendererType } =
391
+ renderBlockData(blockState, self)
392
392
 
393
393
  return rendererType.renderInClient(rpcManager, {
394
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
  ]
@@ -3,6 +3,7 @@ import { makeStyles } from '@material-ui/core/styles'
3
3
  import {
4
4
  Button,
5
5
  Dialog,
6
+ DialogActions,
6
7
  DialogContent,
7
8
  DialogTitle,
8
9
  IconButton,
@@ -30,11 +31,11 @@ export default function ExportSvgDlg({
30
31
  model: LGV
31
32
  handleClose: () => void
32
33
  }) {
33
- const [rasterizeLayers, setRasterizeLayers] = useState(
34
- typeof OffscreenCanvas !== 'undefined',
35
- )
34
+ // @ts-ignore
35
+ const offscreenCanvas = typeof OffscreenCanvas !== 'undefined'
36
+ const [rasterizeLayers, setRasterizeLayers] = useState(offscreenCanvas)
36
37
  const [loading, setLoading] = useState(false)
37
- const [error, setError] = useState<Error>()
38
+ const [error, setError] = useState<unknown>()
38
39
  const classes = useStyles()
39
40
  return (
40
41
  <Dialog open onClose={handleClose}>
@@ -53,8 +54,7 @@ export default function ExportSvgDlg({
53
54
  <Typography display="inline">Creating SVG</Typography>
54
55
  </div>
55
56
  ) : null}
56
-
57
- {typeof OffscreenCanvas !== 'undefined' ? (
57
+ {offscreenCanvas ? (
58
58
  <FormControlLabel
59
59
  control={
60
60
  <Checkbox
@@ -70,7 +70,15 @@ export default function ExportSvgDlg({
70
70
  size may be large
71
71
  </Typography>
72
72
  )}
73
-
73
+ </DialogContent>
74
+ <DialogActions>
75
+ <Button
76
+ variant="contained"
77
+ color="secondary"
78
+ onClick={() => handleClose()}
79
+ >
80
+ Cancel
81
+ </Button>
74
82
  <Button
75
83
  variant="contained"
76
84
  color="primary"
@@ -82,6 +90,7 @@ export default function ExportSvgDlg({
82
90
  await model.exportSvg({ rasterizeLayers })
83
91
  handleClose()
84
92
  } catch (e) {
93
+ console.error(e)
85
94
  setError(e)
86
95
  } finally {
87
96
  setLoading(false)
@@ -90,7 +99,7 @@ export default function ExportSvgDlg({
90
99
  >
91
100
  Submit
92
101
  </Button>
93
- </DialogContent>
102
+ </DialogActions>
94
103
  </Dialog>
95
104
  )
96
105
  }