@jbrowse/plugin-data-management 1.4.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.
Files changed (37) hide show
  1. package/dist/AddTrackWidget/components/TrackSourceSelect.d.ts +2 -1
  2. package/dist/AddTrackWidget/model.d.ts +1 -363
  3. package/dist/PluginStoreWidget/components/CustomPluginForm.d.ts +1 -1
  4. package/dist/PluginStoreWidget/components/PluginCard.d.ts +2 -2
  5. package/dist/SetDefaultSession/SetDefaultSession.d.ts +4 -6
  6. package/dist/index.d.ts +8 -12
  7. package/dist/plugin-data-management.cjs.development.js +627 -501
  8. package/dist/plugin-data-management.cjs.development.js.map +1 -1
  9. package/dist/plugin-data-management.cjs.production.min.js +1 -1
  10. package/dist/plugin-data-management.cjs.production.min.js.map +1 -1
  11. package/dist/plugin-data-management.esm.js +614 -507
  12. package/dist/plugin-data-management.esm.js.map +1 -1
  13. package/package.json +3 -2
  14. package/src/AddConnectionWidget/components/AddConnectionWidget.test.js +3 -8
  15. package/src/AddTrackWidget/components/AddTrackWidget.test.js +2 -3
  16. package/src/AddTrackWidget/components/AddTrackWidget.tsx +4 -2
  17. package/src/AddTrackWidget/components/ConfirmTrack.tsx +160 -88
  18. package/src/AddTrackWidget/components/TrackSourceSelect.tsx +30 -23
  19. package/src/AddTrackWidget/components/__snapshots__/AddTrackWidget.test.js.snap +157 -124
  20. package/src/AddTrackWidget/index.test.jsx +78 -26
  21. package/src/AddTrackWidget/model.ts +5 -14
  22. package/src/AssemblyManager/AssemblyAddForm.tsx +7 -6
  23. package/src/AssemblyManager/AssemblyManager.test.tsx +1 -0
  24. package/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js +27 -17
  25. package/src/HierarchicalTrackSelectorWidget/model.js +3 -2
  26. package/src/PluginStoreWidget/components/CustomPluginForm.tsx +164 -56
  27. package/src/PluginStoreWidget/components/InstalledPlugin.tsx +10 -2
  28. package/src/PluginStoreWidget/components/PluginCard.tsx +7 -9
  29. package/src/PluginStoreWidget/components/PluginStoreWidget.test.js +9 -10
  30. package/src/PluginStoreWidget/components/PluginStoreWidget.tsx +36 -26
  31. package/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap +89 -51
  32. package/src/SetDefaultSession/SetDefaultSession.test.tsx +7 -81
  33. package/src/SetDefaultSession/SetDefaultSession.tsx +51 -162
  34. package/src/index.ts +1 -51
  35. package/src/ucsc-trackhub/configSchema.js +4 -1
  36. package/src/ucsc-trackhub/model.js +31 -31
  37. package/src/ucsc-trackhub/ucscTrackHub.js +40 -12
@@ -17,6 +17,7 @@ import {
17
17
  Menu,
18
18
  MenuItem,
19
19
  TextField,
20
+ Tooltip,
20
21
  Typography,
21
22
  makeStyles,
22
23
  } from '@material-ui/core'
@@ -114,6 +115,7 @@ const Node = props => {
114
115
  toggleCollapse,
115
116
  conf,
116
117
  onMoreInfo,
118
+ drawerPosition,
117
119
  } = data
118
120
 
119
121
  const classes = useStyles()
