@jbrowse/plugin-menus 4.3.0 → 5.0.0-beta.10
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/esm/AboutWidget/components/AboutWidget.d.ts +1 -1
- package/esm/AboutWidget/components/AboutWidget.js +20 -14
- package/esm/AboutWidget/index.js +2 -2
- package/esm/HelpWidget/components/HelpWidget.d.ts +1 -1
- package/esm/HelpWidget/components/HelpWidget.js +25 -2
- package/esm/HelpWidget/index.js +2 -2
- package/esm/ImportSessionWidget/components/ImportError.d.ts +1 -1
- package/esm/ImportSessionWidget/components/ImportError.js +1 -1
- package/esm/ImportSessionWidget/components/ImportSessionWidget.d.ts +1 -1
- package/esm/ImportSessionWidget/components/ImportSessionWidget.js +34 -58
- package/esm/ImportSessionWidget/index.js +2 -2
- package/esm/SessionManager/components/DeleteOldSessionsDialog.d.ts +12 -0
- package/esm/SessionManager/components/DeleteOldSessionsDialog.js +36 -0
- package/esm/SessionManager/components/SessionManager.d.ts +1 -1
- package/esm/SessionManager/components/SessionManager.js +115 -77
- package/esm/SessionManager/components/cells.d.ts +32 -0
- package/esm/SessionManager/components/cells.js +48 -0
- package/esm/SessionManager/components/util.d.ts +33 -15
- package/esm/SessionManager/components/util.js +28 -1
- package/esm/SessionManager/index.d.ts +2 -2
- package/esm/SessionManager/index.js +2 -2
- package/esm/index.js +6 -8
- package/package.json +13 -17
|
@@ -3,7 +3,10 @@ import { ExternalLink } from '@jbrowse/core/ui';
|
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
5
|
import { getEnv } from '@jbrowse/mobx-state-tree';
|
|
6
|
-
import {
|
|
6
|
+
import { effectiveRenderer } from '@jbrowse/render-core/graphicsCapabilities';
|
|
7
|
+
import { useGraphicsCapabilities } from '@jbrowse/render-core/useGraphicsCapabilities';
|
|
8
|
+
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
|
9
|
+
import { Accordion, AccordionDetails, AccordionSummary, Typography, } from '@mui/material';
|
|
7
10
|
import { observer } from 'mobx-react';
|
|
8
11
|
const useStyles = makeStyles()(theme => ({
|
|
9
12
|
root: {
|
|
@@ -13,27 +16,30 @@ const useStyles = makeStyles()(theme => ({
|
|
|
13
16
|
subtitle: {
|
|
14
17
|
margin: theme.spacing(1),
|
|
15
18
|
},
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
marginTop: theme.spacing(5),
|
|
19
|
+
accordions: {
|
|
20
|
+
marginTop: theme.spacing(3),
|
|
19
21
|
},
|
|
20
22
|
}));
|
|
21
23
|
const AboutWidget = observer(function AboutWidget({ model, }) {
|
|
22
24
|
const { classes } = useStyles();
|
|
23
|
-
const
|
|
25
|
+
const session = getSession(model);
|
|
26
|
+
const { version, gitCommit } = session;
|
|
24
27
|
const { pluginManager } = getEnv(model);
|
|
25
28
|
const { plugins } = pluginManager;
|
|
29
|
+
const graphicsCapabilities = useGraphicsCapabilities();
|
|
30
|
+
const defaultRpcDriver = session.rpcManager.driverName;
|
|
26
31
|
const corePlugins = new Set(plugins
|
|
27
32
|
.filter(p => pluginManager.pluginMetadata[p.name]?.isCore)
|
|
28
33
|
.map(p => p.name));
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
const externalPlugins = plugins.filter(plugin => !corePlugins.has(plugin.name));
|
|
35
|
+
return (_jsxs("div", { className: classes.root, children: [_jsx(Typography, { variant: "h4", align: "center", children: "JBrowse 2" }), _jsx(Typography, { variant: "h6", align: "center", className: classes.subtitle, children: version }), gitCommit ? (_jsxs(Typography, { variant: "body2", align: "center", children: ["Commit: ", gitCommit] })) : null, _jsxs(Typography, { align: "center", children: ["JBrowse is a ", _jsx(ExternalLink, { href: "https://gmod.org/", children: "GMOD" }), ' ', "project"] }), _jsx("br", {}), _jsx(Typography, { align: "center", children: "\u00A9 2019-2026 The Evolutionary Software Foundation" }), _jsxs("div", { className: classes.accordions, children: [_jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), children: _jsx(Typography, { children: "Browser settings" }) }), _jsx(AccordionDetails, { children: _jsxs("ul", { children: [graphicsCapabilities ? (_jsxs("li", { children: ["Graphics: ", effectiveRenderer(graphicsCapabilities)] })) : null, _jsxs("li", { children: ["Rendering:", ' ', defaultRpcDriver === 'WebWorkerRpcDriver'
|
|
36
|
+
? 'off main thread'
|
|
37
|
+
: 'on main thread'] })] }) })] }), _jsxs(Accordion, { defaultExpanded: true, children: [_jsx(AccordionSummary, { expandIcon: _jsx(ExpandMoreIcon, {}), children: _jsx(Typography, { children: "Plugins loaded" }) }), _jsxs(AccordionDetails, { children: [_jsx(Typography, { children: "External plugins" }), externalPlugins.length ? (_jsx("ul", { children: externalPlugins.map(plugin => {
|
|
38
|
+
const { url, name, version = '' } = plugin;
|
|
39
|
+
const text = `${name} ${version || ''}`;
|
|
40
|
+
return (_jsx("li", { children: plugin.url ? (_jsx(ExternalLink, { href: url, children: text })) : (_jsx("span", { children: text })) }, plugin.name));
|
|
41
|
+
}) })) : (_jsx(Typography, { children: "No external plugins loaded" })), _jsx(Typography, { children: "Core plugins" }), _jsx("ul", { children: plugins
|
|
42
|
+
.filter(plugin => corePlugins.has(plugin.name))
|
|
43
|
+
.map(plugin => (_jsxs("li", { children: [plugin.name, " ", plugin.version || ''] }, plugin.name))) })] })] })] })] }));
|
|
38
44
|
});
|
|
39
45
|
export default AboutWidget;
|
package/esm/AboutWidget/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
3
2
|
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
4
4
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
5
5
|
import { types } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
const configSchema = ConfigurationSchema('AboutWidget', {});
|
|
@@ -15,7 +15,7 @@ export default function AboutWidgetF(pluginManager) {
|
|
|
15
15
|
heading: 'About',
|
|
16
16
|
configSchema,
|
|
17
17
|
stateModel,
|
|
18
|
-
ReactComponent:
|
|
18
|
+
ReactComponent: lazyWithPreload(() => import('./components/AboutWidget.js')),
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
}
|
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { ExternalLink } from '@jbrowse/core/ui';
|
|
3
3
|
import { getSession } from '@jbrowse/core/util';
|
|
4
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import { Typography } from '@mui/material';
|
|
5
|
+
import { Table, TableBody, TableCell, TableRow, Typography, } from '@mui/material';
|
|
6
6
|
import { observer } from 'mobx-react';
|
|
7
7
|
const useStyles = makeStyles()(theme => ({
|
|
8
8
|
root: {
|
|
@@ -11,10 +11,33 @@ const useStyles = makeStyles()(theme => ({
|
|
|
11
11
|
subtitle: {
|
|
12
12
|
margin: theme.spacing(1),
|
|
13
13
|
},
|
|
14
|
+
section: {
|
|
15
|
+
marginTop: theme.spacing(2),
|
|
16
|
+
},
|
|
17
|
+
key: {
|
|
18
|
+
fontFamily: 'monospace',
|
|
19
|
+
whiteSpace: 'nowrap',
|
|
20
|
+
},
|
|
14
21
|
}));
|
|
22
|
+
// The keyboard shortcuts have existed since the LGV grew `keyboardHandler.ts`
|
|
23
|
+
// and were, until this list, written down nowhere a user could reach — so in
|
|
24
|
+
// practice they did not exist. They belong here rather than in a dialog of their
|
|
25
|
+
// own: this widget is already what the Help menu opens, and a second surface
|
|
26
|
+
// would be one more thing to find. If the list ever outgrows a paragraph, that
|
|
27
|
+
// is the moment to reconsider, not before.
|
|
28
|
+
//
|
|
29
|
+
// Spelled "Ctrl/Cmd" rather than branched on the platform: every reliable way to
|
|
30
|
+
// detect macOS is deprecated (`navigator.platform`) or a user-agent sniff, and
|
|
31
|
+
// naming both modifiers costs one word.
|
|
32
|
+
const shortcuts = [
|
|
33
|
+
['Ctrl/Cmd + ←', 'Pan left'],
|
|
34
|
+
['Ctrl/Cmd + →', 'Pan right'],
|
|
35
|
+
['Ctrl/Cmd + ↑', 'Zoom in'],
|
|
36
|
+
['Ctrl/Cmd + ↓', 'Zoom out'],
|
|
37
|
+
];
|
|
15
38
|
const HelpWidget = observer(function Help({ model, }) {
|
|
16
39
|
const { classes } = useStyles();
|
|
17
40
|
const root = model ? getSession(model) : { version: '' };
|
|
18
|
-
return (_jsxs("div", { className: classes.root, children: [_jsx(Typography, { variant: "h4", align: "center", children: "JBrowse 2" }), _jsx(Typography, { variant: "h6", align: "center", className: classes.subtitle, children: root.version }), _jsx(Typography, { children: "Here are some resources to get help. Please report the version number above when asking questions. Thanks!" }), _jsxs("ul", { children: [_jsx("li", { children: _jsx(ExternalLink, { href: "https://github.com/GMOD/jbrowse-components/discussions", children: "Question & answer forum" }) }), _jsx("li", { children: _jsx(ExternalLink, { href: "https://github.com/GMOD/jbrowse-components/issues/new/choose", children: "Report a bug" }) }), _jsx("li", { children: _jsx(ExternalLink, { href: "https://jbrowse.org/jb2/docs/user_guide", children: "User guide" }) }), _jsx("li", { children: _jsx(ExternalLink, { href: "https://jbrowse.org/jb2/docs/", children: "Documentation" }) })] })] }));
|
|
41
|
+
return (_jsxs("div", { className: classes.root, children: [_jsx(Typography, { variant: "h4", align: "center", children: "JBrowse 2" }), _jsx(Typography, { variant: "h6", align: "center", className: classes.subtitle, children: root.version }), _jsx(Typography, { children: "Here are some resources to get help. Please report the version number above when asking questions. Thanks!" }), _jsxs("ul", { children: [_jsx("li", { children: _jsx(ExternalLink, { href: "https://github.com/GMOD/jbrowse-components/discussions", children: "Question & answer forum" }) }), _jsx("li", { children: _jsx(ExternalLink, { href: "https://github.com/GMOD/jbrowse-components/issues/new/choose", children: "Report a bug" }) }), _jsx("li", { children: _jsx(ExternalLink, { href: "https://jbrowse.org/jb2/docs/user_guide", children: "User guide" }) }), _jsx("li", { children: _jsx(ExternalLink, { href: "https://jbrowse.org/jb2/docs/", children: "Documentation" }) })] }), _jsxs("div", { className: classes.section, children: [_jsx(Typography, { variant: "h6", children: "Keyboard shortcuts" }), _jsx(Typography, { children: "These act on the focused view. Tab moves between views, or click one; the focused view is the one with the arrow beside its title." }), _jsx(Table, { size: "small", children: _jsx(TableBody, { children: shortcuts.map(([keys, description]) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: classes.key, children: keys }), _jsx(TableCell, { children: description })] }, keys))) }) })] })] }));
|
|
19
42
|
});
|
|
20
43
|
export default HelpWidget;
|
package/esm/HelpWidget/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
3
2
|
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
4
4
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
5
5
|
import { types } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
const configSchema = ConfigurationSchema('HelpWidget', {});
|
|
@@ -15,7 +15,7 @@ export default function HelpWidgetF(pluginManager) {
|
|
|
15
15
|
heading: 'Help',
|
|
16
16
|
configSchema,
|
|
17
17
|
stateModel,
|
|
18
|
-
ReactComponent:
|
|
18
|
+
ReactComponent: lazyWithPreload(() => import('./components/HelpWidget.js')),
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
}
|
|
@@ -18,5 +18,5 @@ const useStyles = makeStyles()(theme => ({
|
|
|
18
18
|
}));
|
|
19
19
|
export default function ImportError({ error }) {
|
|
20
20
|
const { classes } = useStyles();
|
|
21
|
-
return (_jsxs(Paper, { className: classes.error, children: [_jsxs("div", { className: classes.errorHeader, children: [_jsx(ErrorIcon, { color: "inherit", fontSize: "large" }), _jsx(
|
|
21
|
+
return (_jsxs(Paper, { className: classes.error, children: [_jsxs("div", { className: classes.errorHeader, children: [_jsx(ErrorIcon, { color: "inherit", fontSize: "large" }), _jsx(Typography, { variant: "h6", color: "inherit", align: "center", children: "Import error" })] }), _jsx(Typography, { className: classes.errorMessage, children: `${error}` })] }));
|
|
22
22
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { IAnyStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
2
2
|
declare const ImportSessionWidget: ({ model, }: {
|
|
3
3
|
model: IAnyStateTreeNode;
|
|
4
|
-
}) => import("react
|
|
4
|
+
}) => import("react").JSX.Element;
|
|
5
5
|
export default ImportSessionWidget;
|
|
@@ -1,69 +1,45 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { FileDropZone } from '@jbrowse/core/ui';
|
|
4
|
+
import { getEnv, getSession } from '@jbrowse/core/util';
|
|
4
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import
|
|
6
|
-
import { Button, Paper, Typography, alpha } from '@mui/material';
|
|
6
|
+
import { Button, Paper, Typography } from '@mui/material';
|
|
7
7
|
import { observer } from 'mobx-react';
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
const MAX_FILE_SIZE = 512 * 1024 ** 2;
|
|
8
|
+
import ImportError from './ImportError.js';
|
|
9
|
+
const MAX_FILE_SIZE = 512 * 1024 ** 2; // 512 MiB
|
|
11
10
|
const useStyles = makeStyles()(theme => ({
|
|
12
|
-
root: {
|
|
13
|
-
margin: theme.spacing(1),
|
|
14
|
-
},
|
|
15
11
|
paper: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
dropZone: {
|
|
20
|
-
textAlign: 'center',
|
|
21
|
-
margin: theme.spacing(2),
|
|
22
|
-
padding: theme.spacing(2),
|
|
23
|
-
borderWidth: 2,
|
|
24
|
-
borderRadius: 2,
|
|
25
|
-
borderStyle: 'dashed',
|
|
26
|
-
outline: 'none',
|
|
27
|
-
transition: 'border .24s ease-in-out, background-color .24s ease-in-out',
|
|
28
|
-
'&:focus': {
|
|
29
|
-
borderColor: theme.palette.secondary.light,
|
|
30
|
-
},
|
|
31
|
-
},
|
|
32
|
-
dropZoneActive: {
|
|
33
|
-
borderColor: theme.palette.secondary.light,
|
|
34
|
-
backgroundColor: alpha(theme.palette.text.primary, theme.palette.action.hoverOpacity),
|
|
35
|
-
},
|
|
36
|
-
dropZoneInactive: {
|
|
37
|
-
borderColor: theme.palette.divider,
|
|
38
|
-
backgroundColor: theme.palette.background.default,
|
|
39
|
-
},
|
|
40
|
-
uploadIcon: {
|
|
41
|
-
color: theme.palette.text.secondary,
|
|
12
|
+
margin: theme.spacing(1),
|
|
13
|
+
padding: theme.spacing(1),
|
|
42
14
|
},
|
|
43
15
|
}));
|
|
44
16
|
const ImportSessionWidget = observer(function ImportSessionWidget({ model, }) {
|
|
45
17
|
const [error, setError] = useState();
|
|
46
|
-
const {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
18
|
+
const { classes } = useStyles();
|
|
19
|
+
return (_jsxs(_Fragment, { children: [_jsx(Paper, { className: classes.paper, children: _jsxs(FileDropZone, { accept: { 'application/json': ['.json'] }, maxSize: MAX_FILE_SIZE, multiple: false, message: "Drag and drop a session file here", onDrop: async (acceptedFiles, rejectedFiles) => {
|
|
20
|
+
try {
|
|
21
|
+
setError(undefined);
|
|
22
|
+
if (rejectedFiles.length > 0) {
|
|
23
|
+
throw new Error(rejectedFiles[0].errors.map(e => e.message).join(', '));
|
|
24
|
+
}
|
|
25
|
+
const sessionText = await acceptedFiles[0].text();
|
|
26
|
+
const parsed = JSON.parse(sessionText);
|
|
27
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
28
|
+
throw new Error('File does not contain a JSON object');
|
|
29
|
+
}
|
|
30
|
+
// session exports wrap the session under a top-level "session"
|
|
31
|
+
// key; fall back to treating the whole object as the session
|
|
32
|
+
const session = 'session' in parsed ? parsed.session : parsed;
|
|
33
|
+
if (!session || typeof session !== 'object') {
|
|
34
|
+
throw new Error('No session found in file. Expected a JBrowse session export (a JSON file with a top-level "session" key).');
|
|
35
|
+
}
|
|
36
|
+
await getEnv(model).pluginManager.preloadSessionTypes(session);
|
|
37
|
+
getSession(model).setSession?.(session);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.error(e);
|
|
41
|
+
setError(e);
|
|
42
|
+
}
|
|
43
|
+
}, children: [_jsx(Typography, { color: "text.secondary", align: "center", variant: "body2", children: "or" }), _jsx(Button, { color: "primary", variant: "contained", children: "Browse Files" })] }) }), error ? _jsx(ImportError, { error: error }) : null] }));
|
|
68
44
|
});
|
|
69
45
|
export default ImportSessionWidget;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
3
2
|
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
4
4
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
5
5
|
import { types } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
const configSchema = ConfigurationSchema('ImportSessionWidget', {});
|
|
@@ -15,7 +15,7 @@ export default function ImportSessionWidgetF(pluginManager) {
|
|
|
15
15
|
heading: 'Import session',
|
|
16
16
|
configSchema,
|
|
17
17
|
stateModel,
|
|
18
|
-
ReactComponent:
|
|
18
|
+
ReactComponent: lazyWithPreload(() => import('./components/ImportSessionWidget.js')),
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SessionModel } from './util.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Confirmation for the bulk "delete old sessions" action, which is the one
|
|
4
|
+
* control here that can destroy many sessions at once and was previously a
|
|
5
|
+
* single unconfirmed click. Shows how many sessions the chosen cutoff actually
|
|
6
|
+
* matches, so "delete" is never a guess.
|
|
7
|
+
*/
|
|
8
|
+
declare const DeleteOldSessionsDialog: ({ session, onClose, }: {
|
|
9
|
+
session: SessionModel;
|
|
10
|
+
onClose: () => void;
|
|
11
|
+
}) => import("react").JSX.Element;
|
|
12
|
+
export default DeleteOldSessionsDialog;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { ConfirmDialog } from '@jbrowse/core/ui';
|
|
4
|
+
import { MenuItem, TextField, Typography } from '@mui/material';
|
|
5
|
+
import { observer } from 'mobx-react';
|
|
6
|
+
import { STALE_CUTOFFS, staleSessions } from './util.js';
|
|
7
|
+
/**
|
|
8
|
+
* Confirmation for the bulk "delete old sessions" action, which is the one
|
|
9
|
+
* control here that can destroy many sessions at once and was previously a
|
|
10
|
+
* single unconfirmed click. Shows how many sessions the chosen cutoff actually
|
|
11
|
+
* matches, so "delete" is never a guess.
|
|
12
|
+
*/
|
|
13
|
+
const DeleteOldSessionsDialog = observer(function DeleteOldSessionsDialog({ session, onClose, }) {
|
|
14
|
+
const [days, setDays] = useState(STALE_CUTOFFS[0].days);
|
|
15
|
+
const matched = staleSessions(session.savedSessionMetadata, {
|
|
16
|
+
days,
|
|
17
|
+
openSessionId: session.id,
|
|
18
|
+
});
|
|
19
|
+
return (_jsxs(ConfirmDialog, { open: true, title: "Delete old sessions", submitText: "Delete", submitColor: "error", submitDisabled: matched.length === 0, onCancel: onClose, onSubmit: () => {
|
|
20
|
+
session
|
|
21
|
+
.deleteSavedSessions(matched.map(m => m.id))
|
|
22
|
+
.then(() => {
|
|
23
|
+
session.notify(`${matched.length} session${matched.length === 1 ? '' : 's'} deleted`, 'success');
|
|
24
|
+
})
|
|
25
|
+
.catch((e) => {
|
|
26
|
+
console.error(e);
|
|
27
|
+
session.notifyError(`${e}`, e);
|
|
28
|
+
});
|
|
29
|
+
onClose();
|
|
30
|
+
}, children: [_jsx(TextField, { select: true, fullWidth: true, label: "Delete sessions last used more than", value: days, onChange: event => {
|
|
31
|
+
setDays(+event.target.value);
|
|
32
|
+
}, children: STALE_CUTOFFS.map(cutoff => (_jsxs(MenuItem, { value: cutoff.days, children: [cutoff.label, " ago"] }, cutoff.days))) }), _jsx(Typography, { children: matched.length === 0
|
|
33
|
+
? 'No sessions match — favorites and the session you have open are never deleted.'
|
|
34
|
+
: `${matched.length} session${matched.length === 1 ? '' : 's'} will be deleted. Favorites and the session you have open are kept.` })] }));
|
|
35
|
+
});
|
|
36
|
+
export default DeleteOldSessionsDialog;
|
|
@@ -1,92 +1,130 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { LabeledCheckbox } from '@jbrowse/core/ui';
|
|
2
4
|
import DataGridFlexContainer from '@jbrowse/core/ui/DataGridFlexContainer';
|
|
3
|
-
import {
|
|
5
|
+
import { useLocalStorage } from '@jbrowse/core/util/hooks';
|
|
4
6
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
5
|
-
import
|
|
6
|
-
import StarIcon from '@mui/icons-material/Star';
|
|
7
|
-
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
|
8
|
-
import { Button, Checkbox, FormControlLabel, IconButton, Link, Tooltip, } from '@mui/material';
|
|
7
|
+
import { Button, Typography } from '@mui/material';
|
|
9
8
|
import { DataGrid } from '@mui/x-data-grid';
|
|
10
|
-
import { differenceInDays, formatDistanceToNow } from 'date-fns';
|
|
11
9
|
import { observer } from 'mobx-react';
|
|
10
|
+
import DeleteOldSessionsDialog from './DeleteOldSessionsDialog.js';
|
|
11
|
+
import { DeleteCell, FavoriteCell, LastUsedCell, NameCell } from './cells.js';
|
|
12
|
+
import { sessionLastUsed } from './util.js';
|
|
12
13
|
const useStyles = makeStyles()(theme => ({
|
|
14
|
+
// The widget fills its drawer and the grid takes what is left below the
|
|
15
|
+
// controls. Not cosmetic: MUI's DataGrid draws its horizontal scrollbar as an
|
|
16
|
+
// absolutely positioned overlay at the bottom of its own box, so a grid sized
|
|
17
|
+
// to its content puts that 14px bar *on top of the last row* — and z-index 60
|
|
18
|
+
// wins the hit test, so the bottom session silently stops responding to
|
|
19
|
+
// clicks. Given room, the bar lands below every row.
|
|
20
|
+
root: {
|
|
21
|
+
display: 'flex',
|
|
22
|
+
flexDirection: 'column',
|
|
23
|
+
height: '100%',
|
|
24
|
+
},
|
|
25
|
+
grid: {
|
|
26
|
+
flex: 1,
|
|
27
|
+
// a flex child defaults to min-height:auto, which refuses to shrink below
|
|
28
|
+
// its content and would push the grid past the drawer instead of scrolling
|
|
29
|
+
minHeight: 0,
|
|
30
|
+
},
|
|
13
31
|
mb: {
|
|
14
32
|
margin: theme.spacing(1),
|
|
15
33
|
marginBottom: theme.spacing(4),
|
|
16
34
|
},
|
|
17
35
|
}));
|
|
36
|
+
/**
|
|
37
|
+
* The rows of the grid: the saved-session metadata the root model read out of
|
|
38
|
+
* IndexedDB, in display shape. `undefined` in, `undefined` out — that is the
|
|
39
|
+
* "database still opening" state, and it is deliberately not the same value as
|
|
40
|
+
* an empty database (see the render below).
|
|
41
|
+
*/
|
|
42
|
+
function buildRows(session, showOnlyFavs) {
|
|
43
|
+
return session.savedSessionMetadata
|
|
44
|
+
?.map(r => {
|
|
45
|
+
const current = r.id === session.id;
|
|
46
|
+
return {
|
|
47
|
+
id: r.id,
|
|
48
|
+
name: r.name,
|
|
49
|
+
label: current ? `${r.name} (current)` : r.name,
|
|
50
|
+
createdAt: r.createdAt,
|
|
51
|
+
lastUsed: sessionLastUsed(r),
|
|
52
|
+
fav: r.favorite,
|
|
53
|
+
current,
|
|
54
|
+
};
|
|
55
|
+
})
|
|
56
|
+
.filter(f => !showOnlyFavs || f.fav);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* One entry per column, each deferring to a named cell in cells.tsx so this
|
|
60
|
+
* reads as the shape of the table rather than as four inline components.
|
|
61
|
+
*
|
|
62
|
+
* Name takes the leftover width (`flex`) rather than a width measured from the
|
|
63
|
+
* session names, and the three fixed columns are kept narrow enough that their
|
|
64
|
+
* sum plus Name's minWidth fits a default-width drawer. `measureGridWidth`
|
|
65
|
+
* hands back a *fixed* width — up to 1000px for a long session name — and any
|
|
66
|
+
* total past the panel width grows the horizontal scrollbar this widget's
|
|
67
|
+
* styles exist to keep off the rows (see useStyles).
|
|
68
|
+
*/
|
|
69
|
+
function buildColumns(session) {
|
|
70
|
+
return [
|
|
71
|
+
{
|
|
72
|
+
field: 'fav',
|
|
73
|
+
headerName: 'Fav',
|
|
74
|
+
// the header of an icon column is short, and its sort arrow and column
|
|
75
|
+
// menu are what push it into an ellipsis. Only sorting earns its space
|
|
76
|
+
// here -- grouping the favorites is a real thing to want -- so the menu
|
|
77
|
+
// goes and the header reads.
|
|
78
|
+
width: 60,
|
|
79
|
+
disableColumnMenu: true,
|
|
80
|
+
renderCell: ({ row }) => (_jsx(FavoriteCell, { row: row, session: session })),
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
field: 'name',
|
|
84
|
+
headerName: 'Name',
|
|
85
|
+
editable: true,
|
|
86
|
+
flex: 1,
|
|
87
|
+
minWidth: 120,
|
|
88
|
+
renderCell: ({ row }) => (_jsx(NameCell, { row: row, session: session })),
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
field: 'lastUsed',
|
|
92
|
+
headerName: 'Last used',
|
|
93
|
+
width: 100,
|
|
94
|
+
renderCell: ({ row }) => (_jsx(LastUsedCell, { row: row, session: session })),
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
field: 'delete',
|
|
98
|
+
headerName: 'Delete',
|
|
99
|
+
// nothing to sort or filter on a column of buttons, and dropping both
|
|
100
|
+
// gives the header room to say "Delete" instead of "Del..."
|
|
101
|
+
width: 66,
|
|
102
|
+
sortable: false,
|
|
103
|
+
filterable: false,
|
|
104
|
+
disableColumnMenu: true,
|
|
105
|
+
renderCell: ({ row }) => (_jsx(DeleteCell, { row: row, session: session })),
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
}
|
|
18
109
|
const SessionManager = observer(function SessionManager({ session, }) {
|
|
19
110
|
const { classes } = useStyles();
|
|
111
|
+
const [deleteOldOpen, setDeleteOldOpen] = useState(false);
|
|
20
112
|
const [showOnlyFavs, setShowOnlyFavs] = useLocalStorage('sessionManager-showOnlyFavs', false);
|
|
21
|
-
const rows = session
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
session.deleteSavedSession(elt.id);
|
|
38
|
-
i++;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
session.notify(`${i} sessions deleted`, 'info');
|
|
43
|
-
}, children: "Delete non-fav sessions older than 7 days?" })] }), rows ? (_jsx(DataGridFlexContainer, { children: _jsx(DataGrid, { disableRowSelectionOnClick: true, columnHeaderHeight: 35, rowHeight: 25, hideFooter: rows.length < 100, slotProps: {
|
|
44
|
-
toolbar: {
|
|
45
|
-
showQuickFilter: true,
|
|
46
|
-
},
|
|
47
|
-
}, rows: rows, columns: [
|
|
48
|
-
{
|
|
49
|
-
field: 'fav',
|
|
50
|
-
headerName: 'Fav',
|
|
51
|
-
width: 20,
|
|
52
|
-
renderCell: ({ row }) => (_jsx(IconButton, { onClick: () => {
|
|
53
|
-
if (row.fav) {
|
|
54
|
-
session.unfavoriteSavedSession(row.id);
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
session.favoriteSavedSession(row.id);
|
|
58
|
-
}
|
|
59
|
-
}, children: row.fav ? _jsx(StarIcon, {}) : _jsx(StarBorderIcon, {}) })),
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
field: 'name',
|
|
63
|
-
headerName: 'Name',
|
|
64
|
-
editable: true,
|
|
65
|
-
width: measureGridWidth(rows.map(r => r.name)),
|
|
66
|
-
renderCell: ({ row }) => (_jsxs(_Fragment, { children: [_jsx(Link, { href: "#", onClick: event => {
|
|
67
|
-
event.preventDefault();
|
|
68
|
-
session.activateSession(row.id);
|
|
69
|
-
}, children: row.name }), session.id === row.id ? ' (current)' : ''] })),
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
headerName: 'Created at',
|
|
73
|
-
field: 'createdAt',
|
|
74
|
-
renderCell: ({ row }) => (_jsx(Tooltip, { disableInteractive: true, slotProps: {
|
|
75
|
-
transition: {
|
|
76
|
-
timeout: 0,
|
|
77
|
-
},
|
|
78
|
-
}, title: row.createdAt.toLocaleString(), children: _jsx("div", { children: formatDistanceToNow(row.createdAt, {
|
|
79
|
-
addSuffix: true,
|
|
80
|
-
}) }) })),
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
field: 'delete',
|
|
84
|
-
width: 10,
|
|
85
|
-
headerName: 'Delete',
|
|
86
|
-
renderCell: ({ row }) => (_jsx(IconButton, { onClick: () => {
|
|
87
|
-
session.deleteSavedSession(row.id);
|
|
88
|
-
}, children: _jsx(DeleteIcon, {}) })),
|
|
89
|
-
},
|
|
90
|
-
] }) })) : (_jsx("div", { children: "No sessions loaded" }))] }));
|
|
113
|
+
const rows = buildRows(session, showOnlyFavs);
|
|
114
|
+
return (_jsxs("div", { className: classes.root, children: [_jsxs("div", { className: classes.mb, children: [_jsx(LabeledCheckbox, { checked: showOnlyFavs, onChange: val => {
|
|
115
|
+
setShowOnlyFavs(val);
|
|
116
|
+
}, label: "Show favorites only?" }), _jsx(Button, { variant: "contained", disabled: !rows, onClick: () => {
|
|
117
|
+
setDeleteOldOpen(true);
|
|
118
|
+
}, children: "Delete old sessions..." })] }), rows ? (_jsx(DataGridFlexContainer, { className: classes.grid, children: _jsx(DataGrid, { disableRowSelectionOnClick: true, columnHeaderHeight: 35, rowHeight: 25, hideFooter: rows.length < 100, showToolbar: true, slotProps: { toolbar: { showQuickFilter: true } }, rows: rows, columns: buildColumns(session), processRowUpdate: (newRow, oldRow) => {
|
|
119
|
+
if (newRow.name !== oldRow.name) {
|
|
120
|
+
void session.renameSavedSession(newRow.id, newRow.name);
|
|
121
|
+
}
|
|
122
|
+
return newRow;
|
|
123
|
+
}, onProcessRowUpdateError: e => {
|
|
124
|
+
console.error(e);
|
|
125
|
+
session.notifyError(`${e}`, e);
|
|
126
|
+
} }) })) : (_jsx(Typography, { className: classes.mb, children: "Loading saved sessions..." })), deleteOldOpen ? (_jsx(DeleteOldSessionsDialog, { session: session, onClose: () => {
|
|
127
|
+
setDeleteOldOpen(false);
|
|
128
|
+
} })) : null] }));
|
|
91
129
|
});
|
|
92
130
|
export default SessionManager;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { SessionModel } from './util.ts';
|
|
2
|
+
/**
|
|
3
|
+
* One row of the saved-session grid. `current` is precomputed rather than
|
|
4
|
+
* re-derived per cell: two cells turn on it, and it is the fact that decides
|
|
5
|
+
* what each of them is allowed to do (see DeleteCell).
|
|
6
|
+
*/
|
|
7
|
+
export interface Row {
|
|
8
|
+
id: string;
|
|
9
|
+
/** the editable value — what a rename writes, never what the cell draws */
|
|
10
|
+
name: string;
|
|
11
|
+
/**
|
|
12
|
+
* what the name cell draws, which is `name` plus a "(current)" suffix on the
|
|
13
|
+
* open session. Precomputed so the column that *measures* the names to pick
|
|
14
|
+
* its width measures the same strings the cells render: measuring `name`
|
|
15
|
+
* alone left the current row — the one that is always at the top — as the one
|
|
16
|
+
* row ellipsed.
|
|
17
|
+
*/
|
|
18
|
+
label: string;
|
|
19
|
+
createdAt: Date;
|
|
20
|
+
lastUsed: Date;
|
|
21
|
+
fav: boolean;
|
|
22
|
+
current: boolean;
|
|
23
|
+
}
|
|
24
|
+
interface CellProps {
|
|
25
|
+
row: Row;
|
|
26
|
+
session: SessionModel;
|
|
27
|
+
}
|
|
28
|
+
export declare const FavoriteCell: ({ row, session, }: CellProps) => import("react").JSX.Element;
|
|
29
|
+
export declare const NameCell: ({ row, session, }: CellProps) => import("react").JSX.Element;
|
|
30
|
+
export declare const LastUsedCell: ({ row }: CellProps) => import("react").JSX.Element;
|
|
31
|
+
export declare const DeleteCell: ({ row, session, }: CellProps) => import("react").JSX.Element;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ActionLink } from '@jbrowse/core/ui';
|
|
3
|
+
import { formatRelativeTime } from '@jbrowse/core/util';
|
|
4
|
+
import DeleteIcon from '@mui/icons-material/Delete';
|
|
5
|
+
import StarIcon from '@mui/icons-material/Star';
|
|
6
|
+
import StarBorderIcon from '@mui/icons-material/StarBorder';
|
|
7
|
+
import { IconButton, Tooltip } from '@mui/material';
|
|
8
|
+
import { observer } from 'mobx-react';
|
|
9
|
+
// Every icon-only control here carries the same string as its tooltip and its
|
|
10
|
+
// aria-label. Without the label a screen reader announces an unnamed button per
|
|
11
|
+
// row; without the tooltip a sighted user has to guess what the icon deletes.
|
|
12
|
+
const IconCell = observer(function IconCell({ label, disabled, onClick, children, }) {
|
|
13
|
+
return (_jsx(Tooltip, { disableInteractive: true, title: label, children: _jsx("span", { children: _jsx(IconButton, { "aria-label": label, disabled: disabled, onClick: onClick, children: children }) }) }));
|
|
14
|
+
});
|
|
15
|
+
export const FavoriteCell = observer(function FavoriteCell({ row, session, }) {
|
|
16
|
+
return (_jsx(IconCell, { label: row.fav ? 'Remove from favorites' : 'Add to favorites', onClick: () => {
|
|
17
|
+
void session.setSavedSessionFavorite(row.id, !row.fav);
|
|
18
|
+
}, children: row.fav ? _jsx(StarIcon, {}) : _jsx(StarBorderIcon, {}) }));
|
|
19
|
+
});
|
|
20
|
+
export const NameCell = observer(function NameCell({ row, session, }) {
|
|
21
|
+
// A native `title`, not a MUI Tooltip: the drawer is narrow enough that a
|
|
22
|
+
// long session name ellipses, and a tooltip here would mean a popper laid
|
|
23
|
+
// over the row underneath — which is exactly how this column lost its clicks
|
|
24
|
+
// once already (see the scrollbar note in SessionManager's useStyles).
|
|
25
|
+
//
|
|
26
|
+
// The open session is not offered as a link: re-opening it from IndexedDB can
|
|
27
|
+
// only lose work, since that row is up to one autosave tick behind the live
|
|
28
|
+
// model. The File menu leaves it out of the recent list for the same reason.
|
|
29
|
+
return row.current ? (_jsx("span", { title: row.label, children: row.label })) : (_jsx(ActionLink, { title: row.label, onClick: () => {
|
|
30
|
+
void session.activateSession(row.id);
|
|
31
|
+
}, children: row.label }));
|
|
32
|
+
});
|
|
33
|
+
// dated by last use, not creation: the rows arrive ordered by last use, so
|
|
34
|
+
// showing createdAt would read as an unsorted list. Both are in the tooltip,
|
|
35
|
+
// since an id survives reloads and the gap between them is often large.
|
|
36
|
+
export const LastUsedCell = observer(function LastUsedCell({ row }) {
|
|
37
|
+
return (_jsx(Tooltip, { disableInteractive: true, slotProps: { transition: { timeout: 0 } }, title: `Last used ${row.lastUsed.toLocaleString()}\nCreated ${row.createdAt.toLocaleString()}`, children: _jsx("div", { children: formatRelativeTime(row.lastUsed) }) }));
|
|
38
|
+
});
|
|
39
|
+
export const DeleteCell = observer(function DeleteCell({ row, session, }) {
|
|
40
|
+
// the open session is rewritten by the autosave autorun every 400ms, so
|
|
41
|
+
// deleting it only makes it vanish until the next edit puts it back — say so
|
|
42
|
+
// on a disabled button rather than on a snackbar after the click
|
|
43
|
+
return (_jsx(IconCell, { label: row.current
|
|
44
|
+
? 'Cannot delete the session that is currently open'
|
|
45
|
+
: `Delete session ${row.name}`, disabled: row.current, onClick: () => {
|
|
46
|
+
void session.deleteSavedSession(row.id);
|
|
47
|
+
}, children: _jsx(DeleteIcon, {}) }));
|
|
48
|
+
});
|
|
@@ -3,24 +3,42 @@ export interface SessionMetadata {
|
|
|
3
3
|
id: string;
|
|
4
4
|
name: string;
|
|
5
5
|
createdAt: Date;
|
|
6
|
+
updatedAt?: Date;
|
|
6
7
|
configPath: string;
|
|
7
8
|
favorite: boolean;
|
|
8
9
|
}
|
|
9
|
-
export
|
|
10
|
-
createdAt: Date;
|
|
11
|
-
session: {
|
|
12
|
-
name: string;
|
|
13
|
-
id: string;
|
|
14
|
-
views?: {
|
|
15
|
-
tracks?: unknown[];
|
|
16
|
-
}[];
|
|
17
|
-
[key: string]: unknown;
|
|
18
|
-
};
|
|
19
|
-
}
|
|
10
|
+
export declare function sessionLastUsed(m: SessionMetadata): Date;
|
|
20
11
|
export interface SessionModel extends AbstractSessionModel {
|
|
21
12
|
savedSessionMetadata?: SessionMetadata[];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
activateSession: (id: string) => Promise<void>;
|
|
14
|
+
deleteSavedSession: (id: string) => Promise<void>;
|
|
15
|
+
deleteSavedSessions: (ids: string[]) => Promise<void>;
|
|
16
|
+
setSavedSessionFavorite: (id: string, favorite: boolean) => Promise<void>;
|
|
17
|
+
renameSavedSession: (id: string, name: string) => Promise<void>;
|
|
26
18
|
}
|
|
19
|
+
export declare const STALE_CUTOFFS: readonly [{
|
|
20
|
+
readonly label: '1 day';
|
|
21
|
+
readonly days: 1;
|
|
22
|
+
}, {
|
|
23
|
+
readonly label: '1 week';
|
|
24
|
+
readonly days: 7;
|
|
25
|
+
}, {
|
|
26
|
+
readonly label: '1 month';
|
|
27
|
+
readonly days: 30;
|
|
28
|
+
}];
|
|
29
|
+
/**
|
|
30
|
+
* The sessions a bulk "delete old sessions" would remove: unfavorited, not the
|
|
31
|
+
* one currently open, and last *used* — not created — longer ago than the
|
|
32
|
+
* cutoff. An id survives reloads, so a session edited every day still carries
|
|
33
|
+
* the createdAt of the day it was opened, and ageing by that would delete live
|
|
34
|
+
* work out from under the user.
|
|
35
|
+
*
|
|
36
|
+
* The open session is excluded here as well as in the model's
|
|
37
|
+
* deleteSavedSessions, so the count the confirmation quotes is the number of
|
|
38
|
+
* sessions that will actually go.
|
|
39
|
+
*/
|
|
40
|
+
export declare function staleSessions(metadata: SessionMetadata[] | undefined, { days, openSessionId, now, }: {
|
|
41
|
+
days: number;
|
|
42
|
+
openSessionId?: string;
|
|
43
|
+
now?: number;
|
|
44
|
+
}): SessionMetadata[];
|
|
@@ -1 +1,28 @@
|
|
|
1
|
-
|
|
1
|
+
// when a session was last touched, see web-core's sessionLastUsed
|
|
2
|
+
export function sessionLastUsed(m) {
|
|
3
|
+
return m.updatedAt ?? m.createdAt;
|
|
4
|
+
}
|
|
5
|
+
// The cutoffs the "delete old sessions" bulk action offers. Days, so the label
|
|
6
|
+
// and the filter can never disagree about what "1 week" means.
|
|
7
|
+
export const STALE_CUTOFFS = [
|
|
8
|
+
{ label: '1 day', days: 1 },
|
|
9
|
+
{ label: '1 week', days: 7 },
|
|
10
|
+
{ label: '1 month', days: 30 },
|
|
11
|
+
];
|
|
12
|
+
const MS_PER_DAY = 1000 * 60 * 60 * 24;
|
|
13
|
+
/**
|
|
14
|
+
* The sessions a bulk "delete old sessions" would remove: unfavorited, not the
|
|
15
|
+
* one currently open, and last *used* — not created — longer ago than the
|
|
16
|
+
* cutoff. An id survives reloads, so a session edited every day still carries
|
|
17
|
+
* the createdAt of the day it was opened, and ageing by that would delete live
|
|
18
|
+
* work out from under the user.
|
|
19
|
+
*
|
|
20
|
+
* The open session is excluded here as well as in the model's
|
|
21
|
+
* deleteSavedSessions, so the count the confirmation quotes is the number of
|
|
22
|
+
* sessions that will actually go.
|
|
23
|
+
*/
|
|
24
|
+
export function staleSessions(metadata, { days, openSessionId, now = Date.now(), }) {
|
|
25
|
+
return (metadata ?? []).filter(m => !m.favorite &&
|
|
26
|
+
m.id !== openSessionId &&
|
|
27
|
+
(now - +sessionLastUsed(m)) / MS_PER_DAY > days);
|
|
28
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type PluginManager from '@jbrowse/core/PluginManager';
|
|
2
|
-
export declare const configSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined>>;
|
|
2
|
+
export declare const configSchema: import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaType<{}, import("@jbrowse/core/configuration/configurationSchema").ConfigurationSchemaOptions<undefined, undefined, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
3
3
|
export declare const stateModel: import("@jbrowse/mobx-state-tree").IModelType<{
|
|
4
4
|
id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
5
|
-
type: import("@jbrowse/mobx-state-tree").
|
|
5
|
+
type: import("@jbrowse/mobx-state-tree").ILiteralType<"SessionManager">;
|
|
6
6
|
}, {}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
|
|
7
7
|
export default function SessionManagerF(pluginManager: PluginManager): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { lazy } from 'react';
|
|
2
1
|
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
3
2
|
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
+
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
4
4
|
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
5
5
|
import { types } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
export const configSchema = ConfigurationSchema('SessionManager', {});
|
|
@@ -15,7 +15,7 @@ export default function SessionManagerF(pluginManager) {
|
|
|
15
15
|
heading: 'Recent sessions',
|
|
16
16
|
configSchema,
|
|
17
17
|
stateModel,
|
|
18
|
-
ReactComponent:
|
|
18
|
+
ReactComponent: lazyWithPreload(() => import('./components/SessionManager.js')),
|
|
19
19
|
});
|
|
20
20
|
});
|
|
21
21
|
}
|
package/esm/index.js
CHANGED
|
@@ -2,10 +2,10 @@ import Plugin from '@jbrowse/core/Plugin';
|
|
|
2
2
|
import { isAbstractMenuManager } from '@jbrowse/core/util';
|
|
3
3
|
import HelpIcon from '@mui/icons-material/Help';
|
|
4
4
|
import InfoIcon from '@mui/icons-material/Info';
|
|
5
|
-
import AboutWidgetF from
|
|
6
|
-
import HelpWidgetF from
|
|
7
|
-
import ImportSessionWidgetF from
|
|
8
|
-
import SessionManagerF from
|
|
5
|
+
import AboutWidgetF from './AboutWidget/index.js';
|
|
6
|
+
import HelpWidgetF from './HelpWidget/index.js';
|
|
7
|
+
import ImportSessionWidgetF from './ImportSessionWidget/index.js';
|
|
8
|
+
import SessionManagerF from './SessionManager/index.js';
|
|
9
9
|
export default class MenusPlugin extends Plugin {
|
|
10
10
|
name = 'MenusPlugin';
|
|
11
11
|
install(pluginManager) {
|
|
@@ -20,16 +20,14 @@ export default class MenusPlugin extends Plugin {
|
|
|
20
20
|
label: 'About',
|
|
21
21
|
icon: InfoIcon,
|
|
22
22
|
onClick: (session) => {
|
|
23
|
-
|
|
24
|
-
session.showWidget(widget);
|
|
23
|
+
session.openWidget('AboutWidget', 'aboutWidget');
|
|
25
24
|
},
|
|
26
25
|
});
|
|
27
26
|
pluginManager.rootModel.appendToMenu('Help', {
|
|
28
27
|
label: 'Help',
|
|
29
28
|
icon: HelpIcon,
|
|
30
29
|
onClick: (session) => {
|
|
31
|
-
|
|
32
|
-
session.showWidget(widget);
|
|
30
|
+
session.openWidget('HelpWidget', 'helpWidget');
|
|
33
31
|
},
|
|
34
32
|
});
|
|
35
33
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-menus",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-beta.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 basic menus",
|
|
6
6
|
"keywords": [
|
|
@@ -21,19 +21,18 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jbrowse/mobx-state-tree": "^
|
|
25
|
-
"@mui/icons-material": "^
|
|
26
|
-
"@mui/material": "^
|
|
27
|
-
"@mui/x-data-grid": "^
|
|
28
|
-
"
|
|
29
|
-
"mobx": "^
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"@jbrowse/core": "^4.3.0"
|
|
24
|
+
"@jbrowse/mobx-state-tree": "^6.6.1",
|
|
25
|
+
"@mui/icons-material": "^9.4.0",
|
|
26
|
+
"@mui/material": "^9.4.0",
|
|
27
|
+
"@mui/x-data-grid": "^9.14.0",
|
|
28
|
+
"mobx": "^7.0.5",
|
|
29
|
+
"mobx-react": "^10.0.2",
|
|
30
|
+
"@jbrowse/core": "^5.0.0-beta.10",
|
|
31
|
+
"@jbrowse/render-core": "^5.0.0-beta.10"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
|
-
"react": ">=
|
|
36
|
-
"react-dom": ">=
|
|
34
|
+
"react": ">=19.0.0",
|
|
35
|
+
"react-dom": ">=19.0.0"
|
|
37
36
|
},
|
|
38
37
|
"publishConfig": {
|
|
39
38
|
"access": "public"
|
|
@@ -43,14 +42,11 @@
|
|
|
43
42
|
"build": "pnpm run /^build:/",
|
|
44
43
|
"test": "cd ../..; jest --passWithNoTests plugins/menus",
|
|
45
44
|
"prebuild": "pnpm clean",
|
|
46
|
-
"build:esm": "tsc
|
|
45
|
+
"build:esm": "node ../../node_modules/typescript7/bin/tsc --build tsconfig.build.esm.json",
|
|
47
46
|
"clean": "rimraf esm *.tsbuildinfo"
|
|
48
47
|
},
|
|
49
48
|
"types": "esm/index.d.ts",
|
|
50
49
|
"exports": {
|
|
51
|
-
".":
|
|
52
|
-
"types": "./esm/index.d.ts",
|
|
53
|
-
"import": "./esm/index.js"
|
|
54
|
-
}
|
|
50
|
+
".": "./esm/index.js"
|
|
55
51
|
}
|
|
56
52
|
}
|