363 lines
13 KiB
TypeScript
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>
|
|
);
|
|
}
|