@@ -121,6 +123,7 @@ const Node = props => {
121
123
  const marginLeft = nestingLevel * width + (isLeaf ? width : 0)
122
124
  const unsupported =
123
125
  name && (name.endsWith('(Unsupported)') || name.endsWith('(Unknown)'))
126
+ const description = (conf && readConfObject(conf, ['description'])) || ''
124
127
 
125
128
  return (
126
129
  <div style={style} className={!isLeaf ? classes.accordionBase : undefined}>
@@ -153,22 +156,27 @@ const Node = props => {
153
156
  </div>
154
157
  ) : (
155
158
  <>
156
- <FormControlLabel
157
- className={classes.checkboxLabel}
158
- control={
159
- <Checkbox
160
- className={classes.compactCheckbox}
161
- checked={checked}
162
- onChange={() => onChange(id)}
163
- color="primary"
164
- disabled={unsupported}
165
- inputProps={{
166
- 'data-testid': `htsTrackEntry-${id}`,
167
- }}
168
- />
169
- }
170
- label={name}
171
- />
159
+ <Tooltip
160
+ title={description}
161
+ placement={drawerPosition === 'left' ? 'right' : 'left'}
162
+ >
163
+ <FormControlLabel
164
+ className={classes.checkboxLabel}
165
+ control={
166
+ <Checkbox
167
+ className={classes.compactCheckbox}
168
+ checked={checked}
169
+ onChange={() => onChange(id)}
170
+ color="primary"
171
+ disabled={unsupported}
172
+ inputProps={{
173
+ 'data-testid': `htsTrackEntry-${id}`,
174
+ }}
175
+ />
176
+ }
177
+ label={name}
178
+ />
179
+ </Tooltip>
172
180
  <IconButton
173
181
  onClick={e => onMoreInfo({ target: e.currentTarget, id, conf })}
174
182
  color="secondary"
@@ -208,14 +216,16 @@ const HierarchicalTree = observer(({ height, tree, model }) => {
208
216
  const treeRef = useRef(null)
209
217
  const [info, setMoreInfo] = useState()
210
218
  const session = getSession(model)
219
+ const { drawerPosition } = session
211
220
 
212
221
  const extra = useMemo(
213
222
  () => ({
214
223
  onChange: trackId => view.toggleTrack(trackId),
215
224
  toggleCollapse: pathName => model.toggleCategory(pathName),
216
225
  onMoreInfo: setMoreInfo,
226
+ drawerPosition,
217
227
  }),
218
- [view, model],
228
+ [view, model, drawerPosition],
219
229
  )
220
230
  const treeWalker = useCallback(
221
231
  function* treeWalker() {
@@ -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,29 +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
- import { makeStyles } from '@material-ui/core/styles'
6
- import Dialog from '@material-ui/core/Dialog'
7
- import DialogTitle from '@material-ui/core/DialogTitle'
8
- import TextField from '@material-ui/core/TextField'
9
- import Button from '@material-ui/core/Button'
6
+ import {
7
+ Button,
8
+ Collapse,
9
+ Dialog,
10
+ DialogActions,
11
+ DialogTitle,
12
+ DialogContent,
13
+ DialogContentText,
14
+ TextField,
15
+ makeStyles,
16
+ } from '@material-ui/core'
10
17
 
18
+ // icons
11
19
  import IconButton from '@material-ui/core/IconButton'
12
20
  import CloseIcon from '@material-ui/icons/Close'
21
+ import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
13
22
 
23
+ import { PluginDefinition } from '@jbrowse/core/PluginLoader'
24
+
25
+ // locals
14
26
  import { PluginStoreModel } from '../model'
15
27
 
16
- const useStyles = makeStyles(() => ({
17
- closeDialog: {
28
+ const useStyles = makeStyles(theme => ({
29
+ closeButton: {
18
30
  position: 'absolute',
19
- right: 0,
20
- top: 0,
31
+ right: theme.spacing(1),
32
+ top: theme.spacing(1),
21
33
  },
22
- dialogContainer: {
23
- margin: 15,
34
+ dialogContent: {
24
35
  display: 'flex',
25
36
  flexDirection: 'column',
26
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
+ },
27
48
  }))
28
49
 
29
50
  function CustomPluginForm({
@@ -32,69 +53,156 @@ function CustomPluginForm({
32
53
  model,
33
54
  }: {
34
55
  open: boolean
35
- onClose: Function
56
+ onClose(): void
36
57
  model: PluginStoreModel
37
58
  }) {
38
59
  const classes = useStyles()
39
- const [formInput, setFormInput] = useState({
40
- name: '',
41
- url: '',
42
- })
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)
43
65
 
44
- const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
45
- setFormInput({
46
- ...formInput,
47
- [event.target.name]: event.target.value,
48
- })
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)
49
84
  }
50
85
 
51
86
  const rootModel = getRoot(model)
52
87
  const { jbrowse } = rootModel
53
88
 
54
- const handleSubmit = () => {
55
- jbrowse.addPlugin({ name: formInput.name, url: formInput.url })
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()
56
117
  }
57
118
 
58
119
  return (
59
- <Dialog open={open} onClose={() => onClose(false)}>
120
+ <Dialog open={open} onClose={handleClose}>
60
121
  <DialogTitle>
122
+ Add custom plugin
61
123
  <IconButton
62
- className={classes.closeDialog}
63
- aria-label="close-dialog"
64
- onClick={() => onClose(false)}
124
+ size="medium"
125
+ className={classes.closeButton}
126
+ onClick={() => onClose()}
65
127
  >
66
128
  <CloseIcon />
67
129
  </IconButton>
68
130
  </DialogTitle>
69
-
70
- <div className={classes.dialogContainer}>
71
- <TextField
72
- id="name-input"
73
- name="name"
74
- label="Plugin name"
75
- variant="outlined"
76
- value={formInput.name}
77
- onChange={handleChange}
78
- multiline
79
- />
80
- <TextField
81
- id="url-input"
82
- name="url"
83
- label="Plugin URL"
84
- variant="outlined"
85
- value={formInput.url}
86
- onChange={handleChange}
87
- multiline
88
- />
89
- <Button
90
- variant="contained"
91
- color="primary"
92
- style={{ marginTop: '1.5rem' }}
93
- onClick={handleSubmit}
94
- >
95
- Add plugin
96
- </Button>
97
- </div>
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>
137
+ <TextField
138
+ id="umd-name-input"
139
+ name="umdName"
140
+ label="Plugin name"
141
+ variant="outlined"
142
+ value={umdPluginName}
143
+ onChange={handleChange}
144
+ />
145
+ <TextField
146
+ id="umd-url-input"
147
+ name="umdUrl"
148
+ label="Plugin URL"
149
+ variant="outlined"
150
+ value={umdPluginUrl}
151
+ onChange={handleChange}
152
+ />
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
  }
@@ -33,12 +33,16 @@ const useStyles = makeStyles(() => ({
33
33
  dialogContainer: {
34
34
  margin: 15,
35
35
  },
36
+ lockedPluginTooltip: {
37
+ marginRight: '0.5rem',
38
+ },
36
39
  }))
37
40
 
38
41
  function LockedPlugin() {
42
+ const classes = useStyles()
39
43
  return (
40
44
  <Tooltip
41
- style={{ marginRight: '0.5rem' }}
45
+ className={classes.lockedPluginTooltip}
42
46
  title="This plugin was installed by an administrator, you cannot remove it."
43
47
  >
44
48
  <LockIcon />
@@ -127,7 +131,11 @@ function InstalledPlugin({
127
131
  onClose={name => {
128
132
  if (name) {
129
133
  const pluginMetadata = pluginManager.pluginMetadata[plugin.name]
130
- const pluginUrl = pluginMetadata.url
134
+ const pluginUrl =
135
+ pluginMetadata.url ||
136
+ pluginMetadata.esmUrl ||
137
+ pluginMetadata.umdUrl ||
138
+ pluginMetadata.cjsUrl
131
139
  if (adminMode) {
132
140
  jbrowse.removePlugin(pluginUrl)
133
141
  } else if (isSessionWithSessionPlugins(session)) {
@@ -1,8 +1,10 @@
1
1
  import React, { useState } from 'react'
2
+ import PluginManager from '@jbrowse/core/PluginManager'
2
3
  import { observer } from 'mobx-react'
3
4
  import { getEnv, getParent } from 'mobx-state-tree'
4
-
5
- import { makeStyles } from '@material-ui/core/styles'
5
+ import { getSession } from '@jbrowse/core/util'
6
+ import { JBrowsePlugin } from '@jbrowse/core/util/types'
7
+ import { isSessionWithSessionPlugins } from '@jbrowse/core/util/types'
6
8
  import {
7
9
  Card,
8
10
  CardActions,
@@ -10,18 +12,14 @@ import {
10
12
  Button,
11
13
  Link,
12
14
  Typography,
15
+ makeStyles,
13
16
  } from '@material-ui/core'
14
17
 
18
+ // icons
15
19
  import PersonIcon from '@material-ui/icons/Person'
16
20
  import AddIcon from '@material-ui/icons/Add'
17
21
  import CheckIcon from '@material-ui/icons/Check'
18
-
19
- import PluginManager from '@jbrowse/core/PluginManager'
20
- import { getSession } from '@jbrowse/core/util'
21
- import type { JBrowsePlugin } from '@jbrowse/core/util/types'
22
- import { isSessionWithSessionPlugins } from '@jbrowse/core/util/types'
23
-
24
- import type { PluginStoreModel } from '../model'
22
+ import { PluginStoreModel } from '../model'
25
23
 
26
24
  const useStyles = makeStyles(() => ({
27
25
  card: {
@@ -12,8 +12,7 @@ const plugins = {
12
12
  authors: ['Colin Diesh'],
13
13
  description: 'multiple sequence alignment browser plugin for JBrowse 2',
14
14
  location: 'https://github.com/GMOD/jbrowse-plugin-msaview',
15
- url:
16
- 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.umd.production.min.js',
15
+ url: 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.umd.production.min.js',
17
16
  license: 'Apache License 2.0',
18
17
  image:
19
18
  'https://raw.githubusercontent.com/GMOD/jbrowse-plugin-list/main/img/msaview-screenshot-fs8.png',
@@ -66,18 +65,18 @@ describe('<PluginStoreWidget />', () => {
66
65
  )
67
66
  await findByText('multiple sequence alignment browser plugin for JBrowse 2')
68
67
  fireEvent.click(getByText('Add custom plugin'))
69
- fireEvent.change(getByLabelText('Plugin name'), {
68
+ fireEvent.change(getByLabelText('Plugin URL'), {
70
69
  target: {
71
- value: 'MsaView',
70
+ value:
71
+ 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.esm.js',
72
72
  },
73
73
  })
74
- fireEvent.change(getByLabelText('Plugin URL'), {
74
+ fireEvent.change(getByLabelText('Plugin name'), {
75
75
  target: {
76
- value:
77
- 'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.umd.production.min.js',
76
+ value: 'MsaView',
78
77
  },
79
78
  })
80
- fireEvent.click(getByText('Add plugin'))
79
+ fireEvent.click(getByText('Submit'))
81
80
 
82
81
  await waitFor(() => {
83
82
  expect(window.location.reload).toHaveBeenCalled()
@@ -86,8 +85,8 @@ describe('<PluginStoreWidget />', () => {
86
85
  expect(getSnapshot(getParent(session)).jbrowse.plugins).toEqual([
87
86
  {
88
87
  name: 'MsaView',
89
- url:
90
- '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',
91
90
  },
92
91
  ])
93
92
  })
@@ -13,29 +13,27 @@ import {
13
13
  makeStyles,
14
14
  } from '@material-ui/core'
15
15
 
16
+ import { JBrowsePlugin } from '@jbrowse/core/util/types'
17
+ import { getSession, isElectron } from '@jbrowse/core/util'
18
+
19
+ // icons
16
20
  import ExpandMoreIcon from '@material-ui/icons/ExpandMore'
17
21
  import ClearIcon from '@material-ui/icons/Clear'
18
22
  import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
19
23
 
20
- import type { JBrowsePlugin } from '@jbrowse/core/util/types'
21
-
22
- import { getSession, isElectron } from '@jbrowse/core/util'
24
+ // locals
23
25
  import InstalledPluginsList from './InstalledPluginsList'
24
26
  import PluginCard from './PluginCard'
25
27
  import CustomPluginForm from './CustomPluginForm'
26
-
27
28
  import { PluginStoreModel } from '../model'
28
29
 
29
30
  const useStyles = makeStyles(theme => ({
30
- accordion: {
31
- marginTop: '1em',
31
+ root: {
32
+ margin: theme.spacing(1),
32
33
  },
33
34
  expandIcon: {
34
35
  color: '#fff',
35
36
  },
36
- searchBox: {
37
- marginBottom: theme.spacing(2),
38
- },
39
37
  adminBadge: {
40
38
  margin: '0.5em',
41
39
  borderRadius: 3,
@@ -54,39 +52,45 @@ const useStyles = makeStyles(theme => ({
54
52
 
55
53
  function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
56
54
  const classes = useStyles()
57
- const [pluginArray, setPluginArray] = useState<JBrowsePlugin[]>([])
58
- const [error, setError] = useState<Error>()
55
+ const [pluginArray, setPluginArray] = useState<JBrowsePlugin[]>()
56
+ const [error, setError] = useState<unknown>()
59
57
  const [customPluginFormOpen, setCustomPluginFormOpen] = useState(false)
60
58
  const { adminMode } = getSession(model)
61
59
  const { pluginManager } = getEnv(model)
62
60
 
63
61
  useEffect(() => {
64
- let killed = false
62
+ const controller = new AbortController()
63
+ const { signal } = controller
65
64
 
66
65
  ;(async () => {
67
66
  try {
68
- const fetchResult = await fetch(
67
+ const response = await fetch(
69
68
  'https://jbrowse.org/plugin-store/plugins.json',
69
+ { signal },
70
70
  )
71
- if (!fetchResult.ok) {
72
- throw new Error('Failed to fetch plugin data')
71
+ if (!response.ok) {
72
+ const err = await response.text()
73
+ throw new Error(
74
+ `Failed to fetch plugin data: ${response.status} ${response.statusText} ${err}`,
75
+ )
73
76
  }
74
- const array = await fetchResult.json()
75
- if (!killed) {
77
+ const array = await response.json()
78
+ if (!signal.aborted) {
76
79
  setPluginArray(array.plugins)
77
80
  }
78
81
  } catch (e) {
82
+ console.error(e)
79
83
  setError(e)
80
84
  }
81
85
  })()
82
86
 
83
87
  return () => {
84
- killed = true
88
+ controller.abort()
85
89
  }
86
90
  }, [])
87
91
 
88
92
  return (
89
- <div>
93
+ <div className={classes.root}>
90
94
  {adminMode && (
91
95
  <>
92
96
  {!isElectron && (
@@ -110,13 +114,12 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
110
114
  </div>
111
115
  <CustomPluginForm
112
116
  open={customPluginFormOpen}
113
- onClose={setCustomPluginFormOpen}
117
+ onClose={() => setCustomPluginFormOpen(false)}
114
118
  model={model}
115
119
  />
116
120
  </>
117
121
  )}
118
122
  <TextField
119
- className={classes.searchBox}
120
123
  label="Filter plugins"
121
124
  value={model.filterText}
122
125
  onChange={event => model.setFilterText(event.target.value)}
@@ -152,13 +155,20 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
152
155
  </AccordionSummary>
153
156
  {error ? (
154
157
  <Typography color="error">{`${error}`}</Typography>
155
- ) : pluginArray.length ? (
158
+ ) : pluginArray ? (
156
159
  pluginArray
157
- .filter(plugin =>
158
- 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
159
169
  .toLowerCase()
160
- .includes(model.filterText.toLowerCase()),
161
- )
170
+ .includes(model.filterText.toLowerCase())
171
+ })
162
172
  .map(plugin => (
163
173
  <PluginCard
164
174
  key={(plugin as JBrowsePlugin).name}