@jbrowse/plugin-data-management 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 (30) hide show
  1. package/dist/AddTrackWidget/model.d.ts +1 -434
  2. package/dist/AssemblyManager/AssemblyTable.d.ts +8 -2
  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 +387 -324
  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 +389 -326
  12. package/dist/plugin-data-management.esm.js.map +1 -1
  13. package/package.json +3 -2
  14. package/src/AddTrackWidget/components/AddTrackWidget.test.js +1 -1
  15. package/src/AddTrackWidget/components/AddTrackWidget.tsx +3 -1
  16. package/src/AddTrackWidget/components/ConfirmTrack.tsx +101 -32
  17. package/src/AddTrackWidget/components/TrackSourceSelect.tsx +2 -3
  18. package/src/AddTrackWidget/index.test.jsx +34 -15
  19. package/src/AddTrackWidget/model.ts +5 -14
  20. package/src/AssemblyManager/AssemblyManager.tsx +3 -1
  21. package/src/AssemblyManager/AssemblyTable.tsx +40 -39
  22. package/src/HierarchicalTrackSelectorWidget/model.js +3 -2
  23. package/src/PluginStoreWidget/components/CustomPluginForm.tsx +164 -56
  24. package/src/PluginStoreWidget/components/InstalledPlugin.tsx +16 -11
  25. package/src/PluginStoreWidget/components/PluginCard.tsx +7 -9
  26. package/src/PluginStoreWidget/components/PluginStoreWidget.test.js +8 -7
  27. package/src/PluginStoreWidget/components/PluginStoreWidget.tsx +34 -25
  28. package/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap +69 -52
  29. package/src/SetDefaultSession/SetDefaultSession.test.tsx +6 -81
  30. package/src/SetDefaultSession/SetDefaultSession.tsx +51 -162
@@ -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
  }
@@ -1,8 +1,6 @@
1
1
  import React, { useState } from 'react'
2
2
  import { observer } from 'mobx-react'
3
3
  import { getParent } from 'mobx-state-tree'
4
-
5
- import { makeStyles } from '@material-ui/core/styles'
6
4
  import {
7
5
  Button,
8
6
  Dialog,
@@ -13,6 +11,7 @@ import {
13
11
  ListItem,
14
12
  Tooltip,
15
13
  Typography,
14
+ makeStyles,
16
15
  } from '@material-ui/core'
17
16
 
18
17
  import CloseIcon from '@material-ui/icons/Close'
@@ -33,12 +32,16 @@ const useStyles = makeStyles(() => ({
33
32
  dialogContainer: {
34
33
  margin: 15,
35
34
  },
35
+ lockedPluginTooltip: {
36
+ marginRight: '0.5rem',
37
+ },
36
38
  }))
37
39
 
38
40
  function LockedPlugin() {
41
+ const classes = useStyles()
39
42
  return (
40
43
  <Tooltip
41
- style={{ marginRight: '0.5rem' }}
44
+ className={classes.lockedPluginTooltip}
42
45
  title="This plugin was installed by an administrator, you cannot remove it."
43
46
  >
44
47
  <LockIcon />
@@ -67,7 +70,9 @@ function PluginDialog({
67
70
  </DialogTitle>
68
71
  <DialogContent>
69
72
  <Typography>
70
- Please confirm that you want to remove {plugin}:
73
+ Please confirm that you want to remove {plugin}. Note: if any
74
+ resources in this session still use this plugin, it may cause your
75
+ session to crash
71
76
  </Typography>
72
77
  <DialogActions>
73
78
  <Button
@@ -109,11 +114,11 @@ function InstalledPlugin({
109
114
  const [dialogPlugin, setDialogPlugin] = useState<string>()
110
115
 
111
116
  const session = getSession(model)
112
-
113
- // @ts-ignore
114
- const { sessionPlugins } = session
117
+ const { sessionPlugins } = session as unknown as {
118
+ sessionPlugins: BasePlugin[]
119
+ }
115
120
  const isSessionPlugin = sessionPlugins?.some(
116
- (p: BasePlugin) => pluginManager.pluginMetadata[plugin.name].url === p.url,
121
+ p => pluginManager.pluginMetadata[plugin.name].url === p.url,
117
122
  )
118
123
 
119
124
  const rootModel = getParent(model, 3)
@@ -127,11 +132,11 @@ function InstalledPlugin({
127
132
  onClose={name => {
128
133
  if (name) {
129
134
  const pluginMetadata = pluginManager.pluginMetadata[plugin.name]
130
- const pluginUrl = pluginMetadata.url
135
+
131
136
  if (adminMode) {
132
- jbrowse.removePlugin(pluginUrl)
137
+ jbrowse.removePlugin(pluginMetadata)
133
138
  } else if (isSessionWithSessionPlugins(session)) {
134
- session.removeSessionPlugin(pluginUrl)
139
+ session.removeSessionPlugin(pluginMetadata)
135
140
  }
136
141
  }
137
142
  setDialogPlugin(undefined)
@@ -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: {
@@ -65,18 +65,18 @@ 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
- fireEvent.click(getByText('Add plugin'))
79
+ fireEvent.click(getByText('Submit'))
80
80
 
81
81
  await waitFor(() => {
82
82
  expect(window.location.reload).toHaveBeenCalled()
@@ -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
  })
@@ -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,25 +52,30 @@ const useStyles = makeStyles(theme => ({
54
52
 
55
53
  function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
56
54
  const classes = useStyles()
57
- const [pluginArray, setPluginArray] = useState<JBrowsePlugin[]>([])
55
+ const [pluginArray, setPluginArray] = useState<JBrowsePlugin[]>()
58
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,12 +85,12 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
82
85
  })()
83
86
 
84
87
  return () => {
85
- killed = true
88
+ controller.abort()
86
89
  }
87
90
  }, [])
88
91
 
89
92
  return (
90
- <div>
93
+ <div className={classes.root}>
91
94
  {adminMode && (
92
95
  <>
93
96
  {!isElectron && (
@@ -111,13 +114,12 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
111
114
  </div>
112
115
  <CustomPluginForm
113
116
  open={customPluginFormOpen}
114
- onClose={setCustomPluginFormOpen}
117
+ onClose={() => setCustomPluginFormOpen(false)}
115
118
  model={model}
116
119
  />
117
120
  </>
118
121
  )}
119
122
  <TextField
120
- className={classes.searchBox}
121
123
  label="Filter plugins"
122
124
  value={model.filterText}
123
125
  onChange={event => model.setFilterText(event.target.value)}
@@ -153,13 +155,20 @@ function PluginStoreWidget({ model }: { model: PluginStoreModel }) {
153
155
  </AccordionSummary>
154
156
  {error ? (
155
157
  <Typography color="error">{`${error}`}</Typography>
156
- ) : pluginArray.length ? (
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}