frontend/src/common/LinearMutationBuilder.tsx

308 lines
10 KiB
TypeScript

import {
Accordion,
AccordionDetails,
AccordionSummary,
Button,
DialogActions,
DialogContent,
DialogTitle,
Grid2 as Grid,
List,
ListItem,
MenuItem,
TextField
} from "@mui/material";
import { ExpandMore } from "@mui/icons-material";
import { Component } from "models";
import { pond, quack } from "protobuf-ts/pond";
import React, { useCallback, useState, useEffect } from "react";
import ResponsiveDialog from "./ResponsiveDialog";
interface Props {
open: boolean;
existingMutation?: pond.LinearMutation;
componentsByDevice: Map<string, Component[]>; //string being device id and component array of that devices components
onClose: () => void;
onSubmit: (mutation: pond.LinearMutation) => void;
}
export default function LinearMutationBuilder(props: Props) {
const { open, onClose, componentsByDevice, onSubmit, existingMutation } = props;
const [coefficientSets, setCoefficientSets] = useState<pond.ComponentCoefficients[]>([]);
const [devOptions, setDevOptions] = useState<string[]>([]);
const [compOptions, setCompOptions] = useState<Component[][]>([]);
const [selectedDevices, setSelectedDevices] = useState<string[]>([]);
const [selectedComponents, setSelectedComponents] = useState<string[]>([]);
const [selectedMutation, setSelectedMutation] = useState(pond.Mutator.MUTATOR_NONE);
const [mutationConstant, setMutationConstant] = useState(0);
const [mutationName, setMutationName] = useState("");
useEffect(() => {
setDevOptions(Array.from(componentsByDevice.keys()));
setMutationName("");
setSelectedMutation(pond.Mutator.MUTATOR_NONE);
setCoefficientSets([]);
setMutationConstant(0);
setSelectedComponents([]);
setSelectedDevices([]);
setCompOptions([]);
if (existingMutation) {
setMutationName(existingMutation.mutationName);
setSelectedMutation(existingMutation.mutation);
setCoefficientSets(existingMutation.componentCoefficients);
setMutationConstant(existingMutation.constant);
let co: Component[][] = [];
let sc: string[] = [];
let sd: string[] = [];
existingMutation.componentCoefficients.forEach(compCo => {
let splitString = compCo.componentKey.split(":", 2);
if (splitString.length > 1) {
sd.push(splitString[0]);
sc.push(splitString[1]);
let c = componentsByDevice.get(splitString[0]);
if (c) {
co.push(c);
}
}
});
setSelectedComponents(sc);
setSelectedDevices(sd);
setCompOptions(co);
}
}, [componentsByDevice, existingMutation]);
const addNewSet = () => {
let cs = coefficientSets;
let sd = selectedDevices;
let co = compOptions;
let sc = selectedComponents;
cs.push(pond.ComponentCoefficients.create());
sd.push("");
co.push([]);
sc.push("");
setCoefficientSets([...cs]);
setSelectedDevices([...sd]);
setCompOptions([...co]);
setSelectedComponents([...sc]);
};
const setOptionsFor = (pos: number, dev: string) => {
let co = compOptions;
let components = componentsByDevice.get(dev);
if (components) {
co[pos] = components;
}
setCompOptions([...co]);
};
const nodeCoefficientDisplay = (
deviceID: string,
componentKey: string,
set: pond.ComponentCoefficients
) => {
set.componentKey = deviceID + ":" + componentKey; //note: changing the set that is passed in is changing the state variable
let componentList = componentsByDevice.get(deviceID);
let component: Component = Component.create();
if (componentList) {
componentList.forEach(comp => {
if (comp.key() === componentKey) {
component = comp;
}
});
}
let nodeList: JSX.Element[] = [];
component.lastMeasurement.forEach((um, k) => {
if (um.values[0]) {
if (set.measurementCoefficients[k] === undefined) {
set.measurementCoefficients[k] = pond.MeasurementCoefficients.create({
coefficients: [],
measurementType: um.type
});
}
for (let i = 0; i < um.values[0].values.length; i++) {
//default to 0 if it isn't set
if (!set.measurementCoefficients[k].coefficients[i]) {
set.measurementCoefficients[k].coefficients[i] = 0;
}
nodeList.push(
<ListItem key={componentKey + "node" + i + um.type}>
<Grid container direction="row" alignContent="center" alignItems="center">
<Grid size={{ xs: 12 }}>
Node {i + 1} ({quack.MeasurementType[um.type]})
</Grid>
<Grid size={{ xs: 12 }}>
<TextField
fullWidth
value={set.measurementCoefficients[k].coefficients[i]}
type="number"
onChange={e => {
set.measurementCoefficients[k].coefficients[i] = +e.target.value;
setCoefficientSets([...coefficientSets]);
}}
/>
</Grid>
</Grid>
</ListItem>
);
}
}
});
return <List>{nodeList}</List>;
};
const displaySets = useCallback(() => {
let setDisplay: JSX.Element[] = [];
coefficientSets.forEach((set, i) => {
setDisplay.push(
<ListItem key={i} style={{ width: 450 }}>
<Grid container direction="column">
<Grid
container
direction="row"
alignContent="center"
alignItems="center"
justifyContent="space-between">
<Grid size={{ xs: 5 }}>
<TextField
fullWidth
label={"Device"}
select
value={selectedDevices[i]}
onChange={event => {
let sd = selectedDevices;
sd[i] = event.target.value as string;
setSelectedDevices([...sd]);
setOptionsFor(i, event.target.value as string);
}}>
{devOptions.map(dev => (
<MenuItem key={dev} value={dev}>
{dev}
</MenuItem>
))}
</TextField>
</Grid>
<Grid size={{ xs: 5 }}>
<TextField
fullWidth
label={"Component"}
select
value={selectedComponents[i]}
onChange={event => {
let sc = selectedComponents;
sc[i] = event.target.value as string;
setSelectedComponents([...sc]);
set.measurementCoefficients = [];
}}>
{compOptions[i] &&
compOptions[i].map(comp => (
<MenuItem key={comp.key()} value={comp.key()}>
{comp.name()}
</MenuItem>
))}
</TextField>
</Grid>
</Grid>
{selectedComponents[i] !== "" && (
<Grid>
<Accordion>
<AccordionSummary expandIcon={<ExpandMore />}>Node Coefficients</AccordionSummary>
<AccordionDetails>
{nodeCoefficientDisplay(selectedDevices[i], selectedComponents[i], set)}
</AccordionDetails>
</Accordion>
</Grid>
)}
</Grid>
</ListItem>
);
});
return <List>{setDisplay}</List>;
}, [coefficientSets, selectedDevices, compOptions, devOptions, selectedComponents]); // eslint-disable-line react-hooks/exhaustive-deps
return (
<ResponsiveDialog open={open} onClose={() => onClose()}>
<DialogTitle>Build New Mutation</DialogTitle>
<DialogContent>
<TextField
style={{ marginBottom: 10 }}
value={mutationName}
fullWidth
label={"Mutation Name"}
onChange={e => {
setMutationName(e.target.value as string);
}}
/>
<TextField
style={{ marginBottom: 10 }}
value={selectedMutation}
fullWidth
select
label={"Mutation Type"}
onChange={e => {
setSelectedMutation(+e.target.value);
}}>
<MenuItem key={pond.Mutator.MUTATOR_NONE} value={pond.Mutator.MUTATOR_NONE}>
Select Type of Calculated Measurement
</MenuItem>
<MenuItem key={pond.Mutator.MUTATOR_FUEL_LEVEL} value={pond.Mutator.MUTATOR_FUEL_LEVEL}>
Fuel Level
</MenuItem>
</TextField>
<TextField
style={{ marginBottom: 10 }}
value={mutationConstant}
fullWidth
type="number"
label={"Mutation Constant"}
onChange={e => {
setMutationConstant(+e.target.value);
}}
/>
{displaySets()}
<Button variant="contained" color="primary" onClick={addNewSet}>
Add Component
</Button>
</DialogContent>
<DialogActions>
{existingMutation ? (
<React.Fragment>
<Button
color="primary"
onClick={() => {
existingMutation.mutationName = mutationName;
existingMutation.constant = mutationConstant;
existingMutation.mutation = selectedMutation;
existingMutation.componentCoefficients = coefficientSets;
onClose();
}}>
Finish
</Button>
</React.Fragment>
) : (
<React.Fragment>
<Button color="primary" onClick={onClose}>
Close
</Button>
<Button
color="primary"
disabled={selectedMutation === pond.Mutator.MUTATOR_NONE}
onClick={() => {
let newMutation = pond.LinearMutation.create();
newMutation.mutation = selectedMutation;
newMutation.componentCoefficients = coefficientSets;
newMutation.constant = mutationConstant;
newMutation.mutationName = mutationName;
onSubmit(newMutation);
onClose();
}}>
Add Mutation
</Button>
</React.Fragment>
)}
</DialogActions>
</ResponsiveDialog>
);
}