added device actions

This commit is contained in:
Carter 2024-12-13 13:27:16 -06:00
parent cd986c4f56
commit aa3301ac35
10 changed files with 557 additions and 15 deletions

149
src/pbHelpers/Device.ts Normal file
View file

@ -0,0 +1,149 @@
import { Device, Tag } from "models";
import moment from "moment";
import { filterByTags } from "pbHelpers/Tag";
import { GetDeviceProductLabel } from "products/DeviceProduct";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
import { capitalize } from "utils/strings";
import { getDevicePlatformLabel } from "./DevicePlatform";
import { filterByDeviceState } from "./DeviceState";
import { filterByPower } from "./Power";
import { getUpgradeChannelLabel } from "./UpgradeChannel";
//complex filter algorithm for searching a list of devices
export function filterDevices(searchValue: string, devices: Device[], tags: Tag[]): Device[] {
return devices.filter(device =>
filterDevice(searchValue.toLowerCase(), device, deviceTags(device, tags))
);
}
//helper function for filterDevices
export function filterDevice(searchValue: string, device: Device, tags: Tag[]): boolean {
const idMatch: boolean = filterByDeviceID(searchValue, device.settings.deviceId.toString());
const nameMatch: boolean = filterByDeviceName(searchValue, device.name());
const tagsMatch: boolean = filterByTags(searchValue, tags);
const deviceStateMatch: boolean = filterByDeviceState(searchValue, device.status.state);
const powerMatch: boolean = filterByPower(
searchValue,
device.status.power ? pond.DevicePower.create(device.status.power) : undefined
);
const lastActiveMatch: boolean = filterByLastActive(searchValue, device.status.lastActive);
return idMatch || nameMatch || tagsMatch || powerMatch || deviceStateMatch || lastActiveMatch;
}
export function filterByDeviceID(searchValue: string, deviceIDString: string): boolean {
return searchValue.replace("id:", "").trim() === deviceIDString;
}
export function filterByDeviceName(searchValue: string, deviceName: string): boolean {
return (
deviceName.toLowerCase().indexOf(
searchValue
.replace("name:", "")
.trim()
.toLowerCase()
) > -1
);
}
export function filterByDeviceDescription(searchValue: string, deviceDescription: string): boolean {
return (
deviceDescription.toLowerCase().indexOf(
searchValue
.replace("description:", "")
.trim()
.toLowerCase()
) > -1
);
}
export function filterByLastActive(searchValue: string, lastActive: string): boolean {
return (
moment(lastActive)
.fromNow()
.indexOf(searchValue.replace("last active:", "")) > -1
);
}
export function deviceTags(device: Device, tags: Tag[]): Tag[] {
return tags.filter(tag => device.status.tagKeys.some(key => key === tag.settings.key));
}
const keyTranslator = new Map<keyof pond.DeviceSettings, string>([
["deviceId", "Device ID"],
["name", "Name"],
["description", "Description"],
["sleepType", "Sleep Type"],
["sleepDurationS", "Sleep Duration"],
["sleepDelayMs", "Sleep Delay"],
["pondCheckPeriodS", "Check Period"],
["removed", "Removed"],
["platform", "Platform"],
["wakePin", "Wake Pin"],
["platform", "Platform"],
["automaticallyUpgrade", "Upgrades Automatically"],
["product", "Product"],
["upgradeChannel", "Upgrade Channel"],
["latitude", "Latitude"],
["longitude", "Longitude"],
["theme", "Marker Theme"],
["extensionComponents", "Extension Components"],
["mutations", "Device Mutations"]
]);
// Keys will be stringified by default if not found in the keyTranslator
export function TranslateKey(key: keyof pond.DeviceSettings): string {
let translatedKey = keyTranslator.get(key);
return translatedKey ? translatedKey : capitalize(key.toString());
}
const valueTranslator = new Map<keyof pond.DeviceSettings, (device: pond.DeviceSettings) => string>(
[
["name", device => device.name],
["description", device => device.description],
[
"sleepType",
device => {
switch (device.sleepType) {
case quack.SleepType.SLEEP_TYPE_DEEP:
return "Deep Sleep";
case quack.SleepType.SLEEP_TYPE_HIBERNATE:
return "Hibernate";
case quack.SleepType.SLEEP_TYPE_NAP:
return "Nap";
default:
return "None";
}
}
],
["sleepDurationS", device => device.sleepDurationS.toString() + "s"],
["sleepDelayMs", device => (device.sleepDelayMs / 1000).toString() + "s"],
["pondCheckPeriodS", device => device.pondCheckPeriodS.toString() + "s"],
["platform", device => getDevicePlatformLabel(device.platform)],
["product", device => GetDeviceProductLabel(device.product)],
["upgradeChannel", device => getUpgradeChannelLabel(device.upgradeChannel)],
["automaticallyUpgrade", device => device.automaticallyUpgrade.toString()],
["latitude", device => device.latitude.toString()],
["longitude", device => device.longitude.toString()],
["theme", device => "Colour: " + device.theme?.color + "Size: " + device.theme?.height],
["extensionComponents", device => device.extensionComponents.toString()],
["mutations", device => device.mutations.toString()]
]
);
// Values will be stringified by default if its key is not found in the valueTranslator
export function TranslateValue(key: keyof pond.DeviceSettings, device: pond.DeviceSettings) {
//console.log(device)
//console.log(key)
let translatorFunc = valueTranslator.get(key);
let dKey = device[key];
return translatorFunc ? translatorFunc(device) : capitalize(dKey ? dKey.toString() : "");
}
//checks the user's path to determine if they are on a shared url
export function isShareableLink(rawDeviceID: string): boolean {
if (!rawDeviceID) {
return false;
}
return isNaN(Number(rawDeviceID));
}

