diff --git a/src/3dModels/CameraControls/OrbitCameraControls.tsx b/src/3dModels/CameraControls/OrbitCameraControls.tsx index a64fc80..445ab70 100644 --- a/src/3dModels/CameraControls/OrbitCameraControls.tsx +++ b/src/3dModels/CameraControls/OrbitCameraControls.tsx @@ -230,7 +230,7 @@ export default function OrbitCameraControls(props: Props) { window.removeEventListener("mouseup", handleMouseUp); canvas.removeEventListener("wheel", handleWheel); }; - }, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi]); + }, [camera, gl, target, clampVerticalRotation, minPhi, maxPhi, initialRadius, maxRadius]); return null; } \ No newline at end of file diff --git a/src/bin/binSummary/BinSummary.tsx b/src/bin/binSummary/BinSummary.tsx index 51d3c1d..092db1a 100644 --- a/src/bin/binSummary/BinSummary.tsx +++ b/src/bin/binSummary/BinSummary.tsx @@ -27,14 +27,14 @@ interface Props { } export default function BinSummary(props: Props){ const {bin, devices, fans, heaters, permissions, componentDevices, componentMap, binPrefs, updateBinCallback} = props - const [currentDevice, setCurrentDevice] = useState(undefined) + //const [currentDevice, setCurrentDevice] = useState(undefined) const isMobile = useMobile() - useEffect(() => { - if(devices.length > 0){ - setCurrentDevice(devices[0]) - } - },[devices]) + // useEffect(() => { + // if(devices.length > 0){ + // setCurrentDevice(devices[0]) + // } + // },[devices]) const activity = (reading: string) => { //if the device hasnt checked in in 6 hours = missing 12 hours = inactive within 6 hours = live @@ -173,7 +173,13 @@ export default function BinSummary(props: Props){ - + diff --git a/src/bin/binSummary/components/binAlerts.tsx b/src/bin/binSummary/components/binAlerts.tsx new file mode 100644 index 0000000..1cd7fcf --- /dev/null +++ b/src/bin/binSummary/components/binAlerts.tsx @@ -0,0 +1,137 @@ +import { LinearProgress } from "@mui/material"; +import { Bin, Component, Device, Interaction } from "models"; +import Alerts, { Alert } from "objects/objectInteractions/Alerts"; +import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction"; +import { extension } from "pbHelpers/ComponentType"; +import { pond } from "protobuf-ts/pond"; +import { useGlobalState, useInteractionsAPI } from "providers"; +import React, { useCallback, useEffect, useState } from "react"; + +interface Props { + linkedComponents: Map; //the component key to the component object + componentDevices: Map; + bin: Bin + devices: Device[]; + permissions: pond.Permission[] +} + +export default function BinAlerts(props: Props){ + const {linkedComponents, componentDevices, permissions} = props + const [{as}] = useGlobalState(); + const interactionsAPI = useInteractionsAPI(); + + //list of alerts, each alert contains a list of interactions with matching conditions + const [alerts, setAlerts] = useState([]); + const [loading, setLoading] = useState(false) + const [newAlert, setNewAlert] = useState(false) + + const matchConditions = (interaction: Interaction, set: Alert) => { + //if the number of conditions are not the same they are different + if (interaction.settings.conditions.length !== set.conditions.length) { + return false; + } + //continue with the comparison + let matching = true; + interaction.settings.conditions.forEach((condition, i) => { + if ( + condition.comparison !== set.conditions[i].comparison || + condition.measurementType !== set.conditions[i].measurementType || + condition.value !== set.conditions[i].value + ) { + matching = false; + } + }); + return matching; + }; + + const load = useCallback(()=>{ +//load the interactions for the linked components +setLoading(true) +const newInteractions: Interaction[] = []; +const interactionSourceMap = new Map(); +const sinkOptions: Component[] = []; +const run = async () => { + // Run all component API fetches in parallel + await Promise.all( + [...linkedComponents].map(async ([key, comp]) => { + const device = componentDevices.get(key); + + if (device) { + const resp = await interactionsAPI.listInteractionsByComponent( + device, + comp.location(), + undefined, + as + ); + + resp.forEach(interaction => interactionSourceMap.set(interaction.key(), key)); + newInteractions.push(...resp); + } + + // Collect controller components + if (extension(comp.type()).isController) { + sinkOptions.push(comp); + } + }) + ); +}; + +run().then(() => { + newInteractions.forEach(interaction => { + //alert and control are not mutally exclusive, it is possible to be both if the control is sending notifications + if (interaction.isAlert()) { + // Find matching alert + let similarAlertIndex = alerts.findIndex(alert => + matchConditions(interaction, alert) + ); + + const compKey = interactionSourceMap.get(interaction.key()); + const component = compKey ? linkedComponents.get(compKey) : undefined; + + if (component) { + if (similarAlertIndex === -1) { + alerts.push({ + conditions: interaction.settings.conditions, + sourceComponents: [component], + }); + } else { + alerts[similarAlertIndex].sourceComponents.push(component); + } + } + + } + }); + setAlerts(alerts); + }).catch(err => { + console.error("Interaction fetch error:", err) + }).finally(() => { + setLoading(false) + }) + },[linkedComponents, interactionsAPI, componentDevices, as]) + + useEffect(()=>{ + load() + },[load]) + + + + return ( + + { + setNewAlert(false) + if(refresh){ + load() + } + }} + linkedComponents={linkedComponents} + componentDevices={componentDevices}/> + {loading ? + + : + {setNewAlert(true)}}/> + } + + ) +} \ No newline at end of file diff --git a/src/bin/binSummary/components/binDetails.tsx b/src/bin/binSummary/components/binDetails.tsx index 1ef4952..519d2ce 100644 --- a/src/bin/binSummary/components/binDetails.tsx +++ b/src/bin/binSummary/components/binDetails.tsx @@ -2,11 +2,18 @@ import { Box, Tab, Tabs } from "@mui/material"; import React from "react"; import { useState } from "react"; import BinTableView from "./binTableView"; -import { Bin } from "models"; +import { Bin, Component, Device } from "models"; +import Alerts from "objects/objectInteractions/Alerts"; +import BinAlerts from "./binAlerts"; +import { pond } from "protobuf-ts/pond"; interface Props { bin: Bin updateBinCallback?: (bin: Bin) => void + linkedComponents: Map; //the component key to the component object + componentDevices: Map; + devices: Device[]; + permissions: pond.Permission[] } interface TabPanelProps { @@ -30,7 +37,7 @@ function TabPanelMine(props: TabPanelProps) { } export default function BinDetails (props: Props) { - const {bin, updateBinCallback} = props + const {bin, updateBinCallback, linkedComponents, componentDevices, devices, permissions} = props const [currentTab, setCurrentTab] = useState(0) //this is where we will have the tabs abd tab panels for the table view, alerts, interactions, and analysis @@ -45,14 +52,14 @@ export default function BinDetails (props: Props) { variant="fullWidth"> - + - + diff --git a/src/objects/objectInteractions/NewObjectInteraction.tsx b/src/objects/objectInteractions/NewObjectInteraction.tsx index 8e68a3f..c28cf91 100644 --- a/src/objects/objectInteractions/NewObjectInteraction.tsx +++ b/src/objects/objectInteractions/NewObjectInteraction.tsx @@ -179,7 +179,6 @@ export default function NewObjectInteraction(props: Props){ setNewAlertComponentType(quack.ComponentType.COMPONENT_TYPE_INVALID); setConditions([]); setSelectedAlertComponents([]); - setValidComponents([]) setValStrings(["0", "0"]); setNumConditions(0) setNodeOptions([]); @@ -190,7 +189,6 @@ export default function NewObjectInteraction(props: Props){ setResultType(quack.InteractionResultType.INTERACTION_RESULT_TYPE_REPORT) setSinkMotor(false) setSelectedSink("")//the key of the component to be the sink - setSinkOptions([]) setResultMode(0) setResultValue("") setDutyCycleEnabled(false) @@ -215,12 +213,15 @@ export default function NewObjectInteraction(props: Props){ //display the components that match the type that was selected for the new alert const listMatchingComponents = () => { + console.log("listing") let matching: Component[] = []; + console.log(validComponents) validComponents.forEach(comp => { if (comp.type() === newAlertComponentType) { matching.push(comp); } }); + console.log(matching) return ( {matching.map(comp => ( @@ -1018,6 +1019,7 @@ export default function NewObjectInteraction(props: Props){ displayEmpty value={newAlertComponentType} onChange={e => { + console.log("type change") setNewAlertComponentType(e.target.value as quack.ComponentType); setSelectedAlertComponents([]); setConditions(initialConditions(e.target.value as quack.ComponentType)); diff --git a/src/pages/BinV2.tsx b/src/pages/BinV2.tsx index 43cfe9b..101352e 100644 --- a/src/pages/BinV2.tsx +++ b/src/pages/BinV2.tsx @@ -126,6 +126,7 @@ export default function BinV2(){ attachedDevIds.push(resp.data.componentDevices[k]); } }); + setComponentDevices(newComponentDevices); let newInteractionDevices = new Map();