517 lines
17 KiB
TypeScript
517 lines
17 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Card,
|
|
Dialog,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogTitle,
|
|
Grid2 as Grid,
|
|
InputAdornment,
|
|
MenuItem,
|
|
Slider,
|
|
TextField,
|
|
Typography
|
|
} from "@mui/material";
|
|
import { useMobile } from "hooks";
|
|
import { cloneDeep } from "lodash";
|
|
import { DevicePreset } from "models/DevicePreset";
|
|
import { ObjectTypeString } from "objects/ObjectDescriber";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useGlobalState, useSnackbar } from "providers";
|
|
import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
interface Props {
|
|
preset: DevicePreset;
|
|
existing?: boolean;
|
|
objectKey?: string;
|
|
objectType?: pond.ObjectType;
|
|
addCallback?: (preset: DevicePreset, tempKey: string) => void;
|
|
removeCallback?: (preset: DevicePreset) => void;
|
|
updateCallback?: (oldPreset: DevicePreset, newPreset: DevicePreset) => void;
|
|
}
|
|
|
|
export default function DevicePresetCard(props: Props) {
|
|
const {
|
|
preset,
|
|
existing,
|
|
objectKey,
|
|
objectType,
|
|
addCallback,
|
|
removeCallback,
|
|
updateCallback
|
|
} = props;
|
|
const devicePresetAPI = useDevicePresetAPI();
|
|
const [presetName, setPresetName] = useState("");
|
|
const [presetType, setPresetType] = useState<pond.PresetType>(1);
|
|
const [controllerType, setControllerType] = useState<pond.ControllerType>(1);
|
|
const [openDeleteConfirmation, setOpenDeleteConfirmation] = useState(false);
|
|
const { openSnack } = useSnackbar();
|
|
const [tempString, setTempString] = useState("0");
|
|
const [tempC, setTempC] = useState(0);
|
|
const [humString, setHumString] = useState("0");
|
|
const [hum, setHum] = useState(0);
|
|
const isMobile = useMobile();
|
|
const [{user}] = useGlobalState();
|
|
|
|
useEffect(() => {
|
|
setPresetName(preset.name);
|
|
setPresetType(preset.settings.type);
|
|
setControllerType(preset.settings.controller);
|
|
setTempC(preset.settings.temperature);
|
|
setHum(preset.settings.humidity);
|
|
setHumString(preset.settings.humidity.toString());
|
|
let displayTemp = preset.settings.temperature.toString();
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed();
|
|
}
|
|
setTempString(displayTemp);
|
|
}, [preset, user]);
|
|
|
|
const saveNewPreset = () => {
|
|
let typestring;
|
|
if (objectType) {
|
|
typestring = ObjectTypeString(objectType);
|
|
}
|
|
let p = preset;
|
|
p.name = presetName;
|
|
p.settings.type = presetType;
|
|
p.settings.controller = controllerType;
|
|
p.settings.temperature = tempC;
|
|
p.settings.humidity = hum;
|
|
|
|
devicePresetAPI
|
|
.addDevicePreset(p.name, p.settings, objectKey, typestring)
|
|
.then(resp => {
|
|
//get the temp key from the preset
|
|
let temp = p.key;
|
|
//update the key in the preset with the key that came back from the add
|
|
p.key = resp.data.key;
|
|
//use the callback function passing in the preset and the temp key
|
|
if (addCallback) {
|
|
addCallback(p, temp);
|
|
}
|
|
openSnack("New preset saved");
|
|
})
|
|
.catch(err => {
|
|
openSnack("Failed to save new preset");
|
|
});
|
|
};
|
|
|
|
const updatePreset = () => {
|
|
let newPreset = cloneDeep(preset);
|
|
let oldPreset = cloneDeep(preset);
|
|
newPreset.name = presetName;
|
|
newPreset.settings.type = presetType;
|
|
newPreset.settings.controller = controllerType;
|
|
newPreset.settings.temperature = tempC;
|
|
newPreset.settings.humidity = hum;
|
|
|
|
devicePresetAPI
|
|
.updateDevicePreset(newPreset.key, newPreset.name, newPreset.settings)
|
|
.then(resp => {
|
|
if (updateCallback) {
|
|
updateCallback(oldPreset, newPreset);
|
|
}
|
|
openSnack("Preset Updated");
|
|
})
|
|
.catch(err => {
|
|
openSnack("There was a problem updating the preset");
|
|
});
|
|
};
|
|
|
|
const removePreset = () => {
|
|
devicePresetAPI
|
|
.removeDevicePreset(preset.key)
|
|
.then(resp => {
|
|
openSnack("Preset has been deleted");
|
|
setOpenDeleteConfirmation(false);
|
|
if (removeCallback) {
|
|
removeCallback(preset);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
openSnack("There was a problem deleting this preset");
|
|
});
|
|
};
|
|
|
|
const deleteConfirmation = () => {
|
|
return (
|
|
<Dialog
|
|
open={openDeleteConfirmation}
|
|
onClose={() => {
|
|
setOpenDeleteConfirmation(false);
|
|
}}>
|
|
<DialogTitle>Delete Preset</DialogTitle>
|
|
<DialogContent>This action will delete the selected preset</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
onClick={() => {
|
|
setOpenDeleteConfirmation(false);
|
|
}}>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
removePreset();
|
|
}}>
|
|
Confirm
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
);
|
|
};
|
|
|
|
const presetComparitors = () => {
|
|
if (controllerType === pond.ControllerType.CONTROLLER_TYPE_HEATER) {
|
|
switch (presetType) {
|
|
case pond.PresetType.PRESET_TYPE_DRYING:
|
|
return { temp: "below", hum: "above" };
|
|
case pond.PresetType.PRESET_TYPE_COOLING:
|
|
return { temp: "below", hum: "above" };
|
|
}
|
|
} else {
|
|
//the controller type is a fan
|
|
switch (presetType) {
|
|
case pond.PresetType.PRESET_TYPE_DRYING:
|
|
return { temp: "above", hum: "below" };
|
|
case pond.PresetType.PRESET_TYPE_COOLING:
|
|
return { temp: "below", hum: "below" };
|
|
case pond.PresetType.PRESET_TYPE_HYDRATING:
|
|
return { temp: "below", hum: "above" };
|
|
}
|
|
}
|
|
//replace having a default case with final return
|
|
return { temp: "", hum: "" };
|
|
};
|
|
|
|
return (
|
|
<Card raised style={{ padding: 10, width: "100%" }}>
|
|
{deleteConfirmation()}
|
|
<Grid container direction="row" alignContent="center" alignItems="center">
|
|
<Grid size={{xs:12, sm:6}}>
|
|
<TextField
|
|
variant="outlined"
|
|
margin="normal"
|
|
style={{ width: "100%" }}
|
|
value={presetName}
|
|
onChange={e => {
|
|
setPresetName(e.target.value);
|
|
}}
|
|
/>
|
|
</Grid>
|
|
<Grid size={{xs:12, sm:6}}>
|
|
{presetType !== pond.PresetType.PRESET_TYPE_UNKNOWN &&
|
|
controllerType !== pond.ControllerType.CONTROLLER_TYPE_UNKNOWN && (
|
|
<Box paddingLeft={2}>
|
|
<Typography>
|
|
Toggle{" "}
|
|
{controllerType === pond.ControllerType.CONTROLLER_TYPE_HEATER ? "heater" : "fan"}{" "}
|
|
on when temperature {presetComparitors().temp} and humidity{" "}
|
|
{presetComparitors().hum} set points
|
|
</Typography>
|
|
</Box>
|
|
)}
|
|
</Grid>
|
|
<Grid size={6}>
|
|
{/* type */}
|
|
<TextField
|
|
style={{ width: "100%", marginRight: 5 }}
|
|
margin="normal"
|
|
select
|
|
label="Type"
|
|
variant="outlined"
|
|
value={presetType}
|
|
onChange={e => {
|
|
setPresetType(+e.target.value as pond.PresetType);
|
|
}}>
|
|
<MenuItem
|
|
key={pond.PresetType.PRESET_TYPE_UNKNOWN}
|
|
value={pond.PresetType.PRESET_TYPE_UNKNOWN}>
|
|
Mode For Preset
|
|
</MenuItem>
|
|
<MenuItem
|
|
key={pond.PresetType.PRESET_TYPE_DRYING}
|
|
value={pond.PresetType.PRESET_TYPE_DRYING}>
|
|
Drying
|
|
</MenuItem>
|
|
<MenuItem
|
|
key={pond.PresetType.PRESET_TYPE_COOLING}
|
|
value={pond.PresetType.PRESET_TYPE_COOLING}>
|
|
Cooling
|
|
</MenuItem>
|
|
<MenuItem
|
|
key={pond.PresetType.PRESET_TYPE_HYDRATING}
|
|
value={pond.PresetType.PRESET_TYPE_HYDRATING}>
|
|
Hydrating
|
|
</MenuItem>
|
|
</TextField>
|
|
</Grid>
|
|
<Grid size={6}>
|
|
{/* controller type */}
|
|
<TextField
|
|
style={{ width: "100%", marginLeft: 5 }}
|
|
margin="normal"
|
|
select
|
|
label="Controller Type"
|
|
variant="outlined"
|
|
value={controllerType}
|
|
onChange={e => {
|
|
setControllerType(+e.target.value as pond.ControllerType);
|
|
}}>
|
|
<MenuItem
|
|
key={pond.ControllerType.CONTROLLER_TYPE_UNKNOWN}
|
|
value={pond.ControllerType.CONTROLLER_TYPE_UNKNOWN}>
|
|
Controller For Preset
|
|
</MenuItem>
|
|
<MenuItem
|
|
key={pond.ControllerType.CONTROLLER_TYPE_HEATER}
|
|
value={pond.ControllerType.CONTROLLER_TYPE_HEATER}>
|
|
Heater
|
|
</MenuItem>
|
|
<MenuItem
|
|
key={pond.ControllerType.CONTROLLER_TYPE_FAN}
|
|
value={pond.ControllerType.CONTROLLER_TYPE_FAN}>
|
|
Fan
|
|
</MenuItem>
|
|
</TextField>
|
|
</Grid>
|
|
{/* temp slider */}
|
|
{isMobile ? (
|
|
<Grid size={12}>
|
|
<Box
|
|
display="flex"
|
|
alignContent="center"
|
|
alignItems="center"
|
|
justifyContent="space-between">
|
|
<Typography>Temperature</Typography>
|
|
<TextField
|
|
style={{ marginLeft: 5 }}
|
|
margin="dense"
|
|
variant="outlined"
|
|
type="number"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
? "°F"
|
|
: "°C"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
error={isNaN(+tempString)}
|
|
value={tempString}
|
|
onChange={e => {
|
|
if (!isNaN(+e.target.value)) {
|
|
let temp = +e.target.value;
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
|
|
}
|
|
setTempC(temp);
|
|
}
|
|
setTempString(e.target.value);
|
|
}}
|
|
/>
|
|
</Box>
|
|
<Box paddingLeft={2} paddingRight={2}>
|
|
<Slider
|
|
//orientation="vertical"
|
|
value={tempC}
|
|
min={-40}
|
|
max={40}
|
|
valueLabelDisplay="auto"
|
|
valueLabelFormat={value => {
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
return ((value * 9) / 5 + 32).toFixed() + "°F";
|
|
}
|
|
return value.toFixed() + "°C";
|
|
}}
|
|
onChange={(_, value) => {
|
|
setTempC(value as number);
|
|
setTempString(value.toString());
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
) : (
|
|
<React.Fragment>
|
|
<Grid size={2}>
|
|
<Typography>Temperature</Typography>
|
|
</Grid>
|
|
<Grid size={7}>
|
|
<Box paddingRight={2}>
|
|
<Slider
|
|
//orientation="vertical"
|
|
value={tempC}
|
|
min={-40}
|
|
max={40}
|
|
valueLabelDisplay="auto"
|
|
valueLabelFormat={value => {
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
return ((value * 9) / 5 + 32).toFixed() + "°F";
|
|
}
|
|
return value.toFixed() + "°C";
|
|
}}
|
|
onChange={(_, value) => {
|
|
setTempC(value as number);
|
|
setTempString(value.toString());
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
<Grid size={3}>
|
|
<TextField
|
|
style={{ width: "100%" }}
|
|
margin="dense"
|
|
variant="outlined"
|
|
type="number"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
{user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
|
? "°F"
|
|
: "°C"}
|
|
</InputAdornment>
|
|
)
|
|
}}
|
|
error={isNaN(+tempString)}
|
|
value={tempString}
|
|
onChange={e => {
|
|
if (!isNaN(+e.target.value)) {
|
|
let temp = +e.target.value;
|
|
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
|
temp = Math.round((((temp - 32) * 5) / 9) * 10) / 10;
|
|
}
|
|
setTempC(temp);
|
|
}
|
|
setTempString(e.target.value);
|
|
}}
|
|
/>
|
|
</Grid>
|
|
</React.Fragment>
|
|
)}
|
|
{/* humidity slider */}
|
|
{isMobile ? (
|
|
<Grid size={12}>
|
|
<Box
|
|
display="flex"
|
|
alignContent="center"
|
|
alignItems="center"
|
|
justifyContent="space-between">
|
|
<Typography>Humidity</Typography>
|
|
<TextField
|
|
style={{ marginLeft: 5 }}
|
|
margin="dense"
|
|
variant="outlined"
|
|
type="number"
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">%</InputAdornment>
|
|
}}
|
|
error={isNaN(+humString)}
|
|
value={humString}
|
|
onChange={e => {
|
|
if (!isNaN(+humString)) {
|
|
setHum(+e.target.value);
|
|
}
|
|
setHumString(e.target.value);
|
|
}}
|
|
/>
|
|
</Box>
|
|
<Box paddingLeft={2} paddingRight={2}>
|
|
<Slider
|
|
//orientation="vertical"
|
|
value={hum}
|
|
min={0}
|
|
max={100}
|
|
valueLabelDisplay="auto"
|
|
valueLabelFormat={value => value.toFixed() + "%"}
|
|
onChange={(_, value) => {
|
|
setHum(value as number);
|
|
setHumString(value.toString());
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
) : (
|
|
<React.Fragment>
|
|
<Grid size={2}>
|
|
<Typography>Humidity</Typography>
|
|
</Grid>
|
|
<Grid size={7}>
|
|
<Box paddingRight={2}>
|
|
<Slider
|
|
//orientation="vertical"
|
|
value={hum}
|
|
min={0}
|
|
max={100}
|
|
valueLabelDisplay="auto"
|
|
valueLabelFormat={value => value.toFixed() + "%"}
|
|
onChange={(_, value) => {
|
|
setHum(value as number);
|
|
setHumString(value.toString());
|
|
}}
|
|
/>
|
|
</Box>
|
|
</Grid>
|
|
<Grid size={3}>
|
|
<TextField
|
|
style={{ width: "100%" }}
|
|
margin="dense"
|
|
variant="outlined"
|
|
type="number"
|
|
InputProps={{
|
|
endAdornment: <InputAdornment position="end">%</InputAdornment>
|
|
}}
|
|
error={isNaN(+humString)}
|
|
value={humString}
|
|
onChange={e => {
|
|
if (!isNaN(+humString)) {
|
|
setHum(+e.target.value);
|
|
}
|
|
setHumString(e.target.value);
|
|
}}
|
|
/>
|
|
</Grid>
|
|
</React.Fragment>
|
|
)}
|
|
</Grid>
|
|
<Box display="flex" justifyContent="flex-end" alignContent="flex-end" marginTop={1}>
|
|
{existing && (
|
|
<Button
|
|
variant="contained"
|
|
style={{ background: "red" }}
|
|
onClick={() => {
|
|
setOpenDeleteConfirmation(true);
|
|
}}>
|
|
Delete
|
|
</Button>
|
|
)}
|
|
{existing ? (
|
|
<Button
|
|
style={{ marginLeft: 5 }}
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => {
|
|
updatePreset();
|
|
}}>
|
|
Update
|
|
</Button>
|
|
) : (
|
|
<Button
|
|
disabled={
|
|
presetType === pond.PresetType.PRESET_TYPE_UNKNOWN ||
|
|
controllerType === pond.ControllerType.CONTROLLER_TYPE_UNKNOWN
|
|
}
|
|
onClick={() => saveNewPreset()}
|
|
variant="contained"
|
|
color="primary">
|
|
Save
|
|
</Button>
|
|
)}
|
|
</Box>
|
|
</Card>
|
|
);
|
|
}
|