import { LinearProgress } from "@mui/material"; import { Bin, Component, Device, Interaction } from "models"; import Controls, { Control } from "objects/objectInteractions/Controls"; import NewObjectInteraction from "objects/objectInteractions/NewObjectInteraction"; import { sameComponentID } from "pbHelpers/Component"; import { extension } from "pbHelpers/ComponentType"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { useGlobalState } from "providers"; import interactionsAPI, { useInteractionsAPI } from "providers/pond/interactionsAPI"; import React, { useCallback, useEffect, useState } from "react"; interface Props { linkedComponents: Map; //the component key to the component object componentDevices: Map; devices: Device[]; permissions: pond.Permission[] } export default function BinControls(props: Props) { const {linkedComponents, componentDevices, devices, permissions} = props const [{as}] = useGlobalState() const interactionsAPI = useInteractionsAPI() const [controls, setControls] = useState([]); const [selectedDevice, setSelectedDevice] = useState(undefined) const [openNewInteraction, setOpenNewInteraction] = useState(false) const [loading, setLoading] = useState(false) const findDevice = (deviceID: number) => { for (let device of devices) { if (device.id() === deviceID) return device } } const findSinkComponent = (sinks: Component[], interactionSink: quack.ComponentID, sourceDevice: number) => { for (let component of sinks) { if (sameComponentID(component.location(), interactionSink) && sourceDevice === componentDevices.get(component.key())){ return component } } } const matchConditions = (interaction: Interaction, set: Control) => { //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 matchResult = (interaction: Interaction, set: Control) => { let interactionResult = interaction.settings.result let setResult = set.result let matching = true // if anything in the result for the interaction is different from the set make matching false if(interactionResult?.type !== setResult?.type || interactionResult?.mode !== setResult?.mode || interactionResult?.value !== setResult?.value || interactionResult?.dutyCycle !== setResult?.dutyCycle ){ matching = false } return matching } const load = useCallback(()=>{ if(!loading){ setLoading(true) const newInteractions: Interaction[] = []; const interactionSourceMap = new Map(); const sinkOptions: Component[] = []; const controls: Control[] = []; 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 => { if (interaction.isControl()) { if (!interaction.settings.sink) return //if there is no sink it cant be a control so move on to the next const compKey = interactionSourceMap.get(interaction.key()); if (!compKey) return //we have to have a valid component key const component = linkedComponents.get(compKey); const deviceID = componentDevices.get(compKey); if (!deviceID) return //we have to know which device it is for const controlDevice = findDevice(deviceID) if (!controlDevice) return //failed to get the device from the array const sinkComponent = findSinkComponent(sinkOptions, interaction.settings.sink, deviceID) if (!sinkComponent) return //failed to get the correct sink component let similarControlIndex = controls.findIndex(control => matchConditions(interaction, control) && matchResult(interaction, control) && sameComponentID(interaction.settings.sink, control.sinkComponent.location()) && //if the sink for the interaction is the same address as the existing control deviceID === control.device.id() //if the device id for source component is the same as the device id in the control ); if (component && interaction.settings.result) { if (similarControlIndex === -1) { controls.push({ conditions: interaction.settings.conditions, result: interaction.settings.result, sourceComponents: [component], sinkComponent: sinkComponent, device: controlDevice }); } else { controls[similarControlIndex].sourceComponents.push(component); } } } }); setControls(controls); }).catch(err => { console.error("Interaction fetch error:", err) }).finally(() => { setLoading(false) }) } },[linkedComponents, interactionsAPI, componentDevices, as]) useEffect(() => { load() }, [load]); return ( { setOpenNewInteraction(false) if(refresh){ load() } }} linkedComponents={linkedComponents} componentDevices={componentDevices} device={selectedDevice}/> {loading ? : { console.log("new control") setSelectedDevice(device) setOpenNewInteraction(true) }} /> } ) }