128
src/pbHelpers/Power.tsx Normal file
View file

@ -0,0 +1,128 @@
import {
Battery20,
Battery30,
Battery50,
Battery60,
Battery80,
Battery90,
BatteryAlert,
BatteryCharging20,
BatteryCharging30,
BatteryCharging50,
BatteryCharging60,
BatteryCharging80,
BatteryCharging90,
BatteryChargingFull,
BatteryFull,
BatteryUnknown,
PowerOff,
Power
} from "@mui/icons-material";
import { pond } from "protobuf-ts/pond";
import { quack } from "protobuf-ts/quack";
export interface PowerDescriber {
description: string;
icon: any;
}
interface batteryThreshold {
threshold: number;
icon: any;
}
let chargingThresholds: batteryThreshold[] = [
{ threshold: 100, icon: <BatteryChargingFull style={{ color: "var(--status-ok)" }} /> },
{ threshold: 90, icon: <BatteryCharging90 style={{ color: "var(--status-ok)" }} /> },
{ threshold: 80, icon: <BatteryCharging80 style={{ color: "var(--status-ok)" }} /> },
{ threshold: 60, icon: <BatteryCharging60 style={{ color: "var(--status-unstable)" }} /> },
{ threshold: 40, icon: <BatteryCharging50 style={{ color: "var(--status-unstable)" }} /> },
{ threshold: 30, icon: <BatteryCharging30 style={{ color: "var(--status-warning)" }} /> },
{ threshold: 20, icon: <BatteryCharging20 style={{ color: "var(--status-warning)" }} /> },
{ threshold: 1, icon: <BatteryCharging20 style={{ color: "var(--status-alert)" }} /> },
{ threshold: 0, icon: <BatteryAlert style={{ color: "var(--status-alert)" }} /> }
];
let notChargingThresholds: batteryThreshold[] = [
{ threshold: 100, icon: <BatteryFull style={{ color: "var(--status-ok)" }} /> },
{ threshold: 90, icon: <Battery90 style={{ color: "var(--status-ok)" }} /> },
{ threshold: 80, icon: <Battery80 style={{ color: "var(--status-ok)" }} /> },
{ threshold: 60, icon: <Battery60 style={{ color: "var(--status-unstable)" }} /> },
{ threshold: 40, icon: <Battery50 style={{ color: "var(--status-unstable)" }} /> },
{ threshold: 30, icon: <Battery30 style={{ color: "var(--status-warning)" }} /> },
{ threshold: 20, icon: <Battery20 style={{ color: "var(--status-warning)" }} /> },
{ threshold: 1, icon: <Battery20 style={{ color: "var(--status-alert)" }} /> },
{ threshold: 0, icon: <BatteryAlert style={{ color: "var(--status-alert)" }} /> }
];
export function describePower(power?: pond.DevicePower | null): PowerDescriber {
let result = {
description: "",
icon: <BatteryUnknown style={{ color: "var(--status-unknown)" }} />
};
if (!power) return result;
if (power.type === quack.PowerSubtype.POWER_SUBTYPE_NO_BATTERY) {
if (power.inputVoltage > 0) {
result.icon = <Power style={{ color: "var(--status-ok)" }} />;
result.description = "Plugged in";
} else {
result.icon = <PowerOff style={{ color: "var(--status-alert)" }} />;
result.description = "Unplugged";
}
} else {
let thresholds = notChargingThresholds;
let suffix = "not charging";
if (power.inputVoltage >= 4) {
thresholds = chargingThresholds;
if (power.chargePercent === 100) {
suffix = "charged";
} else {
suffix = "charging";
}
}
result.description = power.chargePercent.toString() + "%, " + suffix;
for (let i = 0; i < thresholds.length; i++) {
if (power.chargePercent >= thresholds[i].threshold) {
result.icon = thresholds[i].icon;
break;
}
}
}
return result;
}
//returns a boolean indicating whether the search term matches a power query
export function filterByPower(searchValue: string, power: pond.DevicePower | undefined): boolean {
let matches: string[] = [];
if (power) {
switch (power.type) {
case quack.PowerSubtype.POWER_SUBTYPE_NO_BATTERY:
if (power.inputVoltage > 0) {
matches.push("on");
} else {
matches.push("off");
}
break;
default:
const isCharging = power.inputVoltage >= 4.0;
if (isCharging) {
matches.push("charging");
}
if (power.chargePercent >= 0 && power.chargePercent <= 33) {
matches.push("low");
} else if (power.chargePercent <= 66) {
matches.push("medium");
} else {
matches.push("high");
}
break;
}
} else {
matches = ["unknown", "?"];
}
return matches.some(match => match.indexOf(searchValue.replace("power:", "").trim()) > -1);
}

