@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.
- package/dist/AddTrackWidget/components/TrackSourceSelect.d.ts +2 -1
- package/dist/AddTrackWidget/model.d.ts +1 -363
- package/dist/PluginStoreWidget/components/CustomPluginForm.d.ts +1 -1
- package/dist/PluginStoreWidget/components/PluginCard.d.ts +2 -2
- package/dist/SetDefaultSession/SetDefaultSession.d.ts +4 -6
- package/dist/index.d.ts +8 -12
- package/dist/plugin-data-management.cjs.development.js +627 -501
- package/dist/plugin-data-management.cjs.development.js.map +1 -1
- package/dist/plugin-data-management.cjs.production.min.js +1 -1
- package/dist/plugin-data-management.cjs.production.min.js.map +1 -1
- package/dist/plugin-data-management.esm.js +614 -507
- package/dist/plugin-data-management.esm.js.map +1 -1
- package/package.json +3 -2
- package/src/AddConnectionWidget/components/AddConnectionWidget.test.js +3 -8
- package/src/AddTrackWidget/components/AddTrackWidget.test.js +2 -3
- package/src/AddTrackWidget/components/AddTrackWidget.tsx +4 -2
- package/src/AddTrackWidget/components/ConfirmTrack.tsx +160 -88
- package/src/AddTrackWidget/components/TrackSourceSelect.tsx +30 -23
- package/src/AddTrackWidget/components/__snapshots__/AddTrackWidget.test.js.snap +157 -124
- package/src/AddTrackWidget/index.test.jsx +78 -26
- package/src/AddTrackWidget/model.ts +5 -14
- package/src/AssemblyManager/AssemblyAddForm.tsx +7 -6
- package/src/AssemblyManager/AssemblyManager.test.tsx +1 -0
- package/src/HierarchicalTrackSelectorWidget/components/HierarchicalTrackSelector.js +27 -17
- package/src/HierarchicalTrackSelectorWidget/model.js +3 -2
- package/src/PluginStoreWidget/components/CustomPluginForm.tsx +164 -56
- package/src/PluginStoreWidget/components/InstalledPlugin.tsx +10 -2
- package/src/PluginStoreWidget/components/PluginCard.tsx +7 -9
- package/src/PluginStoreWidget/components/PluginStoreWidget.test.js +9 -10
- package/src/PluginStoreWidget/components/PluginStoreWidget.tsx +36 -26
- package/src/PluginStoreWidget/components/__snapshots__/PluginStoreWidget.test.js.snap +89 -51
- package/src/SetDefaultSession/SetDefaultSession.test.tsx +7 -81
- package/src/SetDefaultSession/SetDefaultSession.tsx +51 -162
- package/src/index.ts +1 -51
- package/src/ucsc-trackhub/configSchema.js +4 -1
- package/src/ucsc-trackhub/model.js +31 -31
- 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
|
-
<
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
12
|
+
const filterLower = filter.toLowerCase()
|
|
13
13
|
return (
|
|
14
|
-
!!name.
|
|
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 {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
28
|
+
const useStyles = makeStyles(theme => ({
|
|
29
|
+
closeButton: {
|
|
18
30
|
position: 'absolute',
|
|
19
|
-
right:
|
|
20
|
-
top:
|
|
31
|
+
right: theme.spacing(1),
|
|
32
|
+
top: theme.spacing(1),
|
|
21
33
|
},
|
|
22
|
-
|
|
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:
|
|
56
|
+
onClose(): void
|
|
36
57
|
model: PluginStoreModel
|
|
37
58
|
}) {
|
|
38
59
|
const classes = useStyles()
|
|
39
|
-
const [
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
|
55
|
-
|
|
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={
|
|
120
|
+
<Dialog open={open} onClose={handleClose}>
|
|
60
121
|
<DialogTitle>
|
|
122
|
+
Add custom plugin
|
|
61
123
|
<IconButton
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
onClick={() => onClose(
|
|
124
|
+
size="medium"
|
|
125
|
+
className={classes.closeButton}
|
|
126
|
+
onClick={() => onClose()}
|
|
65
127
|
>
|
|
66
128
|
<CloseIcon />
|
|
67
129
|
</IconButton>
|
|
68
130
|
</DialogTitle>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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 =
|
|
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 {
|
|
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
|
|
68
|
+
fireEvent.change(getByLabelText('Plugin URL'), {
|
|
70
69
|
target: {
|
|
71
|
-
value:
|
|
70
|
+
value:
|
|
71
|
+
'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.esm.js',
|
|
72
72
|
},
|
|
73
73
|
})
|
|
74
|
-
fireEvent.change(getByLabelText('Plugin
|
|
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('
|
|
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
|
-
|
|
90
|
-
'https://unpkg.com/jbrowse-plugin-msaview/dist/jbrowse-plugin-msaview.
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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<
|
|
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
|
-
|
|
62
|
+
const controller = new AbortController()
|
|
63
|
+
const { signal } = controller
|
|
65
64
|
|
|
66
65
|
;(async () => {
|
|
67
66
|
try {
|
|
68
|
-
const
|
|
67
|
+
const response = await fetch(
|
|
69
68
|
'https://jbrowse.org/plugin-store/plugins.json',
|
|
69
|
+
{ signal },
|
|
70
70
|
)
|
|
71
|
-
if (!
|
|
72
|
-
|
|
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
|
|
75
|
-
if (!
|
|
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
|
-
|
|
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
|
|
158
|
+
) : pluginArray ? (
|
|
156
159
|
pluginArray
|
|
157
|
-
.filter(plugin =>
|
|
158
|
-
|
|
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}
|