@jbrowse/plugin-menus 5.0.0-beta.11 → 5.0.0-beta.14
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/ImportSessionWidget/components/ImportError.d.ts +3 -2
- package/esm/ImportSessionWidget/components/ImportError.js +4 -2
- package/esm/ImportSessionWidget/components/ImportSessionWidget.js +2 -11
- package/esm/ImportSessionWidget/parseSessionFile.d.ts +4 -0
- package/esm/ImportSessionWidget/parseSessionFile.js +14 -0
- package/esm/index.js +0 -2
- package/package.json +8 -9
- package/esm/SessionManager/components/DeleteOldSessionsDialog.d.ts +0 -12
- package/esm/SessionManager/components/DeleteOldSessionsDialog.js +0 -36
- package/esm/SessionManager/components/SessionManager.d.ts +0 -5
- package/esm/SessionManager/components/SessionManager.js +0 -130
- package/esm/SessionManager/components/cells.d.ts +0 -32
- package/esm/SessionManager/components/cells.js +0 -48
- package/esm/SessionManager/components/util.d.ts +0 -44
- package/esm/SessionManager/components/util.js +0 -28
- package/esm/SessionManager/index.d.ts +0 -7
- package/esm/SessionManager/index.js +0 -22
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
3
3
|
import ErrorIcon from '@mui/icons-material/Error';
|
|
4
4
|
import { Paper, Typography } from '@mui/material';
|
|
5
|
+
import { observer } from 'mobx-react';
|
|
5
6
|
const useStyles = makeStyles()(theme => ({
|
|
6
7
|
error: {
|
|
7
8
|
margin: theme.spacing(2),
|
|
@@ -16,7 +17,8 @@ const useStyles = makeStyles()(theme => ({
|
|
|
16
17
|
padding: theme.spacing(2),
|
|
17
18
|
},
|
|
18
19
|
}));
|
|
19
|
-
|
|
20
|
+
const ImportError = observer(function ImportError({ error, }) {
|
|
20
21
|
const { classes } = useStyles();
|
|
21
22
|
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
|
-
}
|
|
23
|
+
});
|
|
24
|
+
export default ImportError;
|
|
@@ -5,6 +5,7 @@ import { getEnv, getSession } from '@jbrowse/core/util';
|
|
|
5
5
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
6
6
|
import { Button, Paper, Typography } from '@mui/material';
|
|
7
7
|
import { observer } from 'mobx-react';
|
|
8
|
+
import { parseSessionFile } from '../parseSessionFile.js';
|
|
8
9
|
import ImportError from './ImportError.js';
|
|
9
10
|
const MAX_FILE_SIZE = 512 * 1024 ** 2; // 512 MiB
|
|
10
11
|
const useStyles = makeStyles()(theme => ({
|
|
@@ -22,17 +23,7 @@ const ImportSessionWidget = observer(function ImportSessionWidget({ model, }) {
|
|
|
22
23
|
if (rejectedFiles.length > 0) {
|
|
23
24
|
throw new Error(rejectedFiles[0].errors.map(e => e.message).join(', '));
|
|
24
25
|
}
|
|
25
|
-
const
|
|
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
|
-
}
|
|
26
|
+
const session = parseSessionFile(await acceptedFiles[0].text());
|
|
36
27
|
await getEnv(model).pluginManager.preloadSessionTypes(session);
|
|
37
28
|
getSession(model).setSession?.(session);
|
|
38
29
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The file's id is dropped so the import opens as a new session: keeping it
|
|
2
|
+
// would autosave over the saved session the file was exported from.
|
|
3
|
+
export function parseSessionFile(text) {
|
|
4
|
+
const parsed = JSON.parse(text);
|
|
5
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
6
|
+
throw new Error('File does not contain a JSON object');
|
|
7
|
+
}
|
|
8
|
+
const session = 'session' in parsed ? parsed.session : parsed;
|
|
9
|
+
if (!session || typeof session !== 'object' || Array.isArray(session)) {
|
|
10
|
+
throw new Error('No session found in file. Expected a JBrowse session export (a JSON file with a top-level "session" key).');
|
|
11
|
+
}
|
|
12
|
+
const { id: _id, ...rest } = session;
|
|
13
|
+
return rest;
|
|
14
|
+
}
|
package/esm/index.js
CHANGED
|
@@ -5,14 +5,12 @@ import InfoIcon from '@mui/icons-material/Info';
|
|
|
5
5
|
import AboutWidgetF from './AboutWidget/index.js';
|
|
6
6
|
import HelpWidgetF from './HelpWidget/index.js';
|
|
7
7
|
import ImportSessionWidgetF from './ImportSessionWidget/index.js';
|
|
8
|
-
import SessionManagerF from './SessionManager/index.js';
|
|
9
8
|
export default class MenusPlugin extends Plugin {
|
|
10
9
|
name = 'MenusPlugin';
|
|
11
10
|
install(pluginManager) {
|
|
12
11
|
AboutWidgetF(pluginManager);
|
|
13
12
|
HelpWidgetF(pluginManager);
|
|
14
13
|
ImportSessionWidgetF(pluginManager);
|
|
15
|
-
SessionManagerF(pluginManager);
|
|
16
14
|
}
|
|
17
15
|
configure(pluginManager) {
|
|
18
16
|
if (isAbstractMenuManager(pluginManager.rootModel)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-menus",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 basic menus",
|
|
6
6
|
"keywords": [
|
|
@@ -21,15 +21,14 @@
|
|
|
21
21
|
"esm"
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@jbrowse/mobx-state-tree": "^6.
|
|
25
|
-
"@mui/icons-material": "^9.
|
|
26
|
-
"@mui/material": "^9.
|
|
27
|
-
"@mui/system": "^9.
|
|
28
|
-
"
|
|
29
|
-
"mobx": "^7.0.5",
|
|
24
|
+
"@jbrowse/mobx-state-tree": "^6.9.0",
|
|
25
|
+
"@mui/icons-material": "^9.5.0",
|
|
26
|
+
"@mui/material": "^9.5.0",
|
|
27
|
+
"@mui/system": "^9.5.0",
|
|
28
|
+
"mobx": "^7.0.6",
|
|
30
29
|
"mobx-react": "^10.0.2",
|
|
31
|
-
"@jbrowse/core": "^5.0.0-beta.
|
|
32
|
-
"@jbrowse/
|
|
30
|
+
"@jbrowse/render-core": "^5.0.0-beta.14",
|
|
31
|
+
"@jbrowse/core": "^5.0.0-beta.14"
|
|
33
32
|
},
|
|
34
33
|
"peerDependencies": {
|
|
35
34
|
"react": ">=19.0.0",
|
|
@@ -1,12 +0,0 @@
|
|
|
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;
|
|
@@ -1,36 +0,0 @@
|
|
|
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,130 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
|
-
import { LabeledCheckbox } from '@jbrowse/core/ui';
|
|
4
|
-
import DataGridFlexContainer from '@jbrowse/core/ui/DataGridFlexContainer';
|
|
5
|
-
import { useLocalStorage } from '@jbrowse/core/util/hooks';
|
|
6
|
-
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
7
|
-
import { Button, Typography } from '@mui/material';
|
|
8
|
-
import { DataGrid } from '@mui/x-data-grid';
|
|
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';
|
|
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
|
-
},
|
|
31
|
-
mb: {
|
|
32
|
-
margin: theme.spacing(1),
|
|
33
|
-
marginBottom: theme.spacing(4),
|
|
34
|
-
},
|
|
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
|
-
}
|
|
109
|
-
const SessionManager = observer(function SessionManager({ session, }) {
|
|
110
|
-
const { classes } = useStyles();
|
|
111
|
-
const [deleteOldOpen, setDeleteOldOpen] = useState(false);
|
|
112
|
-
const [showOnlyFavs, setShowOnlyFavs] = useLocalStorage('sessionManager-showOnlyFavs', false);
|
|
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] }));
|
|
129
|
-
});
|
|
130
|
-
export default SessionManager;
|
|
@@ -1,32 +0,0 @@
|
|
|
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 {};
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import type { AbstractSessionModel } from '@jbrowse/core/util';
|
|
2
|
-
export interface SessionMetadata {
|
|
3
|
-
id: string;
|
|
4
|
-
name: string;
|
|
5
|
-
createdAt: Date;
|
|
6
|
-
updatedAt?: Date;
|
|
7
|
-
configPath: string;
|
|
8
|
-
favorite: boolean;
|
|
9
|
-
}
|
|
10
|
-
export declare function sessionLastUsed(m: SessionMetadata): Date;
|
|
11
|
-
export interface SessionModel extends AbstractSessionModel {
|
|
12
|
-
savedSessionMetadata?: SessionMetadata[];
|
|
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>;
|
|
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,28 +0,0 @@
|
|
|
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 +0,0 @@
|
|
|
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, undefined, import("@jbrowse/core/configuration").ConfigurationSchemaRequirement<Partial<Record<string, string[]>>, string>, any>>;
|
|
3
|
-
export declare const stateModel: import("@jbrowse/mobx-state-tree").IModelType<{
|
|
4
|
-
id: import("@jbrowse/mobx-state-tree").IOptionalIType<import("@jbrowse/mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
5
|
-
type: import("@jbrowse/mobx-state-tree").ILiteralType<"SessionManager">;
|
|
6
|
-
}, {}, import("@jbrowse/mobx-state-tree")._NotCustomized, import("@jbrowse/mobx-state-tree")._NotCustomized>;
|
|
7
|
-
export default function SessionManagerF(pluginManager: PluginManager): void;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ConfigurationSchema } from '@jbrowse/core/configuration';
|
|
2
|
-
import { WidgetType } from '@jbrowse/core/pluggableElementTypes';
|
|
3
|
-
import { lazyWithPreload } from '@jbrowse/core/util/lazyWithPreload';
|
|
4
|
-
import { ElementId } from '@jbrowse/core/util/types/mst';
|
|
5
|
-
import { types } from '@jbrowse/mobx-state-tree';
|
|
6
|
-
export const configSchema = ConfigurationSchema('SessionManager', {});
|
|
7
|
-
export const stateModel = types.model('SessionManager', {
|
|
8
|
-
id: ElementId,
|
|
9
|
-
type: types.literal('SessionManager'),
|
|
10
|
-
});
|
|
11
|
-
export default function SessionManagerF(pluginManager) {
|
|
12
|
-
pluginManager.addWidgetType(() => {
|
|
13
|
-
return new WidgetType({
|
|
14
|
-
name: 'SessionManager',
|
|
15
|
-
discardOnClose: true,
|
|
16
|
-
heading: 'Recent sessions',
|
|
17
|
-
configSchema,
|
|
18
|
-
stateModel,
|
|
19
|
-
ReactComponent: lazyWithPreload(() => import('./components/SessionManager.js')),
|
|
20
|
-
});
|
|
21
|
-
});
|
|
22
|
-
}
|