switching from having 3 dialogs to using a stepper
This commit is contained in:
parent
283958ab6a
commit
70bd91bbe7
4 changed files with 576 additions and 285 deletions
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Card,
|
Card,
|
||||||
|
CircularProgress,
|
||||||
darken,
|
darken,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
|
@ -241,7 +242,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
).colour();
|
).colour();
|
||||||
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 [moistureInput, setMoistureInput] = useState<string>("");
|
||||||
const [tempInput, setTempInput] = useState<string>("");
|
const [tempInput, setTempInput] = useState<string>("");
|
||||||
const [outdoorHumidityInput, setOutdoorHumidityInput] = useState<string>("");
|
const [outdoorHumidityInput, setOutdoorHumidityInput] = useState<string>("");
|
||||||
|
|
@ -295,55 +296,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
const [combinedPlenums, setCombinedPlenums] = useState<CombinedPlenum[]>([])
|
const [combinedPlenums, setCombinedPlenums] = useState<CombinedPlenum[]>([])
|
||||||
|
|
||||||
const [openModeChange, setOpenModeChange] = useState(false)
|
const [openModeChange, setOpenModeChange] = useState(false)
|
||||||
|
const [modeChangeInProgress, setModeChangeInProgress] = useState(false)
|
||||||
// const StyledToggle = styled(ToggleButton)(({ theme }: { theme: Theme }) => ({
|
|
||||||
// root: {
|
|
||||||
// backgroundColor: "transparent",
|
|
||||||
// overflow: "visible",
|
|
||||||
// content: "content-box",
|
|
||||||
// "&$selected": {
|
|
||||||
// backgroundColor: "gold",
|
|
||||||
// color: "black",
|
|
||||||
// borderRadius: 24,
|
|
||||||
// fontWeight: "bold"
|
|
||||||
// },
|
|
||||||
// "&$selected:hover": {
|
|
||||||
// backgroundColor: "rgb(255, 255, 0)",
|
|
||||||
// color: "black",
|
|
||||||
// borderRadius: 24
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// selected: {}
|
|
||||||
// }));
|
|
||||||
|
|
||||||
// const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }: { theme: Theme }) => ({
|
|
||||||
// grouped: {
|
|
||||||
// margin: theme.spacing(-0.5),
|
|
||||||
// border: "none",
|
|
||||||
// padding: theme.spacing(1),
|
|
||||||
// "&:not(:first-child):not(:last-child)": {
|
|
||||||
// borderRadius: 24,
|
|
||||||
// marginRight: theme.spacing(0.5),
|
|
||||||
// marginLeft: theme.spacing(0.5)
|
|
||||||
// },
|
|
||||||
// "&:first-child": {
|
|
||||||
// borderRadius: 24,
|
|
||||||
// marginLeft: theme.spacing(0.25)
|
|
||||||
// },
|
|
||||||
// "&:last-child": {
|
|
||||||
// borderRadius: 24,
|
|
||||||
// marginRight: theme.spacing(0.25)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// root: {
|
|
||||||
// backgroundColor: darken(
|
|
||||||
// theme.palette.background.paper,
|
|
||||||
// getThemeType() === "light" ? 0.05 : 0.25
|
|
||||||
// ),
|
|
||||||
// borderRadius: 24,
|
|
||||||
// content: "border-box"
|
|
||||||
// }
|
|
||||||
// }));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setModeTime(moment(bin.status.lastModeChange));
|
setModeTime(moment(bin.status.lastModeChange));
|
||||||
|
|
@ -429,20 +382,6 @@ export default function BinVisualizer(props: Props) {
|
||||||
humids.push(...filteredNodes.humids)
|
humids.push(...filteredNodes.humids)
|
||||||
emcs.push(...filteredNodes.moistures)
|
emcs.push(...filteredNodes.moistures)
|
||||||
}
|
}
|
||||||
// let tempClone = cloneDeep(cable.temperatures).reverse();
|
|
||||||
// let humClone = cloneDeep(cable.humidities).reverse();
|
|
||||||
// let emcClone = cloneDeep(cable.grainMoistures).reverse();
|
|
||||||
//add the cable data to the proper arrays
|
|
||||||
// if (cable.topNode > 0) {
|
|
||||||
// temps.push(...tempClone.splice(0, cable.topNode));
|
|
||||||
// humids.push(...humClone.splice(0, cable.topNode));
|
|
||||||
// emcs.push(...emcClone.splice(0, cable.topNode));
|
|
||||||
// } else {
|
|
||||||
// //if the cable has no fill set (top node) then use all of the nodes
|
|
||||||
// temps = tempClone;
|
|
||||||
// humids = humClone;
|
|
||||||
// emcs = emcClone;
|
|
||||||
// }
|
|
||||||
|
|
||||||
//add the trend data to the proper arrays if the top node is set
|
//add the trend data to the proper arrays if the top node is set
|
||||||
let cableTrendData = binTrend.cableTrend[cable.key()];
|
let cableTrendData = binTrend.cableTrend[cable.key()];
|
||||||
|
|
@ -1796,7 +1735,11 @@ export default function BinVisualizer(props: Props) {
|
||||||
<Typography variant="subtitle1" style={{ fontWeight: 800 }}>
|
<Typography variant="subtitle1" style={{ fontWeight: 800 }}>
|
||||||
Bin Mode
|
Bin Mode
|
||||||
</Typography>
|
</Typography>
|
||||||
|
{modeChangeInProgress &&
|
||||||
|
<CircularProgress color="primary" size={25}/>
|
||||||
|
}
|
||||||
<ButtonGroup
|
<ButtonGroup
|
||||||
|
disableAll={modeChangeInProgress}
|
||||||
buttons={[
|
buttons={[
|
||||||
{
|
{
|
||||||
title: "Storage",
|
title: "Storage",
|
||||||
|
|
@ -1813,48 +1756,14 @@ export default function BinVisualizer(props: Props) {
|
||||||
{
|
{
|
||||||
title: bin.settings.inventory && bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture ? "Hydrating" : "Drying",
|
title: bin.settings.inventory && bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture ? "Hydrating" : "Drying",
|
||||||
function: () => {
|
function: () => {
|
||||||
setShowInputMoisture(true)
|
// setShowInputMoisture(true)
|
||||||
|
setModeConditioning() //will set it to either drying or hydrating based on the initial and target moisture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
toggledButtons={determineToggle(mode)}
|
toggledButtons={determineToggle(mode)}
|
||||||
toggle
|
toggle
|
||||||
/>
|
/>
|
||||||
{/* <StyledToggleButtonGroup
|
|
||||||
id="tour-bin-mode"
|
|
||||||
value={mode}
|
|
||||||
exclusive
|
|
||||||
size="small"
|
|
||||||
aria-label="Bin Mode">
|
|
||||||
<StyledToggle
|
|
||||||
value={pond.BinMode.BIN_MODE_STORAGE}
|
|
||||||
aria-label="Storage Mode"
|
|
||||||
onClick={setModeStorage}>
|
|
||||||
Storage
|
|
||||||
</StyledToggle>
|
|
||||||
<StyledToggle
|
|
||||||
onClick={setModeCooldown}
|
|
||||||
value={pond.BinMode.BIN_MODE_COOLDOWN}
|
|
||||||
aria-label="Off">
|
|
||||||
Cooldown
|
|
||||||
</StyledToggle>
|
|
||||||
{bin.settings.inventory &&
|
|
||||||
bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture ? (
|
|
||||||
<StyledToggle
|
|
||||||
onClick={() => setShowInputMoisture(true)}
|
|
||||||
value={pond.BinMode.BIN_MODE_HYDRATING}
|
|
||||||
aria-label="Hydrating Mode">
|
|
||||||
Hydrating
|
|
||||||
</StyledToggle>
|
|
||||||
) : (
|
|
||||||
<StyledToggle
|
|
||||||
onClick={() => setShowInputMoisture(true)}
|
|
||||||
value={pond.BinMode.BIN_MODE_DRYING}
|
|
||||||
aria-label="Drying Mode">
|
|
||||||
Drying
|
|
||||||
</StyledToggle>
|
|
||||||
)}
|
|
||||||
</StyledToggleButtonGroup> */}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -2037,7 +1946,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
setOpenModeChange(true)
|
setOpenModeChange(true)
|
||||||
};
|
};
|
||||||
|
|
||||||
const setModeDrying = () => {
|
const setModeConditioning = () => {
|
||||||
if (
|
if (
|
||||||
bin.settings.inventory &&
|
bin.settings.inventory &&
|
||||||
bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture
|
bin.settings.inventory?.initialMoisture < bin.settings.inventory?.targetMoisture
|
||||||
|
|
@ -2051,82 +1960,82 @@ export default function BinVisualizer(props: Props) {
|
||||||
setOpenModeChange(true)
|
setOpenModeChange(true)
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeMoistureDialog = () => {
|
// const closeMoistureDialog = () => {
|
||||||
// setNewPreset(0);
|
// // setNewPreset(0);
|
||||||
setNewBinMode(bin.settings.mode);
|
// setNewBinMode(bin.settings.mode);
|
||||||
setShowInputMoisture(false);
|
// setShowInputMoisture(false);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const moistureDialog = () => {
|
// const moistureDialog = () => {
|
||||||
return (
|
// return (
|
||||||
<ResponsiveDialog open={showInputMoisture} onClose={closeMoistureDialog}>
|
// <ResponsiveDialog open={showInputMoisture} onClose={closeMoistureDialog}>
|
||||||
<DialogTitle>Input new grain moisture</DialogTitle>
|
// <DialogTitle>Input new grain moisture</DialogTitle>
|
||||||
<DialogContent>
|
// <DialogContent>
|
||||||
<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)}
|
// onChange={event => setMoistureInput(event.target.value)}
|
||||||
InputProps={{
|
// InputProps={{
|
||||||
endAdornment: <InputAdornment position="end">%</InputAdornment>
|
// endAdornment: <InputAdornment position="end">%</InputAdornment>
|
||||||
}}
|
// }}
|
||||||
style={{ marginTop: theme.spacing(2) }}
|
// style={{ marginTop: theme.spacing(2) }}
|
||||||
fullWidth
|
// fullWidth
|
||||||
variant="outlined"
|
// variant="outlined"
|
||||||
/>
|
// />
|
||||||
<Typography variant="body1" style={{ marginTop: theme.spacing(2) }}>
|
// <Typography variant="body1" style={{ marginTop: theme.spacing(2) }}>
|
||||||
Weather
|
// Weather
|
||||||
</Typography>
|
// </Typography>
|
||||||
<TextField
|
// <TextField
|
||||||
label={"Outdoor Temperature"}
|
// label={"Outdoor Temperature"}
|
||||||
value={tempInput}
|
// value={tempInput}
|
||||||
onChange={event => setTempInput(event.target.value)}
|
// onChange={event => setTempInput(event.target.value)}
|
||||||
InputProps={{
|
// InputProps={{
|
||||||
endAdornment: (
|
// endAdornment: (
|
||||||
<InputAdornment position="end">
|
// <InputAdornment position="end">
|
||||||
{getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
// {getTemperatureUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||||
? "°F"
|
// ? "°F"
|
||||||
: "℃"}
|
// : "℃"}
|
||||||
</InputAdornment>
|
// </InputAdornment>
|
||||||
)
|
// )
|
||||||
}}
|
// }}
|
||||||
style={{ marginTop: theme.spacing(2) }}
|
// style={{ marginTop: theme.spacing(2) }}
|
||||||
fullWidth
|
// fullWidth
|
||||||
variant="outlined"
|
// variant="outlined"
|
||||||
/>
|
// />
|
||||||
<TextField
|
// <TextField
|
||||||
label={"Outdoor Humidity"}
|
// label={"Outdoor Humidity"}
|
||||||
value={outdoorHumidityInput}
|
// value={outdoorHumidityInput}
|
||||||
onChange={event => setOutdoorHumidityInput(event.target.value)}
|
// onChange={event => setOutdoorHumidityInput(event.target.value)}
|
||||||
InputProps={{
|
// InputProps={{
|
||||||
endAdornment: <InputAdornment position="end">%</InputAdornment>
|
// endAdornment: <InputAdornment position="end">%</InputAdornment>
|
||||||
}}
|
// }}
|
||||||
style={{ marginTop: theme.spacing(2) }}
|
// style={{ marginTop: theme.spacing(2) }}
|
||||||
fullWidth
|
// fullWidth
|
||||||
variant="outlined"
|
// variant="outlined"
|
||||||
/>
|
// />
|
||||||
</DialogContent>
|
// </DialogContent>
|
||||||
<DialogActions>
|
// <DialogActions>
|
||||||
<Button
|
// <Button
|
||||||
onClick={() => {
|
// onClick={() => {
|
||||||
// setNewPreset(0);
|
// // setNewPreset(0);
|
||||||
setNewBinMode(bin.settings.mode);
|
// setNewBinMode(bin.settings.mode);
|
||||||
setShowInputMoisture(false);
|
// setShowInputMoisture(false);
|
||||||
}}
|
// }}
|
||||||
color="primary">
|
// color="primary">
|
||||||
Cancel
|
// Cancel
|
||||||
</Button>
|
// </Button>
|
||||||
<Button onClick={setModeDrying} color="primary">
|
// <Button onClick={setModeConditioning} color="primary">
|
||||||
Update
|
// Update
|
||||||
</Button>
|
// </Button>
|
||||||
</DialogActions>
|
// </DialogActions>
|
||||||
</ResponsiveDialog>
|
// </ResponsiveDialog>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Skeleton variant="rectangular" height={460} />;
|
return <Skeleton variant="rectangular" height={460} />;
|
||||||
|
|
@ -2171,7 +2080,7 @@ export default function BinVisualizer(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card raised className={classes.cardContent}>
|
<Card raised className={classes.cardContent}>
|
||||||
{moistureDialog()}
|
{/* {moistureDialog()} */}
|
||||||
{cfmLowDialog()}
|
{cfmLowDialog()}
|
||||||
{cfmHighDialog()}
|
{cfmHighDialog()}
|
||||||
{changeGrain()}
|
{changeGrain()}
|
||||||
|
|
@ -2201,11 +2110,15 @@ export default function BinVisualizer(props: Props) {
|
||||||
compDevMap={componentDevices}
|
compDevMap={componentDevices}
|
||||||
preferences={preferences}
|
preferences={preferences}
|
||||||
onClose={(refresh) => {
|
onClose={(refresh) => {
|
||||||
setShowInputMoisture(false)
|
|
||||||
setOpenModeChange(false)
|
setOpenModeChange(false)
|
||||||
if(refresh) updateBin();
|
if(refresh) updateBin();
|
||||||
}}
|
}}
|
||||||
|
startChange={() => {
|
||||||
|
setModeChangeInProgress(true)
|
||||||
|
}}
|
||||||
|
changeComplete={() => {
|
||||||
|
setModeChangeInProgress(false)
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Box paddingRight={1}>
|
<Box paddingRight={1}>
|
||||||
{binMode()}
|
{binMode()}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Autocomplete, Box, Button, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField } from "@mui/material";
|
import { Autocomplete, Box, Button, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, Stepper, Step, StepLabel, InputAdornment, Typography } 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";
|
||||||
|
|
@ -21,6 +21,7 @@ import { lowerCase } from "lodash";
|
||||||
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||||
import { getTemperatureUnit } from "utils";
|
import { getTemperatureUnit } from "utils";
|
||||||
import { GetGrainExtensionMap } from "grain";
|
import { GetGrainExtensionMap } from "grain";
|
||||||
|
import { PromiseProgress, Stage, Step as PromiseStep } from "common/PromiseProgress";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean
|
open: boolean
|
||||||
|
|
@ -33,6 +34,13 @@ interface Props {
|
||||||
binCables?: GrainCable[];
|
binCables?: GrainCable[];
|
||||||
compDevMap?: Map<string, number>;
|
compDevMap?: Map<string, number>;
|
||||||
presets?: DevicePreset[];
|
presets?: DevicePreset[];
|
||||||
|
startChange: () => void;
|
||||||
|
changeComplete: () => void;
|
||||||
|
defaultTargetMoisture?: number;
|
||||||
|
changeTargetMoisture?: (newTarget: number) => {}
|
||||||
|
defaultOutdoorTemp?: number;
|
||||||
|
changeOutdoorTemp?: (newTemp: number) => {}
|
||||||
|
defaultOutdoorHumidity?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
|
|
@ -41,6 +49,11 @@ interface Option {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DialogStep {
|
||||||
|
label: string;
|
||||||
|
completed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ModeChangeDialog(props: Props){
|
export default function ModeChangeDialog(props: Props){
|
||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
|
|
@ -51,10 +64,13 @@ export default function ModeChangeDialog(props: Props){
|
||||||
preferences,
|
preferences,
|
||||||
binCables,
|
binCables,
|
||||||
compDevMap,
|
compDevMap,
|
||||||
grain
|
grain,
|
||||||
|
startChange,
|
||||||
|
changeComplete,
|
||||||
|
defaultTargetMoisture
|
||||||
} = props
|
} = props
|
||||||
const [{as}] = useGlobalState()
|
const [{as}] = useGlobalState()
|
||||||
// const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
|
const [componentSets, setComponentSets] = useState<ComponentSet[]>([])
|
||||||
const interactionAPI = useInteractionsAPI()
|
const interactionAPI = useInteractionsAPI()
|
||||||
const componentAPI = useComponentAPI();
|
const componentAPI = useComponentAPI();
|
||||||
const [selectedDevice, setSelectedDevice] = useState<Device | undefined>()
|
const [selectedDevice, setSelectedDevice] = useState<Device | undefined>()
|
||||||
|
|
@ -73,6 +89,16 @@ export default function ModeChangeDialog(props: Props){
|
||||||
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 [dialogSteps, setDialogSteps] = useState<DialogStep[]>([])
|
||||||
|
const [currentStep, setCurrentStep] = useState(0)
|
||||||
|
|
||||||
|
//the state variables for the moisture
|
||||||
|
const [moistureInput, setMoistureInput] = useState<string>("")
|
||||||
|
|
||||||
|
useEffect(()=>{
|
||||||
|
setMoistureInput(defaultTargetMoisture?.toFixed(2) ?? "0")
|
||||||
|
},[defaultTargetMoisture])
|
||||||
|
|
||||||
//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(()=>{
|
||||||
|
|
@ -153,6 +179,39 @@ if (!selectedDevice) return;
|
||||||
setOptions(o);
|
setOptions(o);
|
||||||
}, [devices, setOptions]);
|
}, [devices, setOptions]);
|
||||||
|
|
||||||
|
|
||||||
|
//use effect here to determine the steps based on the bin mode
|
||||||
|
useEffect(() => {
|
||||||
|
let steps: DialogStep[] = []
|
||||||
|
switch(binMode){
|
||||||
|
case pond.BinMode.BIN_MODE_STORAGE:
|
||||||
|
steps.push({
|
||||||
|
label: "Storage Settings"
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case pond.BinMode.BIN_MODE_COOLDOWN:
|
||||||
|
steps.push({
|
||||||
|
label: "Set Components"
|
||||||
|
})
|
||||||
|
steps.push({
|
||||||
|
label: "Progress"
|
||||||
|
})
|
||||||
|
break;
|
||||||
|
case pond.BinMode.BIN_MODE_DRYING:
|
||||||
|
case pond.BinMode.BIN_MODE_HYDRATING:
|
||||||
|
steps.push({
|
||||||
|
label: "Weather Conditions"
|
||||||
|
})
|
||||||
|
steps.push({
|
||||||
|
label: "Set Components"
|
||||||
|
})
|
||||||
|
steps.push({
|
||||||
|
label: "Progress"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setDialogSteps(steps)
|
||||||
|
},[binMode])
|
||||||
|
|
||||||
const buildHeaterInteraction = (
|
const buildHeaterInteraction = (
|
||||||
sensor: Plenum | Ambient,
|
sensor: Plenum | Ambient,
|
||||||
heater: Controller
|
heater: Controller
|
||||||
|
|
@ -235,7 +294,8 @@ if (!selectedDevice) return;
|
||||||
});
|
});
|
||||||
conditions.push(tempCondition);
|
conditions.push(tempCondition);
|
||||||
interaction.conditions = conditions;
|
interaction.conditions = conditions;
|
||||||
|
//the the heater output to auto so that when the components are update it uses the new mode
|
||||||
|
heater.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_AUTO
|
||||||
return interaction;
|
return interaction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -333,40 +393,72 @@ if (!selectedDevice) return;
|
||||||
conditions.push(fanConditionTwo);
|
conditions.push(fanConditionTwo);
|
||||||
|
|
||||||
interaction.conditions = conditions;
|
interaction.conditions = conditions;
|
||||||
|
//set the output mode to auto in the fan so that when the components are updated it uses the new mode
|
||||||
|
fan.settings.defaultOutputState = quack.OutputMode.OUTPUT_MODE_AUTO
|
||||||
return interaction;
|
return interaction;
|
||||||
};
|
};
|
||||||
|
|
||||||
//this is what will use the conflicting interactions
|
//this is what will use the conflicting interactions
|
||||||
const execute = () => {
|
const buildPromises = () => {
|
||||||
if(!selectedDevice || !interactionsToAdd) return //if there is no device selected or there are no interactions to add, do nothing
|
//if there is no device selected or there are no interactions to add, do nothing
|
||||||
//create an array of promises to delete the conflicting interactions
|
if(!selectedDevice || !interactionsToAdd) return
|
||||||
let conflictingInteractionsPromise = conflictingSetInteractions.map(i => {
|
|
||||||
return interactionAPI.removeInteraction(selectedDevice.id(), i.key(), as);
|
//build the stages to pass into the PromiseProgress
|
||||||
|
//stage one is to remove conflicting interactions
|
||||||
|
let stages: Stage[] = []
|
||||||
|
let stage1: Stage = {
|
||||||
|
title: "Remove Conflicting Interactions",
|
||||||
|
steps: []
|
||||||
|
}
|
||||||
|
conflictingSetInteractions.forEach(i => {
|
||||||
|
let newStep: PromiseStep = {
|
||||||
|
title: "Removing Interaction",
|
||||||
|
promise: interactionAPI.removeInteraction(selectedDevice.id(), i.key(), as)
|
||||||
|
}
|
||||||
|
stage1.steps.push(newStep);
|
||||||
});
|
});
|
||||||
Promise.all([...conflictingInteractionsPromise]).then(() => {
|
stages.push(stage1)
|
||||||
//the conflicting interactions were removed now add the new ones
|
//stage two is to add the new interactions
|
||||||
interactionAPI.addMultiInteractions(selectedDevice.id(), interactionsToAdd)
|
let stage2: Stage = {
|
||||||
}).catch(err => {
|
title: "Add New Interactions",
|
||||||
//there was a problem removing any conflicting interactions
|
steps: [
|
||||||
|
{
|
||||||
|
title: "Adding Interactions",
|
||||||
|
promise: interactionAPI.addMultiInteractions(selectedDevice.id(), interactionsToAdd)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
stages.push(stage2)
|
||||||
|
|
||||||
|
//stage three is to update the controller components
|
||||||
|
let stage3: Stage = {
|
||||||
|
title: "Update Controllers",
|
||||||
|
steps: []
|
||||||
|
}
|
||||||
|
componentSets.forEach(set => {
|
||||||
|
set.controllers.forEach(controller => {
|
||||||
|
console.log(controller)
|
||||||
|
let newStep: PromiseStep = {
|
||||||
|
title: "Updating " + controller.name(),
|
||||||
|
promise: componentAPI.update(selectedDevice.id(), controller.settings)
|
||||||
|
}
|
||||||
|
stage3.steps.push(newStep)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
stages.push(stage3)
|
||||||
|
//set those stages to a state variable
|
||||||
|
setPromiseStages(stages)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
//this function just takes in the sets as they change and creates the list of conflicting interactions and the settings to create the new ones and returns them
|
||||||
* this function takes in the sets that are passed in and returns the promises needed for removing conflicting interactions and adding the new ones
|
|
||||||
* IT DOES NOT EXECUTE THEM ONLY CREATES THE PROMISES
|
|
||||||
* returns an object containing two values,
|
|
||||||
* one is an array of promises for conflicting interactions to remove and the other is the promise for adding the new interactions
|
|
||||||
* @param sets
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
const createInteractions = (sets: ComponentSet[]) => {
|
const createInteractions = (sets: ComponentSet[]) => {
|
||||||
if(!selectedDevice) return undefined //if there is no device that was selected, do nothing
|
if(!selectedDevice) return undefined //if there is no device that was selected, do nothing
|
||||||
let multiInteractionSettings: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create()
|
let multiInteractionSettings: pond.MultiInteractionSettings = pond.MultiInteractionSettings.create()
|
||||||
let conflictingInteractions: Interaction[] = []
|
let conflictingInteractions: Interaction[] = []
|
||||||
let linkedComponents = deviceComponents.get(selectedDevice.id().toString());
|
let linkedComponents = deviceComponents.get(selectedDevice.id().toString());
|
||||||
//loop through the sets
|
//loop through the sets
|
||||||
sets.forEach(async (set) => {
|
sets.forEach((set) => {
|
||||||
//loop through the controllers
|
//loop through the controllers
|
||||||
set.controllers.forEach(controller => {
|
set.controllers.forEach(controller => {
|
||||||
//filter the conflicting interactions for that controller
|
//filter the conflicting interactions for that controller
|
||||||
|
|
@ -419,6 +511,7 @@ if (!selectedDevice) return;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
setComponentSets([...sets])
|
||||||
return {
|
return {
|
||||||
conflicting: conflictingInteractions,
|
conflicting: conflictingInteractions,
|
||||||
toAdd: multiInteractionSettings
|
toAdd: multiInteractionSettings
|
||||||
|
|
@ -460,16 +553,73 @@ if (!selectedDevice) return;
|
||||||
|
|
||||||
|
|
||||||
const close = (refresh: boolean) => {
|
const close = (refresh: boolean) => {
|
||||||
|
//clear state data
|
||||||
|
setCurrentStep(0)
|
||||||
|
setInteractionsToAdd(undefined)
|
||||||
|
setPromiseStages([])
|
||||||
onClose(refresh)
|
onClose(refresh)
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = () => {
|
//this step will only be used for drying and hydrating
|
||||||
|
const moistureStep = () => {
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<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"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectDeviceStep = () => {
|
||||||
switch(binMode){
|
switch(binMode){
|
||||||
case pond.BinMode.BIN_MODE_STORAGE:
|
case pond.BinMode.BIN_MODE_STORAGE:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Box>
|
||||||
<DialogTitle>Entering Storage Mode</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
{binCables && binCables?.length > 0 && compDevMap && (
|
{binCables && binCables?.length > 0 && compDevMap && (
|
||||||
<Box marginBottom={2}>
|
<Box marginBottom={2}>
|
||||||
<CableTopNodeSummary
|
<CableTopNodeSummary
|
||||||
|
|
@ -484,33 +634,13 @@ if (!selectedDevice) return;
|
||||||
left alone. Heaters/Fans will be turned off.
|
left alone. Heaters/Fans will be turned off.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
{deviceSelector()}
|
{deviceSelector()}
|
||||||
</DialogContent>
|
</Box>
|
||||||
<DialogActions>
|
|
||||||
<Button variant="contained" onClick={()=>{
|
|
||||||
close(false)
|
|
||||||
}}>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
{/* Confirm - button to enter storage and end conditioning */}
|
|
||||||
<Button variant="contained" color="primary" onClick={() => {
|
|
||||||
close(true)
|
|
||||||
if (selectedDevice) {
|
|
||||||
//update the controllers to be off for the selected device
|
|
||||||
// updateControllers();
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
Confirm
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
)
|
||||||
case pond.BinMode.BIN_MODE_COOLDOWN:
|
|
||||||
case pond.BinMode.BIN_MODE_DRYING:
|
case pond.BinMode.BIN_MODE_DRYING:
|
||||||
|
case pond.BinMode.BIN_MODE_COOLDOWN:
|
||||||
case pond.BinMode.BIN_MODE_HYDRATING:
|
case pond.BinMode.BIN_MODE_HYDRATING:
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<Box>
|
||||||
<DialogTitle>Choose Device for {modeDescription()} Method</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
{modeDescription()} control is handled by interactions between multiple components on the
|
{modeDescription()} control is handled by interactions between multiple components on the
|
||||||
same device. Please choose which device and components will handle this bin's{" "}
|
same device. Please choose which device and components will handle this bin's{" "}
|
||||||
|
|
@ -528,34 +658,145 @@ if (!selectedDevice) return;
|
||||||
//then when the submit button gets clicked it will use the conflicting list and toAdd to remove conflicting interactions and add the new ones
|
//then when the submit button gets clicked it will use the conflicting list and toAdd to remove conflicting interactions and add the new ones
|
||||||
let i = createInteractions(sets)
|
let i = createInteractions(sets)
|
||||||
if(i !== undefined){
|
if(i !== undefined){
|
||||||
console.log(i)
|
|
||||||
setConflictingSetInteractions(i.conflicting)
|
setConflictingSetInteractions(i.conflicting)
|
||||||
setInteractionsToAdd(i.toAdd)
|
setInteractionsToAdd(i.toAdd)
|
||||||
}
|
}
|
||||||
}}/>
|
}}
|
||||||
</DialogContent>
|
/>
|
||||||
<DialogActions>
|
</Box>
|
||||||
<Button variant="contained" onClick={()=>{
|
|
||||||
close(false)
|
|
||||||
}}>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
<Button variant="contained" color="primary" onClick={()=>{
|
|
||||||
//add new interactions to the component sets
|
|
||||||
execute()
|
|
||||||
close(true)
|
|
||||||
}}>
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</DialogActions>
|
|
||||||
</React.Fragment>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const progressStep = () => {
|
||||||
return (
|
return (
|
||||||
<ResponsiveDialog open={open} onClose={() => {close(false)}}>
|
<Box>
|
||||||
{content()}
|
<PromiseProgress
|
||||||
</ResponsiveDialog>
|
stages={promiseStages}
|
||||||
|
failFast
|
||||||
|
onComplete={()=>{
|
||||||
|
console.log("COMPLETED PROMISES")
|
||||||
|
setCurrentStep(0)//reset the stepper
|
||||||
|
changeComplete()//change the boolean preventing more mode changes
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepper = () => {
|
||||||
|
return (
|
||||||
|
<Stepper nonLinear activeStep={currentStep} style={{ width: "100%" }}>
|
||||||
|
{dialogSteps.map(dialogStep => {
|
||||||
|
const labelProps: {
|
||||||
|
optional?: React.ReactNode;
|
||||||
|
} = {};
|
||||||
|
return (
|
||||||
|
<Step key={dialogStep.label} completed={dialogStep.completed}>
|
||||||
|
<StepLabel {...labelProps}>{dialogStep.label}</StepLabel>
|
||||||
|
</Step>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Stepper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actions = () => {
|
||||||
|
let buttons: JSX.Element[] = []
|
||||||
|
//this button should be on every step of all of them
|
||||||
|
buttons.push(
|
||||||
|
<Button onClick={() => {close(false)}}>Close</Button>
|
||||||
|
)
|
||||||
|
// 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){
|
||||||
|
buttons.push(
|
||||||
|
<Button onClick={()=>{setCurrentStep(currentStep-1)}}>Back</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// this button should appear if the step they are on is less than the last step -1
|
||||||
|
if(currentStep < dialogSteps.length-2){
|
||||||
|
buttons.push(
|
||||||
|
<Button onClick={() => {setCurrentStep(currentStep+1)}}>Next</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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){
|
||||||
|
buttons.push(
|
||||||
|
<Button onClick={() => {
|
||||||
|
//if the bin mode is storage, just update the controllers for the selected device and close
|
||||||
|
if(binMode === pond.BinMode.BIN_MODE_STORAGE){
|
||||||
|
close(true)
|
||||||
|
if (selectedDevice) {
|
||||||
|
//update the controllers to be off for the selected device
|
||||||
|
heaters.forEach(heater => {
|
||||||
|
componentAPI
|
||||||
|
.update(selectedDevice.id(), heater.settings, undefined, undefined, as)
|
||||||
|
.then()
|
||||||
|
.catch();
|
||||||
|
});
|
||||||
|
fans.forEach(fan => {
|
||||||
|
componentAPI
|
||||||
|
.update(selectedDevice.id(), fan.settings, undefined, undefined, as)
|
||||||
|
.then()
|
||||||
|
.catch();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
setCurrentStep(dialogSteps.length-1)//set the stepper to the progress step
|
||||||
|
buildPromises()
|
||||||
|
}
|
||||||
|
//if it is cooldown, drying, or hydrating, it will need to go to the progress step and initiate all of the promises
|
||||||
|
}}>Confirm</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return buttons
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepperContent = (step: number) => {
|
||||||
|
switch(binMode){
|
||||||
|
case pond.BinMode.BIN_MODE_STORAGE:
|
||||||
|
return selectDeviceStep();
|
||||||
|
case pond.BinMode.BIN_MODE_COOLDOWN:
|
||||||
|
switch (step) {
|
||||||
|
case 1:
|
||||||
|
return progressStep();
|
||||||
|
default:
|
||||||
|
return selectDeviceStep();
|
||||||
|
}
|
||||||
|
case pond.BinMode.BIN_MODE_DRYING:
|
||||||
|
case pond.BinMode.BIN_MODE_HYDRATING:
|
||||||
|
switch (step) {
|
||||||
|
case 1:
|
||||||
|
return selectDeviceStep();
|
||||||
|
case 2:
|
||||||
|
return progressStep();
|
||||||
|
default:
|
||||||
|
return moistureStep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
{/* <PromiseProgress
|
||||||
|
open={openProgress}
|
||||||
|
stages={promiseStages}
|
||||||
|
onClose={()=>{
|
||||||
|
setOpenProgress(false)
|
||||||
|
close(refresh)
|
||||||
|
}} /> */}
|
||||||
|
<ResponsiveDialog open={open} onClose={() => {close(false)}}>
|
||||||
|
<DialogTitle>
|
||||||
|
{binMode === pond.BinMode.BIN_MODE_STORAGE ? "Entering Storage Mode" : "Choose Device for " + modeDescription() + " Method"}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{stepper()}
|
||||||
|
{stepperContent(currentStep)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
{actions()}
|
||||||
|
</DialogActions>
|
||||||
|
</ResponsiveDialog>
|
||||||
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -19,6 +19,10 @@ export interface ButtonData {
|
||||||
* The function to run when the button is clicked or toggled on when toggle is true
|
* The function to run when the button is clicked or toggled on when toggle is true
|
||||||
*/
|
*/
|
||||||
function: () => void
|
function: () => void
|
||||||
|
/**
|
||||||
|
* whether the button is disabled or not
|
||||||
|
*/
|
||||||
|
disabled?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -49,6 +53,10 @@ interface Props {
|
||||||
* Numerical value for the size of the text for buttons that dont use the icon
|
* Numerical value for the size of the text for buttons that dont use the icon
|
||||||
*/
|
*/
|
||||||
textSize?: number
|
textSize?: number
|
||||||
|
/**
|
||||||
|
* When true will disable all of the buttons in the group, will be overridded by individual buttons disabled state in the button data
|
||||||
|
*/
|
||||||
|
disableAll?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => {
|
const useStyles = makeStyles((theme: Theme) => {
|
||||||
|
|
@ -88,7 +96,7 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function ButtonGroup(props: Props){
|
export default function ButtonGroup(props: Props){
|
||||||
const { buttons, toggle, toggledButtons, multiToggle, textSize } = props
|
const { buttons, toggle, toggledButtons, multiToggle, textSize, disableAll } = props
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const [currentToggle, setCurrentToggle] = useState<number[]>([])
|
const [currentToggle, setCurrentToggle] = useState<number[]>([])
|
||||||
|
|
||||||
|
|
@ -135,6 +143,7 @@ export default function ButtonGroup(props: Props){
|
||||||
<Box className={classes.container} display="flex" flexDirection="row">
|
<Box className={classes.container} display="flex" flexDirection="row">
|
||||||
{buttons.map((b, i) => (
|
{buttons.map((b, i) => (
|
||||||
<Button
|
<Button
|
||||||
|
disabled={disableAll ?? b.disabled}
|
||||||
key={i}
|
key={i}
|
||||||
className={checkToggled(i) ? classes.buttonToggled : classes.button}
|
className={checkToggled(i) ? classes.buttonToggled : classes.button}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
||||||
128
src/common/PromiseProgress.tsx
Normal file
128
src/common/PromiseProgress.tsx
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import { AxiosResponse } from "axios"
|
||||||
|
import ResponsiveDialog from "./ResponsiveDialog"
|
||||||
|
import { Box, Button, CircularProgress, DialogActions, DialogContent, DialogTitle, Icon, Typography } from "@mui/material"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { CheckCircle, Error, Pending } from "@mui/icons-material"
|
||||||
|
import React from "react"
|
||||||
|
|
||||||
|
export interface Stage {
|
||||||
|
title: string
|
||||||
|
steps: Step[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Step {
|
||||||
|
title: string
|
||||||
|
promise: Promise<AxiosResponse>
|
||||||
|
onComplete?: () => void
|
||||||
|
onError?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
stages: Stage[]
|
||||||
|
failFast?: boolean
|
||||||
|
onComplete?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
enum progress {
|
||||||
|
Waiting = 1,
|
||||||
|
InProgress = 2,
|
||||||
|
Complete = 3,
|
||||||
|
Failed = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PromiseProgress(props: Props){
|
||||||
|
const {stages, failFast, onComplete} = props
|
||||||
|
const [completion, setCompletion] = useState<Map<string, progress>>(new Map())
|
||||||
|
|
||||||
|
const execute = () => {
|
||||||
|
let completionMap: Map<string, progress> = new Map()
|
||||||
|
stages.forEach((stage, i) => {
|
||||||
|
stage.steps.forEach((_, k) => {
|
||||||
|
completionMap.set(i + "-" + k, progress.Waiting)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
setCompletion(completionMap)
|
||||||
|
|
||||||
|
const runStages = async () => {
|
||||||
|
let progMap = new Map(completionMap)
|
||||||
|
for (const [i, stage] of stages.entries()) {
|
||||||
|
// For each stage, map the steps into their own promise chains
|
||||||
|
const stepPromises = stage.steps.map((step, k) => {
|
||||||
|
// mark step as in progress
|
||||||
|
progMap.set(i+"-"+k, progress.InProgress);
|
||||||
|
setCompletion(new Map(progMap));
|
||||||
|
return step.promise
|
||||||
|
.then(resp => {
|
||||||
|
// mark step as completed
|
||||||
|
progMap.set(i+"-"+k, progress.Complete);
|
||||||
|
setCompletion(new Map(progMap));
|
||||||
|
if (step.onComplete) step.onComplete();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
// mark step as failed
|
||||||
|
progMap.set(i+"-"+k, progress.Failed);
|
||||||
|
setCompletion(new Map(progMap));
|
||||||
|
if(failFast){
|
||||||
|
throw err; // this rejects this step promise
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for all steps in the current stage to finish
|
||||||
|
// (they run in parallel)
|
||||||
|
await Promise.all(stepPromises);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
runStages().catch(err => {
|
||||||
|
console.error("Execution stopped due to error:", err);
|
||||||
|
}).finally(() => {
|
||||||
|
if(onComplete) onComplete()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCompletion = (completion?: progress) => {
|
||||||
|
switch(completion){
|
||||||
|
case progress.InProgress:
|
||||||
|
return <CircularProgress />
|
||||||
|
case progress.Complete:
|
||||||
|
return <CheckCircle />
|
||||||
|
case progress.Failed:
|
||||||
|
return <Error />
|
||||||
|
default://default will run if it is waiting or there was no completion
|
||||||
|
return <Pending />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayStage = (stage: Stage, number: number) => {
|
||||||
|
return (
|
||||||
|
<Box key={number}>
|
||||||
|
<Typography>Stage {stage.title}</Typography>
|
||||||
|
{stage.steps.map((step, i) => (
|
||||||
|
<Box key={number+"-"+i} display="flex" flexDirection="row" justifyContent="space-between" margin={2} alignContent="center" alignItems="center">
|
||||||
|
<Typography>{step.title}</Typography>
|
||||||
|
{getCompletion(completion.get(number + "-" + i))}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<DialogTitle>
|
||||||
|
Progress
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
{stages.map((stage, number) => displayStage(stage, number))}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => {
|
||||||
|
execute()
|
||||||
|
}}>
|
||||||
|
Start
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue