import { Avatar, Box, Card, CardActionArea, CardContent, CardHeader, //Grid2 as Grid, Switch, Theme, Tooltip, Typography } from "@mui/material"; import Grid from '@mui/material/Grid2'; import EventBlocker from "common/EventBlocker"; import PendingChangesIndicator from "common/PendingChangesIndicator"; import MeasurementSummary from "component/MeasurementSummary"; import { useComponentAPI, usePendingChanges, useSnackbar, useThemeType } from "hooks"; import InteractionsOverview from "interactions/InteractionsOverview"; import { cloneDeep } from "lodash"; import { Component, Device, Interaction } from "models"; import moment from "moment"; import { getFriendlyAddressTypeName, getHumanReadableAddress } from "pbHelpers/AddressType"; import { controllerModeLabel } from "pbHelpers/Component"; import { //extension, GetComponentIcon, getMeasurements, isController } from "pbHelpers/ComponentType"; import { DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability"; import { findInteractionsAsSource, HasInteraction } from "pbHelpers/Interaction"; import { canWrite } from "pbHelpers/Permission"; import { pond, quack } from "protobuf-ts/pond"; import React, { useEffect, useState } from "react"; // import { useHistory } from "react-router"; import { hasDeviceFeature } from "services/feature/service"; import ComponentActions from "./ComponentActions"; import { or } from "utils"; import { extractNodes } from "pbHelpers/ComponentTypes"; import UnitMeasurementSummary from "./UnitMeasurementSummary"; import { UnitMeasurement } from "models/UnitMeasurement"; import { useGlobalState } from "providers"; import { makeStyles } from "@mui/styles"; import { useLocation, useNavigate } from "react-router-dom"; const useStyles = makeStyles((theme: Theme) => { return ({ card: { position: "relative", display: "flex", height: "100%", flexDirection: "column", overflow: "visible" }, cardHeader: { padding: theme.spacing(1), paddingLeft: theme.spacing(2) }, cardContent: { display: "flex", flexDirection: "column", justifyContent: "space-between", alignItems: "center", padding: theme.spacing(1), paddingTop: 0 }, moreDetails: { marginLeft: "auto" }, avatarIcon: { width: theme.spacing(3), height: theme.spacing(3) }, sensorDot: { position: "absolute", marginLeft: theme.spacing(1), marginTop: theme.spacing(1) } }) }); interface Sensor { label: string; color: string; value: string; } interface Props { device: Device; component: Component; components: Component[]; availablePositions: DeviceAvailabilityMap; availableOffsets: OffsetAvailabilityMap; permissions: pond.Permission[]; interactions: Interaction[]; refreshCallback: (updatedComponent?: Component) => void; showMobile?: boolean; showSensors?: boolean; deviceComponentPreferences?: pond.DeviceComponentPreferences; } export default function ComponentCard(props: Props) { const componentAPI = useComponentAPI(); const classes = useStyles(); const themeType = useThemeType(); const { error, success } = useSnackbar(); const { device, component, components, availablePositions, availableOffsets, permissions, refreshCallback, interactions, showMobile, showSensors, deviceComponentPreferences } = props; // const history = useHistory(); const navigate = useNavigate() const location = useLocation() const [sensors, setSensors] = useState([]); const [{ user, showErrors, as }] = useGlobalState(); const { pending: pendingChanges, accepted: changesAccepted } = usePendingChanges( component.status.synced ); const updateControllerState = (checked: boolean) => { let updatedComponent = cloneDeep(component); let newMode = checked === false ? 2 : HasInteraction(component.location(), interactions) ? 0 : 1; updatedComponent.settings.defaultOutputState = newMode; let describe = controllerModeLabel(newMode); componentAPI .update(device.id(), updatedComponent.settings, undefined, undefined, as) .then(() => { success(component.name() + "'s mode was set to " + describe); updatedComponent.status.synced = false; updatedComponent.status.lastUpdate = moment().toISOString(); refreshCallback(updatedComponent); }) .catch(() => { error("Failed to set " + component.name() + "'s mode"); }); }; const pathToComponent = () => { let url = location.pathname + "/components/" + component.key(); url = url.replace("//", "/"); return url; }; const openComponentPage = (event: React.MouseEvent) => { const { name, className } = event.target as HTMLButtonElement; const blacklist = ["slider", "controllerSwitch"]; if ( blacklist.includes(name) || blacklist.find( v => className && className .toString() .toLowerCase() .includes(v) ) !== undefined ) { event.preventDefault(); } else { navigate(pathToComponent()); } }; const getAddressDescription = () => { if ( component.settings.addressType === quack.AddressType.ADDRESS_TYPE_CONFIGURABLE_PIN_ARRAY || (component.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 && component.settings.addressType <= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET100) ) { let addressDescription = getHumanReadableAddress( component.settings.addressType, component.settings.type, component.settings.address, device.settings.product ); if (addressDescription === "") { addressDescription = "Internal"; } let port = "Port: " + addressDescription; let cableID = ""; if ( component.settings.addressType >= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET1 && component.settings.addressType <= quack.AddressType.ADDRESS_TYPE_PIN_OFFSET100 ) { cableID = "Cable: " + (component.settings.addressType - 8); } return port + " " + cableID; } else if (component.settings.addressType === quack.AddressType.ADDRESS_TYPE_I2C) { let description = "" let type = getFriendlyAddressTypeName(component.settings.addressType); description = type if(component.settings.expansionLine){ description = description + ": Exp Line " + component.settings.expansionLine } if(component.settings.muxLine){ description = description + " ,Mux Line " + component.settings.muxLine } return description }else{ return getFriendlyAddressTypeName(component.settings.addressType); } }; const header = () => { const canEdit = canWrite(permissions); const componentIcon = GetComponentIcon( component.settings.type, component.settings.subtype, themeType ); const name = component.name(); let measurements: UnitMeasurement[] = []; if (component.status.lastGoodMeasurement) measurements = component.status.lastGoodMeasurement.map(um => UnitMeasurement.any(um, user)); if (showErrors) measurements = component.lastMeasurement.map(um => UnitMeasurement.any(um, user)); return ( ) : ( ) } title={ Subtype: {component.subTypeName()}
ID: {component.key()}
Location: {component.locationString()} } placement="top"> {name} - {getAddressDescription()}
{isController(component.settings.type) && hasDeviceFeature(device.settings.upgradeChannel, "better-controls") && ( { event.stopPropagation(); //event.preventDefault(); }} onMouseDown={event => { event.stopPropagation(); //event.preventDefault(); }} onClick={event => { event.stopPropagation(); //event.preventDefault(); }} disabled={!canEdit} onChange={(_, checked) => updateControllerState(checked)} name="controllerSwitch" inputProps={{ "aria-label": "controller switch" }} /> )}
} className={classes.cardHeader} titleTypographyProps={{ variant: "subtitle1" }} subheader={ {(pendingChanges || changesAccepted) && ( )} {/* !newStructure ? ( ) : ( */} {/* ) */} } action={ } /> ); }; const content = () => { if (findInteractionsAsSource(component.location(), interactions).length <= 0) return null; return ( refreshCallback()} /> ); }; useEffect(() => { if (!component) return; if (!component.status) return; if (!component.status.lastMeasurement) return; let measurements = getMeasurements(component.type(), or(component.settings.subtype, 0)); let nodes = extractNodes(component.status!.lastMeasurement!.measurement!); let sensors: Sensor[] = []; nodes.forEach(node => { Object.values(node).forEach((value, index) => { if (measurements[index]) { let sensor: Sensor = { label: measurements[index].label, value: value, color: measurements[index].colour }; sensors.push(sensor); } }); }); setSensors(sensors); }, [component, setSensors]); const sensorCard = () => { if (sensors.length <= 0 || !showSensors) return null; return ( {sensors.map((sensor, index) => { return ( {sensor.label}: {sensor.value} ); })} ); }; return ( openComponentPage(event)} component="div" style={{ height: "100%" }}> {header()} {content()} {sensorCard()} ); }