@weng-lab/genomebrowser-ui 0.2.0 → 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 +736 -652
- 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
|
@@ -3,17 +3,18 @@ import {
|
|
|
3
3
|
Box,
|
|
4
4
|
Button,
|
|
5
5
|
Dialog,
|
|
6
|
-
DialogActions,
|
|
7
6
|
DialogContent,
|
|
8
|
-
DialogContentText,
|
|
9
7
|
DialogTitle,
|
|
10
8
|
IconButton,
|
|
11
9
|
Stack,
|
|
12
10
|
} from "@mui/material";
|
|
13
11
|
import { TreeViewBaseItem } from "@mui/x-tree-view";
|
|
14
12
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
15
|
-
import { Breadcrumb } from "./FolderList/Breadcrumb";
|
|
16
13
|
import { DataGridWrapper } from "./DataGrid/DataGridWrapper";
|
|
14
|
+
import { ClearDialog } from "./Dialogs/ClearDialog";
|
|
15
|
+
import { LimitDialog } from "./Dialogs/LimitDialog";
|
|
16
|
+
import { ResetDialog } from "./Dialogs/ResetDialog";
|
|
17
|
+
import { Breadcrumb } from "./FolderList/Breadcrumb";
|
|
17
18
|
import { FolderList } from "./FolderList/FolderList";
|
|
18
19
|
import { FolderDefinition, FolderRuntimeConfig } from "./Folders/types";
|
|
19
20
|
import { createSelectionStore, SelectionStoreInstance } from "./store";
|
|
@@ -87,6 +88,7 @@ export default function TrackSelect({
|
|
|
87
88
|
}: TrackSelectProps) {
|
|
88
89
|
const [limitDialogOpen, setLimitDialogOpen] = useState(false);
|
|
89
90
|
const [clearDialogOpen, setClearDialogOpen] = useState(false);
|
|
91
|
+
const [resetDialogOpen, setResetDialogOpen] = useState(false);
|
|
90
92
|
const [runtimeConfigByFolder, setRuntimeConfigByFolder] = useState(() =>
|
|
91
93
|
buildRuntimeConfigMap(folders),
|
|
92
94
|
);
|
|
@@ -273,6 +275,30 @@ export default function TrackSelect({
|
|
|
273
275
|
setClearDialogOpen(true);
|
|
274
276
|
};
|
|
275
277
|
|
|
278
|
+
const handleReset = () => {
|
|
279
|
+
setResetDialogOpen(true);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const confirmReset = () => {
|
|
283
|
+
setResetDialogOpen(false);
|
|
284
|
+
if (!initialSelection) return;
|
|
285
|
+
|
|
286
|
+
// Reset to initial selection
|
|
287
|
+
initialSelection.forEach((ids, folderId) => {
|
|
288
|
+
setSelection(folderId, new Set(ids));
|
|
289
|
+
});
|
|
290
|
+
// Clear any folders not in initialSelection
|
|
291
|
+
folderIds.forEach((folderId) => {
|
|
292
|
+
if (!initialSelection.has(folderId)) {
|
|
293
|
+
setSelection(folderId, new Set<string>());
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const newSnapshot = cloneSelectionMap(initialSelection);
|
|
298
|
+
setCommittedSnapshot(newSnapshot);
|
|
299
|
+
onSubmit(newSnapshot);
|
|
300
|
+
};
|
|
301
|
+
|
|
276
302
|
const confirmClear = () => {
|
|
277
303
|
setClearDialogOpen(false);
|
|
278
304
|
let newSnapshot: Map<string, Set<string>>;
|
|
@@ -322,7 +348,7 @@ export default function TrackSelect({
|
|
|
322
348
|
<Box sx={{ p: 2 }}>No folders available.</Box>
|
|
323
349
|
) : (
|
|
324
350
|
<Box sx={{ flex: 1, pt: 1 }}>
|
|
325
|
-
{/* Toolbar row
|
|
351
|
+
{/* Toolbar row */}
|
|
326
352
|
{(folders.length > 1 ||
|
|
327
353
|
(currentView === "folder-detail" && ToolbarExtras)) && (
|
|
328
354
|
<Box
|
|
@@ -354,9 +380,19 @@ export default function TrackSelect({
|
|
|
354
380
|
</Box>
|
|
355
381
|
)}
|
|
356
382
|
|
|
357
|
-
<Stack
|
|
358
|
-
{
|
|
359
|
-
|
|
383
|
+
<Stack
|
|
384
|
+
direction={{ xs: "column", md: "row" }}
|
|
385
|
+
spacing={2}
|
|
386
|
+
sx={{ width: "100%" }}
|
|
387
|
+
>
|
|
388
|
+
{/* Left panel */}
|
|
389
|
+
<Box
|
|
390
|
+
sx={{
|
|
391
|
+
flex: { xs: "none", md: 3 },
|
|
392
|
+
minWidth: 0,
|
|
393
|
+
width: { xs: "100%", md: "auto" },
|
|
394
|
+
}}
|
|
395
|
+
>
|
|
360
396
|
{currentView === "folder-list" ? (
|
|
361
397
|
<FolderList
|
|
362
398
|
folders={folders}
|
|
@@ -375,8 +411,14 @@ export default function TrackSelect({
|
|
|
375
411
|
/>
|
|
376
412
|
)}
|
|
377
413
|
</Box>
|
|
378
|
-
{/* Right panel
|
|
379
|
-
<Box
|
|
414
|
+
{/* Right panel */}
|
|
415
|
+
<Box
|
|
416
|
+
sx={{
|
|
417
|
+
flex: { xs: "none", md: 2 },
|
|
418
|
+
minWidth: 0,
|
|
419
|
+
width: { xs: "100%", md: "auto" },
|
|
420
|
+
}}
|
|
421
|
+
>
|
|
380
422
|
<TreeViewWrapper
|
|
381
423
|
folderTrees={folderTrees}
|
|
382
424
|
selectedCount={selectedCount}
|
|
@@ -392,13 +434,24 @@ export default function TrackSelect({
|
|
|
392
434
|
gap: 2,
|
|
393
435
|
}}
|
|
394
436
|
>
|
|
395
|
-
<
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
437
|
+
<Box sx={{ display: "flex", gap: 2 }}>
|
|
438
|
+
<Button
|
|
439
|
+
variant="outlined"
|
|
440
|
+
color="secondary"
|
|
441
|
+
onClick={handleClear}
|
|
442
|
+
>
|
|
443
|
+
Clear
|
|
444
|
+
</Button>
|
|
445
|
+
{initialSelection && (
|
|
446
|
+
<Button
|
|
447
|
+
variant="outlined"
|
|
448
|
+
color="secondary"
|
|
449
|
+
onClick={handleReset}
|
|
450
|
+
>
|
|
451
|
+
Reset to Default
|
|
452
|
+
</Button>
|
|
453
|
+
)}
|
|
454
|
+
</Box>
|
|
402
455
|
<Box sx={{ display: "flex", gap: 2 }}>
|
|
403
456
|
<Button variant="outlined" onClick={handleCancel}>
|
|
404
457
|
Cancel
|
|
@@ -412,68 +465,23 @@ export default function TrackSelect({
|
|
|
412
465
|
</Button>
|
|
413
466
|
</Box>
|
|
414
467
|
</Box>
|
|
415
|
-
<
|
|
468
|
+
<LimitDialog
|
|
416
469
|
open={limitDialogOpen}
|
|
417
470
|
onClose={() => setLimitDialogOpen(false)}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
<DialogContentText>
|
|
422
|
-
You can select up to {maxTracksLimit} tracks at a time. Please
|
|
423
|
-
remove a track before adding another.
|
|
424
|
-
</DialogContentText>
|
|
425
|
-
</DialogContent>
|
|
426
|
-
<DialogActions>
|
|
427
|
-
<Button onClick={() => setLimitDialogOpen(false)} autoFocus>
|
|
428
|
-
OK
|
|
429
|
-
</Button>
|
|
430
|
-
</DialogActions>
|
|
431
|
-
</Dialog>
|
|
432
|
-
<Dialog
|
|
471
|
+
maxTracks={maxTracksLimit}
|
|
472
|
+
/>
|
|
473
|
+
<ClearDialog
|
|
433
474
|
open={clearDialogOpen}
|
|
434
475
|
onClose={() => setClearDialogOpen(false)}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
? `Clear ${activeFolder.label}`
|
|
445
|
-
: "Clear All Folders"}
|
|
446
|
-
</DialogTitle>
|
|
447
|
-
<DialogContent sx={{ mt: 2 }}>
|
|
448
|
-
<DialogContentText>
|
|
449
|
-
{currentView === "folder-detail" ? (
|
|
450
|
-
<>
|
|
451
|
-
Are you sure you want to clear the selection for{" "}
|
|
452
|
-
<strong>{activeFolder.label}</strong>?
|
|
453
|
-
</>
|
|
454
|
-
) : (
|
|
455
|
-
"Are you sure you want to clear all selections?"
|
|
456
|
-
)}
|
|
457
|
-
</DialogContentText>
|
|
458
|
-
</DialogContent>
|
|
459
|
-
<DialogActions sx={{ justifyContent: "center", gap: 2, pb: 2 }}>
|
|
460
|
-
<Button
|
|
461
|
-
variant="contained"
|
|
462
|
-
color="primary"
|
|
463
|
-
onClick={() => setClearDialogOpen(false)}
|
|
464
|
-
autoFocus
|
|
465
|
-
>
|
|
466
|
-
Cancel
|
|
467
|
-
</Button>
|
|
468
|
-
<Button
|
|
469
|
-
variant="outlined"
|
|
470
|
-
color="secondary"
|
|
471
|
-
onClick={confirmClear}
|
|
472
|
-
>
|
|
473
|
-
Clear
|
|
474
|
-
</Button>
|
|
475
|
-
</DialogActions>
|
|
476
|
-
</Dialog>
|
|
476
|
+
onConfirm={confirmClear}
|
|
477
|
+
folderLabel={activeFolder.label}
|
|
478
|
+
clearAll={currentView === "folder-list"}
|
|
479
|
+
/>
|
|
480
|
+
<ResetDialog
|
|
481
|
+
open={resetDialogOpen}
|
|
482
|
+
onClose={() => setResetDialogOpen(false)}
|
|
483
|
+
onConfirm={confirmReset}
|
|
484
|
+
/>
|
|
477
485
|
</Box>
|
|
478
486
|
)}
|
|
479
487
|
</DialogContent>
|
|
@@ -1,214 +1,214 @@
|
|
|
1
|
-
import Folder from "@mui/icons-material/Folder";
|
|
2
|
-
import IndeterminateCheckBoxRoundedIcon from "@mui/icons-material/IndeterminateCheckBoxRounded";
|
|
3
|
-
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
|
4
|
-
import Collapse from "@mui/material/Collapse";
|
|
5
|
-
import { alpha, styled } from "@mui/material/styles";
|
|
6
|
-
import {
|
|
7
|
-
TreeItemCheckbox,
|
|
8
|
-
TreeItemIconContainer,
|
|
9
|
-
TreeItemLabel,
|
|
10
|
-
} from "@mui/x-tree-view/TreeItem";
|
|
11
|
-
import { TreeItemIcon } from "@mui/x-tree-view/TreeItemIcon";
|
|
12
|
-
import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
|
|
13
|
-
import { useTreeItemModel } from "@mui/x-tree-view/hooks";
|
|
14
|
-
import { useTreeItem } from "@mui/x-tree-view/useTreeItem";
|
|
15
|
-
import React, { ReactNode } from "react";
|
|
16
|
-
import {
|
|
17
|
-
CustomLabelProps,
|
|
18
|
-
CustomTreeItemProps,
|
|
19
|
-
ExtendedTreeItemProps,
|
|
20
|
-
} from "../types";
|
|
21
|
-
|
|
22
|
-
// Everything below is styling for the custom directory look of the tree view
|
|
23
|
-
const TreeItemRoot = styled("li")(({ theme }) => ({
|
|
24
|
-
listStyle: "none",
|
|
25
|
-
margin: 0,
|
|
26
|
-
padding: 0,
|
|
27
|
-
outline: 4,
|
|
28
|
-
color: theme.palette.grey[400],
|
|
29
|
-
...theme.applyStyles("light", {
|
|
30
|
-
color: theme.palette.grey[600], // controls colors of the MUI icons
|
|
31
|
-
}),
|
|
32
|
-
}));
|
|
33
|
-
|
|
34
|
-
const TreeItemLabelText = styled(Typography)({
|
|
35
|
-
color: "black",
|
|
36
|
-
fontFamily: "inherit",
|
|
37
|
-
overflow: "hidden",
|
|
38
|
-
textOverflow: "ellipsis",
|
|
39
|
-
whiteSpace: "nowrap",
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
function CustomLabel({
|
|
43
|
-
icon: Icon,
|
|
44
|
-
children,
|
|
45
|
-
isAssayItem,
|
|
46
|
-
assayName,
|
|
47
|
-
renderIcon,
|
|
48
|
-
...other
|
|
49
|
-
}: CustomLabelProps) {
|
|
50
|
-
const variant = "body2";
|
|
51
|
-
const fontWeight = 500;
|
|
52
|
-
const labelText = typeof children === "string" ? children : "";
|
|
53
|
-
return (
|
|
54
|
-
<TreeItemLabel
|
|
55
|
-
{...other}
|
|
56
|
-
sx={{
|
|
57
|
-
display: "flex",
|
|
58
|
-
alignItems: "center",
|
|
59
|
-
minWidth: 0,
|
|
60
|
-
overflow: "hidden",
|
|
61
|
-
flex: 1,
|
|
62
|
-
}}
|
|
63
|
-
>
|
|
64
|
-
{Icon && React.isValidElement(Icon) ? (
|
|
65
|
-
<Box className="labelIcon" sx={{ mr: 1, flexShrink: 0 }}>
|
|
66
|
-
{Icon}
|
|
67
|
-
</Box>
|
|
68
|
-
) : (
|
|
69
|
-
<Box
|
|
70
|
-
component={Icon as React.ElementType}
|
|
71
|
-
className="labelIcon"
|
|
72
|
-
color="inherit"
|
|
73
|
-
sx={{ mr: 1, fontSize: "1.2rem", flexShrink: 0 }}
|
|
74
|
-
/>
|
|
75
|
-
)}
|
|
76
|
-
<Stack
|
|
77
|
-
direction="row"
|
|
78
|
-
spacing={1}
|
|
79
|
-
alignItems="center"
|
|
80
|
-
sx={{ minWidth: 0, overflow: "hidden", flex: 1 }}
|
|
81
|
-
>
|
|
82
|
-
{assayName && renderIcon && (
|
|
83
|
-
<Box sx={{ flexShrink: 0 }}>{renderIcon(assayName)}</Box>
|
|
84
|
-
)}
|
|
85
|
-
<Tooltip title={labelText} enterDelay={500} placement="top">
|
|
86
|
-
<TreeItemLabelText fontWeight={fontWeight} variant={variant}>
|
|
87
|
-
{labelText}
|
|
88
|
-
</TreeItemLabelText>
|
|
89
|
-
</Tooltip>
|
|
90
|
-
</Stack>
|
|
91
|
-
</TreeItemLabel>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const TreeItemContent = styled("div")(({ theme }) => ({
|
|
96
|
-
padding: theme.spacing(0.5),
|
|
97
|
-
paddingRight: theme.spacing(2),
|
|
98
|
-
paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`,
|
|
99
|
-
width: "100%",
|
|
100
|
-
boxSizing: "border-box", // prevent width + padding to overflow
|
|
101
|
-
position: "relative",
|
|
102
|
-
display: "flex",
|
|
103
|
-
alignItems: "center",
|
|
104
|
-
gap: theme.spacing(1),
|
|
105
|
-
cursor: "pointer",
|
|
106
|
-
WebkitTapHighlightColor: "transparent",
|
|
107
|
-
flexDirection: "row-reverse",
|
|
108
|
-
borderRadius: theme.spacing(0.7),
|
|
109
|
-
marginBottom: theme.spacing(0.5),
|
|
110
|
-
marginTop: theme.spacing(0.5),
|
|
111
|
-
fontWeight: 500,
|
|
112
|
-
"&:hover": {
|
|
113
|
-
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
|
114
|
-
color: "white",
|
|
115
|
-
...theme.applyStyles("light", {
|
|
116
|
-
color: theme.palette.primary.main,
|
|
117
|
-
}),
|
|
118
|
-
},
|
|
119
|
-
}));
|
|
120
|
-
|
|
121
|
-
const getIconFromTreeItemType = (
|
|
122
|
-
itemType: string,
|
|
123
|
-
renderIcon?: (name: string) => ReactNode,
|
|
124
|
-
) => {
|
|
125
|
-
switch (itemType) {
|
|
126
|
-
case "folder":
|
|
127
|
-
return Folder;
|
|
128
|
-
case "removeable":
|
|
129
|
-
return IndeterminateCheckBoxRoundedIcon;
|
|
130
|
-
default:
|
|
131
|
-
return renderIcon ? renderIcon(itemType) : Folder;
|
|
132
|
-
}
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
|
|
136
|
-
props: CustomTreeItemProps,
|
|
137
|
-
ref: React.Ref<HTMLLIElement>,
|
|
138
|
-
) {
|
|
139
|
-
const {
|
|
140
|
-
id,
|
|
141
|
-
itemId,
|
|
142
|
-
label,
|
|
143
|
-
disabled,
|
|
144
|
-
children,
|
|
145
|
-
onRemove,
|
|
146
|
-
renderIcon,
|
|
147
|
-
...other
|
|
148
|
-
} = props;
|
|
149
|
-
|
|
150
|
-
const {
|
|
151
|
-
getContextProviderProps,
|
|
152
|
-
getRootProps,
|
|
153
|
-
getContentProps,
|
|
154
|
-
getIconContainerProps,
|
|
155
|
-
getCheckboxProps,
|
|
156
|
-
getLabelProps,
|
|
157
|
-
getGroupTransitionProps,
|
|
158
|
-
status,
|
|
159
|
-
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
|
|
160
|
-
|
|
161
|
-
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;
|
|
162
|
-
const icon = getIconFromTreeItemType(item.icon, renderIcon);
|
|
163
|
-
|
|
164
|
-
const handleRemoveIconClick = (e: React.MouseEvent) => {
|
|
165
|
-
e.stopPropagation(); // prevent item expand/select
|
|
166
|
-
onRemove?.(item);
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
return (
|
|
170
|
-
<TreeItemProvider {...getContextProviderProps()}>
|
|
171
|
-
<TreeItemRoot {...getRootProps(other)}>
|
|
172
|
-
<TreeItemContent {...getContentProps()}>
|
|
173
|
-
<TreeItemIconContainer {...getIconContainerProps()}>
|
|
174
|
-
<TreeItemIcon status={status} />
|
|
175
|
-
</TreeItemIconContainer>
|
|
176
|
-
<TreeItemCheckbox {...getCheckboxProps()} />
|
|
177
|
-
<CustomLabel
|
|
178
|
-
{...getLabelProps({
|
|
179
|
-
icon:
|
|
180
|
-
item.icon === "removeable" ? (
|
|
181
|
-
<Box
|
|
182
|
-
onClick={handleRemoveIconClick}
|
|
183
|
-
sx={{
|
|
184
|
-
width: 20,
|
|
185
|
-
height: 20,
|
|
186
|
-
display: "flex",
|
|
187
|
-
alignItems: "center",
|
|
188
|
-
justifyContent: "center",
|
|
189
|
-
borderRadius: "4px",
|
|
190
|
-
cursor: "pointer",
|
|
191
|
-
mr: 1,
|
|
192
|
-
"&:hover": {
|
|
193
|
-
backgroundColor: "rgba(0,0,0,0.1)",
|
|
194
|
-
},
|
|
195
|
-
}}
|
|
196
|
-
>
|
|
197
|
-
<IndeterminateCheckBoxRoundedIcon fontSize="small" />
|
|
198
|
-
</Box>
|
|
199
|
-
) : (
|
|
200
|
-
icon
|
|
201
|
-
),
|
|
202
|
-
expandable: (status.expandable && status.expanded).toString(),
|
|
203
|
-
isAssayItem: item.isAssayItem,
|
|
204
|
-
assayName: item.assayName,
|
|
205
|
-
id: item.id,
|
|
206
|
-
renderIcon,
|
|
207
|
-
})}
|
|
208
|
-
/>
|
|
209
|
-
</TreeItemContent>
|
|
210
|
-
{children && <Collapse {...getGroupTransitionProps()} />}
|
|
211
|
-
</TreeItemRoot>
|
|
212
|
-
</TreeItemProvider>
|
|
213
|
-
);
|
|
214
|
-
});
|
|
1
|
+
import Folder from "@mui/icons-material/Folder";
|
|
2
|
+
import IndeterminateCheckBoxRoundedIcon from "@mui/icons-material/IndeterminateCheckBoxRounded";
|
|
3
|
+
import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
|
4
|
+
import Collapse from "@mui/material/Collapse";
|
|
5
|
+
import { alpha, styled } from "@mui/material/styles";
|
|
6
|
+
import {
|
|
7
|
+
TreeItemCheckbox,
|
|
8
|
+
TreeItemIconContainer,
|
|
9
|
+
TreeItemLabel,
|
|
10
|
+
} from "@mui/x-tree-view/TreeItem";
|
|
11
|
+
import { TreeItemIcon } from "@mui/x-tree-view/TreeItemIcon";
|
|
12
|
+
import { TreeItemProvider } from "@mui/x-tree-view/TreeItemProvider";
|
|
13
|
+
import { useTreeItemModel } from "@mui/x-tree-view/hooks";
|
|
14
|
+
import { useTreeItem } from "@mui/x-tree-view/useTreeItem";
|
|
15
|
+
import React, { ReactNode } from "react";
|
|
16
|
+
import {
|
|
17
|
+
CustomLabelProps,
|
|
18
|
+
CustomTreeItemProps,
|
|
19
|
+
ExtendedTreeItemProps,
|
|
20
|
+
} from "../types";
|
|
21
|
+
|
|
22
|
+
// Everything below is styling for the custom directory look of the tree view
|
|
23
|
+
const TreeItemRoot = styled("li")(({ theme }) => ({
|
|
24
|
+
listStyle: "none",
|
|
25
|
+
margin: 0,
|
|
26
|
+
padding: 0,
|
|
27
|
+
outline: 4,
|
|
28
|
+
color: theme.palette.grey[400],
|
|
29
|
+
...theme.applyStyles("light", {
|
|
30
|
+
color: theme.palette.grey[600], // controls colors of the MUI icons
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const TreeItemLabelText = styled(Typography)({
|
|
35
|
+
color: "black",
|
|
36
|
+
fontFamily: "inherit",
|
|
37
|
+
overflow: "hidden",
|
|
38
|
+
textOverflow: "ellipsis",
|
|
39
|
+
whiteSpace: "nowrap",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
function CustomLabel({
|
|
43
|
+
icon: Icon,
|
|
44
|
+
children,
|
|
45
|
+
isAssayItem,
|
|
46
|
+
assayName,
|
|
47
|
+
renderIcon,
|
|
48
|
+
...other
|
|
49
|
+
}: CustomLabelProps) {
|
|
50
|
+
const variant = "body2";
|
|
51
|
+
const fontWeight = 500;
|
|
52
|
+
const labelText = typeof children === "string" ? children : "";
|
|
53
|
+
return (
|
|
54
|
+
<TreeItemLabel
|
|
55
|
+
{...other}
|
|
56
|
+
sx={{
|
|
57
|
+
display: "flex",
|
|
58
|
+
alignItems: "center",
|
|
59
|
+
minWidth: 0,
|
|
60
|
+
overflow: "hidden",
|
|
61
|
+
flex: 1,
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
{Icon && React.isValidElement(Icon) ? (
|
|
65
|
+
<Box className="labelIcon" sx={{ mr: 1, flexShrink: 0 }}>
|
|
66
|
+
{Icon}
|
|
67
|
+
</Box>
|
|
68
|
+
) : (
|
|
69
|
+
<Box
|
|
70
|
+
component={Icon as React.ElementType}
|
|
71
|
+
className="labelIcon"
|
|
72
|
+
color="inherit"
|
|
73
|
+
sx={{ mr: 1, fontSize: "1.2rem", flexShrink: 0 }}
|
|
74
|
+
/>
|
|
75
|
+
)}
|
|
76
|
+
<Stack
|
|
77
|
+
direction="row"
|
|
78
|
+
spacing={1}
|
|
79
|
+
alignItems="center"
|
|
80
|
+
sx={{ minWidth: 0, overflow: "hidden", flex: 1 }}
|
|
81
|
+
>
|
|
82
|
+
{assayName && renderIcon && (
|
|
83
|
+
<Box sx={{ flexShrink: 0 }}>{renderIcon(assayName)}</Box>
|
|
84
|
+
)}
|
|
85
|
+
<Tooltip title={labelText} enterDelay={500} placement="top">
|
|
86
|
+
<TreeItemLabelText fontWeight={fontWeight} variant={variant}>
|
|
87
|
+
{labelText}
|
|
88
|
+
</TreeItemLabelText>
|
|
89
|
+
</Tooltip>
|
|
90
|
+
</Stack>
|
|
91
|
+
</TreeItemLabel>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const TreeItemContent = styled("div")(({ theme }) => ({
|
|
96
|
+
padding: theme.spacing(0.5),
|
|
97
|
+
paddingRight: theme.spacing(2),
|
|
98
|
+
paddingLeft: `calc(${theme.spacing(1)} + var(--TreeView-itemChildrenIndentation) * var(--TreeView-itemDepth))`,
|
|
99
|
+
width: "100%",
|
|
100
|
+
boxSizing: "border-box", // prevent width + padding to overflow
|
|
101
|
+
position: "relative",
|
|
102
|
+
display: "flex",
|
|
103
|
+
alignItems: "center",
|
|
104
|
+
gap: theme.spacing(1),
|
|
105
|
+
cursor: "pointer",
|
|
106
|
+
WebkitTapHighlightColor: "transparent",
|
|
107
|
+
flexDirection: "row-reverse",
|
|
108
|
+
borderRadius: theme.spacing(0.7),
|
|
109
|
+
marginBottom: theme.spacing(0.5),
|
|
110
|
+
marginTop: theme.spacing(0.5),
|
|
111
|
+
fontWeight: 500,
|
|
112
|
+
"&:hover": {
|
|
113
|
+
backgroundColor: alpha(theme.palette.primary.main, 0.1),
|
|
114
|
+
color: "white",
|
|
115
|
+
...theme.applyStyles("light", {
|
|
116
|
+
color: theme.palette.primary.main,
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
}));
|
|
120
|
+
|
|
121
|
+
const getIconFromTreeItemType = (
|
|
122
|
+
itemType: string,
|
|
123
|
+
renderIcon?: (name: string) => ReactNode,
|
|
124
|
+
) => {
|
|
125
|
+
switch (itemType) {
|
|
126
|
+
case "folder":
|
|
127
|
+
return Folder;
|
|
128
|
+
case "removeable":
|
|
129
|
+
return IndeterminateCheckBoxRoundedIcon;
|
|
130
|
+
default:
|
|
131
|
+
return renderIcon ? renderIcon(itemType) : Folder;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const CustomTreeItem = React.forwardRef(function CustomTreeItem(
|
|
136
|
+
props: CustomTreeItemProps,
|
|
137
|
+
ref: React.Ref<HTMLLIElement>,
|
|
138
|
+
) {
|
|
139
|
+
const {
|
|
140
|
+
id,
|
|
141
|
+
itemId,
|
|
142
|
+
label,
|
|
143
|
+
disabled,
|
|
144
|
+
children,
|
|
145
|
+
onRemove,
|
|
146
|
+
renderIcon,
|
|
147
|
+
...other
|
|
148
|
+
} = props;
|
|
149
|
+
|
|
150
|
+
const {
|
|
151
|
+
getContextProviderProps,
|
|
152
|
+
getRootProps,
|
|
153
|
+
getContentProps,
|
|
154
|
+
getIconContainerProps,
|
|
155
|
+
getCheckboxProps,
|
|
156
|
+
getLabelProps,
|
|
157
|
+
getGroupTransitionProps,
|
|
158
|
+
status,
|
|
159
|
+
} = useTreeItem({ id, itemId, children, label, disabled, rootRef: ref });
|
|
160
|
+
|
|
161
|
+
const item = useTreeItemModel<ExtendedTreeItemProps>(itemId)!;
|
|
162
|
+
const icon = getIconFromTreeItemType(item.icon, renderIcon);
|
|
163
|
+
|
|
164
|
+
const handleRemoveIconClick = (e: React.MouseEvent) => {
|
|
165
|
+
e.stopPropagation(); // prevent item expand/select
|
|
166
|
+
onRemove?.(item);
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<TreeItemProvider {...getContextProviderProps()}>
|
|
171
|
+
<TreeItemRoot {...getRootProps(other)}>
|
|
172
|
+
<TreeItemContent {...getContentProps()}>
|
|
173
|
+
<TreeItemIconContainer {...getIconContainerProps()}>
|
|
174
|
+
<TreeItemIcon status={status} />
|
|
175
|
+
</TreeItemIconContainer>
|
|
176
|
+
<TreeItemCheckbox {...getCheckboxProps()} />
|
|
177
|
+
<CustomLabel
|
|
178
|
+
{...getLabelProps({
|
|
179
|
+
icon:
|
|
180
|
+
item.icon === "removeable" ? (
|
|
181
|
+
<Box
|
|
182
|
+
onClick={handleRemoveIconClick}
|
|
183
|
+
sx={{
|
|
184
|
+
width: 20,
|
|
185
|
+
height: 20,
|
|
186
|
+
display: "flex",
|
|
187
|
+
alignItems: "center",
|
|
188
|
+
justifyContent: "center",
|
|
189
|
+
borderRadius: "4px",
|
|
190
|
+
cursor: "pointer",
|
|
191
|
+
mr: 1,
|
|
192
|
+
"&:hover": {
|
|
193
|
+
backgroundColor: "rgba(0,0,0,0.1)",
|
|
194
|
+
},
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
<IndeterminateCheckBoxRoundedIcon fontSize="small" />
|
|
198
|
+
</Box>
|
|
199
|
+
) : (
|
|
200
|
+
icon
|
|
201
|
+
),
|
|
202
|
+
expandable: (status.expandable && status.expanded).toString(),
|
|
203
|
+
isAssayItem: item.isAssayItem,
|
|
204
|
+
assayName: item.assayName,
|
|
205
|
+
id: item.id,
|
|
206
|
+
renderIcon,
|
|
207
|
+
})}
|
|
208
|
+
/>
|
|
209
|
+
</TreeItemContent>
|
|
210
|
+
{children && <Collapse {...getGroupTransitionProps()} />}
|
|
211
|
+
</TreeItemRoot>
|
|
212
|
+
</TreeItemProvider>
|
|
213
|
+
);
|
|
214
|
+
});
|