frontend/src/bin/binSummary/components/binControls.tsx

188 lines
No EOL
7.5 KiB
TypeScript

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<string, Component>; //the component key to the component object
componentDevices: Map<string, number>;
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<Control[]>([]);
const [selectedDevice, setSelectedDevice] = useState<Device | undefined>(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<string, string>();
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 (
<React.Fragment>
<NewObjectInteraction
open={openNewInteraction}
onClose={(refresh) => {
setOpenNewInteraction(false)
if(refresh){
load()
}
}}
linkedComponents={linkedComponents}
componentDevices={componentDevices}
device={selectedDevice}/>
{loading ?
<LinearProgress />
:
<Controls
controls={controls}
devices={devices}
permissions={permissions}
addNew={(device) => {
console.log("new control")
setSelectedDevice(device)
setOpenNewInteraction(true)
}}
/>
}
</React.Fragment>
)
}