frontend/src/component/ComponentSettings.tsx
csawatzky 3325c9d75b implemented the epansion line stuff
made adjustments to to the grain cable by adding i2c as an option as long as you set and expander line,
also ajusted DeviceAvailability so that an i2c address will not be claimed if the component has an expansion line set, updated the device wizard to removed pin options if the i2c port was selected and vice versa for pins since we now have a components that supports both types, updated the components setting to set the default address type based on the restriction passed in that is set by the wizard, and fall back to what is was before if one is not passed in
2025-07-11 13:56:17 -06:00

1091 lines
35 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);
}, [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;
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.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.lastMeasurement[0] &&
prevComponent.lastMeasurement[0].values[0] &&
prevComponent.lastMeasurement[0].values[0].values
) {
nodes = prevComponent.lastMeasurement[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>
{/* button for testing the pref update */}
{/* {prevComponent && (
<Button
onClick={() => {
let pref = pond.DeviceComponentPreferences.create();
pref.excludedNodes = excludedNodes;
deviceAPI
.updateComponentPreferences(device.id(), prevComponent.key(), pref)
.then(resp => {
console.log("no errors");
})
.catch(err => {
console.log("errors");
});
}}>
Update Prefs
</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="Preferences" 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>
);
}