re-building the condition display to support adding extra conditions to the interaction for drying and passing the device in to the condition display so that it can use its max conditions function to determine if the extra conditions can be added
This commit is contained in:
parent
aa346a6a5a
commit
e91d2f6005
3 changed files with 400 additions and 167 deletions
|
|
@ -465,7 +465,6 @@ export default function DryingMode(props: Props){
|
|||
|
||||
//this step will use the conflicting interactions and interactions to add to display them and allow users to make changes
|
||||
const interactionStep = () => {
|
||||
console.log(interactionsToAdd)
|
||||
return (
|
||||
<Box>
|
||||
<Typography>Interactions</Typography>
|
||||
|
|
@ -474,13 +473,14 @@ export default function DryingMode(props: Props){
|
|||
temp.settings = interaction
|
||||
let sink = sinkMap.get(componentIDToString(interaction.sink))
|
||||
let source = sourceMap.get(componentIDToString(interaction.source))
|
||||
if(sink && source){
|
||||
if(sink && source && selectedDevice){
|
||||
return (
|
||||
<ConditionDisplay
|
||||
grain={grain}
|
||||
customGrain={customGrain}
|
||||
key={index}
|
||||
interaction={temp}
|
||||
interaction={temp}//using temp because it wants a full interaction, not just its settings
|
||||
device={selectedDevice}
|
||||
sink={sink}
|
||||
source={source}
|
||||
changeConditions={(newSettings) => {
|
||||
|
|
|
|||
|
|
@ -339,13 +339,14 @@ export default function DryingMode(props: Props){
|
|||
temp.settings = interaction
|
||||
let sink = sinkMap.get(componentIDToString(interaction.sink))
|
||||
let source = sourceMap.get(componentIDToString(interaction.source))
|
||||
if(sink && source){
|
||||
if(sink && source && selectedDevice){
|
||||
return (
|
||||
<ConditionDisplay
|
||||
key={index}
|
||||
grain={grain}
|
||||
customGrain={customGrain}
|
||||
interaction={temp}
|
||||
device={selectedDevice}
|
||||
sink={sink}
|
||||
source={source}
|
||||
changeConditions={(newSettings) => {
|
||||
|
|
|
|||
|
|
@ -5,19 +5,21 @@ import {
|
|||
Box,
|
||||
darken,
|
||||
Grid,
|
||||
IconButton,
|
||||
Slider,
|
||||
Theme,
|
||||
Tooltip,
|
||||
Typography
|
||||
} from "@mui/material";
|
||||
import { ExpandMore } from "@mui/icons-material";
|
||||
import { AddCircle, ExpandMore, RemoveCircleOutline } from "@mui/icons-material";
|
||||
import { ExtractMoisture } from "grain";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { Component, Interaction } from "models";
|
||||
import { Component, Device, Interaction } from "models";
|
||||
import { interactionConditionText, interactionResultText } from "pbHelpers/Interaction";
|
||||
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
|
||||
import { pond, quack } from "protobuf-ts/pond";
|
||||
import { useGlobalState } from "providers";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { avg, fahrenheitToCelsius } from "utils";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { Mark } from "@mui/material/Slider/useSlider.types";
|
||||
|
|
@ -38,7 +40,6 @@ import {
|
|||
height: 0,
|
||||
borderLeft: "10px solid transparent",
|
||||
borderRight: "10px solid transparent"
|
||||
//borderTop: "10px solid yellow"
|
||||
},
|
||||
sliderRoot: {},
|
||||
sliderThumb: {
|
||||
|
|
@ -71,12 +72,18 @@ import {
|
|||
},
|
||||
sliderMarkLabel: {
|
||||
top: -25
|
||||
},
|
||||
groupHeader: {
|
||||
marginTop: 10,
|
||||
marginBottom: 5,
|
||||
fontWeight: 650
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
interface Props {
|
||||
interaction: Interaction;
|
||||
device: Device;
|
||||
source: Component;
|
||||
sink: Component;
|
||||
grain?: pond.Grain;
|
||||
|
|
@ -84,57 +91,214 @@ import {
|
|||
changeConditions?: (newSettings: pond.InteractionSettings) => void
|
||||
}
|
||||
|
||||
// A "corner" pairing: one temp condition + one humidity condition whose
|
||||
// comparison directions are consistent with each other, so together they
|
||||
// bound a single EMC value.
|
||||
// temp > X && humid < Y -> bounds EMC from ABOVE (an upper limit)
|
||||
// temp < X && humid > Y -> bounds EMC from BELOW (a lower limit)
|
||||
interface EMCCornerPair {
|
||||
temp: pond.InteractionCondition;
|
||||
humid: pond.InteractionCondition;
|
||||
}
|
||||
|
||||
interface PairedConditions {
|
||||
upperBoundPair?: EMCCornerPair; // temp > X && humid < Y
|
||||
lowerBoundPair?: EMCCornerPair; // temp < X && humid > Y
|
||||
unmatched: pond.InteractionCondition[];
|
||||
}
|
||||
|
||||
const isGTorEQ = (op: quack.RelationalOperator) =>
|
||||
op === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN ||
|
||||
op === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO;
|
||||
|
||||
const isLTorEQ = (op: quack.RelationalOperator) =>
|
||||
op === quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN ||
|
||||
op === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO;
|
||||
|
||||
// Groups the flat condition array into (at most) two EMC-bounding corners,
|
||||
// based purely on comparison direction, not array position/order.
|
||||
function pairEMCConditions(conditions: pond.InteractionCondition[]): PairedConditions {
|
||||
const temps = conditions.filter(
|
||||
c => c.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
|
||||
);
|
||||
const humids = conditions.filter(
|
||||
c => c.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT
|
||||
);
|
||||
|
||||
const hiTemp = temps.find(c => isGTorEQ(c.comparison)); // temp > X
|
||||
const loTemp = temps.find(c => isLTorEQ(c.comparison)); // temp < X
|
||||
const loHumid = humids.find(c => isLTorEQ(c.comparison)); // humid < Y
|
||||
const hiHumid = humids.find(c => isGTorEQ(c.comparison)); // humid > Y
|
||||
|
||||
const result: PairedConditions = { unmatched: [] };
|
||||
|
||||
if (hiTemp && loHumid) result.upperBoundPair = { temp: hiTemp, humid: loHumid };
|
||||
if (loTemp && hiHumid) result.lowerBoundPair = { temp: loTemp, humid: hiHumid };
|
||||
|
||||
const paired = new Set<pond.InteractionCondition>(
|
||||
[hiTemp, loHumid, loTemp, hiHumid].filter((c): c is pond.InteractionCondition => !!c)
|
||||
);
|
||||
result.unmatched = conditions.filter(c => !paired.has(c));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export default function ConditionDisplay(props: Props) {
|
||||
const { interaction, source, sink, grain, customGrain, changeConditions} = props;
|
||||
const [sliderVals, setSliderVals] = useState<Map<quack.MeasurementType, number>>(new Map());
|
||||
const { interaction, device, source, sink, grain, customGrain, changeConditions } = props;
|
||||
const [sliderVals, setSliderVals] = useState<Map<pond.InteractionCondition, number>>(new Map());
|
||||
const [sliderMarks, setSliderMarks] = useState<Map<quack.MeasurementType, number>>(new Map());
|
||||
//this is the emc value calculated from the interactions temp and humidity conditions
|
||||
const [baseEMC, setBaseEMC] = useState<number | undefined>();
|
||||
// EMC bound(s) calculated from the temp/humidity condition pairs.
|
||||
// If only one pair exists, only one of these will be set.
|
||||
const [upperEMC, setUpperEMC] = useState<number | undefined>();
|
||||
const [lowerEMC, setLowerEMC] = useState<number | undefined>();
|
||||
const [{ user }] = useGlobalState();
|
||||
const classes = useStyles();
|
||||
// Bumped whenever conditions are added/removed so `pairs` recomputes.
|
||||
// Needed because pushing into interaction.settings.conditions mutates
|
||||
// the array in place without changing the `interaction` reference, so
|
||||
// useMemo keyed only on [interaction] would never see the new conditions.
|
||||
const [conditionsVersion, setConditionsVersion] = useState(0);
|
||||
|
||||
const pairs = useMemo(
|
||||
() => pairEMCConditions(interaction.conditions()),
|
||||
[interaction, conditionsVersion]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
let passedInteraction = interaction;
|
||||
let sliderVals: Map<quack.MeasurementType, number> = new Map();
|
||||
let sliderMarks: Map<quack.MeasurementType, number> = new Map();
|
||||
let vals: Map<pond.InteractionCondition, number> = new Map();
|
||||
let marks: Map<quack.MeasurementType, number> = new Map();
|
||||
passedInteraction.conditions().forEach(condition => {
|
||||
let describer = describeMeasurement(condition.measurementType, source.type(), undefined, undefined, user);
|
||||
//NOTE: toDisplay will convert the temp value to fahrenheit
|
||||
sliderVals.set(condition.measurementType, describer.toDisplay(condition.value));
|
||||
// NOTE: toDisplay will convert the temp value to fahrenheit
|
||||
// keyed by condition object itself (not measurementType) since there
|
||||
// can now be more than one condition per measurement type
|
||||
vals.set(condition, describer.toDisplay(condition.value));
|
||||
});
|
||||
|
||||
source.status.lastGoodMeasurement.forEach(measurement => {
|
||||
let m = pond.UnitMeasurementsForComponent.fromObject(measurement);
|
||||
if (m.values[0]) {
|
||||
let markVal = avg(m.values[0].values);
|
||||
//do this since this is how interactions handle the values so that the slider can use the toDisplay method of the describer for the marks
|
||||
// do this since this is how interactions handle the values so that the slider can use the toDisplay method of the describer for the marks
|
||||
if (m.type === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) markVal = markVal * 10;
|
||||
if (m.type === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) markVal = markVal * 100;
|
||||
sliderMarks.set(m.type, markVal);
|
||||
marks.set(m.type, markVal);
|
||||
}
|
||||
});
|
||||
setSliderVals(sliderVals);
|
||||
setSliderMarks(sliderMarks);
|
||||
setSliderVals(vals);
|
||||
setSliderMarks(marks);
|
||||
}, [interaction, source]);
|
||||
|
||||
useEffect(() => {
|
||||
let temp = sliderVals.get(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE);
|
||||
let hum = sliderVals.get(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT);
|
||||
if (
|
||||
grain !== undefined &&
|
||||
grain !== pond.Grain.GRAIN_INVALID &&
|
||||
temp &&
|
||||
hum
|
||||
) {
|
||||
// Given a corner pair, calculate the EMC that pair implies. Returns
|
||||
// undefined if grain info or either value isn't available yet.
|
||||
const emcForPair = (pair?: EMCCornerPair): number | undefined => {
|
||||
if (!pair || grain === undefined || grain === pond.Grain.GRAIN_INVALID) return undefined;
|
||||
|
||||
let temp = sliderVals.get(pair.temp);
|
||||
let hum = sliderVals.get(pair.humid);
|
||||
if (temp === undefined || hum === undefined) return undefined;
|
||||
|
||||
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
||||
//the emc calc needs the temp to be in celsius
|
||||
// the emc calc needs the temp to be in celsius
|
||||
temp = fahrenheitToCelsius(temp);
|
||||
}
|
||||
let emc = ExtractMoisture(grain, temp, hum, customGrain)
|
||||
setBaseEMC(emc === hum ? undefined : emc);
|
||||
let emc = ExtractMoisture(grain, temp, hum, customGrain);
|
||||
return emc === hum ? undefined : emc;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setUpperEMC(emcForPair(pairs.upperBoundPair));
|
||||
setLowerEMC(emcForPair(pairs.lowerBoundPair));
|
||||
}, [sliderVals, grain, user, pairs]);
|
||||
|
||||
// Builds a new InteractionCondition with sensible defaults, offset away
|
||||
// from an existing reference condition's value so the new slider doesn't
|
||||
// start stacked exactly on top of the one it's paired against.
|
||||
// ASSUMPTION: InteractionCondition is a plain settable class instance,
|
||||
// matching how the rest of this file mutates `condition.value` directly.
|
||||
// If your codebase instead uses a factory (e.g. pond.InteractionCondition.create(...)),
|
||||
// swap the `new ...()` + assignment below for that factory call.
|
||||
const buildCondition = (
|
||||
measurementType: quack.MeasurementType,
|
||||
comparison: quack.RelationalOperator,
|
||||
displayValue: number
|
||||
): pond.InteractionCondition => {
|
||||
let describer = describeMeasurement(measurementType, source?.type(), source.subType(), undefined, user);
|
||||
const condition = new pond.InteractionCondition();
|
||||
condition.measurementType = measurementType;
|
||||
condition.comparison = comparison;
|
||||
condition.value = Math.round(describer.toStored(displayValue));
|
||||
return condition;
|
||||
};
|
||||
|
||||
// Adds the missing opposite corner pair (temp + humidity condition) so
|
||||
// the EMC display becomes a "between" range instead of a single bound.
|
||||
// Only relevant when exactly one clean corner pair currently exists.
|
||||
const addOppositeCornerPair = () => {
|
||||
const existing = pairs.upperBoundPair ?? pairs.lowerBoundPair;
|
||||
if (!existing) return; // nothing to mirror off of -- shouldn't happen if button is only shown in this case
|
||||
|
||||
const addingLowerBound = !!pairs.upperBoundPair; // if we have the "less than" pair, we're adding the "greater than" one
|
||||
const tempComparison = addingLowerBound
|
||||
? quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN
|
||||
: quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN;
|
||||
const humidComparison = addingLowerBound
|
||||
? quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
|
||||
: quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN;
|
||||
|
||||
const tempDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, source?.type(), source.subType(), undefined, user);
|
||||
const humidDescriber = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, source?.type(), source.subType(), undefined, user);
|
||||
|
||||
const existingTempVal = sliderVals.get(existing.temp) ?? tempDescriber.toDisplay(existing.temp.value);
|
||||
const existingHumidVal = sliderVals.get(existing.humid) ?? humidDescriber.toDisplay(existing.humid.value);
|
||||
|
||||
// offset 10% of the slider's range away from the existing threshold,
|
||||
// in the direction that keeps the two thresholds from overlapping
|
||||
const tempRange = tempDescriber.max() - tempDescriber.min();
|
||||
const humidRange = humidDescriber.max() - humidDescriber.min();
|
||||
const tempOffsetDir = addingLowerBound ? -1 : 1;
|
||||
const humidOffsetDir = addingLowerBound ? 1 : -1;
|
||||
|
||||
const newTempVal = existingTempVal + tempOffsetDir * tempRange * 0.1;
|
||||
const newHumidVal = existingHumidVal + humidOffsetDir * humidRange * 0.1;
|
||||
|
||||
const newTemp = buildCondition(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, tempComparison, newTempVal);
|
||||
const newHumid = buildCondition(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, humidComparison, newHumidVal);
|
||||
|
||||
// ASSUMPTION: interaction.settings.conditions is the backing array for
|
||||
// interaction.conditions() -- push directly so changeConditions() sends
|
||||
// the full, updated list. Adjust if conditions are stored/added differently.
|
||||
interaction.settings.conditions.push(newTemp, newHumid);
|
||||
|
||||
const sliders = cloneDeep(sliderVals);
|
||||
sliders.set(newTemp, newTempVal);
|
||||
sliders.set(newHumid, newHumidVal);
|
||||
setSliderVals(sliders);
|
||||
setConditionsVersion(v => v + 1);
|
||||
|
||||
if (changeConditions) {
|
||||
changeConditions(interaction.settings);
|
||||
}
|
||||
}, [sliderVals, grain, user]);
|
||||
};
|
||||
|
||||
// Removes a corner pair's two conditions, collapsing back to a single
|
||||
// bound. Only relevant when both pairs currently exist.
|
||||
const removeCornerPair = (pair: EMCCornerPair) => {
|
||||
interaction.settings.conditions = interaction.settings.conditions.filter(
|
||||
c => c !== pair.temp && c !== pair.humid
|
||||
);
|
||||
|
||||
const sliders = cloneDeep(sliderVals);
|
||||
sliders.delete(pair.temp);
|
||||
sliders.delete(pair.humid);
|
||||
setSliderVals(sliders);
|
||||
setConditionsVersion(v => v + 1);
|
||||
|
||||
if (changeConditions) {
|
||||
changeConditions(interaction.settings);
|
||||
}
|
||||
};
|
||||
|
||||
const customMark = (val: string, arrowColor: string) => {
|
||||
return (
|
||||
|
|
@ -153,27 +317,22 @@ import {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const conditionDisplay = () => {
|
||||
return (
|
||||
<Grid container>
|
||||
{interaction.conditions().map((condition, i) => {
|
||||
// Renders a single condition's slider. Pulled out so both the grouped
|
||||
// (paired) rendering path and the fallback flat rendering path can share it.
|
||||
// onRemove/removeTooltip, when provided, render a small remove icon inline
|
||||
// with this specific slider -- used when two pairs exist, so each row's
|
||||
// control is unambiguous about which single condition it sits on, even
|
||||
// though clicking it removes that condition's whole paired counterpart too.
|
||||
const renderConditionSlider = (
|
||||
condition: pond.InteractionCondition,
|
||||
key: React.Key,
|
||||
onRemove?: () => void,
|
||||
removeTooltip?: string
|
||||
) => {
|
||||
let describer = describeMeasurement(condition.measurementType, source?.type(), source.subType(), undefined, user);
|
||||
let labelTail = describer.unit();
|
||||
let marks: Mark[] = [];
|
||||
|
||||
// if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
|
||||
// if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
||||
// labelTail = "°F";
|
||||
// } else {
|
||||
// labelTail = "°C";
|
||||
// }
|
||||
// }
|
||||
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
|
||||
labelTail = "%";
|
||||
}
|
||||
|
||||
let mark = sliderMarks.get(condition.measurementType);
|
||||
if (mark !== undefined) {
|
||||
marks.push({
|
||||
|
|
@ -181,12 +340,10 @@ import {
|
|||
value: describer.toDisplay(mark)
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid key={i} container item xs={12} alignContent="center" alignItems="center">
|
||||
<Grid item xs={12}>
|
||||
{interactionConditionText(source, condition, false, user)}
|
||||
</Grid>
|
||||
<Grid item xs={12} style={{ marginBottom: 20 }}>
|
||||
<Grid key={key} container item xs={12} alignItems="center" wrap="nowrap">
|
||||
<Grid item xs style={{ marginBottom: 20 }}>
|
||||
<Slider
|
||||
classes={{
|
||||
root: classes.sliderRoot,
|
||||
|
|
@ -201,103 +358,179 @@ import {
|
|||
step={0.1}
|
||||
valueLabelDisplay="on"
|
||||
valueLabelFormat={value => {
|
||||
if (
|
||||
condition.measurementType ===
|
||||
quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE
|
||||
) {
|
||||
if (
|
||||
user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT
|
||||
) {
|
||||
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE) {
|
||||
if (user.tempUnit() === pond.TemperatureUnit.TEMPERATURE_UNIT_FAHRENHEIT) {
|
||||
return value.toFixed(1) + "°F";
|
||||
} else {
|
||||
return value.toFixed(1) + "°C";
|
||||
}
|
||||
}
|
||||
if (
|
||||
condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT
|
||||
) {
|
||||
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT) {
|
||||
return value.toFixed(1) + "%";
|
||||
}
|
||||
}}
|
||||
marks={marks}
|
||||
min={describer.min()}
|
||||
max={describer.max()}
|
||||
value={sliderVals.get(condition.measurementType) ?? describer.min()}
|
||||
value={sliderVals.get(condition) ?? describer.min()}
|
||||
onChange={(_, val) => {
|
||||
//note that changing it here like this is what is changing it in the interaction itself
|
||||
// note that changing it here like this is what is changing it in the interaction itself
|
||||
condition.value = Math.round(describer.toStored(val as number));
|
||||
// if(changeConditions){
|
||||
// changeConditions(interaction.settings)
|
||||
// }
|
||||
let sliders = cloneDeep(sliderVals);
|
||||
sliders.set(condition.measurementType, val as number);
|
||||
sliders.set(condition, val as number);
|
||||
setSliderVals(sliders);
|
||||
}}
|
||||
onChangeCommitted={(_, val) => {
|
||||
if(changeConditions){
|
||||
changeConditions(interaction.settings)
|
||||
onChangeCommitted={() => {
|
||||
if (changeConditions) {
|
||||
changeConditions(interaction.settings);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
{onRemove && (
|
||||
<Grid item>
|
||||
<Tooltip title={removeTooltip ?? "Remove"}>
|
||||
<IconButton color="error" size="small" onClick={onRemove}>
|
||||
<RemoveCircleOutline />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const determineEMCRelation = () => {
|
||||
let relation = "Approximately:";
|
||||
let tempRelation: quack.RelationalOperator | undefined = undefined;
|
||||
let humidRelation: quack.RelationalOperator | undefined = undefined;
|
||||
interaction.conditions().forEach(condition => {
|
||||
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE)
|
||||
tempRelation = condition.comparison;
|
||||
if (condition.measurementType === quack.MeasurementType.MEASUREMENT_TYPE_PERCENT)
|
||||
humidRelation = condition.comparison;
|
||||
});
|
||||
// Text like "Humidity is below 32.5% and above 60.0%" / "Humidity between 32.5% and 60.0%"
|
||||
// Falls back gracefully if only one of the pair exists yet (e.g. mid-edit).
|
||||
const groupLabel = (
|
||||
measurementType: quack.MeasurementType,
|
||||
loCondition: pond.InteractionCondition | undefined,
|
||||
hiCondition: pond.InteractionCondition | undefined
|
||||
) => {
|
||||
let describer = describeMeasurement(measurementType, source?.type(), source.subType(), undefined, user);
|
||||
let name = measurementType === quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE ? "Temperature" : "Humidity";
|
||||
let unit = describer.unit();
|
||||
|
||||
if (
|
||||
(tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN ||
|
||||
tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO) &&
|
||||
(humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN ||
|
||||
humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO)
|
||||
) {
|
||||
relation = "Less Than: ";
|
||||
if (loCondition && hiCondition) {
|
||||
let loVal = sliderVals.get(loCondition);
|
||||
let hiVal = sliderVals.get(hiCondition);
|
||||
if (loVal !== undefined && hiVal !== undefined) {
|
||||
return `${name} is between ${loVal.toFixed(1)}${unit} and ${hiVal.toFixed(1)}${unit}`;
|
||||
}
|
||||
if (
|
||||
(tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_LESS_THAN ||
|
||||
tempRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO) &&
|
||||
(humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN ||
|
||||
humidRelation === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO)
|
||||
) {
|
||||
relation = "Greater Than: ";
|
||||
}
|
||||
|
||||
return relation;
|
||||
// fall back to whichever single condition is present
|
||||
let only = loCondition ?? hiCondition;
|
||||
if (only) return interactionConditionText(source, only, false, user);
|
||||
return name;
|
||||
};
|
||||
|
||||
const conditionDisplay = () => {
|
||||
// If both corner pairs exist, we have two temp conditions and two humid
|
||||
// conditions -- group them together (temp with temp, humid with humid)
|
||||
// instead of rendering per-condition like the single-pair case.
|
||||
const hasTwoPairs = !!pairs.upperBoundPair && !!pairs.lowerBoundPair;
|
||||
|
||||
if (hasTwoPairs) {
|
||||
// upperBoundPair.temp is "temp > X" (the low end of the temp range)
|
||||
// lowerBoundPair.temp is "temp < X" (the high end of the temp range)
|
||||
const tempLo = pairs.upperBoundPair!.temp;
|
||||
const tempHi = pairs.lowerBoundPair!.temp;
|
||||
// upperBoundPair.humid is "humid < Y" (the high end doesn't apply here -
|
||||
// it's the low end of the humidity range)
|
||||
const humidLo = pairs.upperBoundPair!.humid;
|
||||
const humidHi = pairs.lowerBoundPair!.humid;
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography>{groupLabel(quack.MeasurementType.MEASUREMENT_TYPE_TEMPERATURE, tempLo, tempHi)}</Typography>
|
||||
</Grid>
|
||||
{renderConditionSlider(tempLo, "temp-lo", () => removeCornerPair(pairs.upperBoundPair!), "Remove less than range (also removes matching humidity threshold)")}
|
||||
{renderConditionSlider(tempHi, "temp-hi", () => removeCornerPair(pairs.lowerBoundPair!), "Remove greater than range (also removes matching humidity threshold)")}
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Typography>{groupLabel(quack.MeasurementType.MEASUREMENT_TYPE_PERCENT, humidLo, humidHi)}</Typography>
|
||||
</Grid>
|
||||
{renderConditionSlider(humidLo, "humid-lo", () => removeCornerPair(pairs.upperBoundPair!), "Remove less than range (also removes matching temperature threshold)")}
|
||||
{renderConditionSlider(humidHi, "humid-hi", () => removeCornerPair(pairs.lowerBoundPair!), "Remove greater than range (also removes matching temperature threshold)")}
|
||||
|
||||
{pairs.unmatched.map((c, i) => (
|
||||
<React.Fragment key={`unmatched-${i}`}>
|
||||
<Grid item xs={12}>
|
||||
<Typography>{interactionConditionText(source, c, false, user)}</Typography>
|
||||
</Grid>
|
||||
{renderConditionSlider(c, `unmatched-slider-${i}`)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
// single pair (or no clean pair) -- original flat rendering
|
||||
const hasExactlyOnePair =
|
||||
(!!pairs.upperBoundPair && !pairs.lowerBoundPair) ||
|
||||
(!pairs.upperBoundPair && !!pairs.lowerBoundPair);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
{interaction.conditions().map((condition, i) => (
|
||||
<React.Fragment key={i}>
|
||||
<Grid item xs={12}>
|
||||
<Typography>{interactionConditionText(source, condition, false, user)}</Typography>
|
||||
</Grid>
|
||||
{renderConditionSlider(condition, `slider-${i}`)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
{hasExactlyOnePair && (
|
||||
<Grid item xs={12} container justifyContent="center" style={{ marginTop: 10 }}>
|
||||
<Tooltip title={`Add ${pairs.upperBoundPair ? "greater than" : "less than"} range`}>
|
||||
<IconButton disabled={device.maxConditions() < 4} color="primary" onClick={addOppositeCornerPair}>
|
||||
<AddCircle fontSize="large" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
const determineEMCLabel = () => {
|
||||
if (upperEMC !== undefined && lowerEMC !== undefined) return "Between: ";
|
||||
if (upperEMC !== undefined) return "Less Than: ";
|
||||
if (lowerEMC !== undefined) return "Greater Than: ";
|
||||
return "Approximately: ";
|
||||
};
|
||||
|
||||
const emcColour = describeMeasurement(quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC).colour();
|
||||
|
||||
const emcValueDisplay = () => {
|
||||
if (upperEMC !== undefined && lowerEMC !== undefined) {
|
||||
// present low-to-high regardless of which pair produced which,
|
||||
// since "greater than" bound is the smaller number and
|
||||
// "less than" bound is the larger number
|
||||
const lo = Math.min(upperEMC, lowerEMC);
|
||||
const hi = Math.max(upperEMC, lowerEMC);
|
||||
return `${lo.toFixed(1)}% and ${hi.toFixed(1)}%`;
|
||||
}
|
||||
const single = upperEMC ?? lowerEMC;
|
||||
return single !== undefined ? `${single.toFixed(1)}%` : "";
|
||||
};
|
||||
|
||||
const hasAnyEMC = upperEMC !== undefined || lowerEMC !== undefined;
|
||||
|
||||
return (
|
||||
<Grid key={interaction.key()} container className={classes.displayBG}>
|
||||
<Grid item xs={12}>
|
||||
<Typography align="center" style={{ margin: 5, marginBottom: 10, fontWeight: 650 }}>
|
||||
{interactionResultText(interaction, sink)}
|
||||
</Typography>
|
||||
{baseEMC !== undefined ? (
|
||||
{hasAnyEMC ? (
|
||||
<Accordion>
|
||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||
<Box display="flex">
|
||||
<Typography style={{ fontWeight: 650 }}>EMC {determineEMCRelation()}</Typography>
|
||||
<Typography
|
||||
style={{
|
||||
marginLeft: 5,
|
||||
fontWeight: 650,
|
||||
color: describeMeasurement(
|
||||
quack.MeasurementType.MEASUREMENT_TYPE_GRAIN_EMC
|
||||
).colour()
|
||||
}}>
|
||||
{baseEMC.toFixed(1)}%
|
||||
<Typography style={{ fontWeight: 650 }}>EMC {determineEMCLabel()}</Typography>
|
||||
<Typography style={{ marginLeft: 5, fontWeight: 650, color: emcColour }}>
|
||||
{emcValueDisplay()}
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
|
|
@ -310,4 +543,3 @@ import {
|
|||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue