import { Button, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, FormControlLabel, Grid2 as Grid, List, ListItem, MenuItem, Switch, Tab, Tabs, TextField, Theme, Typography } from "@mui/material"; import DeleteButton from "common/DeleteButton"; import PeriodSelect from "common/time/PeriodSelect"; import ResponsiveDialog from "common/ResponsiveDialog"; import { useDeviceAPI, usePrevious, useSnackbar } from "hooks"; import { Component, Device } from "models"; import { IsExtended, ListDeviceProductDescribers } from "products/DeviceProduct"; import { pond } from "protobuf-ts/pond"; import { useGlobalState } from "providers"; import React, { useEffect, useState } from "react"; import { quack } from "protobuf-ts/quack"; import LinearMutationBuilder from "common/LinearMutationBuilder"; import { makeStyles } from "@mui/styles"; import { useNavigate } from "react-router-dom"; import CancelSubmit from "common/CancelSubmit"; interface TabPanelProps { children?: React.ReactNode; index: any; value: any; } function TabPanelMine(props: TabPanelProps) { const { children, value, index, ...other } = props; return ( ); } const useStyles = makeStyles((theme: Theme) => { return ({ dialogContent: { minHeight: "25vh" }, removeDeviceDialog: { zIndex: theme.zIndex.modal + 1 }, tabSmall: { width: "100%", boxSizing: "border-box", flexShrink: 1, minWidth: "48px", marginTop: 0, paddingTop: 0 } }) }); interface Props { device: Device; isDialogOpen: boolean; closeDialogCallback: Function; canEdit: boolean; refreshCallback: Function; components?: Component[]; } const deviceFromForm = (device: Device): Device => { if (device.settings.upgradeChannel === pond.UpgradeChannel.UPGRADE_CHANNEL_INVALID) { device.settings.upgradeChannel = pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE; } return device; }; export default function DeviceSettings(props: Props) { const classes = useStyles(); const [{ user, as }] = useGlobalState(); const navigate = useNavigate(); const { success, error } = useSnackbar(); const deviceAPI = useDeviceAPI(); const { device, isDialogOpen, closeDialogCallback, canEdit, refreshCallback, components } = props; const prevDevice = usePrevious(device); const [deviceForm, setDeviceForm] = useState(deviceFromForm(Device.clone(device))); const [isRemoveDeviceDialogOpen, setIsRemoveDeviceDialogOpen] = useState(false); const [sleeps, setSleeps] = useState(device.settings.sleepDurationS > 0); const [compExtOne, setCompExtOne] = useState(""); const [compExtTwo, setCompExtTwo] = useState(""); const [compExtThree, setCompExtThree] = useState(""); const [currentTab, setCurrentTab] = useState(0); const [linearMutations, setLinearMutations] = useState([]); const [componentsByDevice, setComponentsByDevice] = useState>( new Map() ); const [newMutationDialog, setNewMutationDialog] = useState(false); const [existingMutation, setExistingMutation] = useState(); useEffect(() => { if (prevDevice !== device) { setDeviceForm(deviceFromForm(Device.clone(props.device))); if (device.settings.extensionComponents[0]) { setCompExtOne(device.settings.extensionComponents[0]); } if (device.settings.extensionComponents[1]) { setCompExtTwo(device.settings.extensionComponents[1]); } if (device.settings.extensionComponents[2]) { setCompExtThree(device.settings.extensionComponents[2]); } if (device.settings.mutations) { setLinearMutations(device.settings.mutations); } if (components) { let cbd = new Map(); cbd.set(device.id().toString(), components); setComponentsByDevice(cbd); } } }, [device, prevDevice, props.device, components]); const close = () => { closeDialogCallback(); }; const isSleepDurationValid = (device: Device): boolean => { return ( !sleeps || (sleeps && device.settings.sleepDurationS >= 10 && device.settings.sleepDelayMs >= 1000) ); }; const minCheckPeriodS = (): number => { let defaultPeriod = 60; switch (device.settings.platform) { case pond.DevicePlatform.DEVICE_PLATFORM_ELECTRON: return user.hasFeature("admin") ? defaultPeriod : 300; default: return defaultPeriod; } }; const minCheckPeriodDescription = (): string => { let min = minCheckPeriodS() / 60; return min.toString() + " minute" + (min !== 1 ? "s" : ""); }; const isCheckPeriodValid = (device: Device): boolean => { return ( device.settings.pondCheckPeriodS >= minCheckPeriodS() && device.settings.pondCheckPeriodS <= 3600 ); }; const isFormValid = (): boolean => { return isSleepDurationValid(deviceForm) && isCheckPeriodValid(deviceForm); }; const changeName = (event: any) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.name = String(event.target.value); setDeviceForm(updatedForm); }; const changeDescription = (event: any) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.description = String(event.target.value); setDeviceForm(updatedForm); }; const changeSleepDuration = (ms: number) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.sleepDurationS = ms / 1000; setDeviceForm(updatedForm); }; const changeSleepDelay = (ms: number) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.sleepDelayMs = ms; setDeviceForm(updatedForm); }; const changePondCheckPeriod = (ms: number) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.pondCheckPeriodS = ms / 1000; setDeviceForm(updatedForm); }; const changeUpgradeChannel = (event: any) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.upgradeChannel = event.target.value; setDeviceForm(updatedForm); }; const changeProduct = (event: any) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.product = event.target.value; setDeviceForm(updatedForm); }; const upgradeChannelHelper = (): string => { switch (deviceForm.settings.upgradeChannel) { case pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE: { return ""; } case pond.UpgradeChannel.UPGRADE_CHANNEL_BETA: { return "The newest field tested features as they become available"; } case pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA: { return "The newest lab tested features as they become available"; } case pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT: { return "Features as they're written, expect things to break"; } } return ""; }; const toggleAutomaticallyUpgrade = (event: any) => { let updatedForm = Device.clone(deviceForm); updatedForm.settings.automaticallyUpgrade = event.target.checked; setDeviceForm(updatedForm); }; const toggleSleeps = (event: any) => { let updatedForm = Device.clone(deviceForm); let updatedSleeps = event.target.checked; updatedSleeps ? (updatedForm.settings.sleepType = quack.SleepType.SLEEP_TYPE_NAP) : (updatedForm.settings.sleepType = quack.SleepType.SLEEP_TYPE_NONE); setSleeps(updatedSleeps); setDeviceForm(updatedForm); }; const submitSettings = () => { const deviceName = deviceForm.name(); const deviceID = device.id(); if (!sleeps) { deviceForm.settings.sleepDurationS = 0; } deviceForm.settings.extensionComponents = [compExtOne, compExtTwo, compExtThree]; deviceForm.settings.mutations = linearMutations; deviceAPI .update(deviceID, deviceForm.settings, as) .then((_response: any) => { success(deviceName + " was successfully updated!"); close(); refreshCallback(); }) .catch((_err: any) => { error("Error occured while configuring " + deviceName); close(); }); }; const confirmRemoveDevice = () => { setIsRemoveDeviceDialogOpen(true); }; const closeConfirmRemoveDeviceDialog = () => { setIsRemoveDeviceDialogOpen(false); }; const removeDevice = () => { const deviceName = deviceForm.name(); deviceAPI .remove(device.id(), as) .then((_response: any) => { success(deviceName + " was successfully deleted!"); navigate("/"); }) .catch((_err: any) => { error("Error occured while deleting " + deviceName + "."); closeConfirmRemoveDeviceDialog(); close(); }); }; const canRemove = (): boolean => { return user.allowedTo("remove-devices"); }; const title = () => { return ( Device Settings {deviceForm.name()} - ID: {deviceForm.id()} ); }; const content = () => { return ( {user.hasFeature("sleep") && ( } label={Sleeps} labelPlacement="top" disabled={!canEdit} /> )} Stable Beta Alpha {user.hasFeature("dev-channel") && ( Development )} {user.hasFeature("dev-channel") && ( Recovery )} {user.hasFeature("admin") && ( changeProduct(event)} margin="normal" fullWidth variant="outlined" disabled={!canEdit}> {ListDeviceProductDescribers().map(describer => ( {describer.label} ))} )} {IsExtended(deviceForm.settings.product) && components && ( Select components to display on card setCompExtOne(event.target.value)} margin="normal" fullWidth variant="outlined" disabled={!canEdit}> {components.map(comp => ( {comp.name()} ))} setCompExtTwo(event.target.value)} margin="normal" fullWidth variant="outlined" disabled={!canEdit}> {components.map(comp => ( {comp.name()} ))} setCompExtThree(event.target.value)} margin="normal" fullWidth variant="outlined" disabled={!canEdit}> {components.map(comp => ( {comp.name()} ))} )} {user.hasAdmin() && ( } disabled={!canEdit} label="Automatically Upgrade" labelPlacement="end" /> )} ); }; const mutationsContent = () => { let mutations: JSX.Element[] = []; if (device.settings.mutations) { device.settings.mutations.forEach((mut, i) => { mutations.push( {mut.mutationName} ); }); } let mutationList: JSX.Element = {mutations}; return ( {mutationList} ); }; const actions = () => { return ( {canRemove() && Delete} {/* */} ); }; return ( {title()} {components ? ( setCurrentTab(value)} indicatorColor="primary" textColor="primary" variant="fullWidth" aria-label="device tabs" classes={{ root: classes.tabSmall }}> {user.hasFeature("beta") && ( )} {content()} {mutationsContent()} ) : ( content() )} {actions()} Delete {deviceForm.name()}? WARNING: Clicking 'Accept' will remove the device from all users and groups associated with it. { setNewMutationDialog(false); }} onSubmit={newMutation => { let lm = linearMutations; lm.push(newMutation); setLinearMutations(lm); }} /> ); }