frontend/src/component/ComponentOrder.tsx

189 lines
5.9 KiB
TypeScript

import {
Avatar,
Button,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
List,
ListItem,
ListItemAvatar,
ListItemIcon,
ListItemText,
ListSubheader,
Typography,
Theme
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import { DragHandle as DragIcon } from "@mui/icons-material";
import ResponsiveDialog from "common/ResponsiveDialog";
import { useDeviceAPI, usePrevious, useSnackbar } from "hooks";
import { cloneDeep } from "lodash";
import { Component, Device } from "models";
import { sortComponents } from "pbHelpers/Component";
import { pond, quack } from "protobuf-ts/pond";
import { useEffect, useState } from "react";
import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
import { useThemeType } from "../hooks/useThemeType";
import { useGlobalState } from "providers";
const useStyles = makeStyles((_theme: Theme) => {
const themeType = useThemeType()
return ({
item: {
backgroundColor: "rgba(0,0,0,0)",
"&:hover": {
backgroundColor:
themeType === "light" ? "rgba(0,0,0,0.07)" : "rgba(255,255,255,0.07)"
}
}
})}
);
interface ItemProps {
component: Component;
index: number;
}
function ComponentItem(props: ItemProps) {
const classes = useStyles();
const { component, index } = props;
const name = component.name();
return (
<Draggable draggableId={component.key()} index={index}>
{provided => (
<ListItem
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={classes.item}>
<ListItemIcon>
<DragIcon />
</ListItemIcon>
<ListItemText primary={<Typography color="textPrimary">{name}</Typography>} />
<ListItemAvatar>
<Avatar alt={name}>{index + 1}</Avatar>
</ListItemAvatar>
</ListItem>
)}
</Draggable>
);
}
interface ListProps {
open: boolean;
close: (refresh: boolean) => void;
device: Device;
components: Component[];
devicePreferences: pond.DevicePreferences;
}
const filteredComponents = (components: Component[]) => {
const componentBlacklist = [
quack.ComponentType.COMPONENT_TYPE_POWER,
quack.ComponentType.COMPONENT_TYPE_MODEM
];
return components.filter(c => !componentBlacklist.includes(c.settings.type));
};
export default function ComponentOrder(props: ListProps) {
const { open, close, device, devicePreferences } = props;
const [{as}] = useGlobalState();
const deviceAPI = useDeviceAPI();
const { error, success } = useSnackbar();
const prevDisplayOrder = usePrevious(devicePreferences.childDisplayOrder);
const [components, setComponents] = useState<Component[]>(
Array.from(filteredComponents(props.components).values()).sort((a, b: Component) =>
sortComponents(a, b, devicePreferences.childDisplayOrder)
)
);
useEffect(() => {
if (
props.components.length !== components.length ||
prevDisplayOrder !== devicePreferences.childDisplayOrder
) {
setComponents(
Array.from(filteredComponents(props.components).values()).sort((a, b: Component) =>
sortComponents(a, b, devicePreferences.childDisplayOrder)
)
);
}
}, [props.components, prevDisplayOrder, devicePreferences.childDisplayOrder, components.length]);
const updateComponentOrder = (componentOrder: Component[]) => {
let updatedPreferences = cloneDeep(devicePreferences);
updatedPreferences.childDisplayOrder = componentOrder.map(c => c.locationString());
deviceAPI
.updatePreferences(device.id(), updatedPreferences, undefined, undefined, as)
.then(() => success("Successfully update the component display order"))
.catch(() => {
error("Error occured while updating the component display order");
})
.finally(() => close(true));
};
const submit = () => {
updateComponentOrder(components);
};
const reorder = (components: Component[], startIndex: number, endIndex: number): Component[] => {
const result = Array.from(components);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const onDragEnd = (result: DropResult) => {
if (!result.destination) {
return;
}
setComponents(reorder(components, result.source.index, result.destination.index));
};
return (
<ResponsiveDialog open={open} onClose={() => close(false)} maxWidth="sm" fullWidth>
<DialogTitle id="component-order-dialog-title">
Component Display Order
<Typography variant="body2" color="textSecondary">
{device.name()}
</Typography>
</DialogTitle>
<DialogContent>
{components.length < 1 ? (
<DialogContentText>No components to reorder</DialogContentText>
) : (
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="list">
{provided => (
<List
ref={provided.innerRef}
{...provided.droppableProps}
subheader={
<ListSubheader disableSticky>
<Typography variant="body2" color="textSecondary">
Drag and Drop to change the display order
</Typography>
</ListSubheader>
}>
{components.map((component, index) => (
<ComponentItem key={component.key()} component={component} index={index} />
))}
{provided.placeholder}
</List>
)}
</Droppable>
</DragDropContext>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => close(false)} color="primary">
Close
</Button>
<Button onClick={submit} color="primary">
Submit
</Button>
</DialogActions>
</ResponsiveDialog>
);
}