@weng-lab/genomebrowser-ui 0.2.1 → 0.2.2
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/.env.local +1 -1
- package/dist/TrackSelect/Dialogs/ClearDialog.d.ts +9 -0
- package/dist/TrackSelect/Dialogs/LimitDialog.d.ts +7 -0
- package/dist/TrackSelect/Dialogs/ResetDialog.d.ts +7 -0
- package/dist/TrackSelect/Folders/biosamples/data/human.json.d.ts +57141 -57141
- package/dist/TrackSelect/Folders/biosamples/data/mouse.json.d.ts +10394 -10394
- package/dist/TrackSelect/Folders/genes/data/human.json.d.ts +7 -7
- package/dist/TrackSelect/Folders/genes/data/mouse.json.d.ts +7 -7
- package/dist/genomebrowser-ui.es.js +729 -645
- package/dist/genomebrowser-ui.es.js.map +1 -1
- package/eslint.config.js +30 -30
- package/index.html +14 -14
- package/package.json +2 -1
- package/src/TrackSelect/DataGrid/DataGridWrapper.tsx +137 -137
- package/src/TrackSelect/DataGrid/DefaultGroupingCell.tsx +64 -64
- package/src/TrackSelect/Dialogs/ClearDialog.tsx +63 -0
- package/src/TrackSelect/Dialogs/LimitDialog.tsx +33 -0
- package/src/TrackSelect/Dialogs/ResetDialog.tsx +43 -0
- package/src/TrackSelect/FolderList/Breadcrumb.tsx +38 -38
- package/src/TrackSelect/FolderList/FolderCard.tsx +51 -51
- package/src/TrackSelect/FolderList/FolderList.tsx +47 -47
- package/src/TrackSelect/Folders/NEW.md +929 -929
- package/src/TrackSelect/Folders/biosamples/data/formatBiosamples.go +254 -254
- package/src/TrackSelect/Folders/biosamples/data/human.json +57141 -57141
- package/src/TrackSelect/Folders/biosamples/data/mouse.json +10394 -10394
- package/src/TrackSelect/Folders/biosamples/human.ts +17 -17
- package/src/TrackSelect/Folders/biosamples/mouse.ts +17 -17
- package/src/TrackSelect/Folders/biosamples/shared/AssayToggle.tsx +78 -78
- package/src/TrackSelect/Folders/biosamples/shared/BiosampleGroupingCell.tsx +146 -146
- package/src/TrackSelect/Folders/biosamples/shared/BiosampleTreeItem.tsx +15 -15
- package/src/TrackSelect/Folders/biosamples/shared/columns.tsx +165 -165
- package/src/TrackSelect/Folders/biosamples/shared/constants.tsx +116 -116
- package/src/TrackSelect/Folders/biosamples/shared/createFolder.ts +116 -116
- package/src/TrackSelect/Folders/biosamples/shared/treeBuilder.ts +224 -224
- package/src/TrackSelect/Folders/biosamples/shared/types.ts +48 -48
- package/src/TrackSelect/Folders/genes/data/human.json +7 -7
- package/src/TrackSelect/Folders/genes/data/mouse.json +7 -7
- package/src/TrackSelect/Folders/genes/human.ts +16 -16
- package/src/TrackSelect/Folders/genes/mouse.ts +16 -16
- package/src/TrackSelect/Folders/genes/shared/columns.tsx +42 -42
- package/src/TrackSelect/Folders/genes/shared/createFolder.ts +68 -68
- package/src/TrackSelect/Folders/genes/shared/treeBuilder.ts +45 -45
- package/src/TrackSelect/Folders/genes/shared/types.ts +29 -29
- package/src/TrackSelect/Folders/index.ts +30 -30
- package/src/TrackSelect/Folders/types.ts +106 -106
- package/src/TrackSelect/TrackSelect.tsx +82 -74
- package/src/TrackSelect/TreeView/CustomTreeItem.tsx +214 -214
- package/src/TrackSelect/TreeView/TreeViewWrapper.tsx +145 -145
- package/src/TrackSelect/store.ts +117 -117
- package/src/TrackSelect/types.ts +121 -121
- package/src/lib.ts +13 -13
- package/src/vite-env.d.ts +1 -1
- package/test/main.tsx +369 -369
- package/tsconfig.app.json +25 -25
- package/tsconfig.json +7 -7
- package/tsconfig.node.json +25 -25
- package/vite.config.ts +66 -66
package/eslint.config.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
|
2
|
-
import storybook from "eslint-plugin-storybook";
|
|
3
|
-
|
|
4
|
-
import js from "@eslint/js";
|
|
5
|
-
import globals from "globals";
|
|
6
|
-
import reactHooks from "eslint-plugin-react-hooks";
|
|
7
|
-
import reactRefresh from "eslint-plugin-react-refresh";
|
|
8
|
-
import tseslint from "typescript-eslint";
|
|
9
|
-
|
|
10
|
-
export default tseslint.config(
|
|
11
|
-
{ ignores: ["dist"] },
|
|
12
|
-
{
|
|
13
|
-
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
14
|
-
files: ["**/*.{ts,tsx}"],
|
|
15
|
-
languageOptions: {
|
|
16
|
-
ecmaVersion: 2020,
|
|
17
|
-
globals: globals.browser,
|
|
18
|
-
},
|
|
19
|
-
plugins: {
|
|
20
|
-
"react-hooks": reactHooks,
|
|
21
|
-
"react-refresh": reactRefresh,
|
|
22
|
-
},
|
|
23
|
-
rules: {
|
|
24
|
-
...reactHooks.configs.recommended.rules,
|
|
25
|
-
"@typescript-eslint/no-explicit-any": "warn",
|
|
26
|
-
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
storybook.configs["flat/recommended"]
|
|
30
|
-
);
|
|
1
|
+
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
|
2
|
+
import storybook from "eslint-plugin-storybook";
|
|
3
|
+
|
|
4
|
+
import js from "@eslint/js";
|
|
5
|
+
import globals from "globals";
|
|
6
|
+
import reactHooks from "eslint-plugin-react-hooks";
|
|
7
|
+
import reactRefresh from "eslint-plugin-react-refresh";
|
|
8
|
+
import tseslint from "typescript-eslint";
|
|
9
|
+
|
|
10
|
+
export default tseslint.config(
|
|
11
|
+
{ ignores: ["dist"] },
|
|
12
|
+
{
|
|
13
|
+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
|
14
|
+
files: ["**/*.{ts,tsx}"],
|
|
15
|
+
languageOptions: {
|
|
16
|
+
ecmaVersion: 2020,
|
|
17
|
+
globals: globals.browser,
|
|
18
|
+
},
|
|
19
|
+
plugins: {
|
|
20
|
+
"react-hooks": reactHooks,
|
|
21
|
+
"react-refresh": reactRefresh,
|
|
22
|
+
},
|
|
23
|
+
rules: {
|
|
24
|
+
...reactHooks.configs.recommended.rules,
|
|
25
|
+
"@typescript-eslint/no-explicit-any": "warn",
|
|
26
|
+
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
storybook.configs["flat/recommended"]
|
|
30
|
+
);
|
package/index.html
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
-
<title>Genomebrowser-UI Test Page</title>
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<div id="root"></div>
|
|
11
|
-
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
|
|
12
|
-
<script type="module" src="/test/main.tsx"></script>
|
|
13
|
-
</body>
|
|
14
|
-
</html>
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Genomebrowser-UI Test Page</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>
|
|
12
|
+
<script type="module" src="/test/main.tsx"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weng-lab/genomebrowser-ui",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"publishConfig": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"eslint": "^9.34.0",
|
|
34
34
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
35
35
|
"eslint-plugin-react-refresh": "^0.4.20",
|
|
36
|
+
"typescript": "^5.7.3",
|
|
36
37
|
"typescript-eslint": "^8.42.0",
|
|
37
38
|
"vite": "^6.3.5",
|
|
38
39
|
"vite-plugin-dts": "^4.5.4"
|
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
import { Box, Paper } from "@mui/material";
|
|
2
|
-
import {
|
|
3
|
-
DataGridPremium,
|
|
4
|
-
FilterColumnsArgs,
|
|
5
|
-
GridAutosizeOptions,
|
|
6
|
-
GridColDef,
|
|
7
|
-
GridColumnVisibilityModel,
|
|
8
|
-
useGridApiRef,
|
|
9
|
-
} from "@mui/x-data-grid-premium";
|
|
10
|
-
import { useEffect, useMemo, useState } from "react";
|
|
11
|
-
import { DataGridProps } from "../types";
|
|
12
|
-
import { DefaultGroupingCell } from "./DefaultGroupingCell";
|
|
13
|
-
|
|
14
|
-
const autosizeOptions: GridAutosizeOptions = {
|
|
15
|
-
expand: true,
|
|
16
|
-
includeHeaders: true,
|
|
17
|
-
outliersFactor: 1.5,
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export function DataGridWrapper(props: DataGridProps) {
|
|
21
|
-
const {
|
|
22
|
-
columns,
|
|
23
|
-
groupingModel,
|
|
24
|
-
leafField,
|
|
25
|
-
onSelectionChange,
|
|
26
|
-
rows,
|
|
27
|
-
selectedIds,
|
|
28
|
-
GroupingCellComponent,
|
|
29
|
-
} = props;
|
|
30
|
-
|
|
31
|
-
const GroupingCell = GroupingCellComponent ?? DefaultGroupingCell;
|
|
32
|
-
|
|
33
|
-
const apiRef = useGridApiRef();
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
if (apiRef.current && apiRef.current.autosizeColumns) {
|
|
37
|
-
apiRef.current.autosizeColumns(autosizeOptions);
|
|
38
|
-
}
|
|
39
|
-
}, [columns, groupingModel, leafField]);
|
|
40
|
-
|
|
41
|
-
const baseVisibility = useMemo(() => {
|
|
42
|
-
const visibility: GridColumnVisibilityModel = {
|
|
43
|
-
id: false,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
// Only hide leafField if we have grouping (it shows in grouping column)
|
|
47
|
-
if (groupingModel.length > 0) {
|
|
48
|
-
visibility[leafField] = false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
groupingModel.forEach((field) => {
|
|
52
|
-
visibility[field] = false;
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return visibility;
|
|
56
|
-
}, [groupingModel, leafField]);
|
|
57
|
-
|
|
58
|
-
const [columnVisibilityModel, setColumnVisibilityModel] =
|
|
59
|
-
useState<GridColumnVisibilityModel>(baseVisibility);
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
setColumnVisibilityModel(baseVisibility);
|
|
63
|
-
}, [baseVisibility]);
|
|
64
|
-
|
|
65
|
-
// functions to customize the column and filter panel in the toolbar
|
|
66
|
-
const filterColumns = ({ columns }: FilterColumnsArgs) => {
|
|
67
|
-
return columns
|
|
68
|
-
.filter((column) => column.type !== "custom")
|
|
69
|
-
.map((column) => column.field);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const getTogglableColumns = (columns: GridColDef[]) => {
|
|
73
|
-
return columns
|
|
74
|
-
.filter((column) => column.type !== "custom")
|
|
75
|
-
.map((column) => column.field);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
return (
|
|
79
|
-
<Paper sx={{ width: "100%" }}>
|
|
80
|
-
<Box
|
|
81
|
-
sx={{
|
|
82
|
-
height: 500,
|
|
83
|
-
width: "100%",
|
|
84
|
-
overflow: "auto",
|
|
85
|
-
}}
|
|
86
|
-
>
|
|
87
|
-
<DataGridPremium
|
|
88
|
-
apiRef={apiRef}
|
|
89
|
-
rows={rows}
|
|
90
|
-
columns={columns}
|
|
91
|
-
getRowId={(row) => row.id}
|
|
92
|
-
autosizeOptions={autosizeOptions}
|
|
93
|
-
rowGroupingModel={groupingModel}
|
|
94
|
-
groupingColDef={{
|
|
95
|
-
leafField,
|
|
96
|
-
display: "flex",
|
|
97
|
-
minWidth: 300,
|
|
98
|
-
maxWidth: 500,
|
|
99
|
-
flex: 2,
|
|
100
|
-
renderCell: (params) => <GroupingCell {...params} />,
|
|
101
|
-
}}
|
|
102
|
-
columnVisibilityModel={columnVisibilityModel}
|
|
103
|
-
onColumnVisibilityModelChange={setColumnVisibilityModel}
|
|
104
|
-
onRowSelectionModelChange={(selection) => {
|
|
105
|
-
const ids = (selection as any)?.ids ?? new Set<string>();
|
|
106
|
-
onSelectionChange(new Set(ids));
|
|
107
|
-
}}
|
|
108
|
-
rowSelectionPropagation={{ descendants: true, parents: false }}
|
|
109
|
-
disableRowGrouping={false}
|
|
110
|
-
rowSelectionModel={{
|
|
111
|
-
type: "include",
|
|
112
|
-
ids: selectedIds,
|
|
113
|
-
}}
|
|
114
|
-
slotProps={{
|
|
115
|
-
filterPanel: {
|
|
116
|
-
filterFormProps: {
|
|
117
|
-
filterColumns,
|
|
118
|
-
},
|
|
119
|
-
},
|
|
120
|
-
columnsManagement: {
|
|
121
|
-
getTogglableColumns,
|
|
122
|
-
},
|
|
123
|
-
}}
|
|
124
|
-
keepNonExistentRowsSelected
|
|
125
|
-
showToolbar
|
|
126
|
-
disableAggregation
|
|
127
|
-
disableRowSelectionExcludeModel
|
|
128
|
-
disablePivoting
|
|
129
|
-
checkboxSelection
|
|
130
|
-
autosizeOnMount
|
|
131
|
-
pagination
|
|
132
|
-
hideFooterSelectedRowCount
|
|
133
|
-
/>
|
|
134
|
-
</Box>
|
|
135
|
-
</Paper>
|
|
136
|
-
);
|
|
137
|
-
}
|
|
1
|
+
import { Box, Paper } from "@mui/material";
|
|
2
|
+
import {
|
|
3
|
+
DataGridPremium,
|
|
4
|
+
FilterColumnsArgs,
|
|
5
|
+
GridAutosizeOptions,
|
|
6
|
+
GridColDef,
|
|
7
|
+
GridColumnVisibilityModel,
|
|
8
|
+
useGridApiRef,
|
|
9
|
+
} from "@mui/x-data-grid-premium";
|
|
10
|
+
import { useEffect, useMemo, useState } from "react";
|
|
11
|
+
import { DataGridProps } from "../types";
|
|
12
|
+
import { DefaultGroupingCell } from "./DefaultGroupingCell";
|
|
13
|
+
|
|
14
|
+
const autosizeOptions: GridAutosizeOptions = {
|
|
15
|
+
expand: true,
|
|
16
|
+
includeHeaders: true,
|
|
17
|
+
outliersFactor: 1.5,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export function DataGridWrapper(props: DataGridProps) {
|
|
21
|
+
const {
|
|
22
|
+
columns,
|
|
23
|
+
groupingModel,
|
|
24
|
+
leafField,
|
|
25
|
+
onSelectionChange,
|
|
26
|
+
rows,
|
|
27
|
+
selectedIds,
|
|
28
|
+
GroupingCellComponent,
|
|
29
|
+
} = props;
|
|
30
|
+
|
|
31
|
+
const GroupingCell = GroupingCellComponent ?? DefaultGroupingCell;
|
|
32
|
+
|
|
33
|
+
const apiRef = useGridApiRef();
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (apiRef.current && apiRef.current.autosizeColumns) {
|
|
37
|
+
apiRef.current.autosizeColumns(autosizeOptions);
|
|
38
|
+
}
|
|
39
|
+
}, [columns, groupingModel, leafField]);
|
|
40
|
+
|
|
41
|
+
const baseVisibility = useMemo(() => {
|
|
42
|
+
const visibility: GridColumnVisibilityModel = {
|
|
43
|
+
id: false,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Only hide leafField if we have grouping (it shows in grouping column)
|
|
47
|
+
if (groupingModel.length > 0) {
|
|
48
|
+
visibility[leafField] = false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
groupingModel.forEach((field) => {
|
|
52
|
+
visibility[field] = false;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return visibility;
|
|
56
|
+
}, [groupingModel, leafField]);
|
|
57
|
+
|
|
58
|
+
const [columnVisibilityModel, setColumnVisibilityModel] =
|
|
59
|
+
useState<GridColumnVisibilityModel>(baseVisibility);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
setColumnVisibilityModel(baseVisibility);
|
|
63
|
+
}, [baseVisibility]);
|
|
64
|
+
|
|
65
|
+
// functions to customize the column and filter panel in the toolbar
|
|
66
|
+
const filterColumns = ({ columns }: FilterColumnsArgs) => {
|
|
67
|
+
return columns
|
|
68
|
+
.filter((column) => column.type !== "custom")
|
|
69
|
+
.map((column) => column.field);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const getTogglableColumns = (columns: GridColDef[]) => {
|
|
73
|
+
return columns
|
|
74
|
+
.filter((column) => column.type !== "custom")
|
|
75
|
+
.map((column) => column.field);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<Paper sx={{ width: "100%" }}>
|
|
80
|
+
<Box
|
|
81
|
+
sx={{
|
|
82
|
+
height: 500,
|
|
83
|
+
width: "100%",
|
|
84
|
+
overflow: "auto",
|
|
85
|
+
}}
|
|
86
|
+
>
|
|
87
|
+
<DataGridPremium
|
|
88
|
+
apiRef={apiRef}
|
|
89
|
+
rows={rows}
|
|
90
|
+
columns={columns}
|
|
91
|
+
getRowId={(row) => row.id}
|
|
92
|
+
autosizeOptions={autosizeOptions}
|
|
93
|
+
rowGroupingModel={groupingModel}
|
|
94
|
+
groupingColDef={{
|
|
95
|
+
leafField,
|
|
96
|
+
display: "flex",
|
|
97
|
+
minWidth: 300,
|
|
98
|
+
maxWidth: 500,
|
|
99
|
+
flex: 2,
|
|
100
|
+
renderCell: (params) => <GroupingCell {...params} />,
|
|
101
|
+
}}
|
|
102
|
+
columnVisibilityModel={columnVisibilityModel}
|
|
103
|
+
onColumnVisibilityModelChange={setColumnVisibilityModel}
|
|
104
|
+
onRowSelectionModelChange={(selection) => {
|
|
105
|
+
const ids = (selection as any)?.ids ?? new Set<string>();
|
|
106
|
+
onSelectionChange(new Set(ids));
|
|
107
|
+
}}
|
|
108
|
+
rowSelectionPropagation={{ descendants: true, parents: false }}
|
|
109
|
+
disableRowGrouping={false}
|
|
110
|
+
rowSelectionModel={{
|
|
111
|
+
type: "include",
|
|
112
|
+
ids: selectedIds,
|
|
113
|
+
}}
|
|
114
|
+
slotProps={{
|
|
115
|
+
filterPanel: {
|
|
116
|
+
filterFormProps: {
|
|
117
|
+
filterColumns,
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
columnsManagement: {
|
|
121
|
+
getTogglableColumns,
|
|
122
|
+
},
|
|
123
|
+
}}
|
|
124
|
+
keepNonExistentRowsSelected
|
|
125
|
+
showToolbar
|
|
126
|
+
disableAggregation
|
|
127
|
+
disableRowSelectionExcludeModel
|
|
128
|
+
disablePivoting
|
|
129
|
+
checkboxSelection
|
|
130
|
+
autosizeOnMount
|
|
131
|
+
pagination
|
|
132
|
+
hideFooterSelectedRowCount
|
|
133
|
+
/>
|
|
134
|
+
</Box>
|
|
135
|
+
</Paper>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import { Tooltip, Box, IconButton } from "@mui/material";
|
|
2
|
-
import {
|
|
3
|
-
GridRenderCellParams,
|
|
4
|
-
useGridApiContext,
|
|
5
|
-
GridGroupNode,
|
|
6
|
-
} from "@mui/x-data-grid-premium";
|
|
7
|
-
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
8
|
-
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Default grouping cell that handles expand/collapse, truncation, and tooltips.
|
|
12
|
-
* This is a generic implementation without any folder-specific logic.
|
|
13
|
-
*/
|
|
14
|
-
export function DefaultGroupingCell(params: GridRenderCellParams) {
|
|
15
|
-
const apiRef = useGridApiContext();
|
|
16
|
-
const isGroup = params.rowNode.type === "group";
|
|
17
|
-
const groupNode = params.rowNode as GridGroupNode;
|
|
18
|
-
const isExpanded = isGroup ? groupNode.childrenExpanded : false;
|
|
19
|
-
const depth = params.rowNode.depth ?? 0;
|
|
20
|
-
|
|
21
|
-
const handleExpandClick = (e: React.MouseEvent) => {
|
|
22
|
-
e.stopPropagation();
|
|
23
|
-
apiRef.current.setRowChildrenExpansion(params.id, !isExpanded);
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const value = String(params.value ?? "");
|
|
27
|
-
|
|
28
|
-
// Indent based on depth (2 units per level)
|
|
29
|
-
const indentLevel = depth * 2;
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<Box
|
|
33
|
-
sx={{
|
|
34
|
-
display: "flex",
|
|
35
|
-
alignItems: "center",
|
|
36
|
-
width: "100%",
|
|
37
|
-
ml: indentLevel,
|
|
38
|
-
}}
|
|
39
|
-
>
|
|
40
|
-
{isGroup && (
|
|
41
|
-
<IconButton size="small" onClick={handleExpandClick} sx={{ mr: 0.5 }}>
|
|
42
|
-
{isExpanded ? (
|
|
43
|
-
<ExpandMoreIcon fontSize="small" />
|
|
44
|
-
) : (
|
|
45
|
-
<ChevronRightIcon fontSize="small" />
|
|
46
|
-
)}
|
|
47
|
-
</IconButton>
|
|
48
|
-
)}
|
|
49
|
-
<Tooltip title={value} placement="top-start" enterDelay={500}>
|
|
50
|
-
<Box
|
|
51
|
-
sx={{
|
|
52
|
-
overflow: "hidden",
|
|
53
|
-
textOverflow: "ellipsis",
|
|
54
|
-
whiteSpace: "nowrap",
|
|
55
|
-
flex: 1,
|
|
56
|
-
fontWeight: isGroup ? "bold" : undefined,
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
{params.formattedValue}
|
|
60
|
-
</Box>
|
|
61
|
-
</Tooltip>
|
|
62
|
-
</Box>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
1
|
+
import { Tooltip, Box, IconButton } from "@mui/material";
|
|
2
|
+
import {
|
|
3
|
+
GridRenderCellParams,
|
|
4
|
+
useGridApiContext,
|
|
5
|
+
GridGroupNode,
|
|
6
|
+
} from "@mui/x-data-grid-premium";
|
|
7
|
+
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
|
8
|
+
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default grouping cell that handles expand/collapse, truncation, and tooltips.
|
|
12
|
+
* This is a generic implementation without any folder-specific logic.
|
|
13
|
+
*/
|
|
14
|
+
export function DefaultGroupingCell(params: GridRenderCellParams) {
|
|
15
|
+
const apiRef = useGridApiContext();
|
|
16
|
+
const isGroup = params.rowNode.type === "group";
|
|
17
|
+
const groupNode = params.rowNode as GridGroupNode;
|
|
18
|
+
const isExpanded = isGroup ? groupNode.childrenExpanded : false;
|
|
19
|
+
const depth = params.rowNode.depth ?? 0;
|
|
20
|
+
|
|
21
|
+
const handleExpandClick = (e: React.MouseEvent) => {
|
|
22
|
+
e.stopPropagation();
|
|
23
|
+
apiRef.current.setRowChildrenExpansion(params.id, !isExpanded);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const value = String(params.value ?? "");
|
|
27
|
+
|
|
28
|
+
// Indent based on depth (2 units per level)
|
|
29
|
+
const indentLevel = depth * 2;
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<Box
|
|
33
|
+
sx={{
|
|
34
|
+
display: "flex",
|
|
35
|
+
alignItems: "center",
|
|
36
|
+
width: "100%",
|
|
37
|
+
ml: indentLevel,
|
|
38
|
+
}}
|
|
39
|
+
>
|
|
40
|
+
{isGroup && (
|
|
41
|
+
<IconButton size="small" onClick={handleExpandClick} sx={{ mr: 0.5 }}>
|
|
42
|
+
{isExpanded ? (
|
|
43
|
+
<ExpandMoreIcon fontSize="small" />
|
|
44
|
+
) : (
|
|
45
|
+
<ChevronRightIcon fontSize="small" />
|
|
46
|
+
)}
|
|
47
|
+
</IconButton>
|
|
48
|
+
)}
|
|
49
|
+
<Tooltip title={value} placement="top-start" enterDelay={500}>
|
|
50
|
+
<Box
|
|
51
|
+
sx={{
|
|
52
|
+
overflow: "hidden",
|
|
53
|
+
textOverflow: "ellipsis",
|
|
54
|
+
whiteSpace: "nowrap",
|
|
55
|
+
flex: 1,
|
|
56
|
+
fontWeight: isGroup ? "bold" : undefined,
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
{params.formattedValue}
|
|
60
|
+
</Box>
|
|
61
|
+
</Tooltip>
|
|
62
|
+
</Box>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogContentText,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
} from "@mui/material";
|
|
9
|
+
|
|
10
|
+
interface ClearDialogProps {
|
|
11
|
+
open: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
onConfirm: () => void;
|
|
14
|
+
folderLabel: string;
|
|
15
|
+
clearAll: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ClearDialog({
|
|
19
|
+
open,
|
|
20
|
+
onClose,
|
|
21
|
+
onConfirm,
|
|
22
|
+
folderLabel,
|
|
23
|
+
clearAll,
|
|
24
|
+
}: ClearDialogProps) {
|
|
25
|
+
return (
|
|
26
|
+
<Dialog open={open} onClose={onClose}>
|
|
27
|
+
<DialogTitle
|
|
28
|
+
sx={{
|
|
29
|
+
bgcolor: "#0c184a",
|
|
30
|
+
color: "white",
|
|
31
|
+
fontWeight: "bold",
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
{clearAll ? "Clear All Folders" : `Clear ${folderLabel}`}
|
|
35
|
+
</DialogTitle>
|
|
36
|
+
<DialogContent sx={{ mt: 2 }}>
|
|
37
|
+
<DialogContentText>
|
|
38
|
+
{clearAll ? (
|
|
39
|
+
"Are you sure you want to clear all selections?"
|
|
40
|
+
) : (
|
|
41
|
+
<>
|
|
42
|
+
Are you sure you want to clear the selection for{" "}
|
|
43
|
+
<strong>{folderLabel}</strong>?
|
|
44
|
+
</>
|
|
45
|
+
)}
|
|
46
|
+
</DialogContentText>
|
|
47
|
+
</DialogContent>
|
|
48
|
+
<DialogActions sx={{ justifyContent: "center", gap: 2, pb: 2 }}>
|
|
49
|
+
<Button
|
|
50
|
+
variant="contained"
|
|
51
|
+
color="primary"
|
|
52
|
+
onClick={onClose}
|
|
53
|
+
autoFocus
|
|
54
|
+
>
|
|
55
|
+
Cancel
|
|
56
|
+
</Button>
|
|
57
|
+
<Button variant="outlined" color="secondary" onClick={onConfirm}>
|
|
58
|
+
Clear
|
|
59
|
+
</Button>
|
|
60
|
+
</DialogActions>
|
|
61
|
+
</Dialog>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogContentText,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
} from "@mui/material";
|
|
9
|
+
|
|
10
|
+
interface LimitDialogProps {
|
|
11
|
+
open: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
maxTracks: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function LimitDialog({ open, onClose, maxTracks }: LimitDialogProps) {
|
|
17
|
+
return (
|
|
18
|
+
<Dialog open={open} onClose={onClose}>
|
|
19
|
+
<DialogTitle>Track Limit Reached</DialogTitle>
|
|
20
|
+
<DialogContent>
|
|
21
|
+
<DialogContentText>
|
|
22
|
+
You can select up to {maxTracks} tracks at a time. Please remove a
|
|
23
|
+
track before adding another.
|
|
24
|
+
</DialogContentText>
|
|
25
|
+
</DialogContent>
|
|
26
|
+
<DialogActions>
|
|
27
|
+
<Button onClick={onClose} autoFocus>
|
|
28
|
+
OK
|
|
29
|
+
</Button>
|
|
30
|
+
</DialogActions>
|
|
31
|
+
</Dialog>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Button,
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogContent,
|
|
6
|
+
DialogContentText,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
} from "@mui/material";
|
|
9
|
+
|
|
10
|
+
interface ResetDialogProps {
|
|
11
|
+
open: boolean;
|
|
12
|
+
onClose: () => void;
|
|
13
|
+
onConfirm: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ResetDialog({ open, onClose, onConfirm }: ResetDialogProps) {
|
|
17
|
+
return (
|
|
18
|
+
<Dialog open={open} onClose={onClose}>
|
|
19
|
+
<DialogTitle
|
|
20
|
+
sx={{
|
|
21
|
+
bgcolor: "#0c184a",
|
|
22
|
+
color: "white",
|
|
23
|
+
fontWeight: "bold",
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
Reset to Default
|
|
27
|
+
</DialogTitle>
|
|
28
|
+
<DialogContent sx={{ mt: 2 }}>
|
|
29
|
+
<DialogContentText>
|
|
30
|
+
Are you sure you want to reset all selections to the default?
|
|
31
|
+
</DialogContentText>
|
|
32
|
+
</DialogContent>
|
|
33
|
+
<DialogActions sx={{ justifyContent: "center", gap: 2, pb: 2 }}>
|
|
34
|
+
<Button variant="contained" color="primary" onClick={onClose} autoFocus>
|
|
35
|
+
Cancel
|
|
36
|
+
</Button>
|
|
37
|
+
<Button variant="outlined" color="secondary" onClick={onConfirm}>
|
|
38
|
+
Reset
|
|
39
|
+
</Button>
|
|
40
|
+
</DialogActions>
|
|
41
|
+
</Dialog>
|
|
42
|
+
);
|
|
43
|
+
}
|