19
src/pbHelpers/Tag.tsx Normal file
View file

@ -0,0 +1,19 @@
import { Tag } from "models";
export function filterByTags(searchValue: string, tags: Tag[]): boolean {
return tags.some(tag => filterByTag(searchValue, tag));
}
export function filterByTag(searchValue: string, tag: Tag): boolean {
return (
tag
.name()
.toLowerCase()
.indexOf(
searchValue
.replace("tag:", "")
.trim()
.toLowerCase()
) > -1
);
}

View file

@ -0,0 +1,54 @@
import { pond } from "protobuf-ts/pond";
export interface UpgradeChannelHelper {
label: string;
colour: string;
}
const Unknown: UpgradeChannelHelper = {
label: "Unknown Channel",
colour: "var(--status-ok)"
};
const Alpha: UpgradeChannelHelper = {
label: "Alpha",
colour: "var(--status-risk)"
};
const Beta: UpgradeChannelHelper = {
label: "Beta",
colour: "var(--status-unstable)"
};
const Stable: UpgradeChannelHelper = {
label: "Stable",
colour: "var(--status-ok)"
};
const Development: UpgradeChannelHelper = {
label: "Development",
colour: "var(--status-warning)"
};
export function getUpgradeChannelHelper(channel: pond.UpgradeChannel): UpgradeChannelHelper {
switch (channel) {
case pond.UpgradeChannel.UPGRADE_CHANNEL_ALPHA:
return Alpha;
case pond.UpgradeChannel.UPGRADE_CHANNEL_BETA:
return Beta;
case pond.UpgradeChannel.UPGRADE_CHANNEL_STABLE:
return Stable;
case pond.UpgradeChannel.UPGRADE_CHANNEL_DEVELOPMENT:
return Development;
default:
return Unknown;
}
}
export function getUpgradeChannelLabel(channel: pond.UpgradeChannel): string {
return getUpgradeChannelHelper(channel).label;
}
export function getUpgradeChannelColour(channel: pond.UpgradeChannel): string {
return getUpgradeChannelHelper(channel).colour;
}