added tons of files, got the bin page to render

This commit is contained in:
Carter 2025-03-12 12:41:20 -06:00
parent a027b8a96f
commit 9bbbf0940e
34 changed files with 10492 additions and 19 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,517 @@
import {
Box,
Button,
Card,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
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 { useSnackbar } from "providers";
// import { useDevicePresetAPI } from "providers/pond/devicePresetAPI";
import React, { useEffect, useState } from "react";
import { getTemperatureUnit } from "utils";
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();
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 (getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
displayTemp = ((preset.settings.temperature * 9) / 5 + 32).toFixed();
}
setTempString(displayTemp);
}, [preset]);
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 item xs={12} sm={6}>
<TextField
variant="outlined"
margin="normal"
style={{ width: "100%" }}
value={presetName}
onChange={e => {
setPresetName(e.target.value);
}}
/>
</Grid>
<Grid item 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 item xs={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 item xs={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 item xs={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">
{getTemperatureUnit() === 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 (getTemperatureUnit() === 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 (getTemperatureUnit() === 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 item xs={2}>
<Typography>Temperature</Typography>
</Grid>
<Grid item xs={7}>
<Box paddingRight={2}>
<Slider
//orientation="vertical"
value={tempC}
min={-40}
max={40}
valueLabelDisplay="auto"
valueLabelFormat={value => {
if (getTemperatureUnit() === 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 item xs={3}>
<TextField
style={{ width: "100%" }}
margin="dense"
variant="outlined"
type="number"
InputProps={{
endAdornment: (
<InputAdornment position="end">
{getTemperatureUnit() === 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 (getTemperatureUnit() === 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 item xs={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 item xs={2}>
<Typography>Humidity</Typography>
</Grid>
<Grid item xs={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 item xs={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>
);
}

View file

@ -0,0 +1,208 @@
import {
Box,
Button,
Card,
List,
ListItem,
ListSubheader,
Typography
} from "@mui/material";
import { cloneDeep } from "lodash";
import { DevicePreset } from "models/DevicePreset";
import ObjectDescriber from "objects/ObjectDescriber";
import { pond } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react";
import DevicePresetCard from "./devicePresetCard";
interface Props {
objectKey: string;
objectType: pond.ObjectType;
presets: DevicePreset[];
refreshCallback: (newPresets: DevicePreset[]) => void;
}
export default function DevicePresetController(props: Props) {
const { objectKey, objectType, presets, refreshCallback } = props;
const [newPresets, setNewPresets] = useState<Map<string, DevicePreset>>(new Map());
const [savedPresets, setSavedPresets] = useState<Map<pond.PresetType, DevicePreset[]>>(new Map());
useEffect(() => {
let presetTypeMap: Map<pond.PresetType, DevicePreset[]> = new Map();
presets.forEach(preset => {
let typePresets = presetTypeMap.get(preset.settings.type);
if (typePresets) {
typePresets.push(preset);
} else {
presetTypeMap.set(preset.settings.type, [preset]);
}
});
setSavedPresets(presetTypeMap);
}, [presets]);
const createNewPreset = () => {
//add to the list of new presets using the default preset settings
let newPreset = DevicePreset.create();
newPreset.name = "New Preset";
let p = new Map(newPresets);
let tempKey = p.size.toString(); //generate a unique string for a tempKey
newPreset.key = tempKey;
p.set(tempKey, newPreset);
setNewPresets(p);
};
const addCallback = (preset: DevicePreset, tempKey: string) => {
//add the preset to the list of saved presets
let presetMap = cloneDeep(savedPresets);
if (presetMap.get(preset.settings.type)) {
presetMap.get(preset.settings.type)?.push(preset);
} else {
presetMap.set(preset.settings.type, [preset]);
}
//send the parent component the list of presets after the new one has been saved
let presets: DevicePreset[] = [];
presetMap.forEach(presetList => {
presets = presets.concat(presetList);
});
refreshCallback(presets);
setSavedPresets(presetMap);
//remove the preset from the newpreset map using the temp key
let pMap = cloneDeep(newPresets);
pMap.delete(tempKey);
setNewPresets(pMap);
};
const removeCallback = (preset: DevicePreset) => {
//get the type list this preset is a part of
let map = cloneDeep(savedPresets);
let list = map.get(preset.settings.type);
//remove it from the list
if (list) {
let foundIndex;
list.forEach((p, i) => {
if (p.key === preset.key) {
foundIndex = i;
}
});
if (foundIndex !== undefined) {
list.splice(foundIndex, 1);
}
map.set(preset.settings.type, list);
}
//send the parent the new list of presets after it has been removed
let presets: DevicePreset[] = [];
map.forEach(presetList => {
presets = presets.concat(presetList);
});
refreshCallback(presets);
setSavedPresets(map);
};
const updateCallback = (oldPreset: DevicePreset, newPreset: DevicePreset) => {
//only need to update the lists if the presets if the type changed
if (oldPreset.settings.type !== newPreset.settings.type) {
let map = cloneDeep(savedPresets);
//use the new preset to tget the list to add to
let newPresetList = map.get(newPreset.settings.type);
if (newPresetList) {
newPresetList.push(newPreset);
map.set(newPreset.settings.type, newPresetList);
} else {
map.set(newPreset.settings.type, [newPreset]);
}
//use the type from the old preset to get the list to remove from
let oldPresetList = map.get(oldPreset.settings.type);
if (oldPresetList) {
let foundIndex;
oldPresetList.forEach((preset, i) => {
if (oldPreset.key === preset.key) {
foundIndex = i;
}
});
if (foundIndex !== undefined) {
oldPresetList.splice(foundIndex, 1);
if (oldPresetList.length === 0) {
}
}
map.set(oldPreset.settings.type, oldPresetList);
}
let presets: DevicePreset[] = [];
map.forEach(presetList => {
presets = presets.concat(presetList);
});
refreshCallback(presets);
setSavedPresets(map);
}
};
const savedPresetDisplay = () => {
let display: JSX.Element[] = [];
savedPresets.forEach((presetList, type) => {
let list: JSX.Element = (
<React.Fragment key={type}>
<List>
{/* <ListSubheader>Saved {ObjectDescriber(objectType).name} Presets</ListSubheader> */}
<ListSubheader>{DevicePreset.presetTypeString(type)} Presets</ListSubheader>
{presetList.map((preset, i) => (
<ListItem key={i}>
<DevicePresetCard
preset={preset}
existing
objectType={objectType}
objectKey={objectKey}
removeCallback={removeCallback}
updateCallback={updateCallback}
/>
</ListItem>
))}
</List>
</React.Fragment>
);
display.push(list);
});
return display;
};
return (
<Card raised>
<Box padding={1} display="flex" justifyContent="space-between">
<Typography style={{ fontWeight: 650, fontSize: 25 }}>Custom Bin Presets</Typography>
<Button onClick={createNewPreset} variant="contained" color="primary">
Add New Preset
</Button>
</Box>
{newPresets.size > 0 && (
<React.Fragment>
<Typography style={{ paddingLeft: 10, fontWeight: 650 }}>New Presets</Typography>
<List>
{Array.from(newPresets.values()).map((preset, i) => (
<ListItem key={i}>
<DevicePresetCard
preset={preset}
addCallback={addCallback}
objectType={objectType}
objectKey={objectKey}
removeCallback={removeCallback}
/>
</ListItem>
))}
</List>
</React.Fragment>
)}
{savedPresets.size > 0 ? (
<React.Fragment>
<Typography style={{ paddingLeft: 10, fontWeight: 650 }}>
Saved {ObjectDescriber(objectType).name} Presets
</Typography>
{savedPresetDisplay()}
</React.Fragment>
) : (
<Box padding={2}>
<Typography>Add custom presets to use when changing modes</Typography>
</Box>
)}
</Card>
);
}