import { Box, Grid2 as Grid, Theme, Typography } from "@mui/material"; import {makeStyles} from "@mui/styles" //import { Theme } from "@material-ui/core/styles/createMuiTheme"; import Loader from "common/Loader"; import MapCard from "common/MapCard"; import { Coordinate } from "common/MapGL"; import ComponentCard from "component/ComponentCard"; import ComponentSettings from "component/ComponentSettings"; import DeviceOverview from "device/DeviceOverview"; import { useComponentAPI, useDeviceAPI, useInteractionsAPI, useSnackbar, useUsageAPI, useUserAPI } from "hooks"; import { cloneDeep } from "lodash"; import { Component, Device as DeviceModel, deviceScope, Interaction } from "models"; import moment from "moment"; import { sameComponentID, sortComponents } from "pbHelpers/Component"; import { FindAvailablePositions, DeviceAvailabilityMap, OffsetAvailabilityMap } from "pbHelpers/DeviceAvailability"; import { pond } from "protobuf-ts/pond"; import { quack } from "protobuf-ts/quack"; import { useGlobalState } from "providers"; import React, { useCallback, useEffect, useState } from "react"; //import { Redirect } from "react-router"; import { or } from "utils/types"; import DeviceActions from "./DeviceActions"; const useStyles = makeStyles((theme: Theme) => ({ title: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, noComponents: { marginTop: theme.spacing(16) }, addComponentText: { ...theme.typography.subtitle2, color: theme.palette.primary.main, cursor: "pointer" }, deviceLoading: { position: "relative", height: "100%", width: "100%", display: "flex", marginTop: theme.spacing(16), alignItems: "center", justifyContent: "center" }, rateLimit: { marginTop: theme.spacing(1), color: theme.palette.text.secondary } }) ); interface Props { device: DeviceModel; isMobile?: boolean; } export default function Device(props: Props) { const classes = useStyles(); const [{ user, as }] = useGlobalState(); const { error } = useSnackbar(); const deviceID = props.device.id(); const deviceAPI = useDeviceAPI(); const componentAPI = useComponentAPI(); const interactionsAPI = useInteractionsAPI(); const usageAPI = useUsageAPI(); const userAPI = useUserAPI(); const [isLoading, setIsLoading] = useState(false); const [device, setDevice] = useState(DeviceModel.create()); const [components, setComponents] = useState>(new Map()); const [interactions, setInteractions] = useState([]); const [permissions, setPermissions] = useState([]); const [preferences, setPreferences] = useState( pond.UserPreferences.create() ); const [invalidDevice, setInvalidDevice] = useState(false); const [isAddComponentDialogOpen, setIsAddComponentDialogOpen] = useState(false); const [availablePositions, setAvailablePositions] = useState(new Map()); const [availableOffsets, setAvailableOffsets] = useState(new Map()); const [latitude, setLatitude] = useState(NaN); const [longitude, setLongitude] = useState(NaN); const [cellularUsage, setCellularUsage] = useState(0); const [cellularStatus, setCellularStatus] = useState(""); const rate = 30; const load = useCallback(() => { if (user.id() === "") return; setIsLoading(true); //let devicePromise = deviceAPI.get(deviceID); let userPromise = userAPI.getUser(user.id(), deviceScope(deviceID.toString())); let componentsPromise = componentAPI.list( deviceID, undefined, [deviceID.toString()], ["device"], true, as ); let interactionsPromise = interactionsAPI.listInteractionsByDevice(deviceID, undefined, as); let groupPromise: Promise = Promise.resolve(undefined); Promise.all([userPromise, componentsPromise, interactionsPromise, groupPromise]) .then(([userRes, componentsRes, interactionsRes]) => { //let rDevice = DeviceModel.any(deviceRes.data); let rawComponents: Array = or(componentsRes.data.components, []); let rComponents: Map = new Map(); rawComponents.forEach((rawComponent: any) => { let component = Component.any(rawComponent); rComponents.set(component.key(), component); }); setComponents(rComponents); setInteractions(interactionsRes); setPermissions(userRes.permissions); setPreferences(userRes.preferences); setDevice(props.device); setLongitude(props.device.status.longitude ? props.device.status.longitude : NaN); setLatitude(props.device.status.latitude ? props.device.status.latitude : NaN); let available = FindAvailablePositions( Array.from(rComponents.values()), props.device.settings.product ); setAvailablePositions(available.GetAvailability()); setAvailableOffsets(available.offsetAvailability); }) .catch((err: any) => { setDevice(DeviceModel.create()); setComponents(new Map()); setInteractions([]); setAvailablePositions(new Map()); setAvailableOffsets(new Map()); setPermissions([]); setPreferences(pond.UserPreferences.create()); setInvalidDevice(true); error(err); }) .finally(() => setIsLoading(false)); usageAPI .getUsage(deviceID, moment().subtract(1, "days")) .then((res: any) => { let usage = res.data; if (usage) { let rCellularStatus = "active"; let rCellularUsage = 0; let sessions: any[] = []; if (usage.sessions) { sessions = usage.sessions.filter((session: any) => { return moment(session.begin).isAfter(moment().subtract(1, "days")); }); } sessions.forEach((session: any) => (rCellularUsage += Number(or(session.bytes, 0)))); setCellularStatus(rCellularStatus); setCellularUsage(rCellularUsage); } }) .catch(err => {}); }, [componentAPI, props.device, deviceID, error, interactionsAPI, /*usageAPI*/, userAPI, user, as]); useEffect(() => { load(); }, [load]); const getOrderedComponents = () => { if (preferences.childDisplayOrder && preferences.childDisplayOrder.length === Array.from(components.values()).length) { return Array.from(components.values()).sort((a, b: Component) => sortComponents(a, b, preferences.childDisplayOrder) ); } else { return Array.from(components.values()); } }; const displayedComponentsBlacklist = [ quack.ComponentType.COMPONENT_TYPE_POWER, quack.ComponentType.COMPONENT_TYPE_MODEM ]; const displayedComponents = () => { return [...components.values()].filter( c => !displayedComponentsBlacklist.includes(c.settings.type) ); }; const handleComponentChanged = (component: Component) => { let updatedComponents = cloneDeep(components); let updatedComponent = cloneDeep(component); if (updatedComponents.has(updatedComponent.key())) { updatedComponents.set(updatedComponent.key(), updatedComponent); setComponents(updatedComponents); } }; // const productCard = () => { // return ; // }; const componentCards = () => { let orderedComponents = getOrderedComponents(); let componentCards = []; let hasGPS = false; for (let i = 0; i < orderedComponents.length; i++) { let c = orderedComponents[i]; if (displayedComponentsBlacklist.includes(c.settings.type)) { continue; } let id: quack.IComponentID = quack.ComponentID.fromObject({ type: c.settings.type, addressType: c.settings.addressType, address: c.settings.address }); let filteredInteractions = interactions.filter(interaction => { let isSource = false; let isSink = false; if (interaction.settings) { isSource = sameComponentID(interaction.settings.source, id); isSink = sameComponentID(interaction.settings.sink, id); } return isSource || isSink; }); if (id.type === quack.ComponentType.COMPONENT_TYPE_GPS) { hasGPS = true; } componentCards.push( updatedComponent ? handleComponentChanged(updatedComponent) : load() } /> ); } if (!hasGPS && !isNaN(latitude) && !isNaN(longitude)) { let path: Array = []; path.push({ latitude: latitude, longitude: longitude, timestamp: "" }); componentCards.push(); } return componentCards; }; const getUsage = () => { let usage = undefined; if (cellularStatus && cellularStatus !== "") { usage = { status: cellularStatus, bytes: cellularUsage }; } return usage; }; const anyComponentsReportingFast = () => { let fast = false; components.forEach(c => { if ( c.settings.measurementPeriodMs > 0 && c.settings.reportPeriodMs > 0 && c.settings.reportPeriodMs < rate * 1000 ) { fast = true; } }); return fast; }; const componentsTab = () => { return ( {anyComponentsReportingFast() && ( Live updates are rate limited to {rate}s to ensure a smooth browsing experience )} {isLoading ? ( ) : displayedComponents().length > 0 ? ( {componentCards()} ) : ( No components found {permissions.includes(pond.Permission.PERMISSION_WRITE) && ( setIsAddComponentDialogOpen(true)}> {" Add "} a component to {device.name()}. setIsAddComponentDialogOpen(false)} availablePositions={availablePositions} availableOffsets={availableOffsets} refreshCallback={load} canEdit={permissions.includes(pond.Permission.PERMISSION_WRITE)} /> )} )} ); }; const isPaused = () => { let paused = false; if (cellularStatus && cellularStatus !== "") { if (cellularStatus.toLowerCase().includes("pause")) { paused = true; } } return paused; }; const toggleNotificationPreference = () => { let updatedPreferences = cloneDeep(preferences); updatedPreferences.notify = !preferences.notify; deviceAPI .updatePreferences(deviceID, updatedPreferences, undefined, undefined, as) .then(() => setPreferences(updatedPreferences)) .catch(() => { error( "Error occured while " + (preferences.notify ? "enabling" : "disabling") + " notifications" ); }); }; // if (invalidDevice) { // return ; // } else { return ( {componentsTab()} ); //} }