CEC-4564: add visual nesting to CAN signal control (#406)

* add TrieSelect

* setup menu button

* CEC-4564: add trie select component

* add visual nesting

* remove unused imports
This commit is contained in:
Tristan Timblin
2023-08-02 15:44:08 -04:00
committed by GitHub
parent c118f676ee
commit 27ffdf01b0
7 changed files with 852 additions and 936 deletions

View File

@@ -338,76 +338,9 @@ exports[`Render Render 1`] = `
<div
class="MuiGrid-root MuiGrid-item MuiGrid-grid-xs-12"
>
<div
class="MuiBox-root MuiBox-root-0"
>
<button
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary Mui-disabled Mui-disabled"
disabled=""
tabindex="-1"
type="button"
>
<span
class="MuiButton-label"
>
Select CAN Signals
</span>
</button>
<label
class="MuiFormControlLabel-root MuiFormControlLabel-labelPlacementStart"
>
<span
aria-disabled="false"
class="MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-0 MuiCheckbox-root MuiCheckbox-colorSecondary PrivateSwitchBase-checked-0 Mui-checked MuiIconButton-colorSecondary"
>
<span
class="MuiIconButton-label"
>
<input
checked=""
class="PrivateSwitchBase-input-0"
data-indeterminate="false"
type="checkbox"
value=""
/>
<svg
aria-hidden="true"
class="MuiSvgIcon-root"
focusable="false"
viewBox="0 0 24 24"
>
<path
d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"
/>
</svg>
</span>
<span
class="MuiTouchRipple-root"
/>
</span>
<span
class="MuiTypography-root MuiFormControlLabel-label MuiTypography-body1"
>
Select All 0
</span>
</label>
</div>
<div
class="MuiCollapse-root MuiCollapse-hidden"
style="min-height: 0px;"
>
<div
class="MuiCollapse-wrapper"
>
<div
class="MuiCollapse-wrapperInner"
>
<ul
class="MuiList-root MuiList-padding"
/>
</div>
</div>
</div>
<ul
class="MuiList-root MuiList-padding"
/>
<ul
class="makeStyles-chipList-0"
/>

View File

@@ -169,7 +169,7 @@ const MainForm = ({ id }) => {
</Grid>
<Grid item xs={12}>
<TrieSelect
label="Select CAN Signals"
label="All CAN Signals"
classification="Signals"
options={canSignals.map((signal => signal.signal_name))}
onChange={setSelectedCanSignals}

View File

@@ -1,10 +1,10 @@
import React, { useState } from "react";
import React from "react";
import {
Box,
Button,
Checkbox,
Chip,
Collapse,
Divider,
FormControlLabel,
List,
ListItem,
@@ -41,50 +41,19 @@ const TrieSelectList = ({
classification,
options,
}) => {
const { selected, setSelected, remove } = useTrieSelect();
const [open, setOpen] = useState(false);
const { selected, remove } = useTrieSelect();
const classes = useStyles();
const trie = new Trie(options);
const handleExpand = () => {
setOpen(open => !open);
};
const handleSelectAll = () => {
setSelected((selected) => {
if (selected.length === 0) {
return options;
}
return [];
})
}
return (
<>
<Box sx={{ display: "flex", alignItems: "center" }}>
<Button onClick={handleExpand} variant="contained" color="primary" disabled={options.length === 0}>
{label}
</Button>
<FormControlLabel
label={`Select All ${options.length}`}
labelPlacement="start"
control={
<Checkbox
checked={selected.length === options.length}
onClick={handleSelectAll}
/>
}
<List>
<TrieSelectLevel
node={trie.getRoot()}
classification={classification}
label={label}
/>
</Box>
<Collapse in={open}>
<List>
<TrieSelectLevel
node={trie.getRoot()}
classification={classification}
/>
</List>
</Collapse>
</List>
<ul className={classes.chipList}>
{selected.map((signal) => {
return (
@@ -102,13 +71,18 @@ const TrieSelectLevel = ({
prefix = "",
node,
children,
classification
classification,
label,
level = -1,
}) => {
const classes = useStyles();
const { selected, add, remove } = useTrieSelect();
const [open, setOpen] = React.useState(false);
const completeChildren = Object.values(node.children).filter(child => child.isComplete);
const hasCompleteChildren = completeChildren.length > 0;
const descendantCount = `${node.count} ${classification}`;
const isParentOfMultiple = completeChildren.length > 1;
const isAdoptiveParentOfMultiple = completeChildren.length <= 1 && node.count > 1;
const handleExpand = () => {
setOpen(open => !open);
@@ -140,7 +114,9 @@ const TrieSelectLevel = ({
return Array.from(result);
}
const listItems = (
const indent = (level) => `${12 * level}px`;
const listItems = (level) => (
<>
{children}
{Object.values(node.children).map((child) => {
@@ -152,13 +128,16 @@ const TrieSelectLevel = ({
node={child}
key={fullName}
classification={classification}
level={level}
>
{child.isComplete && (
<ListItem className={classes.whiteBackground}>
<ListItemIcon>
<Checkbox checked={isChecked} onClick={() => handleCheck([fullName], isChecked)} />
</ListItemIcon>
<ListItemText primary={fullName} />
<Box sx={{ paddingLeft: indent(level), display: "flex", alignItems: "center" }}>
<ListItemIcon>
<Checkbox checked={isChecked} onClick={() => handleCheck([fullName], isChecked)} />
</ListItemIcon>
<ListItemText primary={fullName} />
</Box>
</ListItem>
)}
</TrieSelectLevel>
@@ -167,15 +146,15 @@ const TrieSelectLevel = ({
</>
);
const isParentOfMultiple = completeChildren.length > 1;
const isAdoptiveParentOfMultiple = completeChildren.length <= 1 && node.count > 1;
if (isParentOfMultiple || isAdoptiveParentOfMultiple) {
const allDescendants = getWords(node, prefix);
const isSelectAll = allDescendants.every(descendant => selected.includes(descendant));
return (
<>
<ListItem onClick={handleExpand} divider>
<ListItemText primary={prefix} secondary={descendantCount} />
<ListItem onClick={handleExpand} divider className={classes.defaultBackground}>
<Box sx={{ paddingLeft: indent(level) }}>
<ListItemText primary={prefix === "" ? label : prefix} secondary={descendantCount} />
</Box>
<ListItemSecondaryAction>
<Box sx={{ display: "flex", alignItems: "center", gap: "16px" }}>
<FormControlLabel
@@ -188,18 +167,19 @@ const TrieSelectLevel = ({
/>
}
/>
{open ? <ExpandLess /> : <ExpandMore />}
{open ? (<ExpandLess />) : (<ExpandMore />)}
</Box>
</ListItemSecondaryAction>
</ListItem>
<Collapse in={open}>
<List>
{listItems}
{listItems(level + 1)}
</List>
{hasCompleteChildren && <Divider />}
</Collapse>
</>
)
}
return listItems;
return listItems(level);
}

View File

@@ -38,7 +38,7 @@ describe("TrieSelect", () => {
it("properly passes payload to callback", async () => {
const mockCallback = jest.fn();
const { getByText } = render(
const { getAllByText } = render(
<TrieSelect
label={"The input label"}
classification="Signal"
@@ -47,7 +47,7 @@ describe("TrieSelect", () => {
/>
);
const selectAll = getByText("Select All 8");
const selectAll = getAllByText("Select All")[0];
userEvent.click(selectAll);
expect(mockCallback).toHaveBeenCalledWith(options);
});

View File

@@ -1 +1 @@
export { TrieSelect } from "./TrieSelect";
export * from "./TrieSelect";

View File

@@ -274,6 +274,7 @@ const useStyles = makeStyles((theme) => ({
maxWidth: "100%",
},
whiteBackground: { backgroundColor: "White" },
defaultBackground: { backgroundColor: "#fafafa" },
progressIcon: { width: 40, height: 40 },
progressSuccess: { color: "green" },
progressError: { color: "red" },