@jbrowse/plugin-data-management 2.6.2 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/HierarchicalTrackSelectorWidget/components/faceted/FacetedSelector.js +4 -5
- package/dist/HierarchicalTrackSelectorWidget/components/tree/HamburgerMenu.js +35 -19
- package/dist/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.js +10 -6
- package/dist/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.d.ts +2 -1
- package/dist/HierarchicalTrackSelectorWidget/components/tree/TrackCategory.js +22 -0
- package/dist/HierarchicalTrackSelectorWidget/components/tree/TrackLabel.js +1 -1
- package/dist/HierarchicalTrackSelectorWidget/components/util.d.ts +2 -1
- package/dist/HierarchicalTrackSelectorWidget/filterTracks.d.ts +12 -0
- package/dist/HierarchicalTrackSelectorWidget/filterTracks.js +32 -0
- package/dist/HierarchicalTrackSelectorWidget/generateHierarchy.d.ts +21 -0
- package/dist/HierarchicalTrackSelectorWidget/generateHierarchy.js +98 -0
- package/dist/HierarchicalTrackSelectorWidget/model.d.ts +68 -28
- package/dist/HierarchicalTrackSelectorWidget/model.js +149 -117
- package/dist/HierarchicalTrackSelectorWidget/util.d.ts +12 -0
- package/dist/HierarchicalTrackSelectorWidget/util.js +53 -0
- package/dist/PluginStoreWidget/components/AddCustomPluginDialog.d.ts +7 -0
- package/dist/PluginStoreWidget/components/{CustomPluginForm.js → AddCustomPluginDialog.js} +12 -21
- package/dist/PluginStoreWidget/components/DeletePluginDialog.d.ts +5 -0
- package/dist/PluginStoreWidget/components/DeletePluginDialog.js +28 -0
- package/dist/PluginStoreWidget/components/InstalledPlugin.d.ts +2 -5
- package/dist/PluginStoreWidget/components/InstalledPlugin.js +19 -50
- package/dist/PluginStoreWidget/components/InstalledPluginsList.d.ts +2 -3
- package/dist/PluginStoreWidget/components/InstalledPluginsList.js +6 -9
- package/dist/PluginStoreWidget/components/PluginCard.js +3 -5
- package/dist/PluginStoreWidget/components/PluginStoreWidget.js +11 -39
- package/dist/PluginStoreWidget/components/util.d.ts +5 -0
- package/dist/PluginStoreWidget/components/util.js +29 -0
- package/dist/PluginStoreWidget/model.d.ts +2 -2
- package/dist/PluginStoreWidget/model.js +2 -2
- package/esm/HierarchicalTrackSelectorWidget/components/faceted/FacetedSelector.js +1 -2
- package/esm/HierarchicalTrackSelectorWidget/components/tree/HamburgerMenu.js +34 -18
- package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.js +10 -6
- package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.d.ts +2 -1
- package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackCategory.js +22 -0
- package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackLabel.js +1 -1
- package/esm/HierarchicalTrackSelectorWidget/components/util.d.ts +2 -1
- package/esm/HierarchicalTrackSelectorWidget/filterTracks.d.ts +12 -0
- package/esm/HierarchicalTrackSelectorWidget/filterTracks.js +28 -0
- package/esm/HierarchicalTrackSelectorWidget/generateHierarchy.d.ts +21 -0
- package/esm/HierarchicalTrackSelectorWidget/generateHierarchy.js +94 -0
- package/esm/HierarchicalTrackSelectorWidget/model.d.ts +68 -28
- package/esm/HierarchicalTrackSelectorWidget/model.js +151 -116
- package/esm/HierarchicalTrackSelectorWidget/util.d.ts +12 -0
- package/esm/HierarchicalTrackSelectorWidget/util.js +45 -0
- package/esm/PluginStoreWidget/components/AddCustomPluginDialog.d.ts +7 -0
- package/esm/PluginStoreWidget/components/{CustomPluginForm.js → AddCustomPluginDialog.js} +12 -21
- package/esm/PluginStoreWidget/components/DeletePluginDialog.d.ts +5 -0
- package/esm/PluginStoreWidget/components/DeletePluginDialog.js +22 -0
- package/esm/PluginStoreWidget/components/InstalledPlugin.d.ts +2 -5
- package/esm/PluginStoreWidget/components/InstalledPlugin.js +22 -53
- package/esm/PluginStoreWidget/components/InstalledPluginsList.d.ts +2 -3
- package/esm/PluginStoreWidget/components/InstalledPluginsList.js +6 -9
- package/esm/PluginStoreWidget/components/PluginCard.js +3 -5
- package/esm/PluginStoreWidget/components/PluginStoreWidget.js +12 -40
- package/esm/PluginStoreWidget/components/util.d.ts +5 -0
- package/esm/PluginStoreWidget/components/util.js +25 -0
- package/esm/PluginStoreWidget/model.d.ts +2 -2
- package/esm/PluginStoreWidget/model.js +1 -1
- package/package.json +2 -2
- package/dist/PluginStoreWidget/components/CustomPluginForm.d.ts +0 -9
- package/esm/PluginStoreWidget/components/CustomPluginForm.d.ts +0 -9
|
@@ -7,14 +7,11 @@ const react_1 = __importDefault(require("react"));
|
|
|
7
7
|
const mobx_react_1 = require("mobx-react");
|
|
8
8
|
const material_1 = require("@mui/material");
|
|
9
9
|
const InstalledPlugin_1 = __importDefault(require("./InstalledPlugin"));
|
|
10
|
-
function InstalledPluginsList({ pluginManager, model, }) {
|
|
10
|
+
exports.default = (0, mobx_react_1.observer)(function InstalledPluginsList({ pluginManager, model, }) {
|
|
11
11
|
const { plugins } = pluginManager;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
.map(p => p.name));
|
|
15
|
-
const externalPlugins = plugins.filter(plugin => !corePlugins.has(plugin.name));
|
|
12
|
+
const { filterText } = model;
|
|
13
|
+
const externalPlugins = plugins.filter(p => { var _a; return !((_a = pluginManager.pluginMetadata[p.name]) === null || _a === void 0 ? void 0 : _a.isCore); });
|
|
16
14
|
return (react_1.default.createElement(material_1.List, null, externalPlugins.length > 0 ? (externalPlugins
|
|
17
|
-
.filter(
|
|
18
|
-
.map(
|
|
19
|
-
}
|
|
20
|
-
exports.default = (0, mobx_react_1.observer)(InstalledPluginsList);
|
|
15
|
+
.filter(p => p.name.toLowerCase().includes(filterText.toLowerCase()))
|
|
16
|
+
.map(p => react_1.default.createElement(InstalledPlugin_1.default, { key: p.name, plugin: p, model: model }))) : (react_1.default.createElement(material_1.Typography, null, "No plugins currently installed"))));
|
|
17
|
+
});
|
|
@@ -39,7 +39,7 @@ const Add_1 = __importDefault(require("@mui/icons-material/Add"));
|
|
|
39
39
|
const Check_1 = __importDefault(require("@mui/icons-material/Check"));
|
|
40
40
|
const useStyles = (0, mui_1.makeStyles)()({
|
|
41
41
|
card: {
|
|
42
|
-
margin: '
|
|
42
|
+
margin: '0.5em',
|
|
43
43
|
},
|
|
44
44
|
icon: {
|
|
45
45
|
marginLeft: '0.5em',
|
|
@@ -51,7 +51,6 @@ const useStyles = (0, mui_1.makeStyles)()({
|
|
|
51
51
|
dataField: {
|
|
52
52
|
display: 'flex',
|
|
53
53
|
alignItems: 'center',
|
|
54
|
-
margin: '0.4em 0em',
|
|
55
54
|
},
|
|
56
55
|
});
|
|
57
56
|
exports.default = (0, mobx_react_1.observer)(function PluginCard({ plugin, model, adminMode, }) {
|
|
@@ -67,9 +66,8 @@ exports.default = (0, mobx_react_1.observer)(function PluginCard({ plugin, model
|
|
|
67
66
|
const { jbrowse } = rootModel;
|
|
68
67
|
return (react_1.default.createElement(material_1.Card, { variant: "outlined", key: plugin.name, className: classes.card },
|
|
69
68
|
react_1.default.createElement(material_1.CardContent, null,
|
|
70
|
-
react_1.default.createElement(
|
|
71
|
-
react_1.default.createElement(material_1.
|
|
72
|
-
react_1.default.createElement(material_1.Link, { href: `${plugin.location}#readme`, target: "_blank", rel: "noopener" }, plugin.name))),
|
|
69
|
+
react_1.default.createElement(material_1.Typography, { variant: "h5" },
|
|
70
|
+
react_1.default.createElement(material_1.Link, { href: `${plugin.location}#readme`, target: "_blank", rel: "noopener" }, plugin.name)),
|
|
73
71
|
react_1.default.createElement("div", { className: classes.dataField },
|
|
74
72
|
react_1.default.createElement(Person_1.default, { style: { marginRight: '0.5em' } }),
|
|
75
73
|
react_1.default.createElement(material_1.Typography, null, plugin.authors.join(', '))),
|
|
@@ -40,16 +40,14 @@ const InfoOutlined_1 = __importDefault(require("@mui/icons-material/InfoOutlined
|
|
|
40
40
|
// locals
|
|
41
41
|
const InstalledPluginsList_1 = __importDefault(require("./InstalledPluginsList"));
|
|
42
42
|
const PluginCard_1 = __importDefault(require("./PluginCard"));
|
|
43
|
-
const
|
|
43
|
+
const util_2 = require("./util");
|
|
44
|
+
// lazies
|
|
45
|
+
const AddCustomPluginDialog = (0, react_1.lazy)(() => Promise.resolve().then(() => __importStar(require('./AddCustomPluginDialog'))));
|
|
44
46
|
const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
45
|
-
root: {
|
|
46
|
-
margin: theme.spacing(1),
|
|
47
|
-
},
|
|
48
47
|
expandIcon: {
|
|
49
48
|
color: theme.palette.tertiary.contrastText,
|
|
50
49
|
},
|
|
51
50
|
adminBadge: {
|
|
52
|
-
margin: '0.5em',
|
|
53
51
|
borderRadius: 3,
|
|
54
52
|
backgroundColor: theme.palette.quaternary.main,
|
|
55
53
|
padding: '1em',
|
|
@@ -57,43 +55,17 @@ const useStyles = (0, mui_1.makeStyles)()(theme => ({
|
|
|
57
55
|
alignContent: 'center',
|
|
58
56
|
},
|
|
59
57
|
customPluginButton: {
|
|
60
|
-
margin: '
|
|
58
|
+
margin: '1em auto',
|
|
61
59
|
display: 'flex',
|
|
62
|
-
justifyContent: 'center',
|
|
63
60
|
},
|
|
64
61
|
}));
|
|
65
62
|
function PluginStoreWidget({ model }) {
|
|
66
63
|
const { classes } = useStyles();
|
|
67
|
-
const
|
|
68
|
-
const [
|
|
69
|
-
const [customPluginFormOpen, setCustomPluginFormOpen] = (0, react_1.useState)(false);
|
|
64
|
+
const { plugins, error } = (0, util_2.useFetchPlugins)();
|
|
65
|
+
const [open, setOpen] = (0, react_1.useState)(false);
|
|
70
66
|
const { adminMode } = (0, util_1.getSession)(model);
|
|
71
67
|
const { pluginManager } = (0, mobx_state_tree_1.getEnv)(model);
|
|
72
|
-
(
|
|
73
|
-
const controller = new AbortController();
|
|
74
|
-
const { signal } = controller;
|
|
75
|
-
(async () => {
|
|
76
|
-
try {
|
|
77
|
-
const response = await fetch('https://jbrowse.org/plugin-store/plugins.json', { signal });
|
|
78
|
-
if (!response.ok) {
|
|
79
|
-
const err = await response.text();
|
|
80
|
-
throw new Error(`Failed to fetch plugin data: ${response.status} ${response.statusText} ${err}`);
|
|
81
|
-
}
|
|
82
|
-
const array = await response.json();
|
|
83
|
-
if (!signal.aborted) {
|
|
84
|
-
setPluginArray(array.plugins);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
console.error(e);
|
|
89
|
-
setError(e);
|
|
90
|
-
}
|
|
91
|
-
})();
|
|
92
|
-
return () => {
|
|
93
|
-
controller.abort();
|
|
94
|
-
};
|
|
95
|
-
}, []);
|
|
96
|
-
return (react_1.default.createElement("div", { className: classes.root },
|
|
68
|
+
return (react_1.default.createElement("div", null,
|
|
97
69
|
adminMode && (react_1.default.createElement(react_1.default.Fragment, null,
|
|
98
70
|
!util_1.isElectron && (react_1.default.createElement("div", { className: classes.adminBadge },
|
|
99
71
|
react_1.default.createElement(InfoOutlined_1.default, { style: { marginRight: '0.3em' } }),
|
|
@@ -101,9 +73,9 @@ function PluginStoreWidget({ model }) {
|
|
|
101
73
|
"You are using the ",
|
|
102
74
|
react_1.default.createElement("code", null, "admin-server"),
|
|
103
75
|
". Any changes you make will be saved to your configuration file. You also have the ability to add custom plugins that are not in the store."))),
|
|
104
|
-
react_1.default.createElement(
|
|
105
|
-
|
|
106
|
-
|
|
76
|
+
react_1.default.createElement(material_1.Button, { className: classes.customPluginButton, variant: "contained", onClick: () => setOpen(true) }, "Add custom plugin"),
|
|
77
|
+
open ? (react_1.default.createElement(react_1.Suspense, { fallback: react_1.default.createElement(react_1.default.Fragment, null) },
|
|
78
|
+
react_1.default.createElement(AddCustomPluginDialog, { onClose: () => setOpen(false), model: model }))) : null)),
|
|
107
79
|
react_1.default.createElement(material_1.TextField, { label: "Filter plugins", value: model.filterText, onChange: event => model.setFilterText(event.target.value), fullWidth: true, InputProps: {
|
|
108
80
|
endAdornment: (react_1.default.createElement(material_1.InputAdornment, { position: "end" },
|
|
109
81
|
react_1.default.createElement(material_1.IconButton, { onClick: () => model.clearFilterText() },
|
|
@@ -117,7 +89,7 @@ function PluginStoreWidget({ model }) {
|
|
|
117
89
|
react_1.default.createElement(material_1.Accordion, { defaultExpanded: true },
|
|
118
90
|
react_1.default.createElement(material_1.AccordionSummary, { expandIcon: react_1.default.createElement(ExpandMore_1.default, { className: classes.expandIcon }) },
|
|
119
91
|
react_1.default.createElement(material_1.Typography, { variant: "h5" }, "Available plugins")),
|
|
120
|
-
error ? (react_1.default.createElement(material_1.Typography, { color: "error" }, `${error}`)) :
|
|
92
|
+
error ? (react_1.default.createElement(material_1.Typography, { color: "error" }, `${error}`)) : plugins ? (plugins
|
|
121
93
|
.filter(plugin => {
|
|
122
94
|
// If plugin only has cjsUrl, don't display outside desktop
|
|
123
95
|
return (!(util_1.isElectron && plugin.cjsUrl) &&
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useFetchPlugins = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
function useFetchPlugins() {
|
|
6
|
+
const [plugins, setPlugins] = (0, react_1.useState)();
|
|
7
|
+
const [error, setError] = (0, react_1.useState)();
|
|
8
|
+
(0, react_1.useEffect)(() => {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
10
|
+
;
|
|
11
|
+
(async () => {
|
|
12
|
+
try {
|
|
13
|
+
const res = await fetch('https://jbrowse.org/plugin-store/plugins.json');
|
|
14
|
+
if (!res.ok) {
|
|
15
|
+
const err = await res.text();
|
|
16
|
+
throw new Error(`HTTP ${res.status} fetching plugins: ${err}`);
|
|
17
|
+
}
|
|
18
|
+
const array = await res.json();
|
|
19
|
+
setPlugins(array.plugins);
|
|
20
|
+
}
|
|
21
|
+
catch (e) {
|
|
22
|
+
console.error(e);
|
|
23
|
+
setError(e);
|
|
24
|
+
}
|
|
25
|
+
})();
|
|
26
|
+
}, []);
|
|
27
|
+
return { plugins, error };
|
|
28
|
+
}
|
|
29
|
+
exports.useFetchPlugins = useFetchPlugins;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Instance } from 'mobx-state-tree';
|
|
2
2
|
import PluginManager from '@jbrowse/core/PluginManager';
|
|
3
|
-
export default function
|
|
3
|
+
export default function stateModelFactory(pluginManager: PluginManager): import("mobx-state-tree").IModelType<{
|
|
4
4
|
id: import("mobx-state-tree").IOptionalIType<import("mobx-state-tree").ISimpleType<string>, [undefined]>;
|
|
5
5
|
type: import("mobx-state-tree").ISimpleType<"PluginStoreWidget">;
|
|
6
6
|
filterText: import("mobx-state-tree").IType<string | undefined, string, string>;
|
|
@@ -9,5 +9,5 @@ export default function f(pluginManager: PluginManager): import("mobx-state-tree
|
|
|
9
9
|
clearFilterText(): void;
|
|
10
10
|
setFilterText(newText: string): void;
|
|
11
11
|
}, import("mobx-state-tree")._NotCustomized, import("mobx-state-tree")._NotCustomized>;
|
|
12
|
-
export type PluginStoreStateModel = ReturnType<typeof
|
|
12
|
+
export type PluginStoreStateModel = ReturnType<typeof stateModelFactory>;
|
|
13
13
|
export type PluginStoreModel = Instance<PluginStoreStateModel>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const mobx_state_tree_1 = require("mobx-state-tree");
|
|
4
4
|
const mst_1 = require("@jbrowse/core/util/types/mst");
|
|
5
|
-
function
|
|
5
|
+
function stateModelFactory(pluginManager) {
|
|
6
6
|
return mobx_state_tree_1.types
|
|
7
7
|
.model('PluginStoreModel', {
|
|
8
8
|
id: mst_1.ElementId,
|
|
@@ -19,4 +19,4 @@ function f(pluginManager) {
|
|
|
19
19
|
},
|
|
20
20
|
}));
|
|
21
21
|
}
|
|
22
|
-
exports.default =
|
|
22
|
+
exports.default = stateModelFactory;
|
|
@@ -14,8 +14,7 @@ import { getEnv, getSession, measureGridWidth, useDebounce, } from '@jbrowse/cor
|
|
|
14
14
|
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
15
15
|
// icons
|
|
16
16
|
import MoreHoriz from '@mui/icons-material/MoreHoriz';
|
|
17
|
-
|
|
18
|
-
import { matches } from '../../model';
|
|
17
|
+
import { matches } from '../../util';
|
|
19
18
|
import FacetedHeader from './FacetedHeader';
|
|
20
19
|
import FacetFilters from './FacetFilters';
|
|
21
20
|
import { getRootKeys } from './util';
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import React, { Suspense, lazy, useState } from 'react';
|
|
2
|
-
import { IconButton } from '@mui/material';
|
|
3
|
-
import { makeStyles } from 'tss-react/mui';
|
|
4
2
|
import { observer } from 'mobx-react';
|
|
5
|
-
import JBrowseMenu from '@jbrowse/core/ui/Menu';
|
|
6
3
|
import { getSession, isSessionModelWithConnectionEditing, isSessionModelWithConnections, isSessionModelWithWidgets, isSessionWithAddTracks, } from '@jbrowse/core/util';
|
|
7
4
|
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
5
|
+
import CascadingMenuButton from '@jbrowse/core/ui/CascadingMenuButton';
|
|
8
6
|
// icons
|
|
9
7
|
import MenuIcon from '@mui/icons-material/Menu';
|
|
10
8
|
// lazy components
|
|
@@ -12,20 +10,12 @@ const CloseConnectionDlg = lazy(() => import('../dialogs/CloseConnectionDialog')
|
|
|
12
10
|
const DeleteConnectionDlg = lazy(() => import('../dialogs/DeleteConnectionDialog'));
|
|
13
11
|
const ManageConnectionsDlg = lazy(() => import('../dialogs/ManageConnectionsDialog'));
|
|
14
12
|
const ToggleConnectionsDlg = lazy(() => import('../dialogs/ToggleConnectionsDialog'));
|
|
15
|
-
const useStyles = makeStyles()(theme => ({
|
|
16
|
-
menuIcon: {
|
|
17
|
-
marginRight: theme.spacing(1),
|
|
18
|
-
marginBottom: 0,
|
|
19
|
-
},
|
|
20
|
-
}));
|
|
21
13
|
export default observer(function HamburgerMenu({ model, }) {
|
|
22
14
|
const session = getSession(model);
|
|
23
|
-
const [menuEl, setMenuEl] = useState();
|
|
24
15
|
const [modalInfo, setModalInfo] = useState();
|
|
25
16
|
const [deleteDlgDetails, setDeleteDlgDetails] = useState();
|
|
26
17
|
const [connectionToggleOpen, setConnectionToggleOpen] = useState(false);
|
|
27
18
|
const [connectionManagerOpen, setConnectionManagerOpen] = useState(false);
|
|
28
|
-
const { classes } = useStyles();
|
|
29
19
|
function breakConnection(connectionConf, deletingConnection) {
|
|
30
20
|
const name = readConfObject(connectionConf, 'name');
|
|
31
21
|
const result = session.prepareToBreakConnection(connectionConf);
|
|
@@ -48,12 +38,7 @@ export default observer(function HamburgerMenu({ model, }) {
|
|
|
48
38
|
}
|
|
49
39
|
}
|
|
50
40
|
return (React.createElement(React.Fragment, null,
|
|
51
|
-
React.createElement(
|
|
52
|
-
React.createElement(MenuIcon, null)),
|
|
53
|
-
React.createElement(JBrowseMenu, { anchorEl: menuEl, open: Boolean(menuEl), onMenuItemClick: (_, callback) => {
|
|
54
|
-
callback();
|
|
55
|
-
setMenuEl(undefined);
|
|
56
|
-
}, onClose: () => setMenuEl(undefined), menuItems: [
|
|
41
|
+
React.createElement(CascadingMenuButton, { menuItems: [
|
|
57
42
|
...(isSessionWithAddTracks(session)
|
|
58
43
|
? [
|
|
59
44
|
{
|
|
@@ -92,7 +77,38 @@ export default observer(function HamburgerMenu({ model, }) {
|
|
|
92
77
|
},
|
|
93
78
|
]
|
|
94
79
|
: []),
|
|
95
|
-
|
|
80
|
+
{ type: 'divider' },
|
|
81
|
+
{
|
|
82
|
+
label: 'Sort tracks by name',
|
|
83
|
+
type: 'checkbox',
|
|
84
|
+
checked: model.activeSortTrackNames,
|
|
85
|
+
onClick: () => model.setSortTrackNames(!model.activeSortTrackNames),
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
label: 'Sort categories by name',
|
|
89
|
+
type: 'checkbox',
|
|
90
|
+
checked: model.activeSortCategories,
|
|
91
|
+
onClick: () => model.setSortCategories(!model.activeSortCategories),
|
|
92
|
+
},
|
|
93
|
+
{ type: 'divider' },
|
|
94
|
+
...(model.hasAnySubcategories
|
|
95
|
+
? [
|
|
96
|
+
{
|
|
97
|
+
label: 'Collapse subcategories',
|
|
98
|
+
onClick: () => model.collapseSubCategories(),
|
|
99
|
+
},
|
|
100
|
+
]
|
|
101
|
+
: []),
|
|
102
|
+
{
|
|
103
|
+
label: 'Collapse top-level categories',
|
|
104
|
+
onClick: () => model.collapseTopLevelCategories(),
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
label: 'Expand all categories',
|
|
108
|
+
onClick: () => model.expandAllCategories(),
|
|
109
|
+
},
|
|
110
|
+
] },
|
|
111
|
+
React.createElement(MenuIcon, null)),
|
|
96
112
|
React.createElement(Suspense, { fallback: React.createElement(React.Fragment, null) },
|
|
97
113
|
modalInfo ? (React.createElement(CloseConnectionDlg, { modalInfo: modalInfo, onClose: () => setModalInfo(undefined) })) : null,
|
|
98
114
|
deleteDlgDetails ? (React.createElement(DeleteConnectionDlg, { handleClose: () => setDeleteDlgDetails(undefined), deleteDialogDetails: deleteDlgDetails, session: session })) : null,
|
|
@@ -17,19 +17,23 @@ const useStyles = makeStyles()(theme => ({
|
|
|
17
17
|
marginBottom: 0,
|
|
18
18
|
},
|
|
19
19
|
}));
|
|
20
|
+
const SearchTracksTextField = observer(function ({ model, }) {
|
|
21
|
+
const { filterText } = model;
|
|
22
|
+
const { classes } = useStyles();
|
|
23
|
+
return (React.createElement(TextField, { className: classes.searchBox, label: "Filter tracks", value: filterText, onChange: event => model.setFilterText(event.target.value), fullWidth: true, InputProps: {
|
|
24
|
+
endAdornment: (React.createElement(InputAdornment, { position: "end" },
|
|
25
|
+
React.createElement(IconButton, { onClick: () => model.clearFilterText() },
|
|
26
|
+
React.createElement(ClearIcon, null)))),
|
|
27
|
+
} }));
|
|
28
|
+
});
|
|
20
29
|
function HierarchicalTrackSelectorHeader({ model, setHeaderHeight, }) {
|
|
21
30
|
const { classes } = useStyles();
|
|
22
31
|
const [facetedOpen, setFacetedOpen] = useState(false);
|
|
23
|
-
const { filterText } = model;
|
|
24
32
|
return (React.createElement("div", { ref: ref => setHeaderHeight((ref === null || ref === void 0 ? void 0 : ref.getBoundingClientRect().height) || 0), "data-testid": "hierarchical_track_selector" },
|
|
25
33
|
React.createElement("div", { style: { display: 'flex' } },
|
|
26
34
|
React.createElement(HamburgerMenu, { model: model }),
|
|
27
35
|
React.createElement(ShoppingCart, { model: model }),
|
|
28
|
-
React.createElement(
|
|
29
|
-
endAdornment: (React.createElement(InputAdornment, { position: "end" },
|
|
30
|
-
React.createElement(IconButton, { onClick: () => model.clearFilterText() },
|
|
31
|
-
React.createElement(ClearIcon, null)))),
|
|
32
|
-
} }),
|
|
36
|
+
React.createElement(SearchTracksTextField, { model: model }),
|
|
33
37
|
React.createElement(Button, { className: classes.menuIcon, onClick: () => setFacetedOpen(true) }, "Open faceted selector")),
|
|
34
38
|
React.createElement(Suspense, { fallback: React.createElement("div", null) }, facetedOpen ? (React.createElement(FacetedDialog, { handleClose: () => setFacetedOpen(false), model: model })) : null)));
|
|
35
39
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { TreeNode
|
|
2
|
+
import { TreeNode } from '../../generateHierarchy';
|
|
3
|
+
import { HierarchicalTrackSelectorModel } from '../../model';
|
|
3
4
|
declare const _default: ({ height, tree, model, }: {
|
|
4
5
|
height: number;
|
|
5
6
|
tree: TreeNode;
|
|
@@ -52,6 +52,28 @@ export default function Category({ isOpen, setOpen, data, }) {
|
|
|
52
52
|
model.removeFromSelection(getAllChildren(r));
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
label: 'Show all tracks',
|
|
57
|
+
onClick: () => {
|
|
58
|
+
var _a;
|
|
59
|
+
for (const entry of ((_a = treeToMap(tree).get(id)) === null || _a === void 0 ? void 0 : _a.children) || []) {
|
|
60
|
+
if (!entry.children.length) {
|
|
61
|
+
model.view.showTrack(entry.id);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
label: 'Hide all tracks',
|
|
68
|
+
onClick: () => {
|
|
69
|
+
var _a;
|
|
70
|
+
for (const entry of ((_a = treeToMap(tree).get(id)) === null || _a === void 0 ? void 0 : _a.children) || []) {
|
|
71
|
+
if (!entry.children.length) {
|
|
72
|
+
model.view.hideTrack(entry.id);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
},
|
|
55
77
|
], onMenuItemClick: (_event, callback) => {
|
|
56
78
|
callback();
|
|
57
79
|
setMenuEl(null);
|
|
@@ -31,7 +31,7 @@ export default function TrackLabel({ data }) {
|
|
|
31
31
|
React.createElement(FormControlLabel, { className: classes.checkboxLabel, control: React.createElement(Checkbox, { className: classes.compactCheckbox, checked: checked, onChange: () => onChange(id), disabled: isUnsupported(name), inputProps: {
|
|
32
32
|
// @ts-expect-error
|
|
33
33
|
'data-testid': `htsTrackEntry-${id}`,
|
|
34
|
-
} }), label: React.createElement("div", { style: { background: selected ? '#cccc' : undefined } },
|
|
34
|
+
} }), label: React.createElement("div", { "data-testid": `htsTrackLabel-${id}`, style: { background: selected ? '#cccc' : undefined } },
|
|
35
35
|
React.createElement(SanitizedHTML, { html: name })) })),
|
|
36
36
|
React.createElement(IconButton, { onClick: e => setInfo({ target: e.currentTarget, id, conf }), style: { padding: 0 }, "data-testid": `htsTrackEntryMenu-${id}` },
|
|
37
37
|
React.createElement(MoreHorizIcon, null)),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
|
-
import { HierarchicalTrackSelectorModel
|
|
2
|
+
import { HierarchicalTrackSelectorModel } from '../model';
|
|
3
|
+
import { TreeNode } from '../generateHierarchy';
|
|
3
4
|
export interface NodeData {
|
|
4
5
|
nestingLevel: number;
|
|
5
6
|
checked: boolean;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
|
+
export declare function filterTracks(tracks: AnyConfigurationModel[], self: {
|
|
3
|
+
view?: {
|
|
4
|
+
type: string;
|
|
5
|
+
trackSelectorAnyOverlap?: boolean;
|
|
6
|
+
};
|
|
7
|
+
assemblyNames: string[];
|
|
8
|
+
}): ({
|
|
9
|
+
[x: string]: any;
|
|
10
|
+
} & import("mobx-state-tree/dist/internal").NonEmptyObject & {
|
|
11
|
+
setSubschema(slotName: string, data: unknown): any;
|
|
12
|
+
} & import("mobx-state-tree").IStateTreeNode<import("@jbrowse/core/configuration").AnyConfigurationSchemaType>)[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
2
|
+
import { getEnv, getSession, notEmpty } from '@jbrowse/core/util';
|
|
3
|
+
import { hasAllOverlap, hasAnyOverlap } from './util';
|
|
4
|
+
export function filterTracks(tracks, self) {
|
|
5
|
+
const { assemblyManager } = getSession(self);
|
|
6
|
+
const { pluginManager } = getEnv(self);
|
|
7
|
+
const { view } = self;
|
|
8
|
+
if (!view) {
|
|
9
|
+
return [];
|
|
10
|
+
}
|
|
11
|
+
const trackListAssemblies = self.assemblyNames
|
|
12
|
+
.map(a => assemblyManager.get(a))
|
|
13
|
+
.filter(notEmpty);
|
|
14
|
+
return tracks
|
|
15
|
+
.filter(c => {
|
|
16
|
+
const trackAssemblyNames = readConfObject(c, 'assemblyNames');
|
|
17
|
+
const trackAssemblies = trackAssemblyNames === null || trackAssemblyNames === void 0 ? void 0 : trackAssemblyNames.map(name => assemblyManager.get(name)).filter(notEmpty);
|
|
18
|
+
return view.trackSelectorAnyOverlap
|
|
19
|
+
? hasAnyOverlap(trackAssemblies, trackListAssemblies)
|
|
20
|
+
: hasAllOverlap(trackAssemblies, trackListAssemblies);
|
|
21
|
+
})
|
|
22
|
+
.filter(c => {
|
|
23
|
+
const { displayTypes } = pluginManager.getViewType(view.type);
|
|
24
|
+
const compatDisplays = displayTypes.map(d => d.name);
|
|
25
|
+
const trackDisplays = c.displays.map((d) => d.type);
|
|
26
|
+
return hasAnyOverlap(compatDisplays, trackDisplays);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { AnyConfigurationModel } from '@jbrowse/core/configuration';
|
|
2
|
+
export type TreeNode = {
|
|
3
|
+
name: string;
|
|
4
|
+
id: string;
|
|
5
|
+
conf?: AnyConfigurationModel;
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
isOpenByDefault?: boolean;
|
|
8
|
+
children: TreeNode[];
|
|
9
|
+
};
|
|
10
|
+
export declare function generateHierarchy(model: {
|
|
11
|
+
filterText: string;
|
|
12
|
+
activeSortTrackNames: boolean;
|
|
13
|
+
activeSortCategories: boolean;
|
|
14
|
+
view?: {
|
|
15
|
+
tracks: {
|
|
16
|
+
configuration: AnyConfigurationModel;
|
|
17
|
+
}[];
|
|
18
|
+
};
|
|
19
|
+
}, trackConfs: AnyConfigurationModel[], collapsed: {
|
|
20
|
+
get: (arg: string) => boolean | undefined;
|
|
21
|
+
}, extra?: string): TreeNode[];
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { readConfObject, } from '@jbrowse/core/configuration';
|
|
2
|
+
import { getSession } from '@jbrowse/core/util';
|
|
3
|
+
import { getTrackName } from '@jbrowse/core/util/tracks';
|
|
4
|
+
// locals
|
|
5
|
+
import { matches } from './util';
|
|
6
|
+
function sortConfs(confs, sortNames, sortCategories) {
|
|
7
|
+
// uses readConfObject instead of getTrackName so that the undefined
|
|
8
|
+
// reference sequence track is sorted to the top
|
|
9
|
+
const ret = confs.map(c => {
|
|
10
|
+
var _a, _b, _c;
|
|
11
|
+
return [
|
|
12
|
+
c,
|
|
13
|
+
readConfObject(c, 'name'),
|
|
14
|
+
((_a = readConfObject(c, 'category')) === null || _a === void 0 ? void 0 : _a[0]) || '',
|
|
15
|
+
((_b = readConfObject(c, 'category')) === null || _b === void 0 ? void 0 : _b[1]) || '',
|
|
16
|
+
((_c = readConfObject(c, 'category')) === null || _c === void 0 ? void 0 : _c[2]) || '',
|
|
17
|
+
];
|
|
18
|
+
});
|
|
19
|
+
if (sortNames) {
|
|
20
|
+
ret.sort((a, b) => a[1].localeCompare(b[1]));
|
|
21
|
+
}
|
|
22
|
+
if (sortCategories) {
|
|
23
|
+
// sort up to three sub-category levels, harder to code it to go deeper
|
|
24
|
+
// than this and likely rarely used
|
|
25
|
+
ret.sort((a, b) => {
|
|
26
|
+
if (a[2] !== b[2]) {
|
|
27
|
+
return a[2].localeCompare(b[2]);
|
|
28
|
+
}
|
|
29
|
+
else if (a[3] !== b[3]) {
|
|
30
|
+
return a[3].localeCompare(b[3]);
|
|
31
|
+
}
|
|
32
|
+
else if (a[4] !== b[4]) {
|
|
33
|
+
return a[4].localeCompare(b[4]);
|
|
34
|
+
}
|
|
35
|
+
return 0;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return ret.map(a => a[0]);
|
|
39
|
+
}
|
|
40
|
+
export function generateHierarchy(model, trackConfs, collapsed, extra) {
|
|
41
|
+
const hierarchy = { children: [] };
|
|
42
|
+
const { filterText, activeSortTrackNames, activeSortCategories, view } = model;
|
|
43
|
+
if (!view) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const session = getSession(model);
|
|
47
|
+
const viewTracks = view.tracks;
|
|
48
|
+
const confs = trackConfs.filter(conf => matches(filterText, conf, session));
|
|
49
|
+
// uses getConf
|
|
50
|
+
for (const conf of sortConfs(confs, activeSortTrackNames, activeSortCategories)) {
|
|
51
|
+
// copy the categories since this array can be mutated downstream
|
|
52
|
+
const categories = [...(readConfObject(conf, 'category') || [])];
|
|
53
|
+
// hack where if trackId ends with sessionTrack, then push it to a
|
|
54
|
+
// category that starts with a space to force sort to the top
|
|
55
|
+
if (conf.trackId.endsWith('sessionTrack')) {
|
|
56
|
+
categories.unshift(' Session tracks');
|
|
57
|
+
}
|
|
58
|
+
let currLevel = hierarchy;
|
|
59
|
+
// find existing category to put track into or create it
|
|
60
|
+
for (let i = 0; i < categories.length; i++) {
|
|
61
|
+
const category = categories[i];
|
|
62
|
+
const ret = currLevel.children.find(c => c.name === category);
|
|
63
|
+
const id = [extra, categories.slice(0, i + 1).join(',')]
|
|
64
|
+
.filter(f => !!f)
|
|
65
|
+
.join('-');
|
|
66
|
+
if (!ret) {
|
|
67
|
+
const n = {
|
|
68
|
+
children: [],
|
|
69
|
+
name: category,
|
|
70
|
+
id,
|
|
71
|
+
isOpenByDefault: !collapsed.get(id),
|
|
72
|
+
};
|
|
73
|
+
currLevel.children.push(n);
|
|
74
|
+
currLevel = n;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
currLevel = ret;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// uses splice to try to put all leaf nodes above "category nodes" if you
|
|
81
|
+
// change the splice to a simple push and open
|
|
82
|
+
// test_data/test_order/config.json you will see the weirdness
|
|
83
|
+
const r = currLevel.children.findIndex(elt => elt.children.length);
|
|
84
|
+
const idx = r === -1 ? currLevel.children.length : r;
|
|
85
|
+
currLevel.children.splice(idx, 0, {
|
|
86
|
+
id: conf.trackId,
|
|
87
|
+
name: getTrackName(conf, session),
|
|
88
|
+
conf,
|
|
89
|
+
checked: viewTracks.some(f => f.configuration === conf),
|
|
90
|
+
children: [],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return hierarchy.children;
|
|
94
|
+
}
|