frontend/src/component/ComponentSettings.tsx

1076 lines
34 KiB
TypeScript

import {
Box,
Button,
Checkbox,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
Divider,
FormControlLabel,
Grid2 as Grid,
IconButton,
MenuItem,
Step,
StepLabel,
Stepper,
Switch,
Tab,
Tabs,
TextField,
Theme,
Tooltip,
Typography
} from "@mui/material";
import DeleteButton from "common/DeleteButton";
import ResponsiveDialog from "common/ResponsiveDialog";
import SearchSelect, { Option } from "common/SearchSelect";
import { useComponentAPI, useDeviceAPI, usePrevious, useSnackbar } from "hooks";
import { cloneDeep } from "lodash";
import { Component, Device } from "models";
import {
getFriendlyAddressTypeName,
getHumanReadableAddress,
isSingular
} from "pbHelpers/AddressType";
import {
extension,
getAddressTypes,
GetComponentTypeOptions,
getFriendlyName,
getMeasurements,
isController
} from "pbHelpers/ComponentType";
import {
ComponentAvailabilityMap,
DeviceAvailabilityMap,
OffsetAvailabilityMap
} from "pbHelpers/DeviceAvailability";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import React, { useCallback, useEffect, useState } from "react";
import { or } from "utils/types";
import { ConfigurablePin } from "pbHelpers/AddressTypes";
import ColourPicker from "common/ColourPicker";
import classNames from "classnames";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import ComponentForm from "./ComponentForm";
import { green, red } from "@mui/material/colors";
import { makeStyles } from "@mui/styles";
import {
Remove as RemoveIcon,
Add as AddIcon,
} from "@mui/icons-material"
import CancelSubmit from "common/CancelSubmit";
import { useGlobalState } from "providers";
const useStyles = makeStyles((theme: Theme) => {
return ({
stepper: {
paddingRight: 0,
paddingLeft: 0
},
switchControl: {
marginLeft: theme.spacing(0.25),
[theme.breakpoints.up("sm")]: {
marginLeft: theme.spacing(1)
}
},
selectInput: {
minHeight: "48px"
},
fullWidth: {
width: "100%"
},
heading: {
fontSize: theme.typography.pxToRem(13),
flexBasis: "25%",
flexShrink: 0
},
secondaryHeading: {
fontSize: theme.typography.pxToRem(13),
color: theme.palette.text.secondary
},
greenButton: {
color: green["600"]
},
redButton: {
color: red["600"]
},
noPadding: {
padding: 0
},
dataUsageCard: { width: "100%" },
dataUsageHeader: { paddingBottom: 0 },
dataUsageIcon: { color: "var(--status-warning)" },
dataUsageContent: { paddingTop: 0 }
})
});
interface Props {
device: Device;
component?: Component;
isDialogOpen: boolean;
closeDialogCallback: () => void;
availablePositions: DeviceAvailabilityMap;
availableOffsets: OffsetAvailabilityMap; //deprecated: this is no longer used with the change of cable id to be a number entry
mode?: "add" | "remove" | "update";
refreshCallback: () => void;
canEdit: boolean;
addressTypeRestriction?: quack.AddressType;
deviceComponentPrefs?: pond.DeviceComponentPreferences;
}
interface TabPanelProps {
children?: React.ReactNode;
index: any;
value: any;
}
function TabPanelMine(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
aria-labelledby={`simple-tab-${index}`}
{...other}>
{value === index && <React.Fragment>{children}</React.Fragment>}
</div>
);
}
export default function ComponentSettings(props: Props) {
const classes = useStyles();
const componentAPI = useComponentAPI();
const deviceAPI = useDeviceAPI();
const { error, warning, success } = useSnackbar();
const {
device,
isDialogOpen,
closeDialogCallback,
availablePositions,
mode,
refreshCallback,
canEdit,
addressTypeRestriction,
deviceComponentPrefs
} = props;
const [{as}] = useGlobalState();
const prevComponent = usePrevious(props.component);
const prevIsDialogOpen = usePrevious(isDialogOpen);
const steps = [0, 1];
const [activeStep, setActiveStep] = useState(0);
const [componentType, setComponentType] = useState<Option | null | undefined>();
const componentTypeOptions: Option[] = GetComponentTypeOptions(addressTypeRestriction);
const [removeDialogOpen, setRemoveDialogOpen] = useState(false);
const [formComponent, setFormComponent] = useState<Component>(new Component());
const [cableID, setCableID] = useState(0);
const [expansionLine, setExpansionLine] = useState(0);
const [overlayIndex, setOverlayIndex] = useState(0);
const [overlayColourDialog, setOverlayCoulourDialog] = useState(false);
const [tabVal, setTabVal] = useState(0);
const [formValid, setFormValid] = useState(false);
const [excludedNodes, setExcludedNodes] = useState<number[]>([]);
const [nodeToExclude, setNodeToExclude] = useState(0);
const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
setTabVal(newValue);
};
// useEffect(() => {
// if (deviceComponentPrefs) {
// setExcludedNodes(deviceComponentPrefs.excludedNodes);
// }
// }, [deviceComponentPrefs]);
const init = useCallback(() => {
let component = props.component;
let initComponent = new Component();
if (component) {
initComponent.settings = cloneDeep(component.settings);
initComponent.status = cloneDeep(component.status);
}
if (
initComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE &&
initComponent.settings.grainType === pond.Grain.GRAIN_INVALID
) {
initComponent.settings.grainType = pond.Grain.GRAIN_NONE;
}
setComponentType(
componentTypeOptions.find(
t =>
t.value ===
initComponent.settings.type.toString() + ":" + initComponent.settings.subtype.toString()
)
);
setFormComponent(initComponent);
setExcludedNodes(initComponent.settings.excludedNodes)
}, [props.component, componentTypeOptions]);
useEffect(() => {
if (props.component !== prevComponent || (!isDialogOpen && prevIsDialogOpen)) {
init();
}
}, [props.component, prevComponent, init, isDialogOpen, prevIsDialogOpen]);
const close = () => {
closeDialogCallback();
};
const isFormValid = () => {
//let { component } = form;
let type = or(formComponent.settings.type, "");
if(supportsExpansion() && expansionLine === 0){
return false
}
return isComponentTypeValid(type) && isAddressValid(formComponent);
};
const isComponentTypeValid = (componentType: quack.ComponentType) => {
if (!componentType) {
return false;
}
return true;
};
const getAvailablePositions = (
addressType: quack.AddressType,
componentType: quack.ComponentType
): number[] => {
let positions = availablePositions.get(addressType);
if (!positions) return [];
switch (addressType) {
case quack.AddressType.ADDRESS_TYPE_I2C:
case quack.AddressType.ADDRESS_TYPE_SPI:
let componentPositions;
if (positions) {
componentPositions = (positions as ComponentAvailabilityMap).get(componentType);
}
return componentPositions ? (componentPositions as number[]) : [];
case quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY:
return positions ? (positions as ConfigurablePin[]).map(p => p.address) : [];
default:
return positions ? (positions as number[]) : [];
}
};
const isAddressValid = (component: Component) => {
let availablePosition = true;
if (mode === "add") {
let positions = getAvailablePositions(
component.settings.addressType,
component.settings.type
);
availablePosition = positions ? positions.length > 0 : false;
}
return (
availablePosition &&
(isSingular(component.settings.addressType, component.settings.type) ||
or(component.settings.address, 0) >= 0)
);
};
const hasAvailablePositions = (component: Component) => {
// return true // TODO: actually fix this
if (component.settings.addressType === quack.AddressType.ADDRESS_TYPE_INCREMENTAL) {
return true;
}
const addressTypes: quack.AddressType[] = getAddressTypes(
component.settings.type,
component.settings.subtype
);
for (let i = 0; i < addressTypes.length; i++) {
if (getAvailablePositions(addressTypes[i], component.settings.type).length > 0) {
return true;
}
}
return false;
};
const stepBack = () => {
setActiveStep(activeStep - 1);
};
const stepNext = () => {
setActiveStep(activeStep + 1);
};
const refresh = () => {
if (refreshCallback) {
refreshCallback();
}
};
const addComponent = () => {
const component = formComponent;
//component.settings.calibrationCoefficient = Number(form.coefficient);
if (cableID > 0) component.settings.addressType = cableID + 8;
component.settings.expansionLine = expansionLine
componentAPI
.add(device.id(), component.settings, as)
.then((_response: any) => {
success(component.name() + " was successfully added!");
refresh();
})
.catch((_err: any) => {
error("Error occured while adding " + component.name() + ".");
})
.finally(() => close());
};
const updateComponent = () => {
const component = formComponent;
component.settings.excludedNodes = excludedNodes
componentAPI
.update(device.id(), component.settings, getContextKeys(), getContextTypes(), as)
.then((_response: any) => {
success(component.name() + " was successfully updated!");
// let updatedPrefs = pond.DeviceComponentPreferences.create();
// updatedPrefs.excludedNodes = excludedNodes;
// deviceAPI
// .updateComponentPreferences(device.id(), component.key(), updatedPrefs, undefined, undefined, as)
// .then(_resp => {
// console.log("Preferences updated");
// })
// .catch(_err => {
// console.log("Preferences failed to update");
// });
refresh();
})
.catch((_err: any) => {
error("Error occured while updating " + component.name() + ".");
})
.finally(() => {
close();
});
};
const removeComponent = () => {
componentAPI
.remove(device.id(), formComponent.key(), getContextKeys(), getContextTypes(), as)
.then((_response: any) => {
success(formComponent.name() + " was successfully removed!");
refresh();
})
.catch((err: any) => {
err.response.data.error
? warning(err.response.data.error)
: error("Error occured while removing " + formComponent.name() + ".");
})
.finally(() => {
close();
});
};
const handleTypeChanged = (option: Option | null) => {
setComponentType(option);
if (!option) {
formComponent.settings = pond.ComponentSettings.create();
} else {
let rawType = option.value.split(":");
let type = quack.ComponentType[rawType[0] as keyof typeof quack.ComponentType];
let subtype = parseInt(rawType[1], 10);
let ext = extension(type, subtype);
formComponent.settings.type = type;
formComponent.settings.subtype = subtype;
formComponent.settings.addressType = or(addressTypeRestriction, getAddressTypes(
formComponent.settings.type,
formComponent.settings.subtype
)[0]);
formComponent.settings.address = or(
getAvailablePositions(formComponent.settings.addressType, formComponent.settings.type)[0],
Component.create().settings.address
);
formComponent.settings.name = option.label;
if (type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE) {
formComponent.settings.grainType = pond.Grain.GRAIN_NONE;
}
let periodMs = Math.max(60000, ext.minMeasurementPeriodMs);
formComponent.settings.measurementPeriodMs = periodMs;
formComponent.settings.reportPeriodMs = periodMs;
if (ext.isController && ext.minCycleTimeMs) {
formComponent.settings.minCycleTimeMs = ext.minCycleTimeMs;
}
//if there is a default cable id set it here
if (option.defaultCableID) {
setCableID(option.defaultCableID);
}
}
};
const handlePositionChanged = (event: any) => {
setCableID(0)
setExpansionLine(0)
let f = cloneDeep(formComponent);
//split the string
let split: string[] = event.target.value.toString().split(":")
// pop the addres off of the end of the array
f.settings.address = parseInt(split.pop() ?? "0")
// pop the address type out of the array next
f.settings.addressType = parseInt(split.pop() ?? "0")
// f.settings.address = event.target.value
// .toString()
// .split(":")
// .pop();
setFormComponent(f);
};
const getAvailableMenuItemPositions = (type: quack.ComponentType, subtype: number) => {
const addressTypes = getAddressTypes(type, subtype);
let availableMenuItemPositions = [];
for (let i = 0; i < addressTypes.length; i++) {
let addressType = addressTypes[i];
let availablePositions = getAvailablePositions(addressType, type);
availableMenuItemPositions.push(
<MenuItem key={addressType} divider disabled>
{getFriendlyAddressTypeName(addressType)}
</MenuItem>
);
for (let j = 0; j < availablePositions.length; j++) {
availableMenuItemPositions.push(
<MenuItem
key={i.toString() + "-" + j.toString()}
value={addressType + ":" + availablePositions[j].toString()}>
{getHumanReadableAddress(
addressType,
type,
availablePositions[j],
device.settings.product
)}
</MenuItem>
);
}
}
return availableMenuItemPositions;
};
const toggleOverlays = () => {
let f = cloneDeep(formComponent);
f.settings.hasOverlays = !f.settings.hasOverlays;
setFormComponent(f);
};
const title = () => {
if (mode === "add") {
return (
<DialogTitle id="add-component-dialog-title">
Add Component
<Typography variant="body2" color="textSecondary">
{device.name()} - ID: {device.id()}
</Typography>
</DialogTitle>
);
}
return (
<DialogTitle id="component-settings-dialog-title">
{formComponent.name()} Settings
<Typography variant="body2" color="textSecondary">
{formComponent.subTypeName()}
</Typography>
<Typography variant="body2" color="textSecondary">
{device.name()} - ID: {device.id()}
</Typography>
</DialogTitle>
);
};
const content = () => {
if (mode === "add") {
return addForm();
}
return (
<DialogContent>
<ComponentForm
component={formComponent}
device={device}
canEdit={canEdit}
componentChanged={(formComp, isValid) => {
formComponent.settings = formComp.settings;
setFormValid(isValid);
}}
/>
</DialogContent>
);
};
const isCableComponent = () => {
return (formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE ||
formComponent.settings.type ===
quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE ||
formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_PRESSURE_CABLE ||
(formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_CAPACITOR_CABLE &&
formComponent.subType() ===
quack.CapacitorCableSubtype.CAPACITOR_CABLE_SUBTYPE_FROG))
}
const supportsExpansion = () => {
return ((formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_GRAIN_CABLE ||
formComponent.settings.type === quack.ComponentType.COMPONENT_TYPE_DRAGER_GAS_DONGLE) &&
formComponent.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C)
}
const addForm = () => {
return (
<DialogContent>
{activeStep === 0 ? (
<React.Fragment>
<SearchSelect
selected={componentType}
changeSelection={handleTypeChanged}
label="Type"
options={componentTypeOptions}
group
/>
<TextField
select
fullWidth
required
id="position"
name="position"
label="Position"
value={
formComponent.settings.addressType + ":" + formComponent.settings.address.toString()
}
onChange={handlePositionChanged}
margin="normal"
autoFocus={false}
disabled={
!isAddressValid(formComponent) ||
isSingular(formComponent.settings.addressType, formComponent.settings.type)
}
helperText={
isComponentTypeValid(formComponent.settings.type)
? hasAvailablePositions(formComponent)
? isSingular(formComponent.settings.addressType, formComponent.settings.type) ||
formComponent.settings.address >= 0
? ""
: "* Please select a position *"
: "No positions available for " +
getFriendlyName(formComponent.settings.type) +
" components"
: "* Position depends on the type *"
}
error={
isComponentTypeValid(formComponent.settings.type) &&
!hasAvailablePositions(formComponent)
}
variant="outlined"
InputLabelProps={{ shrink: true }}
SelectProps={{ className: classes.selectInput }}>
{getAvailableMenuItemPositions(
formComponent.settings.type,
formComponent.settings.subtype
)}
</TextField>
{supportsExpansion() && setExpLine()}
{(isCableComponent() && !supportsExpansion())&& setComponentAddrType()}
</React.Fragment>
) : activeStep === 1 ? (
<ComponentForm
component={formComponent}
device={device}
canEdit={canEdit}
componentChanged={(formComp, isValid) => {
formComponent.settings = formComp.settings;
setFormValid(isValid);
}}
/>
) : null}
<Stepper activeStep={activeStep} className={classes.stepper}>
<Step key={0}>
<StepLabel>Required</StepLabel>
</Step>
<Step key={1}>
<StepLabel
optional={
<Typography variant="caption" color="textSecondary">
Optional
</Typography>
}>
General
</StepLabel>
</Step>
</Stepper>
</DialogContent>
);
};
const setExpLine = () => {
return (
<TextField
label="Expansion Line"
type="number"
margin="normal"
error={expansionLine < 1 || expansionLine > 12}
fullWidth
variant="outlined"
value={expansionLine}
helperText="Enter line the cable is connected to on your expander"
onChange={e => {
let number = parseInt(e.target.value);
if (number < 0 || isNaN(number)) number = 0;
if (number > 12) number = 12;
setExpansionLine(number);
}}
/>
);
};
const setComponentAddrType = () => {
return (
<TextField
label="Cable ID"
type="number"
margin="normal"
fullWidth
variant="outlined"
value={cableID}
InputLabelProps={{ shrink: true }}
helperText="Enter cable ID, 0 for auto"
onChange={e => {
let number = parseInt(e.target.value);
if (number < 0 || isNaN(number)) number = 0;
if (number > 100) number = 100;
setCableID(number);
}}
/>
);
};
const createNewOverlay = () => {
let f = cloneDeep(formComponent);
f.settings.overlays.push(
pond.ComponentOverlays.create({
min: 0,
max: 0,
colour: "#" + Math.floor(Math.random() * 16777215).toString(16),
message: "",
measurementType: getMeasurements(formComponent.type(), formComponent.subType())[0]
.measurementType
})
);
setFormComponent(f);
};
const removeOverlay = (index: number) => {
let f = cloneDeep(formComponent);
f.settings.overlays.splice(index, 1);
setFormComponent(f);
};
const changeOverlayMin = (index: number, min: number) => {
let f = cloneDeep(formComponent);
f.settings.overlays[index].min = min;
setFormComponent(f);
};
const changeOverlayMax = (index: number, max: number) => {
let f = cloneDeep(formComponent);
f.settings.overlays[index].max = max;
setFormComponent(f);
};
const changeOverlayMessage = (index: number, message: string) => {
let f = cloneDeep(formComponent);
f.settings.overlays[index].message = message;
setFormComponent(f);
};
const changeOverlayColour = (index: number, colour: string) => {
let f = cloneDeep(formComponent);
f.settings.overlays[index].colour = colour;
setFormComponent(f);
};
const changeMeasurementType = (index: number, type: quack.MeasurementType) => {
let f = cloneDeep(formComponent);
f.settings.overlays[index].measurementType = type;
setFormComponent(f);
};
const overlayGroup = () => {
//const { component } = form;
let types = getMeasurements(formComponent.type(), formComponent.subType());
const groups = [];
for (let i = 0; i < formComponent.settings.overlays.length; i++) {
groups[i] = (
<React.Fragment key={"overlay" + i}>
<Grid size={{ xs: 6, sm: 6 }}>
<TextField
id="min"
label="Minimum"
disabled={!formComponent.settings.hasOverlays}
value={formComponent.settings.overlays[i].min}
onChange={e => changeOverlayMin(i, +e.target.value)}
margin="normal"
fullWidth
type="number"
variant="outlined"
/>
<TextField
id="max"
label="Maximum"
disabled={!formComponent.settings.hasOverlays}
value={formComponent.settings.overlays[i].max}
onChange={e => changeOverlayMax(i, +e.target.value)}
margin="normal"
fullWidth
type="number"
variant="outlined"
/>
<TextField
id="message"
label="message"
disabled={!formComponent.settings.hasOverlays}
value={formComponent.settings.overlays[i].message}
onChange={e => changeOverlayMessage(i, e.target.value)}
margin="normal"
fullWidth
type="text"
variant="outlined"
/>
</Grid>
<Grid size={{ xs: 6, sm: 6 }}>
<Grid container alignContent="center">
<Grid size={{ xs: 12, sm: 12 }} style={{ marginBottom: 30 }}>
<TextField
select
id="measurementType"
required={true}
helperText="Measurement"
disabled={!formComponent.settings.hasOverlays}
value={formComponent.settings.overlays[i].measurementType}
onChange={event => changeMeasurementType(i, +event.target.value)}
autoFocus={false}
margin="dense"
variant="standard"
fullWidth
InputLabelProps={{ shrink: true }}>
{types.map(type => (
<MenuItem key={type.measurementType} value={type.measurementType}>
{type.label}
</MenuItem>
))}
</TextField>
</Grid>
<Grid size={{ xs: 12, sm: 12 }}>
<Box
width={"100%"}
height={50}
marginBottom={3}
style={{
background: formComponent.settings.overlays[i].colour,
cursor: "pointer"
}}
onClick={() => {
setOverlayIndex(i);
setOverlayCoulourDialog(true);
}}
/>
</Grid>
<Grid size={{ xs: 12, sm: 12 }}>
<Tooltip title="Remove Overlay">
<Box width={"100%"} style={{ textAlign: "center", padding: 10 }}>
<IconButton
color="default"
disabled={!formComponent.settings.hasOverlays || !canEdit}
aria-label="Remove overlay"
onClick={() => removeOverlay(i)}
className={classNames(classes.redButton)}>
<RemoveIcon />
</IconButton>
</Box>
</Tooltip>
</Grid>
</Grid>
</Grid>
</React.Fragment>
);
}
return (
<React.Fragment>
<Grid size={{ xs: 12, sm: 12 }}>
<Grid container alignItems="center">
<Grid size={{ xs: 6, sm: 6 }}>
<Tooltip
title="Creates an indicator for when measurements are within a given range"
placement="bottom">
<FormControlLabel
control={
<Switch
checked={formComponent.settings.hasOverlays}
onChange={toggleOverlays}
name="overlays"
aria-label="overlays"
color="secondary"
/>
}
label="Overlays"
labelPlacement="top"
className={classes.switchControl}
disabled={!canEdit}
/>
</Tooltip>
</Grid>
<Grid size={{ xs: 6, sm: 6 }}>
<Tooltip title="Create New Overlay">
<div>
<IconButton
color="primary"
disabled={!formComponent.settings.hasOverlays || !canEdit}
aria-label="Add another overlay"
onClick={createNewOverlay}
className={classNames(classes.greenButton, classes.noPadding)}>
<AddIcon />
</IconButton>
</div>
</Tooltip>
</Grid>
</Grid>
</Grid>
{groups}
{formComponent.settings.overlays[overlayIndex] && (
<ResponsiveDialog
open={overlayColourDialog}
onClose={() => setOverlayCoulourDialog(false)}>
<DialogContent>
<Box width={200}>
<ColourPicker
colour={formComponent.settings.overlays[overlayIndex].colour}
onChange={colour => changeOverlayColour(overlayIndex, colour)}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setOverlayCoulourDialog(false)} color="primary">
Done
</Button>
</DialogActions>
</ResponsiveDialog>
)}
</React.Fragment>
);
};
const actions = () => {
return (
<DialogActions>
<Grid container justifyContent="space-between" direction="row" width={"100%"}>
<Grid size={{ xs: 5 }} >
{mode === "add" ? (
<Button disabled={activeStep === 0} onClick={stepBack} color="primary">
Back
</Button>
) : (
<DeleteButton disabled={!canEdit} onClick={() => setRemoveDialogOpen(true)}>Delete</DeleteButton>
)}
</Grid>
<Grid size={{ xs: 7 }} container justifyContent="flex-end">
{mode === "add" ? (
<React.Fragment>
<Button onClick={close} color="primary">
<Typography variant="subtitle2" color="textPrimary">
Cancel
</Typography>
</Button>
{activeStep === steps.length - 1 ? (
<Button color="primary" onClick={addComponent} disabled={!formValid}>
Submit
</Button>
) : (
<Button color="primary" onClick={stepNext} disabled={!isFormValid()}>
Next
</Button>
)}
</React.Fragment>
) : (
<React.Fragment>
<CancelSubmit onSubmit={updateComponent} onCancel={close} submitDisabled={!canEdit || !formValid}/>
</React.Fragment>
)}
</Grid>
</Grid>
</DialogActions>
);
};
const remove = () => {
return (
<Dialog
open={isDialogOpen}
onClose={close}
aria-labelledby="confirm-remove-component-label"
aria-describedby="confirm-remove-component-description">
<DialogTitle id="confirm-remove-component-title">Delete Confirmation</DialogTitle>
<DialogContent>
<DialogContentText>Are you sure you want to delete this component?</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={close} color="primary">
No
</Button>
<Button onClick={removeComponent} color="primary">
Yes
</Button>
</DialogActions>
</Dialog>
);
};
const componentPrefs = () => {
let nodes;
if (
prevComponent &&
prevComponent.status.measurement[0] &&
prevComponent.status.measurement[0].values[0] &&
prevComponent.status.measurement[0].values[0].values
) {
nodes = prevComponent.status.measurement[0].values[0].values;
}
return (
<Box paddingLeft={2} paddingRight={2}>
<Typography style={{ fontWeight: 650 }}>Nodes to Exclude</Typography>
{/* node exclusion */}
{nodes ? (
<Box>
{/* if we have how many nodes are found use checkboxes for each node to add to what is excluded */}
{nodes.map((_, i) => (
<FormControlLabel
key={i}
control={
<Checkbox
checked={excludedNodes.includes(i)}
onChange={(e, checked) => {
let exclNodes = cloneDeep(excludedNodes);
if (checked) {
exclNodes.push(i);
} else {
exclNodes.splice(exclNodes.indexOf(i), 1);
}
setExcludedNodes(exclNodes);
}}
/>
}
label={"Node " + (i + 1)}
/>
))}
</Box>
) : (
<Box>
<Box marginBottom={2}>
<Typography>
Cannot determine number of nodes as the component has not taken a measurement yet.
If you know the number of nodes and which ones you would like to exclude you may
enter them below. Note that the nodes are 0 based so to exclude the first node you
would enter 0
</Typography>
</Box>
<Grid
container
direction="row"
justifyContent="space-between"
alignContent="center"
alignItems="center">
{/* if we don't have how many nodes the component has use a entry box to enter the number of the node you wnat to exclude */}
<Grid size={{ xs: 12 }}>
<Typography>Excluded Nodes: {excludedNodes.toString()}</Typography>
</Grid>
<Grid>
<TextField
type="number"
variant="outlined"
InputProps={{ inputProps: { min: 0 } }}
value={nodeToExclude}
onChange={e => {
setNodeToExclude(+e.target.value);
}}
/>
</Grid>
<Grid>
<Button
onClick={() => {
let eNodes = cloneDeep(excludedNodes);
if (!eNodes.includes(nodeToExclude)) {
eNodes.push(nodeToExclude);
}
setExcludedNodes(eNodes);
}}>
Add
</Button>
</Grid>
</Grid>
</Box>
)}
<Button
variant="contained"
color="primary"
onClick={() => {
setExcludedNodes([]);
}}>
Clear Excluded Nodes
</Button>
</Box>
);
};
return (
<React.Fragment>
{mode === "remove" || removeDialogOpen
? remove()
: isDialogOpen && (
<ResponsiveDialog
maxWidth="sm"
fullWidth
open={isDialogOpen}
onClose={close}
scroll="paper">
{title()}
<Divider />
{!isController(formComponent.settings.type) && (
<Tabs
value={tabVal}
indicatorColor="primary"
textColor="primary"
onChange={handleChange}>
<Tab label="General" />
<Tab label="Overlays" />
<Tab label="Nodes" disabled={!prevComponent} />
</Tabs>
)}
<TabPanelMine value={tabVal} index={0}>
{content()}
</TabPanelMine>
<TabPanelMine value={tabVal} index={1}>
<DialogContent>
<Grid
container
direction="row"
justifyContent="space-between"
alignItems="center"
alignContent="center"
spacing={1}>
{overlayGroup()}
</Grid>
</DialogContent>
</TabPanelMine>
<TabPanelMine value={tabVal} index={2}>
{componentPrefs()}
</TabPanelMine>
{actions()}
</ResponsiveDialog>
)}
</React.Fragment>
);
}