@jbrowse/plugin-data-management 1.5.1 → 1.5.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/plugin-data-management",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "JBrowse 2 linear genome view",
5
5
  "keywords": [
6
6
  "jbrowse",
@@ -37,6 +37,7 @@
37
37
  "dependencies": {
38
38
  "@gmod/ucsc-hub": "^0.1.3",
39
39
  "@material-ui/icons": "^4.9.1",
40
+ "clsx": "^1.1.0",
40
41
  "pluralize": "^8.0.0",
41
42
  "react-virtualized-auto-sizer": "^1.0.2",
42
43
  "react-vtree": "^3.0.0-beta.1",
@@ -55,5 +56,5 @@
55
56
  "publishConfig": {
56
57
  "access": "public"
57
58
  },
58
- "gitHead": "284e408c72a3d4d7a0d603197501a8fc8f68c4bc"
59
+ "gitHead": "94fdfbc34787ab8f12a87e00038da74b247b42fa"
59
60
  }
@@ -9,9 +9,10 @@ const hasAnyOverlap = (a1 = [], a2 = []) =>
9
9
  function passesFilter(filter, config) {
10
10
  const name = getTrackName(config)
11
11
  const categories = readConfObject(config, 'category') || []
12
- const regexp = new RegExp(filter, 'i')
12
+ const filterLower = filter.toLowerCase()
13
13
  return (
14
- !!name.match(regexp) || categories.filter(cat => !!cat.match(regexp)).length
14
+ !!name.toLowerCase().includes(filterLower) ||
15
+ categories.filter(cat => !!cat.toLowerCase().includes(filterLower)).length
15
16
  )
16
17
  }
17
18
 
@@ -1,36 +1,50 @@
1
1
  import React, { useState } from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
  import { getRoot } from 'mobx-state-tree'
4
+ import clsx from 'clsx'
4
5
 
5
6
  import {
6
7
  Button,
8
+ Collapse,
7
9
  Dialog,
8
10
  DialogActions,
9
11
  DialogTitle,
10
12
  DialogContent,
13
+ DialogContentText,
11
14
  TextField,
12
- Typography,
13
15
  makeStyles,
14
16
  } from '@material-ui/core'
15
17
 
16
18
  // icons
17
19
  import IconButton from '@material-ui/core/IconButton'
18
20
  import CloseIcon from '@material-ui/icons/Close'
21
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
22
+
23
+ import { PluginDefinition } from '@jbrowse/core/PluginLoader'
19
24
 
20
25
  // locals
21
26
  import { PluginStoreModel } from '../model'
22
27
 
23
- const useStyles = makeStyles(() => ({
24
- closeDialog: {
28
+ const useStyles = makeStyles(theme => ({
29
+ closeButton: {
25
30
  position: 'absolute',
26
- right: 0,
27
- top: 0,
31
+ right: theme.spacing(1),
32
+ top: theme.spacing(1),
28
33
  },
29
- dialogContainer: {
30
- margin: 15,
34
+ dialogContent: {
31
35
  display: 'flex',
32
36
  flexDirection: 'column',
33
37
  },
38
+ expand: {
39
+ transform: 'rotate(0deg)',
40
+ marginLeft: 'auto',
41
+ transition: theme.transitions.create('transform', {
42
+ duration: theme.transitions.duration.shortest,
43
+ }),
44
+ },
45
+ expandOpen: {
46
+ transform: 'rotate(180deg)',
47
+ },
34
48
  }))
35
49
 
36
50
  function CustomPluginForm({
@@ -39,62 +53,156 @@ function CustomPluginForm({
39
53
  model,
40
54
  }: {
41
55
  open: boolean
42
- onClose: () => void
56
+ onClose(): void
43
57
  model: PluginStoreModel
44
58
  }) {
45
59
  const classes = useStyles()
46
- const [formName, setFormName] = useState('')
47
- const [formUrl, setFormUrl] = useState('')
60
+ const [umdPluginName, setUMDPluginName] = useState('')
61
+ const [umdPluginUrl, setUMDPluginUrl] = useState('')
62
+ const [esmPluginUrl, setESMPluginUrl] = useState('')
63
+ const [cjsPluginUrl, setCJSPluginUrl] = useState('')
64
+ const [advancedOptionsOpen, setAdvancedOptionsOpen] = useState(false)
65
+
66
+ function handleChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
67
+ const { name, value } = event.target
68
+ if (name === 'umdName') {
69
+ setUMDPluginName(value)
70
+ }
71
+ if (name === 'umdUrl') {
72
+ setUMDPluginUrl(value)
73
+ }
74
+ if (name === 'esmUrl') {
75
+ setESMPluginUrl(value)
76
+ }
77
+ if (name === 'cjsUrl') {
78
+ setCJSPluginUrl(value)
79
+ }
80
+ }
81
+
82
+ function handleOpenAdvancedOptions() {
83
+ setAdvancedOptionsOpen(!advancedOptionsOpen)
84
+ }
85
+
48
86
  const rootModel = getRoot(model)
49
87
  const { jbrowse } = rootModel
50
88
 
89
+ const ready = Boolean(
90
+ (umdPluginName && umdPluginUrl) || esmPluginUrl || cjsPluginUrl,
91
+ )
92
+
93
+ function handleSubmit() {
94
+ if (!ready) {
95
+ return
96
+ }
97
+ const pluginDefinition: PluginDefinition = {}
98
+ if (umdPluginName && umdPluginUrl) {
99
+ pluginDefinition.name = umdPluginName
100
+ pluginDefinition.umdUrl = umdPluginUrl
101
+ }
102
+ if (esmPluginUrl) {
103
+ pluginDefinition.esmUrl = esmPluginUrl
104
+ }
105
+ if (cjsPluginUrl) {
106
+ pluginDefinition.cjsUrl = cjsPluginUrl
107
+ }
108
+ jbrowse.addPlugin(pluginDefinition)
109
+ }
110
+
111
+ function handleClose() {
112
+ setUMDPluginName('')
113
+ setUMDPluginUrl('')
114
+ setESMPluginUrl('')
115
+ setCJSPluginUrl('')
116
+ onClose()
117
+ }
118
+
51
119
  return (
52
- <Dialog open={open} onClose={() => onClose()}>
120
+ <Dialog open={open} onClose={handleClose}>
53
121
  <DialogTitle>
54
122
  Add custom plugin
55
- <IconButton className={classes.closeDialog} onClick={() => onClose()}>
123
+ <IconButton
124
+ size="medium"
125
+ className={classes.closeButton}
126
+ onClick={() => onClose()}
127
+ >
56
128
  <CloseIcon />
57
129
  </IconButton>
58
130
  </DialogTitle>
59
-
60
- <DialogContent>
61
- <div className={classes.dialogContainer}>
62
- <Typography>
63
- Specify the name and URL path of your plugin source
64
- </Typography>
131
+ <form onSubmit={handleSubmit}>
132
+ <DialogContent className={classes.dialogContent}>
133
+ <DialogContentText>
134
+ Enter the name of the plugin and its URL. The name should match what
135
+ is defined in the plugin's build.
136
+ </DialogContentText>
65
137
  <TextField
66
- id="name-input"
67
- name="name"
138
+ id="umd-name-input"
139
+ name="umdName"
68
140
  label="Plugin name"
69
141
  variant="outlined"
70
- value={formName}
71
- onChange={event => setFormName(event.target.value)}
142
+ value={umdPluginName}
143
+ onChange={handleChange}
72
144
  />
73
145
  <TextField
74
- id="url-input"
75
- name="url"
146
+ id="umd-url-input"
147
+ name="umdUrl"
76
148
  label="Plugin URL"
77
149
  variant="outlined"
78
- value={formUrl}
79
- onChange={event => setFormUrl(event.target.value)}
150
+ value={umdPluginUrl}
151
+ onChange={handleChange}
80
152
  />
81
- </div>
82
- </DialogContent>
83
- <DialogActions>
84
- <Button variant="contained" color="secondary" onClick={() => onClose()}>
85
- Cancel
86
- </Button>
87
- <Button
88
- variant="contained"
89
- color="primary"
90
- onClick={() => {
91
- jbrowse.addPlugin({ name: formName, url: formUrl })
92
- onClose()
93
- }}
94
- >
95
- Submit
96
- </Button>
97
- </DialogActions>
153
+ <DialogContentText onClick={handleOpenAdvancedOptions}>
154
+ <IconButton
155
+ className={clsx(classes.expand, {
156
+ [classes.expandOpen]: advancedOptionsOpen,
157
+ })}
158
+ aria-expanded={advancedOptionsOpen}
159
+ aria-label="show more"
160
+ >
161
+ <ExpandMoreIcon />
162
+ </IconButton>
163
+ Advanced options
164
+ </DialogContentText>
165
+ <Collapse in={advancedOptionsOpen}>
166
+ <div className={classes.dialogContent}>
167
+ <DialogContentText>
168
+ The above fields assume that the plugin is built in UMD format.
169
+ If your plugin is in another format, or you have additional
170
+ builds you want to add (such as a CJS build for using NodeJS
171
+ APIs in desktop), you can enter the URLs for those builds below.
172
+ </DialogContentText>
173
+ <TextField
174
+ id="esm-url-input"
175
+ name="esmUrl"
176
+ label="ESM build URL"
177
+ variant="outlined"
178
+ value={esmPluginUrl}
179
+ onChange={handleChange}
180
+ />
181
+ <TextField
182
+ id="cjs-url-input"
183
+ name="cjsUrl"
184
+ label="CJS build URL"
185
+ variant="outlined"
186
+ value={cjsPluginUrl}
187
+ onChange={handleChange}
188
+ />
189
+ </div>
190
+ </Collapse>
191
+ </DialogContent>
192
+ <DialogActions>
193
+ <Button variant="contained" onClick={handleClose}>
194
+ Cancel
195
+ </Button>
196
+ <Button
197
+ variant="contained"
198
+ color="primary"
199
+ onClick={handleSubmit}
200
+ disabled={!ready}
201
+ >
202
+ Submit
203
+ </Button>
204
+ </DialogActions>
205
+ </form>
98
206
  </Dialog>
99
207
  )
100
208
  }
@@ -131,7 +131,11 @@ function InstalledPlugin({
131
131
  onClose={name => {
132
132
  if (name) {
133
133
  const pluginMetadata = pluginManager.pluginMetadata[plugin.name]
134
- const pluginUrl = pluginMetadata.url
134
+ const pluginUrl =
135
+ pluginMetadata.url ||
136
+ pluginMetadata.esmUrl ||
137
+ pluginMetadata.umdUrl ||
138
+ pluginMetadata.cjsUrl
135
139
  if (adminMode) {
136
140
  jbrowse.removePlugin(pluginUrl)
137
141
  } else if (isSessionWithSessionPlugins(session)) {
@@ -65,15 +65,15 @@ describe('<PluginStoreWidget />', () => {
65
65
  )
66
66
  await findByText('multiple sequence alignment browser plugin for JBrowse 2')
67
67
  fireEvent.click(getByText('Add custom plugin'))
68
- fireEvent.change(getByLabelText('Plugin name'), {
68
+ fireEvent.change(getByLabelText('Plugin URL'), {
69
69
  target: {
70
- value: 'MsaView',
70
+ value:
71
+ 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.esm.js',
71
72
  },
72
73
  })
73
- fireEvent.change(getByLabelText('Plugin URL'), {
74
+ fireEvent.change(getByLabelText('Plugin name'), {
74
75
  target: {
75
- value:
76
- 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.umd.production.min.js',
76
+ value: 'MsaView',
77
77
  },
78
78
  })
79
79
  fireEvent.click(getByText('Submit'))
@@ -85,7 +85,8 @@ describe('<PluginStoreWidget />', () => {
85
85
  expect(getSnapshot(getParent(session)).jbrowse.plugins).toEqual([
86
86
  {
87
87
  name: 'MsaView',
88
- url: 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.umd.production.min.js',
88
+ umdUrl:
89
+ 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.esm.js',
89
90
  },
90
91
  ])
91
92
  })
@@ -59,12 +59,14 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
59
59
  const { pluginManager } = getEnv(model)
60
60
 
61
61
  useEffect(() => {
62
- let killed = false
62
+ const controller = new AbortController()
63
+ const { signal } = controller
63
64
 
64
65
  ;(async () => {
65
66
  try {
66
67
  const response = await fetch(
67
68
  'https://jbrowse.org/plugin-store/plugins.json',
69
+ { signal },
68
70
  )
69
71
  if (!response.ok) {
70
72
  const err = await response.text()
@@ -73,7 +75,7 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
73
75
  )
74
76
  }
75
77
  const array = await response.json()
76
- if (!killed) {
78
+ if (!signal.aborted) {
77
79
  setPluginArray(array.plugins)
78
80
  }
79
81
  } catch (e) {
@@ -83,7 +85,7 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
83
85
  })()
84
86
 
85
87
  return () => {
86
- killed = true
88
+ controller.abort()
87
89
  }
88
90
  }, [])
89
91
 
@@ -155,11 +157,18 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
155
157
  <Typography color="error">{`${error}`}</Typography>
156
158
  ) : pluginArray ? (
157
159
  pluginArray
158
- .filter(plugin =>
159
- plugin.name
160
+ .filter(plugin => {
161
+ // If pugin only has cjsUrl, don't display outside desktop
162
+ if (
163
+ !isElectron &&
164
+ !(plugin.esmUrl || plugin.url || plugin.umdUrl)
165
+ ) {
166
+ return false
167
+ }
168
+ return plugin.name
160
169
  .toLowerCase()
161
- .includes(model.filterText.toLowerCase()),
162
- )
170
+ .includes(model.filterText.toLowerCase())
171
+ })
163
172
  .map(plugin => (
164
173
  <PluginCard
165
174
  key={(plugin as JBrowsePlugin).name}
@@ -393,6 +393,26 @@ exports[`<PluginStoreWidget /> renders with the available plugins 1`] = `
393
393
  LollipopPlugin
394
394
  </p>
395
395
  </li>
396
+ <li
397
+ class="MuiListItem-root MuiListItem-gutters"
398
+ >
399
+ <svg
400
+ aria-hidden="true"
401
+ class="MuiSvgIcon-root makeStyles-lockedPluginTooltip"
402
+ focusable="false"
403
+ title="This plugin was installed by an administrator, you cannot remove it."
404
+ viewBox="0 0 24 24"
405
+ >
406
+ <path
407
+ d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"
408
+ />
409
+ </svg>
410
+ <p
411
+ class="MuiTypography-root MuiTypography-body1"
412
+ >
413
+ ArcRenderer
414
+ </p>
415
+ </li>
396
416
  <li
397
417
  class="MuiListItem-root MuiListItem-gutters"
398
418
  >