188 lines
No EOL
7.5 KiB
TypeScript
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>
|
|
)
|
|
} |