@weng-lab/genomebrowser-ui 0.1.5 → 0.1.7
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/TrackSelect/TreeView/TreeViewWrapper.d.ts +1 -1
- package/dist/TrackSelect/store.d.ts +2 -2
- package/dist/TrackSelect/treeViewHelpers.d.ts +1 -0
- package/dist/TrackSelect/types.d.ts +10 -6
- package/dist/genomebrowser-ui.es.js +926 -1031
- package/dist/genomebrowser-ui.es.js.map +1 -1
- package/package.json +2 -2
- package/src/TrackSelect/.claude/settings.local.json +7 -0
- package/src/TrackSelect/DataGrid/CustomToolbar.tsx +0 -8
- package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +53 -58
- package/src/TrackSelect/DataGrid/columns.tsx +107 -97
- package/src/TrackSelect/TrackSelect.tsx +151 -104
- package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +6 -4
- package/src/TrackSelect/TreeView/treeViewHelpers.tsx +12 -13
- package/src/TrackSelect/store.ts +17 -9
- package/src/TrackSelect/treeViewHelpers.tsx +0 -0
- package/src/TrackSelect/types.ts +12 -6
- package/test/main.tsx +5 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weng-lab/genomebrowser-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.7",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@mui/x-data-grid-premium": "^8.19.0",
|
|
23
23
|
"react": "^19.0.0",
|
|
24
24
|
"react-dom": "^19.0.0",
|
|
25
|
-
"@weng-lab/genomebrowser": "1.7.2-beta.
|
|
25
|
+
"@weng-lab/genomebrowser": "1.7.2-beta.4"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@eslint/js": "^9.34.0",
|
|
@@ -2,7 +2,6 @@ import * as React from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Toolbar,
|
|
4
4
|
ToolbarButton,
|
|
5
|
-
ColumnsPanelTrigger,
|
|
6
5
|
FilterPanelTrigger,
|
|
7
6
|
ExportCsv,
|
|
8
7
|
ExportPrint,
|
|
@@ -13,7 +12,6 @@ import {
|
|
|
13
12
|
import Tooltip from "@mui/material/Tooltip";
|
|
14
13
|
import Menu from "@mui/material/Menu";
|
|
15
14
|
import Badge from "@mui/material/Badge";
|
|
16
|
-
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
|
17
15
|
import FilterListIcon from "@mui/icons-material/FilterList";
|
|
18
16
|
import FileDownloadIcon from "@mui/icons-material/FileDownload";
|
|
19
17
|
import MenuItem from "@mui/material/MenuItem";
|
|
@@ -77,12 +75,6 @@ export function CustomToolbar({
|
|
|
77
75
|
</>
|
|
78
76
|
)}
|
|
79
77
|
|
|
80
|
-
<Tooltip title="Columns">
|
|
81
|
-
<ColumnsPanelTrigger render={<ToolbarButton />}>
|
|
82
|
-
<ViewColumnIcon fontSize="small" htmlColor={iconColor} />
|
|
83
|
-
</ColumnsPanelTrigger>
|
|
84
|
-
</Tooltip>
|
|
85
|
-
|
|
86
78
|
<Tooltip title="Filters">
|
|
87
79
|
<FilterPanelTrigger
|
|
88
80
|
render={(props, state) => (
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { Box, Paper } from "@mui/material";
|
|
2
2
|
import {
|
|
3
3
|
DataGridPremium,
|
|
4
|
-
|
|
5
|
-
ToolbarPropsOverrides,
|
|
4
|
+
FilterColumnsArgs,
|
|
6
5
|
GridAutosizeOptions,
|
|
7
|
-
useGridApiRef,
|
|
8
6
|
GridColDef,
|
|
9
|
-
|
|
7
|
+
GridColumnVisibilityModel,
|
|
8
|
+
useGridApiRef,
|
|
10
9
|
} from "@mui/x-data-grid-premium";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
11
|
import { DataGridProps } from "../types";
|
|
12
|
-
import {
|
|
13
|
-
import { useEffect, useMemo } from "react";
|
|
14
|
-
import { sortedByAssayColumns, defaultColumns } from "./columns";
|
|
12
|
+
import { defaultColumns, sortedByAssayColumns } from "./columns";
|
|
15
13
|
|
|
16
14
|
const autosizeOptions: GridAutosizeOptions = {
|
|
17
15
|
expand: true,
|
|
@@ -21,63 +19,58 @@ const autosizeOptions: GridAutosizeOptions = {
|
|
|
21
19
|
|
|
22
20
|
// TODO: figure out where mui stores the number of rows in a row grouping so that can be bolded too
|
|
23
21
|
export function DataGridWrapper(props: DataGridProps) {
|
|
24
|
-
const {
|
|
25
|
-
label,
|
|
26
|
-
labelTooltip,
|
|
27
|
-
downloadFileName,
|
|
28
|
-
toolbarSlot,
|
|
29
|
-
toolbarStyle,
|
|
30
|
-
toolbarIconColor,
|
|
31
|
-
sortedAssay,
|
|
32
|
-
handleSelection,
|
|
33
|
-
rows,
|
|
34
|
-
selectedTracks
|
|
35
|
-
} = props;
|
|
36
|
-
|
|
37
|
-
const CustomToolbarWrapper = useMemo(() => {
|
|
38
|
-
const customToolbarProps = {
|
|
39
|
-
label,
|
|
40
|
-
downloadFileName,
|
|
41
|
-
labelTooltip,
|
|
42
|
-
toolbarSlot,
|
|
43
|
-
toolbarStyle,
|
|
44
|
-
toolbarIconColor,
|
|
45
|
-
};
|
|
46
|
-
return (props: GridToolbarProps & ToolbarPropsOverrides) => <CustomToolbar {...props} {...customToolbarProps} />;
|
|
47
|
-
}, [label, labelTooltip, toolbarSlot]);
|
|
22
|
+
const { sortedAssay, handleSelection, rows, selectedIds } = props;
|
|
48
23
|
|
|
49
24
|
const apiRef = useGridApiRef();
|
|
50
|
-
|
|
25
|
+
|
|
26
|
+
// Resize columns when toggling between sort modes
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (apiRef.current && apiRef.current.autosizeColumns) {
|
|
29
|
+
apiRef.current.autosizeColumns(autosizeOptions);
|
|
30
|
+
}
|
|
31
|
+
}, [sortedAssay]);
|
|
32
|
+
|
|
33
|
+
const groupingModel = sortedAssay
|
|
34
|
+
? ["assay", "ontology"]
|
|
35
|
+
: ["ontology", "displayname"];
|
|
51
36
|
const columnModel = sortedAssay ? sortedByAssayColumns : defaultColumns;
|
|
37
|
+
const leafField = sortedAssay ? "displayname" : "assay";
|
|
38
|
+
|
|
39
|
+
// Hide columns that are used in grouping or as leaf field
|
|
40
|
+
const baseVisibility: GridColumnVisibilityModel = sortedAssay
|
|
41
|
+
? { assay: false, ontology: false, displayname: false } // sort by assay: assay & ontology are grouping, displayname is leaf
|
|
42
|
+
: { ontology: false, displayname: false, assay: false }; // default: ontology & displayname are grouping, assay is leaf
|
|
52
43
|
|
|
53
|
-
|
|
44
|
+
const [columnVisibilityModel, setColumnVisibilityModel] =
|
|
45
|
+
useState<GridColumnVisibilityModel>(baseVisibility);
|
|
46
|
+
|
|
47
|
+
// Update visibility when sort mode changes
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
setColumnVisibilityModel(baseVisibility);
|
|
50
|
+
}, [sortedAssay]);
|
|
51
|
+
|
|
52
|
+
// functions to customize the column and filter panel in the toolbar
|
|
54
53
|
const filterColumns = ({ columns }: FilterColumnsArgs) => {
|
|
55
|
-
return columns
|
|
54
|
+
return columns
|
|
55
|
+
.filter((column) => column.type !== "custom")
|
|
56
|
+
.map((column) => column.field);
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
const getTogglableColumns = (columns: GridColDef[]) => {
|
|
59
|
-
return columns
|
|
60
|
+
return columns
|
|
61
|
+
.filter((column) => column.type !== "custom")
|
|
62
|
+
.map((column) => column.field);
|
|
60
63
|
};
|
|
61
64
|
|
|
62
|
-
const handleResizeCols = () => {
|
|
63
|
-
// need to check .autosizeColumns since the current was being set with an empty object
|
|
64
|
-
if (!apiRef.current?.autosizeColumns) return;
|
|
65
|
-
apiRef.current.autosizeColumns(autosizeOptions);
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// trigger resize when rows or columns change so that rows/columns don't need to be memoized outisde of this component
|
|
69
|
-
// otherwise sometimes would snap back to default widths when rows/columns change
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
handleResizeCols();
|
|
72
|
-
}, [rows, defaultColumns, sortedByAssayColumns, handleResizeCols]);
|
|
73
|
-
|
|
74
65
|
return (
|
|
75
66
|
<Paper sx={{ width: "100%" }}>
|
|
76
|
-
<Box
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
67
|
+
<Box
|
|
68
|
+
sx={{
|
|
69
|
+
height: 500,
|
|
70
|
+
width: "100%",
|
|
71
|
+
overflow: "auto",
|
|
72
|
+
}}
|
|
73
|
+
>
|
|
81
74
|
<DataGridPremium
|
|
82
75
|
apiRef={apiRef}
|
|
83
76
|
rows={rows}
|
|
@@ -85,13 +78,15 @@ export function DataGridWrapper(props: DataGridProps) {
|
|
|
85
78
|
getRowId={(row) => row.experimentAccession}
|
|
86
79
|
autosizeOptions={autosizeOptions}
|
|
87
80
|
rowGroupingModel={groupingModel}
|
|
88
|
-
groupingColDef={{ leafField
|
|
89
|
-
columnVisibilityModel={
|
|
81
|
+
groupingColDef={{ leafField, display: "flex" }}
|
|
82
|
+
columnVisibilityModel={columnVisibilityModel}
|
|
83
|
+
onColumnVisibilityModelChange={setColumnVisibilityModel}
|
|
90
84
|
onRowSelectionModelChange={handleSelection}
|
|
91
|
-
rowSelectionPropagation={{ descendants: true }}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
rowSelectionPropagation={{ descendants: true, parents: true }}
|
|
86
|
+
disableRowGrouping={false}
|
|
87
|
+
rowSelectionModel={{
|
|
88
|
+
type: "include",
|
|
89
|
+
ids: selectedIds,
|
|
95
90
|
}}
|
|
96
91
|
slotProps={{
|
|
97
92
|
filterPanel: {
|
|
@@ -12,123 +12,133 @@ const displayNameCol: GridColDef<RowInfo> = {
|
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
const sortedByAssayOntologyCol: GridColDef<RowInfo> = {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
const val = params.value;
|
|
25
|
-
return (
|
|
26
|
-
<div><b>{val}</b></div>
|
|
27
|
-
)
|
|
15
|
+
field: "ontology",
|
|
16
|
+
headerName: "Ontology",
|
|
17
|
+
type: "singleSelect",
|
|
18
|
+
valueOptions: ontologyTypes,
|
|
19
|
+
renderCell: (params) => {
|
|
20
|
+
if (params.rowNode.type === "group") {
|
|
21
|
+
if (params.value === undefined) {
|
|
22
|
+
return null;
|
|
28
23
|
}
|
|
29
|
-
|
|
24
|
+
const val = params.value;
|
|
25
|
+
return (
|
|
26
|
+
<div>
|
|
27
|
+
<b>{val}</b>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
30
32
|
};
|
|
31
33
|
|
|
32
|
-
const sortedByAssayAssayCol
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
const sortedByAssayAssayCol: GridColDef<RowInfo> = {
|
|
35
|
+
field: "assay",
|
|
36
|
+
headerName: "Assay",
|
|
37
|
+
valueOptions: assayTypes,
|
|
38
|
+
renderCell: (params) => {
|
|
39
|
+
if (params.rowNode.type === "group") {
|
|
40
|
+
if (params.value === undefined) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const val = params.value;
|
|
44
|
+
return (
|
|
45
|
+
<Stack direction="row" spacing={2} alignItems="center">
|
|
46
|
+
{AssayIcon(val)}
|
|
47
|
+
<div>
|
|
48
|
+
<b>{val}</b>
|
|
49
|
+
</div>
|
|
50
|
+
</Stack>
|
|
51
|
+
);
|
|
49
52
|
}
|
|
50
|
-
}
|
|
53
|
+
},
|
|
54
|
+
};
|
|
51
55
|
|
|
52
56
|
const defaultOntologyCol: GridColDef<RowInfo> = {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
57
|
+
field: "ontology",
|
|
58
|
+
headerName: "Ontology",
|
|
59
|
+
type: "singleSelect",
|
|
60
|
+
valueOptions: ontologyTypes,
|
|
61
|
+
renderCell: (params) => {
|
|
62
|
+
if (params.rowNode.type === "group") {
|
|
63
|
+
if (params.value === undefined) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const val = params.value;
|
|
67
|
+
return (
|
|
68
|
+
<div>
|
|
69
|
+
<b>{val}</b>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
67
72
|
}
|
|
73
|
+
},
|
|
68
74
|
};
|
|
69
75
|
|
|
70
|
-
const defaultAssayCol: GridColDef<RowInfo> = {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
const val = params.value;
|
|
80
|
-
return (
|
|
81
|
-
<Stack direction="row" spacing={2} alignItems="center">
|
|
82
|
-
{AssayIcon(val)}
|
|
83
|
-
<div>{val}</div>
|
|
84
|
-
</Stack>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
76
|
+
const defaultAssayCol: GridColDef<RowInfo> = {
|
|
77
|
+
field: "assay",
|
|
78
|
+
headerName: "Assay",
|
|
79
|
+
valueOptions: assayTypes,
|
|
80
|
+
renderCell: (params) => {
|
|
81
|
+
if (params.value === undefined) {
|
|
82
|
+
return null;
|
|
87
83
|
}
|
|
88
|
-
|
|
84
|
+
const val = params.value;
|
|
85
|
+
return (
|
|
86
|
+
<Stack direction="row" spacing={2} alignItems="center" sx={{ ml: 6 }}>
|
|
87
|
+
{AssayIcon(val)}
|
|
88
|
+
<div>{val}</div>
|
|
89
|
+
</Stack>
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
};
|
|
89
93
|
|
|
90
94
|
const sampleTypeCol: GridColDef<RowInfo> = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
field: "sampleType",
|
|
96
|
+
headerName: "Sample Type",
|
|
97
|
+
type: "singleSelect",
|
|
98
|
+
valueOptions: [
|
|
99
|
+
"tissue",
|
|
100
|
+
"primary cell",
|
|
101
|
+
"cell line",
|
|
102
|
+
"in vitro differentiated cells",
|
|
103
|
+
"organoid",
|
|
104
|
+
],
|
|
105
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
96
106
|
};
|
|
97
107
|
|
|
98
108
|
const lifeStageCol: GridColDef<RowInfo> = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
109
|
+
field: "lifeStage",
|
|
110
|
+
headerName: "Life Stage",
|
|
111
|
+
type: "singleSelect",
|
|
112
|
+
valueOptions: ["adult", "embryonic"],
|
|
113
|
+
valueFormatter: (value) => value && capitalize(value),
|
|
104
114
|
};
|
|
105
115
|
|
|
106
|
-
const experimentCol: GridColDef<RowInfo> = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
116
|
+
const experimentCol: GridColDef<RowInfo> = {
|
|
117
|
+
field: "experimentAccession",
|
|
118
|
+
headerName: "Experiment Accession",
|
|
119
|
+
};
|
|
110
120
|
|
|
111
121
|
const fileCol: GridColDef<RowInfo> = {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
122
|
+
field: "fileAccession",
|
|
123
|
+
headerName: "File Accession",
|
|
124
|
+
};
|
|
115
125
|
|
|
116
126
|
export const sortedByAssayColumns: GridColDef<RowInfo>[] = [
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
]
|
|
127
|
+
displayNameCol,
|
|
128
|
+
sortedByAssayOntologyCol,
|
|
129
|
+
sampleTypeCol,
|
|
130
|
+
lifeStageCol,
|
|
131
|
+
sortedByAssayAssayCol,
|
|
132
|
+
experimentCol,
|
|
133
|
+
fileCol,
|
|
134
|
+
];
|
|
125
135
|
|
|
126
136
|
export const defaultColumns: GridColDef<RowInfo>[] = [
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
]
|
|
137
|
+
defaultAssayCol,
|
|
138
|
+
sampleTypeCol,
|
|
139
|
+
lifeStageCol,
|
|
140
|
+
defaultOntologyCol,
|
|
141
|
+
displayNameCol,
|
|
142
|
+
experimentCol,
|
|
143
|
+
fileCol,
|
|
144
|
+
];
|