finished the refactoring of the bin mode change to allow for more components to be selected

This commit is contained in:
csawatzky 2025-11-20 15:39:29 -06:00
parent 70bd91bbe7
commit 3df6db8a01
6 changed files with 350 additions and 272 deletions

View file

@ -7,11 +7,9 @@ import {
darken, darken,
DialogActions, DialogActions,
DialogContent, DialogContent,
DialogContentText,
DialogTitle, DialogTitle,
Grid2 as Grid, Grid2 as Grid,
IconButton, IconButton,
InputAdornment,
lighten, lighten,
Link, Link,
Skeleton, Skeleton,
@ -19,8 +17,6 @@ import {
Switch, Switch,
TextField, TextField,
Theme, Theme,
ToggleButton,
ToggleButtonGroup,
Typography, Typography,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
@ -31,8 +27,8 @@ import HumidityIcon from "component/HumidityIcon";
import TemperatureIcon from "component/TemperatureIcon"; import TemperatureIcon from "component/TemperatureIcon";
import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber"; import GrainDescriber, { GrainOptions, ToGrainOption } from "grain/GrainDescriber";
import useViewport from "hooks/useViewport"; import useViewport from "hooks/useViewport";
import { cloneDeep, round } from "lodash"; import { round } from "lodash";
import { Bin, Component, Device, Interaction } from "models"; import { Bin, Component, Device } from "models";
import { GetComponentIcon } from "pbHelpers/ComponentType"; import { GetComponentIcon } from "pbHelpers/ComponentType";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { useMobile } from "hooks"; import { useMobile } from "hooks";
@ -49,7 +45,6 @@ import { useBinAPI } from "providers/pond/binAPI";
import BindaptIcon from "products/Bindapt/BindaptIcon"; import BindaptIcon from "products/Bindapt/BindaptIcon";
import { import {
AccessTime, AccessTime,
ArrowForward,
ArrowForwardIos, ArrowForwardIos,
CheckCircleOutline, CheckCircleOutline,
Error, Error,
@ -59,7 +54,6 @@ import {
TrendingUp, TrendingUp,
Warning Warning
} from "@mui/icons-material"; } from "@mui/icons-material";
import DevicePresetsFromPicker from "device/DevicePresetsFromPicker";
import { Plenum } from "models/Plenum"; import { Plenum } from "models/Plenum";
import { Pressure } from "models/Pressure"; import { Pressure } from "models/Pressure";
import { GrainCable } from "models/GrainCable"; import { GrainCable } from "models/GrainCable";
@ -73,7 +67,7 @@ import { Ambient } from "models/Ambient";
import { ExtractMoisture } from "grain"; import { ExtractMoisture } from "grain";
import SearchSelect, { Option } from "common/SearchSelect"; import SearchSelect, { Option } from "common/SearchSelect";
import Edit from "@mui/icons-material/Edit"; import Edit from "@mui/icons-material/Edit";
import { makeStyles, styled } from "@mui/styles"; import { makeStyles } from "@mui/styles";
import ButtonGroup from "common/ButtonGroup"; import ButtonGroup from "common/ButtonGroup";
import ModeChangeDialog from "./conditioning/modeChangeDialog"; import ModeChangeDialog from "./conditioning/modeChangeDialog";
@ -191,8 +185,6 @@ interface Props {
ambient?: Ambient; ambient?: Ambient;
cables?: GrainCable[]; cables?: GrainCable[];
pressures?: Pressure[]; pressures?: Pressure[];
interactions?: Interaction[];
//changeBinMode: (binMode: pond.BinMode) => boolean;
refresh: (showSnack?: boolean) => void; refresh: (showSnack?: boolean) => void;
afterUpdate?(): void; afterUpdate?(): void;
preferences?: Map<string, pond.BinComponentPreferences>; preferences?: Map<string, pond.BinComponentPreferences>;
@ -204,8 +196,6 @@ interface Props {
export default function BinVisualizer(props: Props) { export default function BinVisualizer(props: Props) {
const { const {
bin, bin,
//changeBinMode,
//changeGrainAmount,
plenums, plenums,
ambient, ambient,
pressures, pressures,
@ -216,7 +206,6 @@ export default function BinVisualizer(props: Props) {
preferences, preferences,
componentDevices, componentDevices,
permissions, permissions,
//interactions,
refresh, refresh,
updateComponentCallback, updateComponentCallback,
binPresets binPresets
@ -243,9 +232,9 @@ export default function BinVisualizer(props: Props) {
const pressColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE).colour(); const pressColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PRESSURE).colour();
const emcColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour(); const emcColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour();
// const [showInputMoisture, setShowInputMoisture] = useState<boolean>(false); // const [showInputMoisture, setShowInputMoisture] = useState<boolean>(false);
const [moistureInput, setMoistureInput] = useState<string>(""); const [targetMoisture, setTargetMoisture] = useState(0);
const [tempInput, setTempInput] = useState<string>(""); const [outdoorTemp, setOutdoorTemp] = useState(0);
const [outdoorHumidityInput, setOutdoorHumidityInput] = useState<string>(""); const [outdoorHumidity, setOutdoorHumidity] = useState(0);
const [modeTime, setModeTime] = useState<Moment>(moment()); const [modeTime, setModeTime] = useState<Moment>(moment());
const [grainUpdate, setGrainUpdate] = useState<boolean>(false); const [grainUpdate, setGrainUpdate] = useState<boolean>(false);
const [openStorageTime, setOpenStorageTime] = useState(false); const [openStorageTime, setOpenStorageTime] = useState(false);
@ -331,7 +320,7 @@ export default function BinVisualizer(props: Props) {
setStorageType(bin.storage()); setStorageType(bin.storage());
// setNewPreset(pond.BinMode.BIN_MODE_NONE); // setNewPreset(pond.BinMode.BIN_MODE_NONE);
if (bin.settings.inventory) { if (bin.settings.inventory) {
setMoistureInput(bin.settings.inventory.initialMoisture.toString()); setTargetMoisture(bin.settings.inventory.initialMoisture);
setCustomTypeName(bin.settings.inventory.customTypeName); setCustomTypeName(bin.settings.inventory.customTypeName);
setGrainType(bin.settings.inventory.grainType); setGrainType(bin.settings.inventory.grainType);
setGrainOption(ToGrainOption(bin.settings.inventory.grainType)); setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
@ -344,9 +333,9 @@ export default function BinVisualizer(props: Props) {
if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
t = CtoF(t); t = CtoF(t);
} }
setTempInput(t.toString()); setOutdoorTemp(t);
} }
if (bin.settings) setOutdoorHumidityInput(bin.settings.outdoorHumidity.toString()); if (bin.settings) setOutdoorHumidity(bin.settings.outdoorHumidity);
}, [bin, user]); }, [bin, user]);
useEffect(() => { useEffect(() => {
@ -774,7 +763,7 @@ export default function BinVisualizer(props: Props) {
setIsCustomInventory(bin.storage() !== pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN); setIsCustomInventory(bin.storage() !== pond.BinStorage.BIN_STORAGE_SUPPORTED_GRAIN);
setStorageType(bin.storage()); setStorageType(bin.storage());
if (bin.settings.inventory) { if (bin.settings.inventory) {
setMoistureInput(bin.settings.inventory.initialMoisture.toString()); setTargetMoisture(bin.settings.inventory.initialMoisture);
setCustomTypeName(bin.settings.inventory.customTypeName); setCustomTypeName(bin.settings.inventory.customTypeName);
setGrainType(bin.settings.inventory.grainType); setGrainType(bin.settings.inventory.grainType);
setGrainOption(ToGrainOption(bin.settings.inventory.grainType)); setGrainOption(ToGrainOption(bin.settings.inventory.grainType));
@ -1960,83 +1949,6 @@ export default function BinVisualizer(props: Props) {
setOpenModeChange(true) setOpenModeChange(true)
}; };
// const closeMoistureDialog = () => {
// // setNewPreset(0);
// setNewBinMode(bin.settings.mode);
// setShowInputMoisture(false);
// };
// const moistureDialog = () => {
// return (
// <ResponsiveDialog open={showInputMoisture} onClose={closeMoistureDialog}>
// <DialogTitle>Input new grain moisture</DialogTitle>
// <DialogContent>
// <DialogContentText style={{ paddingBottom: theme.spacing(0) }}>
// Updating the current grain moisture will calibrate the estimate for more accurate
// results. The outdoor temperature will have some effect on the estimate as well; consider
// updating with drastic changes in the weather or using a predicted average.
// </DialogContentText>
// <TextField
// label={"Grain Moisture"}
// value={moistureInput}
// onChange={event => setMoistureInput(event.target.value)}
// InputProps={{
// endAdornment: <InputAdornment position="end">%</InputAdornment>
// }}
// style={{ marginTop: theme.spacing(2) }}
// fullWidth
// variant="outlined"
// />
// <Typography variant="body1" style={{ marginTop: theme.spacing(2) }}>
// Weather
// </Typography>
// <TextField
// label={"Outdoor Temperature"}
// value={tempInput}
// onChange={event => setTempInput(event.target.value)}
// InputProps={{
// endAdornment: (
// <InputAdornment position="end">
// {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
// ? "°F"
// : "℃"}
// </InputAdornment>
// )
// }}
// style={{ marginTop: theme.spacing(2) }}
// fullWidth
// variant="outlined"
// />
// <TextField
// label={"Outdoor Humidity"}
// value={outdoorHumidityInput}
// onChange={event => setOutdoorHumidityInput(event.target.value)}
// InputProps={{
// endAdornment: <InputAdornment position="end">%</InputAdornment>
// }}
// style={{ marginTop: theme.spacing(2) }}
// fullWidth
// variant="outlined"
// />
// </DialogContent>
// <DialogActions>
// <Button
// onClick={() => {
// // setNewPreset(0);
// setNewBinMode(bin.settings.mode);
// setShowInputMoisture(false);
// }}
// color="primary">
// Cancel
// </Button>
// <Button onClick={setModeConditioning} color="primary">
// Update
// </Button>
// </DialogActions>
// </ResponsiveDialog>
// );
// };
if (loading) { if (loading) {
return <Skeleton variant="rectangular" height={460} />; return <Skeleton variant="rectangular" height={460} />;
} }
@ -2046,7 +1958,7 @@ export default function BinVisualizer(props: Props) {
b.settings.storage = storageType; b.settings.storage = storageType;
if (b.settings.inventory) { if (b.settings.inventory) {
if (b.settings.inventory.initialMoisture) if (b.settings.inventory.initialMoisture)
b.settings.inventory.initialMoisture = Number(moistureInput); b.settings.inventory.initialMoisture = targetMoisture;
//update all the grain stuff //update all the grain stuff
b.settings.inventory.grainType = grainType; b.settings.inventory.grainType = grainType;
b.settings.inventory.customTypeName = customTypeName; b.settings.inventory.customTypeName = customTypeName;
@ -2057,14 +1969,14 @@ export default function BinVisualizer(props: Props) {
} }
if (b.settings.outdoorTemp !== undefined) { if (b.settings.outdoorTemp !== undefined) {
let t = Number(tempInput); let t = outdoorTemp;
if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) { if (user.settings.temperatureUnit === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
t = FtoC(Number(tempInput)); t = FtoC(outdoorTemp);
} }
b.settings.outdoorTemp = t; b.settings.outdoorTemp = t;
} }
if (b.settings.outdoorHumidity !== undefined) if (b.settings.outdoorHumidity !== undefined)
b.settings.outdoorHumidity = Number(outdoorHumidityInput); b.settings.outdoorHumidity = outdoorHumidity;
if (b.settings.mode != newBinMode){ if (b.settings.mode != newBinMode){
if(b.settings.inventory){ if(b.settings.inventory){
@ -2080,26 +1992,10 @@ export default function BinVisualizer(props: Props) {
return ( return (
<Card raised className={classes.cardContent}> <Card raised className={classes.cardContent}>
{/* {moistureDialog()} */}
{cfmLowDialog()} {cfmLowDialog()}
{cfmHighDialog()} {cfmHighDialog()}
{changeGrain()} {changeGrain()}
{storageTimeDialog()} {storageTimeDialog()}
{/* <DevicePresetsFromPicker
preferences={preferences}
binKey={bin.key()}
devices={devices}
refreshCallback={success => {
setShowInputMoisture(false);
setNewPreset(0)
if (success) updateBin();
}}
parentPreset={newPreset}
grain={bin.settings.inventory?.grainType}
binCables={cables}
compDevMap={componentDevices}
presets={binPresets}
/> */}
<ModeChangeDialog <ModeChangeDialog
binKey={bin.key()} binKey={bin.key()}
binMode={newBinMode} binMode={newBinMode}
@ -2107,18 +2003,32 @@ export default function BinVisualizer(props: Props) {
devices={devices} devices={devices}
open={openModeChange} open={openModeChange}
binCables={cables} binCables={cables}
presets={binPresets}
compDevMap={componentDevices} compDevMap={componentDevices}
preferences={preferences} preferences={preferences}
onClose={(refresh) => { onClose={(refresh) => {
setOpenModeChange(false) setOpenModeChange(false)
if(refresh) updateBin(); if(refresh) updateBin();
}} }}
defaultTargetMoisture={bin.settings.inventory?.initialMoisture}
// defaultOutdoorTemp={get this from any ambient sensors}
// defaultOutdoorHumidity={get this from any ambient sensors}
changeTargetMoisture={newMoisture => {
setTargetMoisture(newMoisture)
}}
changeOutdoorTemp={newTemp => {
setOutdoorTemp(newTemp)
}}
changeOutdoorHumidity={newHumidity => {
setOutdoorHumidity(newHumidity)
}}
startChange={() => { startChange={() => {
setModeChangeInProgress(true) setModeChangeInProgress(true)
}} }}
changeComplete={() => { changeComplete={() => {
setModeChangeInProgress(false) setModeChangeInProgress(false)
}} }}
/> />
<Box paddingRight={1}> <Box paddingRight={1}>
{binMode()} {binMode()}

View file

@ -1,10 +1,12 @@
import { Box, Checkbox, DialogActions, DialogContent, FormControlLabel, Typography } from "@mui/material" import { Typography } from "@mui/material"
import { Ambient } from "models/Ambient" import { Ambient } from "models/Ambient"
import { Controller } from "models/Controller" import { Controller } from "models/Controller"
import { Plenum } from "models/Plenum" import { Plenum } from "models/Plenum"
import React, { useEffect, useState } from "react" import React, { useState } from "react"
import PickComponentSet, { ComponentSet } from "./pickComponentSet" import PickComponentSet, { ComponentSet } from "./pickComponentSet"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
import { DevicePreset } from "models/DevicePreset"
import { pond } from "protobuf-ts/pond"
interface Props { interface Props {
@ -12,12 +14,14 @@ interface Props {
ambients: Ambient[] ambients: Ambient[]
heaters: Controller[] heaters: Controller[]
fans: Controller[] fans: Controller[]
binMode: pond.BinMode
presets?: DevicePreset[]
updateSets: (sets: ComponentSet[]) => void updateSets: (sets: ComponentSet[]) => void
} }
export default function ConditioningSelector(props: Props){ export default function ConditioningSelector(props: Props){
const {plenums, ambients, heaters, fans, updateSets} = props const {plenums, ambients, heaters, fans, updateSets, presets, binMode} = props
const [currentSets, setCurrentSets] = useState<ComponentSet[]>([]) const [currentSets, setCurrentSets] = useState<ComponentSet[]>([])
const sensorIndex = (key: string) => { const sensorIndex = (key: string) => {
@ -64,6 +68,8 @@ export default function ConditioningSelector(props: Props){
sensor={plenum} sensor={plenum}
heaters={heaters} heaters={heaters}
fans={fans} fans={fans}
binMode={binMode}
presets={presets}
updateSet={setUpdate} updateSet={setUpdate}
/> />
)})} )})}
@ -74,6 +80,8 @@ export default function ConditioningSelector(props: Props){
sensor={ambient} sensor={ambient}
heaters={heaters} heaters={heaters}
fans={fans} fans={fans}
binMode={binMode}
presets={presets}
updateSet={setUpdate} updateSet={setUpdate}
/> />
)})} )})}

View file

@ -1,4 +1,4 @@
import { Autocomplete, Box, Button, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, Stepper, Step, StepLabel, InputAdornment, Typography } from "@mui/material"; import { Autocomplete, Box, Button, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, Stepper, Step, StepLabel, InputAdornment, Typography, useTheme } from "@mui/material";
import ResponsiveDialog from "common/ResponsiveDialog"; import ResponsiveDialog from "common/ResponsiveDialog";
import { useComponentAPI, useInteractionsAPI } from "hooks"; import { useComponentAPI, useInteractionsAPI } from "hooks";
import { Component, Device, Interaction } from "models"; import { Component, Device, Interaction } from "models";
@ -19,7 +19,7 @@ import { sameComponentID } from "pbHelpers/Component";
import moment from "moment"; import moment from "moment";
import { lowerCase } from "lodash"; import { lowerCase } from "lodash";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { getTemperatureUnit } from "utils"; import { fahrenheitToCelsius, getTemperatureUnit } from "utils";
import { GetGrainExtensionMap } from "grain"; import { GetGrainExtensionMap } from "grain";
import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress"; import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress";
@ -37,10 +37,11 @@ interface Props {
startChange: () => void; startChange: () => void;
changeComplete: () => void; changeComplete: () => void;
defaultTargetMoisture?: number; defaultTargetMoisture?: number;
changeTargetMoisture?: (newTarget: number) => {} changeTargetMoisture?: (newTarget: number) => void
defaultOutdoorTemp?: number; defaultOutdoorTemp?: number;
changeOutdoorTemp?: (newTemp: number) => {} changeOutdoorTemp?: (newTemp: number) => void
defaultOutdoorHumidity?: number; defaultOutdoorHumidity?: number;
changeOutdoorHumidity?: (newHumidity: number) => void
} }
interface Option { interface Option {
@ -67,7 +68,13 @@ export default function ModeChangeDialog(props: Props){
grain, grain,
startChange, startChange,
changeComplete, changeComplete,
defaultTargetMoisture defaultTargetMoisture,
defaultOutdoorTemp,
defaultOutdoorHumidity,
changeTargetMoisture,
changeOutdoorTemp,
changeOutdoorHumidity,
presets
} = props } = props
const [{as}] = useGlobalState() const [{as}] = useGlobalState()
const [componentSets, setComponentSets] = useState<ComponentSet[]>([]) const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
@ -85,20 +92,24 @@ export default function ModeChangeDialog(props: Props){
const [ambients, setAmbients] = useState<Ambient[]>([]) const [ambients, setAmbients] = useState<Ambient[]>([])
const [heaters, setHeaters] = useState<Controller[]>([]) const [heaters, setHeaters] = useState<Controller[]>([])
const [fans, setFans] = useState<Controller[]>([]) const [fans, setFans] = useState<Controller[]>([])
const [selectedPreset, setSelectedPreset] = useState<DevicePreset | undefined>(undefined);
const grainExtensionMap = GetGrainExtensionMap(); const grainExtensionMap = GetGrainExtensionMap();
const [conflictingSetInteractions, setConflictingSetInteractions] = useState<Interaction[]>([]) const [conflictingSetInteractions, setConflictingSetInteractions] = useState<Interaction[]>([])
const [interactionsToAdd, setInteractionsToAdd] = useState<pond.MultiInteractionSettings>() const [interactionsToAdd, setInteractionsToAdd] = useState<pond.MultiInteractionSettings>()
const [promiseStages, setPromiseStages] = useState<Stage[]>([]) const [promiseStages, setPromiseStages] = useState<Stage[]>([])
const [dialogSteps, setDialogSteps] = useState<DialogStep[]>([]) const [dialogSteps, setDialogSteps] = useState<DialogStep[]>([])
const [currentStep, setCurrentStep] = useState(0) const [currentStep, setCurrentStep] = useState(0)
const theme = useTheme()
//the state variables for the moisture //the state variables for the moisture
const [moistureInput, setMoistureInput] = useState<string>("") const [moistureInput, setMoistureInput] = useState<string>("")
const [outdoorTempInput, setOutdoorTempInput] = useState<string>("")
const [outdoorHumidityInput, setOutdoorHumidityInput] = useState<string>("")
useEffect(()=>{ useEffect(()=>{
setMoistureInput(defaultTargetMoisture?.toFixed(2) ?? "0") setMoistureInput(defaultTargetMoisture?.toFixed(2) ?? "0")
},[defaultTargetMoisture]) setOutdoorTempInput(defaultOutdoorTemp?.toFixed(2) ?? "0")
setOutdoorHumidityInput(defaultOutdoorHumidity?.toFixed(2) ?? "0")
},[defaultTargetMoisture, defaultOutdoorTemp, defaultOutdoorHumidity])
//get the interactions and components for the device when it is selected from the dropdown //get the interactions and components for the device when it is selected from the dropdown
useEffect(()=>{ useEffect(()=>{
@ -214,7 +225,8 @@ if (!selectedDevice) return;
const buildHeaterInteraction = ( const buildHeaterInteraction = (
sensor: Plenum | Ambient, sensor: Plenum | Ambient,
heater: Controller heater: Controller,
customPreset?: DevicePreset
): pond.InteractionSettings => { ): pond.InteractionSettings => {
let interaction = pond.InteractionSettings.create({ let interaction = pond.InteractionSettings.create({
source: sensor.location(), source: sensor.location(),
@ -236,11 +248,10 @@ if (!selectedDevice) return;
let temp = 0; let temp = 0;
let hum = 0; let hum = 0;
//if a preset was selected use its temp/hum values //if a preset was selected use its temp/hum values
if (selectedPreset !== undefined) { if (customPreset !== undefined) {
temp = selectedPreset.temp(); temp = customPreset.temp();
hum = selectedPreset.hum(); hum = customPreset.hum();
} else { } else {
//otherwise use default values //otherwise use default values
switch(binMode){ switch(binMode){
@ -303,7 +314,8 @@ if (!selectedDevice) return;
sensor: Plenum | Ambient, sensor: Plenum | Ambient,
fan: Controller, fan: Controller,
tempComparison: "greater" | "less", tempComparison: "greater" | "less",
humidityComparison: "greater" | "less" humidityComparison: "greater" | "less",
customPreset?: DevicePreset
) => { ) => {
let interaction = pond.InteractionSettings.create({ let interaction = pond.InteractionSettings.create({
source: sensor.location(), source: sensor.location(),
@ -323,13 +335,13 @@ if (!selectedDevice) return;
}) })
}); });
let tempPreset = 0; let tempPreset = 0;
let humidityPreset = 0; let humidityPreset = 0;
//if a custom preset was selected use it //if a custom preset was selected use it
if (selectedPreset !== undefined) { if (customPreset !== undefined) {
tempPreset = selectedPreset.temp(); tempPreset = customPreset.temp();
humidityPreset = selectedPreset.hum(); humidityPreset = customPreset.hum();
} else { } else {
//otherwise use one of the defaults //otherwise use one of the defaults
switch (binMode) { switch (binMode) {
@ -437,7 +449,6 @@ if (!selectedDevice) return;
} }
componentSets.forEach(set => { componentSets.forEach(set => {
set.controllers.forEach(controller => { set.controllers.forEach(controller => {
console.log(controller)
let newStep: PromiseStep = { let newStep: PromiseStep = {
title: "Updating " + controller.name(), title: "Updating " + controller.name(),
promise: componentAPI.update(selectedDevice.id(), controller.settings) promise: componentAPI.update(selectedDevice.id(), controller.settings)
@ -461,6 +472,7 @@ if (!selectedDevice) return;
sets.forEach((set) => { sets.forEach((set) => {
//loop through the controllers //loop through the controllers
set.controllers.forEach(controller => { set.controllers.forEach(controller => {
let customPreset = set.selectedPresets.get(controller.key())
//filter the conflicting interactions for that controller //filter the conflicting interactions for that controller
let c = existingInteractions.filter(i => { let c = existingInteractions.filter(i => {
let conflicting = false; let conflicting = false;
@ -478,17 +490,17 @@ if (!selectedDevice) return;
case pond.BinMode.BIN_MODE_COOLDOWN: case pond.BinMode.BIN_MODE_COOLDOWN:
// if the controller is a heater create heater interaction // if the controller is a heater create heater interaction
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){ if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller)) multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller, customPreset))
}else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN || }else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){ controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
// if the controller is a fan create a fan interaction // if the controller is a fan create a fan interaction
multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "less")) multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "less", customPreset))
} }
break; break;
case pond.BinMode.BIN_MODE_DRYING: case pond.BinMode.BIN_MODE_DRYING:
// if the controller is a heater create heater interaction // if the controller is a heater create heater interaction
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){ if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_HEATER){
multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller)) multiInteractionSettings.interactions.push(buildHeaterInteraction(set.sensor, controller, customPreset))
}else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN || }else if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){// if the controller is a fan controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){// if the controller is a fan
//if it is a combination set using a heater as well just turn the fan on //if it is a combination set using a heater as well just turn the fan on
@ -496,7 +508,7 @@ if (!selectedDevice) return;
controller.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_ON controller.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_ON
}else{ }else{
//otherwise make a fan interaction //otherwise make a fan interaction
multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "greater", "less")) multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "greater", "less", customPreset))
} }
} }
break; break;
@ -505,7 +517,7 @@ if (!selectedDevice) return;
if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN || if(controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_AERATION_FAN ||
controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){ controller.subType() === quack.BooleanOutputSubtype.BOOLEAN_OUTPUT_SUBTYPE_EXHAUST_FAN){
//create fan interaction //create fan interaction
multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "greater")) multiInteractionSettings.interactions.push(buildFanInteraction(set.sensor, controller, "less", "greater", customPreset))
} }
break; break;
} }
@ -563,55 +575,81 @@ if (!selectedDevice) return;
//this step will only be used for drying and hydrating //this step will only be used for drying and hydrating
const moistureStep = () => { const moistureStep = () => {
return ( return (
<Box> <Box>
<DialogContentText style={{ paddingBottom: theme.spacing(0) }}> <DialogContentText style={{ paddingBottom: theme.spacing(0) }}>
Updating the current grain moisture will calibrate the estimate for more accurate Updating the current grain moisture will calibrate the estimate for more accurate
results. The outdoor temperature will have some effect on the estimate as well; consider results. The outdoor temperature will have some effect on the estimate as well; consider
updating with drastic changes in the weather or using a predicted average. updating with drastic changes in the weather or using a predicted average.
</DialogContentText> </DialogContentText>
<TextField <TextField
label={"Grain Moisture"} label={"Grain Moisture"}
value={moistureInput} value={moistureInput}
onChange={event => setMoistureInput(event.target.value)} error={isNaN(parseFloat(moistureInput))}
InputProps={{ helperText={isNaN(parseFloat(moistureInput)) ? "Must be a valid number" : ""}
endAdornment: <InputAdornment position="end">%</InputAdornment> onChange={event => {
}} setMoistureInput(event.target.value)
style={{ marginTop: theme.spacing(2) }} if(changeTargetMoisture && !isNaN(parseFloat(event.target.value))){
fullWidth changeTargetMoisture(parseFloat(event.target.value))
variant="outlined" }
/> }}
<Typography variant="body1" style={{ marginTop: theme.spacing(2) }}> InputProps={{
Weather endAdornment: <InputAdornment position="end">%</InputAdornment>
</Typography> }}
<TextField style={{ marginTop: theme.spacing(2) }}
label={"Outdoor Temperature"} fullWidth
value={tempInput} variant="outlined"
onChange={event => setTempInput(event.target.value)} />
InputProps={{ <Typography variant="body1" style={{ marginTop: theme.spacing(2) }}>
endAdornment: ( Weather
<InputAdornment position="end"> </Typography>
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT <TextField
? "°F" label={"Outdoor Temperature"}
: "℃"} value={outdoorTempInput}
</InputAdornment> error={isNaN(parseFloat(outdoorTempInput))}
) helperText={isNaN(parseFloat(outdoorTempInput)) ? "Must be a valid number" : ""}
}} onChange={event => {
style={{ marginTop: theme.spacing(2) }} setOutdoorTempInput(event.target.value)
fullWidth if(changeOutdoorTemp && !isNaN(parseFloat(event.target.value))){
variant="outlined" //if the users prefs are F will need to convert it to C when sending it back
/> let val = parseFloat(event.target.value)
<TextField if(getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT){
label={"Outdoor Humidity"} val = fahrenheitToCelsius(val)
value={outdoorHumidityInput} }
onChange={event => setOutdoorHumidityInput(event.target.value)} changeOutdoorTemp(val)
InputProps={{ }
endAdornment: <InputAdornment position="end">%</InputAdornment> }}
}} InputProps={{
style={{ marginTop: theme.spacing(2) }} endAdornment: (
fullWidth <InputAdornment position="end">
variant="outlined" {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
/> ? "°F"
</Box> : "℃"}
</InputAdornment>
)
}}
style={{ marginTop: theme.spacing(2) }}
fullWidth
variant="outlined"
/>
<TextField
label={"Outdoor Humidity"}
value={outdoorHumidityInput}
error={isNaN(parseFloat(outdoorHumidityInput))}
helperText={isNaN(parseFloat(outdoorHumidityInput)) ? "Must be a valid number" : ""}
onChange={event => {
setOutdoorHumidityInput(event.target.value)
if(changeOutdoorHumidity && !isNaN(parseFloat(event.target.value))){
changeOutdoorHumidity(parseFloat(event.target.value))
}
}}
InputProps={{
endAdornment: <InputAdornment position="end">%</InputAdornment>
}}
style={{ marginTop: theme.spacing(2) }}
fullWidth
variant="outlined"
/>
</Box>
) )
}; };
@ -652,6 +690,8 @@ if (!selectedDevice) return;
ambients={ambients} ambients={ambients}
fans={fans} fans={fans}
heaters={heaters} heaters={heaters}
presets={presets}
binMode={binMode}
updateSets={(sets) => { updateSets={(sets) => {
//update the component sets that will be used for the interactions //update the component sets that will be used for the interactions
//when the sets change it will create the interactions and any conflicting ones that need to be removed will be put into a list //when the sets change it will create the interactions and any conflicting ones that need to be removed will be put into a list
@ -673,9 +713,13 @@ if (!selectedDevice) return;
<Box> <Box>
<PromiseProgress <PromiseProgress
stages={promiseStages} stages={promiseStages}
automaticStart
failFast failFast
onStart={() => {
startChange()
}}
onComplete={()=>{ onComplete={()=>{
console.log("COMPLETED PROMISES") close(true)
setCurrentStep(0)//reset the stepper setCurrentStep(0)//reset the stepper
changeComplete()//change the boolean preventing more mode changes changeComplete()//change the boolean preventing more mode changes
}} }}
@ -686,13 +730,13 @@ if (!selectedDevice) return;
const stepper = () => { const stepper = () => {
return ( return (
<Stepper nonLinear activeStep={currentStep} style={{ width: "100%" }}> <Stepper nonLinear activeStep={currentStep} style={{ width: "100%", marginBottom: theme.spacing(2) }}>
{dialogSteps.map(dialogStep => { {dialogSteps.map((dialogStep, i) => {
const labelProps: { const labelProps: {
optional?: React.ReactNode; optional?: React.ReactNode;
} = {}; } = {};
return ( return (
<Step key={dialogStep.label} completed={dialogStep.completed}> <Step key={i} completed={dialogStep.completed}>
<StepLabel {...labelProps}>{dialogStep.label}</StepLabel> <StepLabel {...labelProps}>{dialogStep.label}</StepLabel>
</Step> </Step>
); );
@ -705,24 +749,24 @@ if (!selectedDevice) return;
let buttons: JSX.Element[] = [] let buttons: JSX.Element[] = []
//this button should be on every step of all of them //this button should be on every step of all of them
buttons.push( buttons.push(
<Button onClick={() => {close(false)}}>Close</Button> <Button key={"close"} onClick={() => {close(false)}}>Close</Button>
) )
// this button should appear if the step they are on is not 0 and not the last step // this button should appear if the step they are on is not 0 and not the last step
if(currentStep !== 0 && currentStep !== dialogSteps.length-1){ if(currentStep !== 0 && currentStep !== dialogSteps.length-1){
buttons.push( buttons.push(
<Button onClick={()=>{setCurrentStep(currentStep-1)}}>Back</Button> <Button key={"back"} onClick={()=>{setCurrentStep(currentStep-1)}}>Back</Button>
) )
} }
// this button should appear if the step they are on is less than the last step -1 // this button should appear if the step they are on is less than the last step -1
if(currentStep < dialogSteps.length-2){ if(currentStep < dialogSteps.length-2){
buttons.push( buttons.push(
<Button onClick={() => {setCurrentStep(currentStep+1)}}>Next</Button> <Button key={"next"} onClick={() => {setCurrentStep(currentStep+1)}}>Next</Button>
) )
} }
if((binMode === pond.BinMode.BIN_MODE_STORAGE || binMode === pond.BinMode.BIN_MODE_COOLDOWN && currentStep === 0) || if(((binMode === pond.BinMode.BIN_MODE_STORAGE || binMode === pond.BinMode.BIN_MODE_COOLDOWN) && currentStep === 0) ||
binMode === pond.BinMode.BIN_MODE_DRYING || binMode === pond.BinMode.BIN_MODE_HYDRATING && currentStep === 1){ (binMode === pond.BinMode.BIN_MODE_DRYING || binMode === pond.BinMode.BIN_MODE_HYDRATING) && currentStep === 1){
buttons.push( buttons.push(
<Button onClick={() => { <Button key={"confirm"} onClick={() => {
//if the bin mode is storage, just update the controllers for the selected device and close //if the bin mode is storage, just update the controllers for the selected device and close
if(binMode === pond.BinMode.BIN_MODE_STORAGE){ if(binMode === pond.BinMode.BIN_MODE_STORAGE){
close(true) close(true)
@ -741,11 +785,10 @@ if (!selectedDevice) return;
.catch(); .catch();
}); });
} }
}else{ }else{//if it is cooldown, drying, or hydrating, it will need to go to the progress step and initiate all of the promises
setCurrentStep(dialogSteps.length-1)//set the stepper to the progress step
buildPromises() buildPromises()
setCurrentStep(dialogSteps.length-1)//set the stepper to the progress step
} }
//if it is cooldown, drying, or hydrating, it will need to go to the progress step and initiate all of the promises
}}>Confirm</Button> }}>Confirm</Button>
) )
} }
@ -778,13 +821,6 @@ if (!selectedDevice) return;
return ( return (
<React.Fragment> <React.Fragment>
{/* <PromiseProgress
open={openProgress}
stages={promiseStages}
onClose={()=>{
setOpenProgress(false)
close(refresh)
}} /> */}
<ResponsiveDialog open={open} onClose={() => {close(false)}}> <ResponsiveDialog open={open} onClose={() => {close(false)}}>
<DialogTitle> <DialogTitle>
{binMode === pond.BinMode.BIN_MODE_STORAGE ? "Entering Storage Mode" : "Choose Device for " + modeDescription() + " Method"} {binMode === pond.BinMode.BIN_MODE_STORAGE ? "Entering Storage Mode" : "Choose Device for " + modeDescription() + " Method"}

View file

@ -1,30 +1,89 @@
import { CheckBox, ExpandMore } from "@mui/icons-material"; import { CheckBox, ExpandMore } from "@mui/icons-material";
import { Accordion, AccordionDetails, AccordionSummary, Box, Checkbox, FormControlLabel, Typography } from "@mui/material"; import { Accordion, AccordionDetails, AccordionSummary, Autocomplete, Box, Checkbox, FormControlLabel, Grid2, TextField, Typography } from "@mui/material";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { Ambient } from "models/Ambient"; import { Ambient } from "models/Ambient";
import { Controller } from "models/Controller"; import { Controller } from "models/Controller";
import { DevicePreset } from "models/DevicePreset";
import { Plenum } from "models/Plenum"; import { Plenum } from "models/Plenum";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack"; import { quack } from "protobuf-ts/quack";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
export interface ComponentSet { export interface ComponentSet {
sensor: Plenum | Ambient, sensor: Plenum | Ambient,
controllers: Controller[] controllers: Controller[]
selectedPresets: Map<string, DevicePreset>
combo?: boolean //if the set contains both a heater and a fan combo?: boolean //if the set contains both a heater and a fan
} }
interface Option {
label: string;
id: number;
icon?: string;
}
interface Props { interface Props {
sensor: Plenum | Ambient sensor: Plenum | Ambient
binMode: pond.BinMode
heaters?: Controller[] heaters?: Controller[]
fans?: Controller[] fans?: Controller[]
presets?: DevicePreset[]
updateSet: (sensorKey: string, componentSet?: ComponentSet) => void updateSet: (sensorKey: string, componentSet?: ComponentSet) => void
} }
export default function PickComponentSet(props: Props) { export default function PickComponentSet(props: Props) {
const {sensor, heaters, fans, updateSet} = props const {sensor, heaters, fans, updateSet, presets, binMode} = props
const [sensorSelected, setSensorSelected] = useState(false) const [sensorSelected, setSensorSelected] = useState(false)
const [currentSet, setCurrentSet] = useState<ComponentSet | undefined>() const [currentSet, setCurrentSet] = useState<ComponentSet | undefined>()
useEffect(()=>{
if (presets){
let presetOptions: Option[] = [];
presets.forEach((devicePreset, i) => {
//add the preset to the possible options
if (devicePreset.favorite()) {
//if it is favorited add it to the front of the array
presetOptions.unshift({
label: devicePreset.name,
id: i
});
} else {
//otherwise add it to the end
presetOptions.push({
label: devicePreset.name,
id: i
});
}
});
}
},[presets])
const getOptionsForController = (controllerType: pond.ControllerType) => {
let validOps: Option[] = []
if(presets){
presets.forEach((devicePreset, i) => {
if(devicePreset.controllerType() === controllerType && devicePreset.compareToBinMode(binMode)){
//add the preset to the possible options
if (devicePreset.favorite()) {
//if it is favorited add it to the front of the array
validOps.unshift({
label: devicePreset.name,
id: i
});
} else {
//otherwise add it to the end
validOps.push({
label: devicePreset.name,
id: i
});
}
}
})
}
return validOps
}
const controllerIndex = (key: string) => { const controllerIndex = (key: string) => {
if(!currentSet) return -1 if(!currentSet) return -1
let index = -1 let index = -1
@ -64,7 +123,8 @@ export default function PickComponentSet(props: Props) {
if(checked){ if(checked){
newSet = { newSet = {
sensor: sensor, sensor: sensor,
controllers: [] controllers: [],
selectedPresets: new Map<string, DevicePreset>()
} }
} }
setCurrentSet(newSet) setCurrentSet(newSet)
@ -78,32 +138,59 @@ export default function PickComponentSet(props: Props) {
<Accordion> <Accordion>
<AccordionSummary expandIcon={<ExpandMore />}>Controllers Selected: {currentSet.controllers.length}</AccordionSummary> <AccordionSummary expandIcon={<ExpandMore />}>Controllers Selected: {currentSet.controllers.length}</AccordionSummary>
<AccordionDetails> <AccordionDetails>
{heaters && heaters.length > 0 && {heaters && heaters.length > 0 && binMode !== pond.BinMode.BIN_MODE_HYDRATING &&
<React.Fragment> <React.Fragment>
<Typography>Heaters</Typography> <Typography>Heaters</Typography>
{heaters.map(heater => ( {heaters.map(heater => (
<FormControlLabel <Grid2 key={heater.key()} container justifyContent="space-between" direction="row" alignContent="center" alignItems="center">
key={heater.key()} <Grid2 size={{xs: 12, sm: 6}}>
control={ <FormControlLabel
<Checkbox control={
onChange={(_, checked) => { <Checkbox
let set = cloneDeep(currentSet) onChange={(_, checked) => {
if(checked){//if checked need to add it to the controllers let set = cloneDeep(currentSet)
set.controllers.push(heater) if(checked){//if checked need to add it to the controllers
}else{//will need to make sure it is not in the controllers set.controllers.push(heater)
let index = controllerIndex(heater.key()) }else{//will need to make sure it is not in the controllers
if (index !== -1){ let index = controllerIndex(heater.key())
set.controllers.splice(index, 1) if (index !== -1){
} set.controllers.splice(index, 1)
} }
set.combo = comboCheck(set) }
setCurrentSet(set) set.combo = comboCheck(set)
updateSet(sensor.key(), set) setCurrentSet(set)
}} updateSet(sensor.key(), set)
}}
/>
}
label={heater.name()}
/> />
} </Grid2>
label={heater.name()} <Grid2 size={{xs: 12, sm: 6}}>
/> {presets && getOptionsForController(pond.ControllerType.CONTROLLER_TYPE_HEATER).length > 0 &&
<Autocomplete
disablePortal
options={getOptionsForController(pond.ControllerType.CONTROLLER_TYPE_HEATER)}
fullWidth
getOptionLabel={option => option.label || ""}
onChange={(_, newValue) => {
if (newValue) {
let presetId = newValue.id;
if (presets && presets[presetId]) {
//setSelectedPreset(presets[presetId]);
let set = cloneDeep(currentSet)
set.selectedPresets.set(heater.key(), presets[presetId])
set.combo = comboCheck(set)
setCurrentSet(set)
updateSet(sensor.key(), set)
}
}
}}
renderInput={params => <TextField {...params} variant="outlined" label="Preset" />}
/>
}
</Grid2>
</Grid2>
))} ))}
</React.Fragment> </React.Fragment>
} }
@ -111,28 +198,55 @@ export default function PickComponentSet(props: Props) {
<React.Fragment> <React.Fragment>
<Typography>Fans</Typography> <Typography>Fans</Typography>
{fans.map(fan => ( {fans.map(fan => (
<FormControlLabel <Grid2 key={fan.key()} container justifyContent="space-between" direction="row" alignContent="center" alignItems="center">
key={fan.key()} <Grid2 size={{xs: 12, sm: 6}}>
control={ <FormControlLabel
<Checkbox control={
onChange={(_, checked) => { <Checkbox
let set = cloneDeep(currentSet) onChange={(_, checked) => {
if(checked){//if checked need to add it to the controllers let set = cloneDeep(currentSet)
set.controllers.push(fan) if(checked){//if checked need to add it to the controllers
}else{//will need to make sure it is not in the controllers set.controllers.push(fan)
let index = controllerIndex(fan.key()) }else{//will need to make sure it is not in the controllers
if (index !== -1){ let index = controllerIndex(fan.key())
set.controllers.splice(index, 1) if (index !== -1){
set.controllers.splice(index, 1)
}
}
set.combo = comboCheck(set)
setCurrentSet(set)
updateSet(sensor.key(), set)
}}
/>
}
label={fan.name()}
/>
</Grid2>
<Grid2 size={{xs: 12, sm: 6}}>
{presets && getOptionsForController(pond.ControllerType.CONTROLLER_TYPE_FAN).length > 0 &&
<Autocomplete
disablePortal
options={getOptionsForController(pond.ControllerType.CONTROLLER_TYPE_FAN)}
fullWidth
getOptionLabel={option => option.label || ""}
onChange={(_, newValue) => {
if (newValue) {
let presetId = newValue.id;
if (presets && presets[presetId]) {
//setSelectedPreset(presets[presetId]);
let set = cloneDeep(currentSet)
set.selectedPresets.set(fan.key(), presets[presetId])
set.combo = comboCheck(set)
setCurrentSet(set)
updateSet(sensor.key(), set)
} }
} }
set.combo = comboCheck(set)
setCurrentSet(set)
updateSet(sensor.key(), set)
}} }}
renderInput={params => <TextField {...params} variant="outlined" label="Preset" />}
/> />
} }
label={fan.name()} </Grid2>
/> </Grid2>
))} ))}
</React.Fragment> </React.Fragment>
} }

View file

@ -1,6 +1,5 @@
import { AxiosResponse } from "axios" import { AxiosResponse } from "axios"
import ResponsiveDialog from "./ResponsiveDialog" import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Typography } from "@mui/material"
import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Icon, Typography } from "@mui/material"
import { useEffect, useState } from "react" import { useEffect, useState } from "react"
import { CheckCircle, Error, Pending } from "@mui/icons-material" import { CheckCircle, Error, Pending } from "@mui/icons-material"
import React from "react" import React from "react"
@ -19,7 +18,9 @@ export interface Step {
interface Props { interface Props {
stages: Stage[] stages: Stage[]
automaticStart: boolean
failFast?: boolean failFast?: boolean
onStart?: () => void
onComplete?: () => void onComplete?: () => void
} }
@ -31,7 +32,7 @@ enum progress {
} }
export function PromiseProgress(props: Props){ export function PromiseProgress(props: Props){
const {stages, failFast, onComplete} = props const {stages, failFast, onStart, onComplete, automaticStart} = props
const [completion, setCompletion] = useState<Map<string, progress>>(new Map()) const [completion, setCompletion] = useState<Map<string, progress>>(new Map())
const execute = () => { const execute = () => {
@ -52,7 +53,7 @@ export function PromiseProgress(props: Props){
progMap.set(i+"-"+k, progress.InProgress); progMap.set(i+"-"+k, progress.InProgress);
setCompletion(new Map(progMap)); setCompletion(new Map(progMap));
return step.promise return step.promise
.then(resp => { .then(() => {
// mark step as completed // mark step as completed
progMap.set(i+"-"+k, progress.Complete); progMap.set(i+"-"+k, progress.Complete);
setCompletion(new Map(progMap)); setCompletion(new Map(progMap));
@ -73,13 +74,22 @@ export function PromiseProgress(props: Props){
await Promise.all(stepPromises); await Promise.all(stepPromises);
} }
}; };
runStages().catch(err => { if(onStart){
console.error("Execution stopped due to error:", err); onStart()
}).finally(() => { }
if(onComplete) onComplete() runStages().catch(err => {
}); console.error("Execution stopped due to error:", err);
}).finally(() => {
if(onComplete) onComplete()
});
} }
useEffect(() => {
if(automaticStart){
execute()
}
},[automaticStart, execute])
const getCompletion = (completion?: progress) => { const getCompletion = (completion?: progress) => {
switch(completion){ switch(completion){
case progress.InProgress: case progress.InProgress:
@ -116,11 +126,13 @@ export function PromiseProgress(props: Props){
{stages.map((stage, number) => displayStage(stage, number))} {stages.map((stage, number) => displayStage(stage, number))}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => { {!automaticStart &&
execute() <Button onClick={() => {
}}> execute()
Start }}>
</Button> Start
</Button>
}
</DialogActions> </DialogActions>
</React.Fragment> </React.Fragment>
) )

View file

@ -698,8 +698,6 @@ export default function Bin(props: Props) {
components={components} components={components}
componentDevices={componentDevices} componentDevices={componentDevices}
devices={devices} devices={devices}
interactions={interactions}
//changeBinMode={changeBinMode}
afterUpdate={refresh} afterUpdate={refresh}
refresh={refresh} refresh={refresh}
preferences={preferences} preferences={preferences}