@jbrowse/plugin-menus 2.6.1
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/LICENSE +201 -0
- package/dist/AboutWidget/components/AboutWidget.d.ts +7 -0
- package/dist/AboutWidget/components/AboutWidget.js +63 -0
- package/dist/AboutWidget/components/AboutWidget.js.map +1 -0
- package/dist/AboutWidget/index.d.ts +5 -0
- package/dist/AboutWidget/index.js +12 -0
- package/dist/AboutWidget/index.js.map +1 -0
- package/dist/HelpWidget/components/HelpWidget.d.ts +7 -0
- package/dist/HelpWidget/components/HelpWidget.js +37 -0
- package/dist/HelpWidget/components/HelpWidget.js.map +1 -0
- package/dist/HelpWidget/index.d.ts +5 -0
- package/dist/HelpWidget/index.js +12 -0
- package/dist/HelpWidget/index.js.map +1 -0
- package/dist/ImportSessionWidget/components/ImportError.d.ts +4 -0
- package/dist/ImportSessionWidget/components/ImportError.js +35 -0
- package/dist/ImportSessionWidget/components/ImportError.js.map +1 -0
- package/dist/ImportSessionWidget/components/ImportSessionWidget.d.ts +7 -0
- package/dist/ImportSessionWidget/components/ImportSessionWidget.js +114 -0
- package/dist/ImportSessionWidget/components/ImportSessionWidget.js.map +1 -0
- package/dist/ImportSessionWidget/index.d.ts +5 -0
- package/dist/ImportSessionWidget/index.js +12 -0
- package/dist/ImportSessionWidget/index.js.map +1 -0
- package/dist/SessionManager/components/DeleteDialog.d.ts +6 -0
- package/dist/SessionManager/components/DeleteDialog.js +18 -0
- package/dist/SessionManager/components/DeleteDialog.js.map +1 -0
- package/dist/SessionManager/components/SessionManager.d.ts +20 -0
- package/dist/SessionManager/components/SessionManager.js +102 -0
- package/dist/SessionManager/components/SessionManager.js.map +1 -0
- package/dist/SessionManager/index.d.ts +5 -0
- package/dist/SessionManager/index.js +12 -0
- package/dist/SessionManager/index.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/esm/AboutWidget/components/AboutWidget.d.ts +7 -0
- package/esm/AboutWidget/components/AboutWidget.js +58 -0
- package/esm/AboutWidget/components/AboutWidget.js.map +1 -0
- package/esm/AboutWidget/index.d.ts +5 -0
- package/esm/AboutWidget/index.js +9 -0
- package/esm/AboutWidget/index.js.map +1 -0
- package/esm/HelpWidget/components/HelpWidget.d.ts +7 -0
- package/esm/HelpWidget/components/HelpWidget.js +32 -0
- package/esm/HelpWidget/components/HelpWidget.js.map +1 -0
- package/esm/HelpWidget/index.d.ts +5 -0
- package/esm/HelpWidget/index.js +9 -0
- package/esm/HelpWidget/index.js.map +1 -0
- package/esm/ImportSessionWidget/components/ImportError.d.ts +4 -0
- package/esm/ImportSessionWidget/components/ImportError.js +29 -0
- package/esm/ImportSessionWidget/components/ImportError.js.map +1 -0
- package/esm/ImportSessionWidget/components/ImportSessionWidget.d.ts +7 -0
- package/esm/ImportSessionWidget/components/ImportSessionWidget.js +86 -0
- package/esm/ImportSessionWidget/components/ImportSessionWidget.js.map +1 -0
- package/esm/ImportSessionWidget/index.d.ts +5 -0
- package/esm/ImportSessionWidget/index.js +9 -0
- package/esm/ImportSessionWidget/index.js.map +1 -0
- package/esm/SessionManager/components/DeleteDialog.d.ts +6 -0
- package/esm/SessionManager/components/DeleteDialog.js +12 -0
- package/esm/SessionManager/components/DeleteDialog.js.map +1 -0
- package/esm/SessionManager/components/SessionManager.d.ts +20 -0
- package/esm/SessionManager/components/SessionManager.js +74 -0
- package/esm/SessionManager/components/SessionManager.js.map +1 -0
- package/esm/SessionManager/index.d.ts +5 -0
- package/esm/SessionManager/index.js +9 -0
- package/esm/SessionManager/index.js.map +1 -0
- package/esm/index.d.ts +7 -0
- package/esm/index.js +75 -0
- package/esm/index.js.map +1 -0
- package/package.json +61 -0
- package/src/AboutWidget/components/AboutWidget.test.tsx +35 -0
- package/src/AboutWidget/components/AboutWidget.tsx +91 -0
- package/src/AboutWidget/components/__snapshots__/AboutWidget.test.tsx.snap +65 -0
- package/src/AboutWidget/index.ts +10 -0
- package/src/HelpWidget/components/HelpWidget.test.js +10 -0
- package/src/HelpWidget/components/HelpWidget.tsx +75 -0
- package/src/HelpWidget/components/__snapshots__/HelpWidget.test.js.snap +63 -0
- package/src/HelpWidget/index.ts +10 -0
- package/src/ImportSessionWidget/components/ImportError.tsx +38 -0
- package/src/ImportSessionWidget/components/ImportSessionWidget.tsx +109 -0
- package/src/ImportSessionWidget/index.ts +10 -0
- package/src/SessionManager/components/DeleteDialog.tsx +41 -0
- package/src/SessionManager/components/SessionManager.test.tsx +14 -0
- package/src/SessionManager/components/SessionManager.tsx +166 -0
- package/src/SessionManager/components/__snapshots__/SessionManager.test.tsx.snap +22 -0
- package/src/SessionManager/index.ts +10 -0
- package/src/index.test.ts +12 -0
- package/src/index.ts +100 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
2
|
+
import { ElementId } from '@jbrowse/core/util/types/mst'
|
|
3
|
+
import { types } from 'mobx-state-tree'
|
|
4
|
+
|
|
5
|
+
export const configSchema = ConfigurationSchema('HelpWidget', {})
|
|
6
|
+
|
|
7
|
+
export const stateModel = types.model('HelpWidget', {
|
|
8
|
+
id: ElementId,
|
|
9
|
+
type: types.literal('HelpWidget'),
|
|
10
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Paper, Typography } from '@mui/material'
|
|
3
|
+
import { makeStyles } from 'tss-react/mui'
|
|
4
|
+
|
|
5
|
+
// icons
|
|
6
|
+
import ErrorIcon from '@mui/icons-material/Error'
|
|
7
|
+
|
|
8
|
+
const useStyles = makeStyles()(theme => ({
|
|
9
|
+
error: {
|
|
10
|
+
margin: theme.spacing(2),
|
|
11
|
+
},
|
|
12
|
+
errorHeader: {
|
|
13
|
+
background: theme.palette.error.light,
|
|
14
|
+
color: theme.palette.error.contrastText,
|
|
15
|
+
padding: theme.spacing(2),
|
|
16
|
+
textAlign: 'center',
|
|
17
|
+
},
|
|
18
|
+
errorMessage: {
|
|
19
|
+
padding: theme.spacing(2),
|
|
20
|
+
},
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
export default function ImportError({ error }: { error: unknown }) {
|
|
24
|
+
const { classes } = useStyles()
|
|
25
|
+
return (
|
|
26
|
+
<Paper className={classes.error}>
|
|
27
|
+
<div className={classes.errorHeader}>
|
|
28
|
+
<ErrorIcon color="inherit" fontSize="large" />
|
|
29
|
+
<div>
|
|
30
|
+
<Typography variant="h6" color="inherit" align="center">
|
|
31
|
+
Import error
|
|
32
|
+
</Typography>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
<Typography className={classes.errorMessage}>{`${error}`}</Typography>
|
|
36
|
+
</Paper>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { Button, Paper, Typography, alpha } from '@mui/material'
|
|
3
|
+
import { getSession } from '@jbrowse/core/util'
|
|
4
|
+
import { makeStyles } from 'tss-react/mui'
|
|
5
|
+
import { observer } from 'mobx-react'
|
|
6
|
+
import { useDropzone } from 'react-dropzone'
|
|
7
|
+
|
|
8
|
+
// icons
|
|
9
|
+
import CloudUploadIcon from '@mui/icons-material/CloudUpload'
|
|
10
|
+
|
|
11
|
+
// locals
|
|
12
|
+
import ImportError from './ImportError'
|
|
13
|
+
import { IAnyStateTreeNode } from 'mobx-state-tree'
|
|
14
|
+
|
|
15
|
+
const MAX_FILE_SIZE = 512 * 1024 ** 2 // 512 MiB
|
|
16
|
+
|
|
17
|
+
function styledBy(property: string, mapping: { [key: string]: string }) {
|
|
18
|
+
return (props: { [key: string]: string }) => mapping[props[property]]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// @ts-expect-error
|
|
22
|
+
const useStyles = makeStyles()(theme => ({
|
|
23
|
+
root: {
|
|
24
|
+
margin: theme.spacing(1),
|
|
25
|
+
},
|
|
26
|
+
paper: {
|
|
27
|
+
display: 'flex',
|
|
28
|
+
flexDirection: 'column',
|
|
29
|
+
},
|
|
30
|
+
dropZone: {
|
|
31
|
+
textAlign: 'center',
|
|
32
|
+
margin: theme.spacing(2),
|
|
33
|
+
padding: theme.spacing(2),
|
|
34
|
+
borderWidth: 2,
|
|
35
|
+
borderRadius: 2,
|
|
36
|
+
borderColor: styledBy('isDragActive', {
|
|
37
|
+
true: theme.palette.secondary.light,
|
|
38
|
+
false: theme.palette.divider,
|
|
39
|
+
}),
|
|
40
|
+
borderStyle: 'dashed',
|
|
41
|
+
backgroundColor: styledBy('isDragActive', {
|
|
42
|
+
true: alpha(
|
|
43
|
+
theme.palette.text.primary,
|
|
44
|
+
theme.palette.action.hoverOpacity,
|
|
45
|
+
),
|
|
46
|
+
false: theme.palette.background.default,
|
|
47
|
+
}),
|
|
48
|
+
outline: 'none',
|
|
49
|
+
transition: 'border .24s ease-in-out',
|
|
50
|
+
'&:focus': {
|
|
51
|
+
borderColor: theme.palette.secondary.light,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
uploadIcon: {
|
|
55
|
+
color: theme.palette.text.secondary,
|
|
56
|
+
},
|
|
57
|
+
}))
|
|
58
|
+
|
|
59
|
+
function ImportSession({ model }: { model: IAnyStateTreeNode }) {
|
|
60
|
+
const [error, setError] = useState<unknown>()
|
|
61
|
+
const { getRootProps, getInputProps, isDragActive } = useDropzone({
|
|
62
|
+
// @ts-expect-error
|
|
63
|
+
accept: 'application/json',
|
|
64
|
+
maxSize: MAX_FILE_SIZE,
|
|
65
|
+
multiple: false,
|
|
66
|
+
onDrop: async (acceptedFiles, rejectedFiles) => {
|
|
67
|
+
try {
|
|
68
|
+
if (rejectedFiles.length > 0) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`${rejectedFiles[0].errors.map(e => `${e}`).join(', ')}`,
|
|
71
|
+
)
|
|
72
|
+
} else {
|
|
73
|
+
const sessionText = await acceptedFiles[0].text()
|
|
74
|
+
getSession(model).setSession(JSON.parse(sessionText).session)
|
|
75
|
+
}
|
|
76
|
+
} catch (e) {
|
|
77
|
+
console.error(e)
|
|
78
|
+
setError(e)
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
// @ts-expect-error
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
+
const { classes } = useStyles({ isDragActive }) as any
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div className={classes.root}>
|
|
89
|
+
<Paper className={classes.paper}>
|
|
90
|
+
<div {...getRootProps({ className: classes.dropZone })}>
|
|
91
|
+
<input {...getInputProps()} />
|
|
92
|
+
<CloudUploadIcon className={classes.uploadIcon} fontSize="large" />
|
|
93
|
+
<Typography color="textSecondary" align="center" variant="body1">
|
|
94
|
+
Drag and drop files here
|
|
95
|
+
</Typography>
|
|
96
|
+
<Typography color="textSecondary" align="center" variant="body2">
|
|
97
|
+
or
|
|
98
|
+
</Typography>
|
|
99
|
+
<Button color="primary" variant="contained">
|
|
100
|
+
Browse Files
|
|
101
|
+
</Button>
|
|
102
|
+
</div>
|
|
103
|
+
</Paper>
|
|
104
|
+
{error ? <ImportError error={error} /> : null}
|
|
105
|
+
</div>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default observer(ImportSession)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
2
|
+
import { ElementId } from '@jbrowse/core/util/types/mst'
|
|
3
|
+
import { types } from 'mobx-state-tree'
|
|
4
|
+
|
|
5
|
+
export const configSchema = ConfigurationSchema('ImportSessionWidget', {})
|
|
6
|
+
|
|
7
|
+
export const stateModel = types.model('ImportSessionWidget', {
|
|
8
|
+
id: ElementId,
|
|
9
|
+
type: types.literal('ImportSessionWidget'),
|
|
10
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Button,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogContentText,
|
|
7
|
+
} from '@mui/material'
|
|
8
|
+
import { Dialog } from '@jbrowse/core/ui'
|
|
9
|
+
|
|
10
|
+
export default function DeleteDialog({
|
|
11
|
+
open,
|
|
12
|
+
sessionNameToDelete,
|
|
13
|
+
handleClose,
|
|
14
|
+
}: {
|
|
15
|
+
sessionNameToDelete: string
|
|
16
|
+
open: boolean
|
|
17
|
+
handleClose: (arg?: boolean) => void
|
|
18
|
+
}) {
|
|
19
|
+
return (
|
|
20
|
+
<Dialog
|
|
21
|
+
open={open}
|
|
22
|
+
aria-labelledby="alert-dialog-title"
|
|
23
|
+
aria-describedby="alert-dialog-description"
|
|
24
|
+
title={`Delete session "${sessionNameToDelete}"?`}
|
|
25
|
+
>
|
|
26
|
+
<DialogContent>
|
|
27
|
+
<DialogContentText id="alert-dialog-description">
|
|
28
|
+
This action cannot be undone
|
|
29
|
+
</DialogContentText>
|
|
30
|
+
</DialogContent>
|
|
31
|
+
<DialogActions>
|
|
32
|
+
<Button onClick={() => handleClose()} color="primary">
|
|
33
|
+
Cancel
|
|
34
|
+
</Button>
|
|
35
|
+
<Button onClick={() => handleClose(true)} color="primary" autoFocus>
|
|
36
|
+
Delete
|
|
37
|
+
</Button>
|
|
38
|
+
</DialogActions>
|
|
39
|
+
</Dialog>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createTestSession } from '@jbrowse/web/src/rootModel'
|
|
2
|
+
|
|
3
|
+
import React from 'react'
|
|
4
|
+
import { render } from '@testing-library/react'
|
|
5
|
+
import SessionManager from './SessionManager'
|
|
6
|
+
jest.mock('@jbrowse/web/src/makeWorkerInstance', () => () => {})
|
|
7
|
+
|
|
8
|
+
describe('<SessionManager />', () => {
|
|
9
|
+
it('renders', () => {
|
|
10
|
+
const session = createTestSession()
|
|
11
|
+
const { container } = render(<SessionManager session={session} />)
|
|
12
|
+
expect(container.firstChild).toMatchSnapshot()
|
|
13
|
+
})
|
|
14
|
+
})
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
IconButton,
|
|
4
|
+
List,
|
|
5
|
+
ListItem,
|
|
6
|
+
ListItemIcon,
|
|
7
|
+
ListItemSecondaryAction,
|
|
8
|
+
ListItemText,
|
|
9
|
+
ListSubheader,
|
|
10
|
+
Paper,
|
|
11
|
+
Typography,
|
|
12
|
+
} from '@mui/material'
|
|
13
|
+
import { makeStyles } from 'tss-react/mui'
|
|
14
|
+
|
|
15
|
+
import { observer } from 'mobx-react'
|
|
16
|
+
import pluralize from 'pluralize'
|
|
17
|
+
import { AbstractSessionModel } from '@jbrowse/core/util'
|
|
18
|
+
|
|
19
|
+
// icons
|
|
20
|
+
import DeleteIcon from '@mui/icons-material/Delete'
|
|
21
|
+
import ViewListIcon from '@mui/icons-material/ViewList'
|
|
22
|
+
import DeleteDialog from './DeleteDialog'
|
|
23
|
+
|
|
24
|
+
const useStyles = makeStyles()(theme => ({
|
|
25
|
+
root: {
|
|
26
|
+
margin: theme.spacing(1),
|
|
27
|
+
},
|
|
28
|
+
message: {
|
|
29
|
+
padding: theme.spacing(3),
|
|
30
|
+
},
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
interface SessionSnap {
|
|
34
|
+
name: string
|
|
35
|
+
views?: { tracks: unknown[] }[]
|
|
36
|
+
[key: string]: unknown
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface SessionModel extends AbstractSessionModel {
|
|
40
|
+
savedSessions: SessionSnap[]
|
|
41
|
+
removeSavedSession: (arg: SessionSnap) => void
|
|
42
|
+
activateSession: (arg: string) => void
|
|
43
|
+
loadAutosaveSession: () => void
|
|
44
|
+
previousAutosaveId: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const AutosaveEntry = observer(({ session }: { session: SessionModel }) => {
|
|
48
|
+
const { classes } = useStyles()
|
|
49
|
+
const autosavedSession = JSON.parse(
|
|
50
|
+
localStorage.getItem(session.previousAutosaveId) || '{}',
|
|
51
|
+
).session as SessionSnap
|
|
52
|
+
|
|
53
|
+
const { views = [] } = autosavedSession || {}
|
|
54
|
+
const totalTracks = views
|
|
55
|
+
.map(view => view.tracks.length)
|
|
56
|
+
.reduce((a, b) => a + b, 0)
|
|
57
|
+
|
|
58
|
+
return autosavedSession ? (
|
|
59
|
+
<Paper className={classes.root}>
|
|
60
|
+
<List subheader={<ListSubheader>Previous autosaved entry</ListSubheader>}>
|
|
61
|
+
<ListItem button onClick={() => session.loadAutosaveSession()}>
|
|
62
|
+
<ListItemIcon>
|
|
63
|
+
<ViewListIcon />
|
|
64
|
+
</ListItemIcon>
|
|
65
|
+
<ListItemText
|
|
66
|
+
primary={autosavedSession.name}
|
|
67
|
+
secondary={
|
|
68
|
+
session.name === autosavedSession.name
|
|
69
|
+
? 'Currently open'
|
|
70
|
+
: `${views.length} ${pluralize(
|
|
71
|
+
'view',
|
|
72
|
+
views.length,
|
|
73
|
+
)}; ${totalTracks}
|
|
74
|
+
open ${pluralize('track', totalTracks)}`
|
|
75
|
+
}
|
|
76
|
+
/>
|
|
77
|
+
</ListItem>
|
|
78
|
+
</List>
|
|
79
|
+
</Paper>
|
|
80
|
+
) : null
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const SessionManager = observer(({ session }: { session: SessionModel }) => {
|
|
84
|
+
const { classes } = useStyles()
|
|
85
|
+
const [sessionIndexToDelete, setSessionIndexToDelete] = useState<number>()
|
|
86
|
+
const [open, setOpen] = useState(false)
|
|
87
|
+
|
|
88
|
+
function handleDialogClose(deleteSession = false) {
|
|
89
|
+
if (deleteSession && sessionIndexToDelete !== undefined) {
|
|
90
|
+
session.removeSavedSession(session.savedSessions[sessionIndexToDelete])
|
|
91
|
+
}
|
|
92
|
+
setSessionIndexToDelete(undefined)
|
|
93
|
+
setOpen(false)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const sessionNameToDelete =
|
|
97
|
+
sessionIndexToDelete !== undefined
|
|
98
|
+
? session.savedSessions[sessionIndexToDelete].name
|
|
99
|
+
: ''
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<>
|
|
103
|
+
<AutosaveEntry session={session} />
|
|
104
|
+
<Paper className={classes.root}>
|
|
105
|
+
<List subheader={<ListSubheader>Saved sessions</ListSubheader>}>
|
|
106
|
+
{session.savedSessions.length ? (
|
|
107
|
+
session.savedSessions.map((sessionSnapshot, idx) => {
|
|
108
|
+
const { views = [] } = sessionSnapshot
|
|
109
|
+
const totalTracks = views
|
|
110
|
+
.map(view => view.tracks.length)
|
|
111
|
+
.reduce((a, b) => a + b, 0)
|
|
112
|
+
return (
|
|
113
|
+
<ListItem
|
|
114
|
+
button
|
|
115
|
+
disabled={session.name === sessionSnapshot.name}
|
|
116
|
+
onClick={() => session.activateSession(sessionSnapshot.name)}
|
|
117
|
+
key={sessionSnapshot.name}
|
|
118
|
+
>
|
|
119
|
+
<ListItemIcon>
|
|
120
|
+
<ViewListIcon />
|
|
121
|
+
</ListItemIcon>
|
|
122
|
+
<ListItemText
|
|
123
|
+
primary={sessionSnapshot.name}
|
|
124
|
+
secondary={
|
|
125
|
+
session.name === sessionSnapshot.name
|
|
126
|
+
? 'Currently open'
|
|
127
|
+
: `${views.length} ${pluralize(
|
|
128
|
+
'view',
|
|
129
|
+
views.length,
|
|
130
|
+
)}; ${totalTracks}
|
|
131
|
+
open ${pluralize('track', totalTracks)}`
|
|
132
|
+
}
|
|
133
|
+
/>
|
|
134
|
+
<ListItemSecondaryAction>
|
|
135
|
+
<IconButton
|
|
136
|
+
edge="end"
|
|
137
|
+
disabled={session.name === sessionSnapshot.name}
|
|
138
|
+
aria-label="Delete"
|
|
139
|
+
onClick={() => {
|
|
140
|
+
setSessionIndexToDelete(idx)
|
|
141
|
+
setOpen(true)
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<DeleteIcon />
|
|
145
|
+
</IconButton>
|
|
146
|
+
</ListItemSecondaryAction>
|
|
147
|
+
</ListItem>
|
|
148
|
+
)
|
|
149
|
+
})
|
|
150
|
+
) : (
|
|
151
|
+
<Typography className={classes.message}>
|
|
152
|
+
No saved sessions found
|
|
153
|
+
</Typography>
|
|
154
|
+
)}
|
|
155
|
+
</List>
|
|
156
|
+
</Paper>
|
|
157
|
+
<DeleteDialog
|
|
158
|
+
open={open}
|
|
159
|
+
sessionNameToDelete={sessionNameToDelete}
|
|
160
|
+
handleClose={handleDialogClose}
|
|
161
|
+
/>
|
|
162
|
+
</>
|
|
163
|
+
)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
export default SessionManager
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
+
|
|
3
|
+
exports[`<SessionManager /> renders 1`] = `
|
|
4
|
+
<div
|
|
5
|
+
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation1 css-1l9cidk-MuiPaper-root-root"
|
|
6
|
+
>
|
|
7
|
+
<ul
|
|
8
|
+
class="MuiList-root MuiList-padding MuiList-subheader css-p64g7a-MuiList-root"
|
|
9
|
+
>
|
|
10
|
+
<li
|
|
11
|
+
class="MuiListSubheader-root MuiListSubheader-gutters MuiListSubheader-sticky css-uob957-MuiListSubheader-root"
|
|
12
|
+
>
|
|
13
|
+
Saved sessions
|
|
14
|
+
</li>
|
|
15
|
+
<p
|
|
16
|
+
class="MuiTypography-root MuiTypography-body1 css-g9z2qm-MuiTypography-root-message"
|
|
17
|
+
>
|
|
18
|
+
No saved sessions found
|
|
19
|
+
</p>
|
|
20
|
+
</ul>
|
|
21
|
+
</div>
|
|
22
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ConfigurationSchema } from '@jbrowse/core/configuration'
|
|
2
|
+
import { ElementId } from '@jbrowse/core/util/types/mst'
|
|
3
|
+
import { types } from 'mobx-state-tree'
|
|
4
|
+
|
|
5
|
+
export const configSchema = ConfigurationSchema('SessionManager', {})
|
|
6
|
+
|
|
7
|
+
export const stateModel = types.model('SessionManager', {
|
|
8
|
+
id: ElementId,
|
|
9
|
+
type: types.literal('SessionManager'),
|
|
10
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
2
|
+
import ThisPlugin from '.'
|
|
3
|
+
|
|
4
|
+
test("won't add if already added", () => {
|
|
5
|
+
const pluginManager = new PluginManager([new ThisPlugin()])
|
|
6
|
+
pluginManager.createPluggableElements()
|
|
7
|
+
pluginManager.configure()
|
|
8
|
+
|
|
9
|
+
expect(() => pluginManager.addPlugin(new ThisPlugin())).toThrow(
|
|
10
|
+
/JBrowse already configured, cannot add plugins/,
|
|
11
|
+
)
|
|
12
|
+
})
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { lazy } from 'react'
|
|
2
|
+
import WidgetType from '@jbrowse/core/pluggableElementTypes/WidgetType'
|
|
3
|
+
import Plugin from '@jbrowse/core/Plugin'
|
|
4
|
+
import PluginManager from '@jbrowse/core/PluginManager'
|
|
5
|
+
import { SessionWithWidgets, isAbstractMenuManager } from '@jbrowse/core/util'
|
|
6
|
+
|
|
7
|
+
import HelpIcon from '@mui/icons-material/Help'
|
|
8
|
+
import InfoIcon from '@mui/icons-material/Info'
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
configSchema as aboutConfigSchema,
|
|
12
|
+
stateModel as aboutStateModel,
|
|
13
|
+
} from './AboutWidget'
|
|
14
|
+
import {
|
|
15
|
+
configSchema as helpConfigSchema,
|
|
16
|
+
stateModel as helpStateModel,
|
|
17
|
+
} from './HelpWidget'
|
|
18
|
+
import {
|
|
19
|
+
configSchema as importSessionConfigSchema,
|
|
20
|
+
stateModel as importSessionStateModel,
|
|
21
|
+
} from './ImportSessionWidget'
|
|
22
|
+
import {
|
|
23
|
+
configSchema as sessionManagerConfigSchema,
|
|
24
|
+
stateModel as sessionManagerStateModel,
|
|
25
|
+
} from './SessionManager'
|
|
26
|
+
|
|
27
|
+
export default class extends Plugin {
|
|
28
|
+
name = 'MenusPlugin'
|
|
29
|
+
|
|
30
|
+
install(pluginManager: PluginManager) {
|
|
31
|
+
pluginManager.addWidgetType(() => {
|
|
32
|
+
return new WidgetType({
|
|
33
|
+
name: 'AboutWidget',
|
|
34
|
+
heading: 'About',
|
|
35
|
+
configSchema: aboutConfigSchema,
|
|
36
|
+
stateModel: aboutStateModel,
|
|
37
|
+
ReactComponent: lazy(
|
|
38
|
+
() => import('./AboutWidget/components/AboutWidget'),
|
|
39
|
+
),
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
pluginManager.addWidgetType(() => {
|
|
44
|
+
return new WidgetType({
|
|
45
|
+
name: 'HelpWidget',
|
|
46
|
+
heading: 'Help',
|
|
47
|
+
configSchema: helpConfigSchema,
|
|
48
|
+
stateModel: helpStateModel,
|
|
49
|
+
ReactComponent: lazy(
|
|
50
|
+
() => import('./HelpWidget/components/HelpWidget'),
|
|
51
|
+
),
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
pluginManager.addWidgetType(() => {
|
|
56
|
+
return new WidgetType({
|
|
57
|
+
name: 'ImportSessionWidget',
|
|
58
|
+
heading: 'Import session',
|
|
59
|
+
configSchema: importSessionConfigSchema,
|
|
60
|
+
stateModel: importSessionStateModel,
|
|
61
|
+
ReactComponent: lazy(
|
|
62
|
+
() => import('./ImportSessionWidget/components/ImportSessionWidget'),
|
|
63
|
+
),
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
pluginManager.addWidgetType(() => {
|
|
68
|
+
return new WidgetType({
|
|
69
|
+
name: 'SessionManager',
|
|
70
|
+
heading: 'Sessions',
|
|
71
|
+
configSchema: sessionManagerConfigSchema,
|
|
72
|
+
stateModel: sessionManagerStateModel,
|
|
73
|
+
ReactComponent: lazy(
|
|
74
|
+
() => import('./SessionManager/components/SessionManager'),
|
|
75
|
+
),
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
configure(pluginManager: PluginManager) {
|
|
81
|
+
if (isAbstractMenuManager(pluginManager.rootModel)) {
|
|
82
|
+
pluginManager.rootModel.appendToMenu('Help', {
|
|
83
|
+
label: 'About',
|
|
84
|
+
icon: InfoIcon,
|
|
85
|
+
onClick: (session: SessionWithWidgets) => {
|
|
86
|
+
const widget = session.addWidget('AboutWidget', 'aboutWidget')
|
|
87
|
+
session.showWidget(widget)
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
pluginManager.rootModel.appendToMenu('Help', {
|
|
91
|
+
label: 'Help',
|
|
92
|
+
icon: HelpIcon,
|
|
93
|
+
onClick: (session: SessionWithWidgets) => {
|
|
94
|
+
const widget = session.addWidget('HelpWidget', 'helpWidget')
|
|
95
|
+
session.showWidget(widget)
|
|
96
|
+
},
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|