frontend/src/ventilation/drawers/ComponentCards.tsx

363 lines
13 KiB
TypeScript

import { useComponentAPI, useMineAPI, useThemeType } from "hooks";
import React, { useEffect, useState } from "react";
import { Component } from "models";
import {
Avatar,
Box,
Card,
CardContent,
CardHeader,
Checkbox,
CircularProgress,
Grid2 as Grid,
Theme,
Typography,
useTheme
} from "@mui/material";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { pond } from "protobuf-ts/pond";
import { GetComponentIcon } from "pbHelpers/ComponentType";
import EventBlocker from "common/EventBlocker";
import UnitMeasurementSummary from "component/UnitMeasurementSummary";
import { UnitMeasurement } from "models/UnitMeasurement";
import { useGlobalState } from "providers";
import DelayedTextField from "common/DelayedTextInput";
import { makeStyles } from "@mui/styles";
import { useNavigate } from "react-router-dom";
interface Props {
deviceId: number;
showMobile?: boolean;
mineComponents: Component[];
setMineComponents: React.Dispatch<React.SetStateAction<Component[]>>;
mineComponentPreferences: Map<string, pond.MineComponentPreferences>;
setMineComponentPreferences: React.Dispatch<
React.SetStateAction<Map<string, pond.MineComponentPreferences>>
>;
keys?: string[];
types?: string[];
}
const useStyles = makeStyles((theme: Theme) => {
return ({
avatarIcon: {
width: theme.spacing(3),
height: theme.spacing(3)
},
card: {
position: "relative",
display: "flex",
flexDirection: "column",
overflow: "visible",
width: "100%"
},
cardContent: {
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
padding: theme.spacing(1),
paddingTop: 0
},
onHover: {
"&:hover": {
textDecoration: "underline",
cursor: "pointer"
}
}
})
});
export default function ComponentCards(props: Props) {
const {
deviceId,
showMobile,
setMineComponents,
mineComponents,
mineComponentPreferences,
setMineComponentPreferences
} = props;
const componentAPI = useComponentAPI();
const mineAPI = useMineAPI();
const [cards, setCards] = useState<JSX.Element[]>([]);
const [loading, setLoading] = useState(false);
const [firstLoad, setFirstLoad] = useState(true);
const themeType = useThemeType();
const classes = useStyles();
const [{ user, as }] = useGlobalState();
const theme = useTheme();
const navigate = useNavigate()
const keys = props.keys ? props.keys : getContextKeys();
const types = props.types ? props.types : getContextTypes();
const removeComponent = (device: string, component: Component) => {
let mine = "";
types.forEach((type, index) => {
if (type === "mine") mine = keys[index];
});
mineAPI.addComponent(mine, device, component.key(), [pond.Permission.PERMISSION_INVALID], as);
};
const addComponent = (device: string, component: Component) => {
let mine = "";
types.forEach((type, index) => {
if (type === "mine") mine = keys[index];
});
mineAPI
.addComponent(mine, deviceId.toString(), component.key(), [
pond.Permission.PERMISSION_READ,
pond.Permission.PERMISSION_SHARE,
pond.Permission.PERMISSION_WRITE,
pond.Permission.PERMISSION_USERS
], as)
.then(() => {
let prefs = getPreferences(component);
mineComponentPreferences.set(component.key(), prefs);
setMineComponentPreferences(new Map(mineComponentPreferences));
let c = mineComponents;
c.push(component);
setMineComponents([...c]);
});
};
const getPreferences = (component: Component) => {
let exists = mineComponents.find(comp => comp.key() === component.key());
let prefs = mineComponentPreferences.get(component.key());
if ((!prefs?.sensors || prefs.sensors.length < 1) && exists) {
prefs = pond.MineComponentPreferences.create();
prefs.sensors = [];
if (component.lastMeasurement.length > 0) {
if (component.lastMeasurement[0].values)
component.lastMeasurement[0].values[0].values.forEach((v, i) => {
let sensor = pond.MineSensorPreferences.create();
sensor.enabled = false;
sensor.x = 20;
sensor.y = 20;
sensor.nickname = component.name();
if (component.lastMeasurement[0].values[0].values.length > 1) {
sensor.nickname = "Sensor " + i;
}
prefs!.sensors.push(sensor);
});
mineComponentPreferences.set(component.key(), prefs);
setMineComponentPreferences(new Map(mineComponentPreferences));
}
}
return prefs!;
};
useEffect(() => {
let k = [...keys];
k.push(deviceId.toString());
let t = [...types];
t.push("device");
setLoading(true);
componentAPI
.list(deviceId, false, k, t, true)
.then(resp => {
if (loading) return;
if (!resp.data.components) return;
setLoading(true);
let components: Component[] = [];
let cards: JSX.Element[] = [];
resp.data.components.forEach((comp, index) => {
if (!comp) return;
let component = Component.create(comp);
// Ignore component without data
if (!component.lastMeasurement) return;
let measurement: UnitMeasurement[] = [];
if (component.lastMeasurement.forEach) {
component.lastMeasurement.forEach(m => {
measurement.push(UnitMeasurement.any(m, user));
});
}
components.push(component);
const exists = mineComponents.find(comp => comp.key() === component.key());
const componentIcon = GetComponentIcon(component.type(), component.subType(), themeType);
let prefs = getPreferences(component);
cards.push(
<div key={cards.length + 1} style={{ padding: 6 }}>
<Card raised className={classes.card}>
<CardHeader
avatar={
componentIcon ? (
<Avatar
variant="square"
src={componentIcon}
className={classes.avatarIcon}
alt={component.name()}
/>
) : (
<Box className={classes.avatarIcon} />
)
}
title={
component.name() ? (
<Typography
className={classes.onHover}
onClick={() => {
navigate(
window.location.pathname +
"/devices/" +
deviceId +
"/components/" +
component.key()
, { replace: true });
}}>
{component.name()}
</Typography>
) : (
"No Name"
)
}
subheader={
<UnitMeasurementSummary
component={component}
reading={UnitMeasurement.convertLastMeasurement(measurement)}
dense
/>
}
action={
<EventBlocker>
<Checkbox
checked={!!exists}
disabled={loading}
onChange={() => {
if (!exists) {
addComponent(deviceId.toString(), component);
} else {
removeComponent(deviceId.toString(), component);
}
}}
/>
</EventBlocker>
}
/>
{exists && (
<CardContent className={classes.cardContent}>
<EventBlocker>
{prefs?.sensors.map((sensor, i) => {
let reading = UnitMeasurement.convertLastMeasurementByNode(measurement, i);
return (
<Grid
container
spacing={2}
key={"grid-" + i}
direction="row"
style={{
boxShadow: "inset 0px 0px 12px rgba(0,0,0,0.5)",
marginTop: i > 0 ? 10 : 0
}}>
<Grid size={{ xs: 10 }}>
<Grid container direction="column">
<DelayedTextField
value={sensor.nickname}
variant="outlined"
title="Nickname"
onChange={e => {
let prefs = mineComponentPreferences.get(component.key());
if (prefs) {
prefs.sensors[i].nickname = e.toString();
mineComponentPreferences.set(component.key(), prefs);
setMineComponentPreferences(
new Map(mineComponentPreferences)
);
}
}}
delayMS={2000}
size="small"
inputProps={{
style: { fontSize: 12 }
}}
disabled={loading}
/>
</Grid>
<Grid container spacing={1}>
<Grid style={{ marginLeft: 6 }}>
<UnitMeasurementSummary
component={component}
reading={reading}
dense
/>
</Grid>
<Grid >
<Typography variant={"caption"} style={{ fontSize: 12 }}>
{"(" + sensor.x + ", " + sensor.y + ")"}
</Typography>
</Grid>
</Grid>
</Grid>
<Grid size={{ xs: 2 }}>
<Box
display="flex"
justifyContent={"center"}
alignItems={"center"}
minHeight="100%"
maxHeight="100%">
<Checkbox
checked={sensor.enabled}
size="small"
onChange={() => {
let prefs = mineComponentPreferences.get(component.key());
if (sensor.enabled) {
if (prefs) {
prefs.sensors[i].enabled = false;
sensor.enabled = false;
mineComponentPreferences.set(component.key(), prefs);
setMineComponentPreferences(
new Map(mineComponentPreferences)
);
}
} else {
if (prefs) {
sensor.enabled = true;
mineComponentPreferences.set(component.key(), prefs);
setMineComponentPreferences(
new Map(mineComponentPreferences)
);
}
}
}}
/>
</Box>
</Grid>
</Grid>
);
})}
</EventBlocker>
</CardContent>
)}
</Card>
</div>
);
});
setCards(cards);
//setComponents(components);
})
.finally(() => {
setFirstLoad(false);
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [deviceId, componentAPI, showMobile, keys, types]);
return (
<Grid container direction="row" justifyContent="flex-start" spacing={2}>
{firstLoad ? (
<div
style={{
margin: theme.spacing(2),
marginLeft: "auto",
marginRight: "auto"
}}>
<CircularProgress />
</div>
) : (
cards
)}
</Grid>
);
}