finished refactoring the object alerts into object interactions

This commit is contained in:
csawatzky 2025-12-01 14:38:51 -06:00
parent bb92077d0b
commit c9d2eac6c3
5 changed files with 86 additions and 28 deletions

View file

@ -1,5 +1,5 @@
import { Add, ExpandMore } from "@mui/icons-material"; import { Add, ExpandMore } from "@mui/icons-material";
import { Accordion, AccordionDetails, AccordionSummary, Box, Grid2, IconButton, List, ListItem, ListSubheader, Typography } from "@mui/material"; import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Grid2, IconButton, List, ListItem, ListSubheader, Typography } from "@mui/material";
import { Component } from "models"; import { Component } from "models";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber"; import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond, quack } from "protobuf-ts/pond"; import { pond, quack } from "protobuf-ts/pond";
@ -71,7 +71,13 @@ export default function Alerts(props: Props){
Alerts Alerts
</Typography> </Typography>
{addNew && {addNew &&
<IconButton disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)} onClick={()=>{addNew()}} color="primary"><Add /></IconButton> <Button
disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)}
onClick={()=>{addNew()}}
variant="contained"
color="primary">
<Add /> Alert
</Button>
} }
</ListSubheader> </ListSubheader>
{alerts.map((alert, i) => ( {alerts.map((alert, i) => (

View file

@ -1,7 +1,9 @@
import { Add } from "@mui/icons-material"; import { Add, ExpandMore } from "@mui/icons-material";
import { Accordion, AccordionDetails, AccordionSummary, Divider, IconButton, List, ListItem, ListSubheader, Menu, MenuItem, Typography } from "@mui/material"; import { Accordion, AccordionDetails, AccordionSummary, Box, Button, Divider, IconButton, List, ListItem, ListSubheader, Menu, MenuItem, Typography } from "@mui/material";
import { Component, Device } from "models"; import { Component, Device, Interaction } from "models";
import { pond } from "protobuf-ts/pond"; import { interactionResultText } from "pbHelpers/Interaction";
import { describeMeasurement } from "pbHelpers/MeasurementDescriber";
import { pond, quack } from "protobuf-ts/pond";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
export interface Control { export interface Control {
@ -39,11 +41,49 @@ export default function Controls(props: Props){
setDeviceControls(map) setDeviceControls(map)
},[controls]) },[controls])
const readableConditions = (control: Control) => {
let conditions: string = ""
let firstSource = control.sourceComponents[0]
control.conditions.forEach((condition, index) => {
let describer = describeMeasurement(condition.measurementType, firstSource.type(), firstSource.subType());
let type = describer.label();
let comparison =
condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_EQUAL_TO
? "Exactly"
: condition.comparison === quack.RelationalOperator.RELATIONAL_OPERATOR_GREATER_THAN
? "Above"
: "Below";
let value = describer.toDisplay(condition.value);
conditions = conditions + (index !== 0 ? " and " : "") + type + " " + comparison + " " + value + describer.GetUnit()
})
return conditions
};
const controlAccordion = (control: Control) => { const controlAccordion = (control: Control) => {
let tempInteraction = Interaction.create()
tempInteraction.settings.result = control.result
let resultText = interactionResultText(tempInteraction, control.sinkComponent)
return ( return (
<Accordion> <Accordion sx={{width: "100%"}}>
<AccordionSummary>Show what the control does</AccordionSummary> <AccordionSummary expandIcon={<ExpandMore />}>
<AccordionDetails>show the components that are doing it</AccordionDetails> <Box>
<Typography>
{resultText}:
</Typography>
<Typography>
{readableConditions(control)}
</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<List>
<ListSubheader>Controlling Components</ListSubheader>
{control.sourceComponents.map((comp, i) => (
<ListItem key={i}>{comp.name()}</ListItem>
))}
</List>
</AccordionDetails>
</Accordion> </Accordion>
) )
} }
@ -79,17 +119,20 @@ export default function Controls(props: Props){
<List style={{ margin: 5 }}> <List style={{ margin: 5 }}>
<ListSubheader sx={{paddingY: 1, display: "flex", justifyContent: "space-between", alignItems: "center"}}> <ListSubheader sx={{paddingY: 1, display: "flex", justifyContent: "space-between", alignItems: "center"}}>
<Typography> <Typography>
Alerts Controls
</Typography> </Typography>
<IconButton {addNew &&
<Button
disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)} disabled={!permissions.includes(pond.Permission.PERMISSION_WRITE)}
variant="contained"
onClick={(e)=>{ onClick={(e)=>{
setOpenDeviceMenu(true) setOpenDeviceMenu(true)
setAnchor(e.currentTarget) setAnchor(e.currentTarget)
}} }}
color="primary"> color="primary">
<Add /> <Add />Control
</IconButton> </Button>
}
</ListSubheader> </ListSubheader>
{devices.map(device => ( {devices.map(device => (
<React.Fragment key={device.id()}> <React.Fragment key={device.id()}>

View file

@ -996,6 +996,8 @@ export default function NewObjectInteraction(props: Props){
}) })
.catch(_err => { .catch(_err => {
openSnack("there was a problem adding the interaction to the selected components"); openSnack("there was a problem adding the interaction to the selected components");
}).finally(() => {
close(true)
}); });
}; };

View file

@ -79,7 +79,11 @@ export default function Notifications(props: Props){
return ( return (
<List style={{ margin: 5 }}> <List style={{ margin: 5 }}>
<ListSubheader>Notifications</ListSubheader> <ListSubheader>
<Typography sx={{paddingY: 2}}>
Notifications
</Typography>
</ListSubheader>
{recentNotifications && ( {recentNotifications && (
<React.Fragment> <React.Fragment>
{recentNotifications.map((notification, i) => ( {recentNotifications.map((notification, i) => (

View file

@ -1,5 +1,5 @@
import { Box, Button, Card, Typography } from "@mui/material"; import { Box, Button, Card, Typography } from "@mui/material";
import { useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import Alerts, { Alert } from "./Alerts"; import Alerts, { Alert } from "./Alerts";
import Controls, { Control } from "./Controls"; import Controls, { Control } from "./Controls";
import { Component, Device, Interaction } from "models"; import { Component, Device, Interaction } from "models";
@ -21,11 +21,10 @@ interface Props {
objectType: pond.ObjectType objectType: pond.ObjectType
devices: Device[]; devices: Device[];
permissions: pond.Permission[] permissions: pond.Permission[]
refreshCallback?: () => {}
} }
export default function ObjectInteractions(props: Props) { export default function ObjectInteractions(props: Props) {
const { linkedComponents, componentDevices, devices, objectKey, objectType, refreshCallback, permissions } = props const { linkedComponents, componentDevices, devices, objectKey, objectType, permissions } = props
const [{as}] = useGlobalState() const [{as}] = useGlobalState()
//list of alerts, each alert contains a list of interactions with matching conditions //list of alerts, each alert contains a list of interactions with matching conditions
const [alerts, setAlerts] = useState<Alert[]>([]); const [alerts, setAlerts] = useState<Alert[]>([]);
@ -86,7 +85,7 @@ export default function ObjectInteractions(props: Props) {
} }
} }
useEffect(() => { const load = useCallback(()=>{
const newInteractions: Interaction[] = []; const newInteractions: Interaction[] = [];
const interactionSourceMap = new Map<string, string>(); const interactionSourceMap = new Map<string, string>();
const sinkOptions: Component[] = []; const sinkOptions: Component[] = [];
@ -183,7 +182,11 @@ export default function ObjectInteractions(props: Props) {
}).catch(err => { }).catch(err => {
console.error("Interaction fetch error:", err) console.error("Interaction fetch error:", err)
}) })
}, [linkedComponents, interactionsAPI, componentDevices, as]); },[linkedComponents, interactionsAPI, componentDevices, as])
useEffect(() => {
load()
}, [load]);
@ -193,8 +196,8 @@ export default function ObjectInteractions(props: Props) {
open={openNewInteraction} open={openNewInteraction}
onClose={(refresh) => { onClose={(refresh) => {
setOpenNewInteraction(false) setOpenNewInteraction(false)
if(refresh && refreshCallback){ if(refresh){
refreshCallback() load()
} }
}} }}
linkedComponents={linkedComponents} linkedComponents={linkedComponents}