import { Separator } from "@app/components/UI/Seperator"; import { Heading } from "@app/components/UI/Typography/Heading"; import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; import { formatQuantity } from "@app/core/utils/string"; import { Avatar } from "@components/UI/Avatar"; import { Mono } from "@components/generic/Mono.tsx"; import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { Protobuf } from "@meshtastic/js"; import type { Protobuf as ProtobufType } from "@meshtastic/js"; import { numberToHexUnpadded } from "@noble/curves/abstract/utils"; import { BatteryChargingIcon, BatteryFullIcon, BatteryLowIcon, BatteryMediumIcon, Dot, LockIcon, LockOpenIcon, MountainSnow, Star, } from "lucide-react"; export interface NodeDetailProps { node: ProtobufType.Mesh.NodeInfo; } export const NodeDetail = ({ node }: NodeDetailProps) => { const name = node.user?.longName || `!${numberToHexUnpadded(node.num)}`; const hwModel = node.user?.hwModel ?? 0; const hardwareType = Protobuf.Mesh.HardwareModel[hwModel]?.replaceAll("_", " ") ?? `${hwModel}`; return (
{node.user?.publicKey && node.user?.publicKey.length > 0 ? ( ) : ( )}
{name} {hardwareType !== "UNSET" && {hardwareType}} {!!node.deviceMetrics?.batteryLevel && (
{node.deviceMetrics?.batteryLevel > 100 ? ( ) : node.deviceMetrics?.batteryLevel > 80 ? ( ) : node.deviceMetrics?.batteryLevel > 20 ? ( ) : ( )} {node.deviceMetrics?.batteryLevel > 100 ? "Charging" : `${node.deviceMetrics?.batteryLevel}%`}
)}
{node.user?.shortName &&
"{node.user?.shortName}"
} {node.user?.id &&
{node.user?.id}
}
{node.lastHeard > 0 && (
Heard
)}
{node.viaMqtt && (
MQTT
)}
{Number.isNaN(node.hopsAway) ? "?" : node.hopsAway}
{node.hopsAway === 1 ? "Hop" : "Hops"}
{node.position?.altitude && (
{formatQuantity(node.position?.altitude, { one: "meter", other: "meters", })}
)}
{!!node.deviceMetrics?.channelUtilization && (
Channel Util
{node.deviceMetrics?.channelUtilization.toPrecision(3)}%
)} {!!node.deviceMetrics?.airUtilTx && (
Airtime Util
{node.deviceMetrics?.airUtilTx.toPrecision(3)}%
)}
{node.snr !== 0 && (
SNR
{node.snr}db {Math.min(Math.max((node.snr + 10) * 5, 0), 100)}% {(node.snr + 10) * 5}raw
)}
); };