@jbrowse/plugin-data-management 4.1.14 → 4.1.15
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/HierarchicalTrackSelectorWidget/components/ClearableSearchField.js +8 -7
- package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalHeader.js +9 -5
- package/esm/HierarchicalTrackSelectorWidget/components/tree/HierarchicalTree.js +34 -21
- package/esm/HierarchicalTrackSelectorWidget/components/tree/SharedTooltip.d.ts +5 -0
- package/esm/HierarchicalTrackSelectorWidget/components/tree/SharedTooltip.js +39 -0
- package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackCategory.js +8 -3
- package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackLabel.d.ts +2 -8
- package/esm/HierarchicalTrackSelectorWidget/components/tree/TrackLabel.js +12 -16
- package/esm/HierarchicalTrackSelectorWidget/components/tree/TreeItem.d.ts +2 -2
- package/esm/HierarchicalTrackSelectorWidget/generateHierarchy.js +1 -0
- package/esm/HierarchicalTrackSelectorWidget/model.js +3 -2
- package/esm/HierarchicalTrackSelectorWidget/types.d.ts +1 -0
- package/package.json +11 -8
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect,
|
|
2
|
+
import { useEffect, useEffectEvent, useState } from 'react';
|
|
3
|
+
import { useDebounce } from '@jbrowse/core/util';
|
|
3
4
|
import ClearIcon from '@mui/icons-material/Clear';
|
|
4
5
|
import { IconButton, InputAdornment, TextField } from '@mui/material';
|
|
5
6
|
export default function ClearableSearchField({ value, onChange, label, className, }) {
|
|
6
7
|
const [localValue, setLocalValue] = useState(value);
|
|
7
|
-
const
|
|
8
|
+
const debouncedValue = useDebounce(localValue, 300);
|
|
9
|
+
const onChangeEvent = useEffectEvent(onChange);
|
|
8
10
|
useEffect(() => {
|
|
9
11
|
if (value === '') {
|
|
10
12
|
setLocalValue('');
|
|
11
13
|
}
|
|
12
14
|
}, [value]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
onChangeEvent(debouncedValue);
|
|
17
|
+
}, [debouncedValue]);
|
|
13
18
|
return (_jsx(TextField, { className: className, label: label, value: localValue, onChange: event => {
|
|
14
|
-
|
|
15
|
-
setLocalValue(newValue);
|
|
16
|
-
startTransition(() => {
|
|
17
|
-
onChange(newValue);
|
|
18
|
-
});
|
|
19
|
+
setLocalValue(event.target.value);
|
|
19
20
|
}, slotProps: {
|
|
20
21
|
input: {
|
|
21
22
|
endAdornment: (_jsx(InputAdornment, { position: "end", children: _jsx(IconButton, { onClick: () => {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect } from 'react';
|
|
2
3
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
4
|
+
import useMeasure from '@jbrowse/core/util/useMeasure';
|
|
3
5
|
import { observer } from 'mobx-react';
|
|
4
6
|
import ClearableSearchField from "../ClearableSearchField.js";
|
|
5
7
|
import ShoppingCart from "../ShoppingCart.js";
|
|
@@ -13,10 +15,12 @@ const useStyles = makeStyles()(theme => ({
|
|
|
13
15
|
}));
|
|
14
16
|
const HierarchicalTrackSelectorHeader = observer(function HierarchicalTrackSelectorHeader({ model, setHeaderHeight, }) {
|
|
15
17
|
const { classes } = useStyles();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
const [ref, { height }] = useMeasure();
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (height !== undefined) {
|
|
21
|
+
setHeaderHeight(height);
|
|
22
|
+
}
|
|
23
|
+
}, [height, setHeaderHeight]);
|
|
24
|
+
return (_jsx("div", { ref: ref, "data-testid": "hierarchical_track_selector", children: _jsxs("div", { style: { display: 'flex' }, children: [_jsx(HamburgerMenu, { model: model }), _jsx(ShoppingCart, { model: model }), _jsx(ClearableSearchField, { className: classes.searchBox, label: "Filter tracks", value: model.filterText, onChange: model.setFilterText }), _jsx(RecentlyUsedTracks, { model: model }), _jsx(FavoriteTracks, { model: model })] }) }));
|
|
21
25
|
});
|
|
22
26
|
export default HierarchicalTrackSelectorHeader;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
+
import { getSession } from '@jbrowse/core/util';
|
|
3
4
|
import { observer } from 'mobx-react';
|
|
5
|
+
import SharedTooltip from "./SharedTooltip.js";
|
|
4
6
|
import TreeItem from "./TreeItem.js";
|
|
5
7
|
const HierarchicalTree = observer(function HierarchicalTree({ height, model, }) {
|
|
6
8
|
const { flattenedItems, shownTrackIds } = model;
|
|
9
|
+
const { drawerPosition } = getSession(model);
|
|
7
10
|
const containerRef = useRef(null);
|
|
8
11
|
const [scrollTop, setScrollTop] = useState(0);
|
|
9
12
|
const { startIndex, endIndex, totalHeight, itemOffsets } = model.itemOffsets(height, scrollTop);
|
|
@@ -12,35 +15,45 @@ const HierarchicalTree = observer(function HierarchicalTree({ height, model, })
|
|
|
12
15
|
if (!container) {
|
|
13
16
|
return;
|
|
14
17
|
}
|
|
18
|
+
let rafId;
|
|
15
19
|
const onScroll = () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
20
|
+
if (rafId !== undefined) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
rafId = requestAnimationFrame(() => {
|
|
24
|
+
rafId = undefined;
|
|
25
|
+
const newScrollTop = container.scrollTop;
|
|
26
|
+
setScrollTop(prev => {
|
|
27
|
+
const { startIndex: prevStart, endIndex: prevEnd } = model.itemOffsets(height, prev);
|
|
28
|
+
const { startIndex: nextStart, endIndex: nextEnd } = model.itemOffsets(height, newScrollTop);
|
|
29
|
+
if (prevStart === nextStart && prevEnd === nextEnd) {
|
|
30
|
+
return prev;
|
|
31
|
+
}
|
|
32
|
+
return newScrollTop;
|
|
33
|
+
});
|
|
24
34
|
});
|
|
25
35
|
};
|
|
26
36
|
container.addEventListener('scroll', onScroll, { passive: true });
|
|
27
37
|
return () => {
|
|
28
38
|
container.removeEventListener('scroll', onScroll);
|
|
39
|
+
if (rafId !== undefined) {
|
|
40
|
+
cancelAnimationFrame(rafId);
|
|
41
|
+
}
|
|
29
42
|
};
|
|
30
43
|
}, [height, model]);
|
|
31
|
-
return (
|
|
44
|
+
return (_jsxs("div", { ref: containerRef, style: {
|
|
32
45
|
height,
|
|
33
46
|
overflowY: 'auto',
|
|
34
|
-
}, children: _jsx("div", { style: {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
}, children: [_jsx("div", { style: {
|
|
48
|
+
height: totalHeight,
|
|
49
|
+
width: '100%',
|
|
50
|
+
position: 'relative',
|
|
51
|
+
}, children: Array.from({ length: endIndex - startIndex + 1 }, (_, i) => {
|
|
52
|
+
const index = startIndex + i;
|
|
53
|
+
const item = flattenedItems[index];
|
|
54
|
+
return item ? (_jsx(TreeItem, { model: model, item: item, top: itemOffsets[index], checked: item.type === 'track'
|
|
55
|
+
? shownTrackIds.has(item.trackId)
|
|
56
|
+
: undefined }, item.id)) : null;
|
|
57
|
+
}) }), _jsx(SharedTooltip, { containerRef: containerRef, placement: drawerPosition === 'left' ? 'right' : 'left' })] }));
|
|
45
58
|
});
|
|
46
59
|
export default HierarchicalTree;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Tooltip } from '@mui/material';
|
|
4
|
+
export default function SharedTooltip({ containerRef, placement, }) {
|
|
5
|
+
const [state, setState] = useState(null);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const container = containerRef.current;
|
|
8
|
+
if (!container) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const handleOver = (e) => {
|
|
12
|
+
const target = e.target.closest('[data-tooltip]');
|
|
13
|
+
if (target) {
|
|
14
|
+
setState({ anchorEl: target, text: target.dataset.tooltip ?? '' });
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const handleOut = (e) => {
|
|
18
|
+
const target = e.target.closest('[data-tooltip]');
|
|
19
|
+
const related = e.relatedTarget?.closest('[data-tooltip]');
|
|
20
|
+
if (target && target !== related) {
|
|
21
|
+
setState(null);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
container.addEventListener('mouseover', handleOver);
|
|
25
|
+
container.addEventListener('mouseout', handleOut);
|
|
26
|
+
return () => {
|
|
27
|
+
container.removeEventListener('mouseover', handleOver);
|
|
28
|
+
container.removeEventListener('mouseout', handleOut);
|
|
29
|
+
};
|
|
30
|
+
}, [containerRef]);
|
|
31
|
+
if (!state?.text) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return (_jsx(Tooltip, { open: true, title: state.text, placement: placement, slotProps: {
|
|
35
|
+
popper: {
|
|
36
|
+
anchorEl: state.anchorEl,
|
|
37
|
+
},
|
|
38
|
+
}, children: _jsx("span", {}) }));
|
|
39
|
+
}
|
|
@@ -19,9 +19,14 @@ const useStyles = makeStyles()(theme => ({
|
|
|
19
19
|
}));
|
|
20
20
|
function getAllSubcategories(node) {
|
|
21
21
|
const categoryIds = [];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
const stack = [node];
|
|
23
|
+
while (stack.length > 0) {
|
|
24
|
+
const curr = stack.pop();
|
|
25
|
+
for (const child of curr.children) {
|
|
26
|
+
if (child.type === 'category') {
|
|
27
|
+
categoryIds.push(child.id);
|
|
28
|
+
stack.push(child);
|
|
29
|
+
}
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
return categoryIds;
|
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import type { HierarchicalTrackSelectorModel } from '../../model.ts';
|
|
2
2
|
import type { TreeTrackNode } from '../../types.ts';
|
|
3
|
-
|
|
4
|
-
export interface InfoArgs {
|
|
5
|
-
target: HTMLElement;
|
|
6
|
-
id: string;
|
|
7
|
-
conf: AnyConfigurationModel;
|
|
8
|
-
}
|
|
9
|
-
declare const TrackLabel: import("react").NamedExoticComponent<{
|
|
3
|
+
declare const TrackLabel: import("react").MemoExoticComponent<({ model, item, checked, }: {
|
|
10
4
|
model: HierarchicalTrackSelectorModel;
|
|
11
5
|
item: TreeTrackNode;
|
|
12
6
|
checked: boolean;
|
|
13
|
-
}>;
|
|
7
|
+
}) => import("react/jsx-runtime").JSX.Element>;
|
|
14
8
|
export default TrackLabel;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { memo, useCallback } from 'react';
|
|
3
|
-
import { readConfObject } from '@jbrowse/core/configuration';
|
|
4
3
|
import SanitizedHTML from '@jbrowse/core/ui/SanitizedHTML';
|
|
5
|
-
import { getSession } from '@jbrowse/core/util';
|
|
6
4
|
import { makeStyles } from '@jbrowse/core/util/tss-react';
|
|
7
|
-
import { Checkbox, FormControlLabel
|
|
5
|
+
import { Checkbox, FormControlLabel } from '@mui/material';
|
|
8
6
|
import { observer } from 'mobx-react';
|
|
9
7
|
import { isUnsupported } from "../util.js";
|
|
10
8
|
import TrackSelectorTrackMenu from "./TrackSelectorTrackMenu.js";
|
|
@@ -35,22 +33,20 @@ const TrackLabelText = observer(function TrackLabelText({ model, conf, id, name,
|
|
|
35
33
|
});
|
|
36
34
|
const TrackLabel = memo(function TrackLabel({ model, item, checked, }) {
|
|
37
35
|
const { classes } = useStyles();
|
|
38
|
-
const {
|
|
39
|
-
const { id, name, conf, trackId } = item;
|
|
40
|
-
const description = readConfObject(conf, 'description');
|
|
36
|
+
const { id, name, conf, trackId, description } = item;
|
|
41
37
|
const onChange = useCallback(() => {
|
|
42
38
|
model.view.toggleTrack(trackId);
|
|
43
39
|
}, [model.view, trackId]);
|
|
44
|
-
return (_jsxs(_Fragment, { children: [_jsx(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
model.addToSelection([conf]);
|
|
51
|
-
}
|
|
52
|
-
event.preventDefault();
|
|
40
|
+
return (_jsxs(_Fragment, { children: [_jsx(FormControlLabel, { className: classes.checkboxLabel, "data-tooltip": description || undefined, "aria-description": description || undefined, onClick: event => {
|
|
41
|
+
if (event.ctrlKey || event.metaKey) {
|
|
42
|
+
if (model.selectionSet.has(conf)) {
|
|
43
|
+
model.removeFromSelection([conf]);
|
|
53
44
|
}
|
|
54
|
-
|
|
45
|
+
else {
|
|
46
|
+
model.addToSelection([conf]);
|
|
47
|
+
}
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
}
|
|
50
|
+
}, control: _jsx(TrackCheckbox, { checked: checked, onChange: onChange, id: id, disabled: isUnsupported(name), className: classes.compactCheckbox }), label: _jsx(TrackLabelText, { model: model, conf: conf, id: id, name: name, selectedClass: classes.selected }) }), _jsx(TrackSelectorTrackMenu, { model: model, id: id, conf: conf })] }));
|
|
55
51
|
});
|
|
56
52
|
export default TrackLabel;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { HierarchicalTrackSelectorModel } from '../../model.ts';
|
|
2
2
|
import type { TreeNode } from '../../types.ts';
|
|
3
|
-
declare const TreeItem: import("react").
|
|
3
|
+
declare const TreeItem: import("react").MemoExoticComponent<({ item, model, top, checked, }: {
|
|
4
4
|
item: TreeNode;
|
|
5
5
|
model: HierarchicalTrackSelectorModel;
|
|
6
6
|
top: number;
|
|
7
7
|
checked?: boolean;
|
|
8
|
-
}>;
|
|
8
|
+
}) => import("react/jsx-runtime").JSX.Element>;
|
|
9
9
|
export default TreeItem;
|
|
@@ -51,6 +51,7 @@ export function generateHierarchy({ model, trackConfs, extra, noCategories, menu
|
|
|
51
51
|
id: extra ? `${extra},${conf.trackId}` : conf.trackId,
|
|
52
52
|
trackId: conf.trackId,
|
|
53
53
|
name: getTrackName(conf, session),
|
|
54
|
+
description: readConfObject(conf, 'description') || '',
|
|
54
55
|
conf,
|
|
55
56
|
children: [],
|
|
56
57
|
nestingLevel: nestingLevel + 1,
|
|
@@ -287,11 +287,12 @@ export default function stateTreeFactory(pluginManager) {
|
|
|
287
287
|
return flatten(self.hierarchy.children);
|
|
288
288
|
},
|
|
289
289
|
get flattenedItemOffsets() {
|
|
290
|
+
const items = this.flattenedItems;
|
|
290
291
|
const offsets = [];
|
|
291
292
|
let cumulativeHeight = 0;
|
|
292
|
-
for (let i = 0, l =
|
|
293
|
+
for (let i = 0, l = items.length; i < l; i++) {
|
|
293
294
|
offsets.push(cumulativeHeight);
|
|
294
|
-
cumulativeHeight += getItemHeight(
|
|
295
|
+
cumulativeHeight += getItemHeight(items[i]);
|
|
295
296
|
}
|
|
296
297
|
return { cumulativeHeight, offsets };
|
|
297
298
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbrowse/plugin-data-management",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "JBrowse 2 linear genome view",
|
|
6
6
|
"keywords": [
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@gmod/ucsc-hub": "^2.0.3",
|
|
25
|
-
"@jbrowse/mobx-state-tree": "^5.
|
|
26
|
-
"@mui/icons-material": "^7.3.
|
|
27
|
-
"@mui/material": "^7.3.
|
|
28
|
-
"@mui/x-data-grid": "^8.
|
|
25
|
+
"@jbrowse/mobx-state-tree": "^5.6.0",
|
|
26
|
+
"@mui/icons-material": "^7.3.9",
|
|
27
|
+
"@mui/material": "^7.3.9",
|
|
28
|
+
"@mui/x-data-grid": "^8.28.2",
|
|
29
29
|
"deepmerge": "^4.3.1",
|
|
30
30
|
"mobx": "^6.15.0",
|
|
31
31
|
"mobx-react": "^9.2.1",
|
|
32
|
-
"@jbrowse/core": "^4.1.
|
|
33
|
-
"@jbrowse/
|
|
34
|
-
"@jbrowse/
|
|
32
|
+
"@jbrowse/product-core": "^4.1.15",
|
|
33
|
+
"@jbrowse/core": "^4.1.15",
|
|
34
|
+
"@jbrowse/plugin-config": "^4.1.15"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=18.0.0"
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"sideEffects": false,
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@jbrowse/app-core": "4.1.15"
|
|
45
|
+
},
|
|
43
46
|
"scripts": {
|
|
44
47
|
"build": "pnpm run /^build:/",
|
|
45
48
|
"test": "cd ../..; jest --passWithNoTests plugins/data-management",
|