297 lines
9.6 KiB
TypeScript
297 lines
9.6 KiB
TypeScript
import {
|
|
Button,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogTitle,
|
|
Grid2 as Grid,
|
|
MenuItem,
|
|
TextField,
|
|
Theme,
|
|
Typography
|
|
} from "@mui/material";
|
|
import { makeStyles } from "@mui/styles";
|
|
import DeleteButton from "common/DeleteButton";
|
|
import ResponsiveDialog from "common/ResponsiveDialog";
|
|
import SearchSelect, { Option } from "common/SearchSelect";
|
|
import { usePreferenceAPI, usePrevious, useSnackbar } from "hooks";
|
|
import { cloneDeep } from "lodash";
|
|
import { Backpack, Component, Device, Interaction } from "models";
|
|
import { backpackOptions } from "pbHelpers/Backpack";
|
|
import { DeviceComponentKey } from "pbHelpers/Component";
|
|
import { ListDeviceProductDescribers } from "products/DeviceProduct";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useBackpackAPI } from "providers/pond/backpackAPI";
|
|
import React, { useCallback, useEffect, useState } from "react";
|
|
|
|
const useStyles = makeStyles((theme: Theme) => {
|
|
return ({
|
|
contentSpacing: {
|
|
marginTop: theme.spacing(1),
|
|
marginBottom: theme.spacing(1)
|
|
}
|
|
})
|
|
});
|
|
|
|
interface Props {
|
|
isDialogOpen: boolean;
|
|
closeDialogCallback: Function;
|
|
refreshCallback: Function;
|
|
device: Device;
|
|
components: Component[];
|
|
interactions: Interaction[];
|
|
tagIds: string[];
|
|
}
|
|
|
|
export default function SaveDeviceProfile(props: Props) {
|
|
const backpackAPI = useBackpackAPI();
|
|
const preferenceAPI = usePreferenceAPI();
|
|
const classes = useStyles();
|
|
const {
|
|
isDialogOpen,
|
|
closeDialogCallback,
|
|
refreshCallback,
|
|
device,
|
|
components,
|
|
interactions,
|
|
tagIds
|
|
} = props;
|
|
const { success, error } = useSnackbar();
|
|
const prevOpen = usePrevious(isDialogOpen);
|
|
const [backpacks, setBackpacks] = useState<Backpack[]>([]);
|
|
const [backpackName, setBackpackName] = useState<string>();
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [isNew, setIsNew] = useState<boolean>(false);
|
|
const [targetBackpackID, setTargetBackpackID] = useState<number>(0);
|
|
const [product, setProduct] = useState<pond.DeviceProduct>(0);
|
|
|
|
const defaultState = () => {
|
|
setBackpacks([]);
|
|
setBackpackName("");
|
|
setIsNew(false);
|
|
setTargetBackpackID(0);
|
|
};
|
|
|
|
const close = () => {
|
|
closeDialogCallback();
|
|
defaultState();
|
|
};
|
|
|
|
const loadBackpacks = useCallback(() => {
|
|
const { listBackpacks } = backpackAPI;
|
|
setLoading(true);
|
|
listBackpacks()
|
|
.then((response: any) => {
|
|
const rawBackpacks = response.data.backpacks;
|
|
const backpacks: Backpack[] = [];
|
|
|
|
if (rawBackpacks && rawBackpacks.length > 0) {
|
|
rawBackpacks.forEach((b: any) => {
|
|
backpacks.push(Backpack.create(pond.Backpack.fromObject(b)));
|
|
});
|
|
}
|
|
|
|
setBackpacks(backpacks.sort((b1, b2) => (b1.name() > b2.name() ? 1 : -1)));
|
|
})
|
|
.catch((err: any) => {
|
|
setBackpacks([]);
|
|
error(err ? err : "Error occured while loading device profiles");
|
|
})
|
|
.finally(() => setLoading(false));
|
|
}, [backpackAPI, error]);
|
|
|
|
useEffect(() => {
|
|
if (isDialogOpen === true && isDialogOpen !== prevOpen) {
|
|
loadBackpacks();
|
|
}
|
|
}, [isDialogOpen, prevOpen, loadBackpacks]);
|
|
|
|
const assembleBackpack = (): Promise<pond.BackpackSettings> => {
|
|
const { getPreferences } = preferenceAPI;
|
|
return new Promise((resolve, reject) => {
|
|
let getDevicePreference = getPreferences("device", [device.id().toString()]);
|
|
let getComponentPreferences = getPreferences(
|
|
"component",
|
|
components.map(c => DeviceComponentKey(device, c))
|
|
);
|
|
let promises = [getDevicePreference, getComponentPreferences];
|
|
Promise.all(promises)
|
|
.then(responses => {
|
|
let rDevicePreferences = responses[0];
|
|
let rComponentPreferences = responses[1];
|
|
let componentPreferences = {} as {
|
|
[k: string]: pond.UserPreferences;
|
|
};
|
|
rComponentPreferences.forEach(pref => {
|
|
let match = components.find(c => DeviceComponentKey(device, c) === pref.key);
|
|
if (match && pref.preferences) {
|
|
componentPreferences[match.locationString()] = pref.preferences;
|
|
}
|
|
});
|
|
let deviceCopy = cloneDeep(device.settings);
|
|
deviceCopy.deviceId = 0;
|
|
deviceCopy.platform = pond.DevicePlatform.DEVICE_PLATFORM_INVALID;
|
|
if (rDevicePreferences.length < 1) {
|
|
rDevicePreferences.push(pond.ModelPreferences.create());
|
|
}
|
|
let backpack = pond.BackpackSettings.create({
|
|
backpackId: targetBackpackID,
|
|
product: product,
|
|
name: backpackName,
|
|
device: deviceCopy,
|
|
components: components.map(c => {
|
|
let component = cloneDeep(c.settings);
|
|
component.key = "";
|
|
return component;
|
|
}),
|
|
interactions: interactions.map(i => {
|
|
let interaction = cloneDeep(i.settings);
|
|
interaction.key = "";
|
|
return interaction;
|
|
}),
|
|
tagKeys: tagIds,
|
|
devicePreferences: rDevicePreferences[0].preferences,
|
|
componentPreferences: componentPreferences
|
|
});
|
|
resolve(backpack);
|
|
})
|
|
.catch(() => {
|
|
let reason = "Failed loading user preferences";
|
|
error(reason);
|
|
reject(reason);
|
|
});
|
|
});
|
|
};
|
|
|
|
const submit = () => {
|
|
const { addBackpack, updateBackpack } = backpackAPI;
|
|
assembleBackpack().then((backpack: pond.BackpackSettings) => {
|
|
if (isNew) {
|
|
addBackpack(backpack)
|
|
.then((response: any) => {
|
|
success("Successfully created " + backpackName + " using " + device.name() + "!");
|
|
refreshCallback();
|
|
})
|
|
.catch((err: any) => {
|
|
error(err ? err : "Error occured while creating " + backpackName + ".");
|
|
})
|
|
.finally(() => close());
|
|
} else {
|
|
updateBackpack(targetBackpackID, backpack)
|
|
.then((response: any) => {
|
|
success("Successfully updated " + backpackName + " using " + device.name() + "!");
|
|
refreshCallback();
|
|
})
|
|
.catch((err: any) => {
|
|
error(err ? err : "Error occured while updating " + backpackName + ".");
|
|
})
|
|
.finally(() => close());
|
|
}
|
|
});
|
|
};
|
|
|
|
const removeSelectedProfile = () => {
|
|
const { removeBackpack } = backpackAPI;
|
|
|
|
const backpackID = targetBackpackID;
|
|
const backpack = backpacks.find(b => b.id() === backpackID);
|
|
if (targetBackpackID && backpack) {
|
|
let backpackName = backpack.name();
|
|
removeBackpack(backpackID)
|
|
.then(() => {
|
|
success("Successfully removed " + backpackName);
|
|
let updatedBackpacks = cloneDeep(backpacks);
|
|
defaultState();
|
|
setBackpacks(updatedBackpacks.filter(b => b.id() !== backpackID));
|
|
})
|
|
.catch(err => error(err ? err : "Error occured while removing " + backpackName));
|
|
}
|
|
};
|
|
|
|
const changeTargetBackpack = (option: Option | null) => {
|
|
let isNew = option && option.new === true ? true : false;
|
|
setIsNew(isNew);
|
|
setTargetBackpackID(isNew || !(option && option.value) ? 0 : Number(option.value));
|
|
setBackpackName(option && option.label ? option.label : backpackName);
|
|
};
|
|
|
|
const isFormValid = (): boolean => {
|
|
const isValidBackpackName = isNew ? backpackName !== "" : true;
|
|
const isValidTargetBackpack = isNew ? true : targetBackpackID > 0;
|
|
return isValidBackpackName && isValidTargetBackpack;
|
|
};
|
|
|
|
// UI Begins
|
|
|
|
const content = () => {
|
|
const options = backpackOptions(backpacks);
|
|
let selected: Option | undefined = options.find(
|
|
option => (option.value as number) === targetBackpackID
|
|
);
|
|
|
|
return (
|
|
<React.Fragment>
|
|
<SearchSelect
|
|
label={"Save to an existing or new profile"}
|
|
selected={selected}
|
|
options={options}
|
|
changeSelection={(option: Option | null) => changeTargetBackpack(option)}
|
|
loading={loading}
|
|
creatable
|
|
/>
|
|
<TextField
|
|
id="deviceProduct"
|
|
label="Device Product"
|
|
select
|
|
value={product}
|
|
onChange={e => setProduct(+e.target.value)}
|
|
margin="normal"
|
|
fullWidth
|
|
variant="outlined">
|
|
{ListDeviceProductDescribers().map(describer => (
|
|
<MenuItem key={describer.product} value={describer.product}>
|
|
{describer.label}
|
|
</MenuItem>
|
|
))}
|
|
</TextField>
|
|
</React.Fragment>
|
|
);
|
|
};
|
|
|
|
const actions = () => {
|
|
return (
|
|
<Grid container justifyContent="space-between" direction="row">
|
|
<Grid size={{ xs: 5 }}>
|
|
<DeleteButton disabled={!isFormValid()} onClick={() => removeSelectedProfile()}>
|
|
Delete
|
|
</DeleteButton>
|
|
</Grid>
|
|
<Grid size={{ xs: 7 }} container justifyItems="flex-end">
|
|
<Button size="small" color="primary" onClick={close}>
|
|
Cancel
|
|
</Button>
|
|
<Button size="small" disabled={!isFormValid()} color="primary" onClick={submit}>
|
|
{isNew ? "Create" : "Update"}
|
|
</Button>
|
|
</Grid>
|
|
</Grid>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<ResponsiveDialog
|
|
open={isDialogOpen}
|
|
onClose={close}
|
|
aria-labelledby="save-device-profile"
|
|
maxWidth="sm"
|
|
fullWidth>
|
|
<DialogTitle id="save-device-profile-title">
|
|
Save Device Profile
|
|
<Typography variant="body2" color="textSecondary">
|
|
{device.name()}
|
|
</Typography>
|
|
</DialogTitle>
|
|
<DialogContent className={classes.contentSpacing}>{content()}</DialogContent>
|
|
<DialogActions>{actions()}</DialogActions>
|
|
</ResponsiveDialog>
|
|
);
|
|
}
|