11 changed files with 243 additions and 77 deletions
@ -0,0 +1,18 @@ |
|||
import type React from "react"; |
|||
|
|||
export const Card = ({ |
|||
children, |
|||
className, |
|||
...rest |
|||
}: JSX.IntrinsicElements["div"]): JSX.Element => { |
|||
return ( |
|||
<div |
|||
className={`flex overflow-hidden rounded-2xl bg-white text-sm text-black shadow-md ${ |
|||
className ?? "" |
|||
}`}
|
|||
{...rest} |
|||
> |
|||
{children} |
|||
</div> |
|||
); |
|||
}; |
|||
@ -0,0 +1,16 @@ |
|||
import type React from "react"; |
|||
|
|||
export const Mono = ({ |
|||
children, |
|||
className, |
|||
...rest |
|||
}: JSX.IntrinsicElements["span"]): JSX.Element => { |
|||
return ( |
|||
<span |
|||
className={`font-mono text-sm text-slate-500 ${className ?? ""}`} |
|||
{...rest} |
|||
> |
|||
{children} |
|||
</span> |
|||
); |
|||
}; |
|||
@ -0,0 +1,30 @@ |
|||
import type React from "react"; |
|||
|
|||
import { BoltIcon } from "@heroicons/react/24/outline"; |
|||
|
|||
import { Card } from "../Card.js"; |
|||
|
|||
export interface BatteryWidgetProps { |
|||
batteryLevel: number; |
|||
voltage: number; |
|||
} |
|||
|
|||
export const BatteryWidget = ({ |
|||
batteryLevel, |
|||
voltage, |
|||
}: BatteryWidgetProps): JSX.Element => { |
|||
return ( |
|||
<Card> |
|||
<div className="flex w-20 bg-slate-700 p-3"> |
|||
<BoltIcon className="m-auto h-12 text-white" /> |
|||
</div> |
|||
<div className="w-full"> |
|||
<div className="flex h-8 bg-slate-100"> |
|||
<span className="m-auto text-lg font-medium">Power</span> |
|||
</div> |
|||
<span>{batteryLevel}</span> |
|||
<span>{voltage}</span> |
|||
</div> |
|||
</Card> |
|||
); |
|||
}; |
|||
@ -1,11 +1,38 @@ |
|||
import type React from "react"; |
|||
|
|||
export interface NodeInfoWidgetProps {} |
|||
import type { Protobuf } from "@meshtastic/meshtasticjs"; |
|||
|
|||
export const NodeInfoWidget = ({}: NodeInfoWidgetProps): JSX.Element => { |
|||
import { Card } from "../Card.js"; |
|||
|
|||
export interface NodeInfoWidgetProps { |
|||
hardware: Protobuf.MyNodeInfo; |
|||
} |
|||
|
|||
export const NodeInfoWidget = ({ |
|||
hardware, |
|||
}: NodeInfoWidgetProps): JSX.Element => { |
|||
return ( |
|||
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> |
|||
node info |
|||
</div> |
|||
<Card className="flex-col"> |
|||
<div className="flex h-8 bg-slate-100"> |
|||
<span className="m-auto text-lg font-medium">Information</span> |
|||
</div> |
|||
<div className="flex flex-col gap-2 p-3"> |
|||
<dl className="mt-2 border-b border-gray-200"> |
|||
<div className="flex justify-between py-1 text-sm font-medium"> |
|||
<dt className="text-gray-500">Firmware version</dt> |
|||
<dd className="cursor-pointer whitespace-nowrap text-gray-900 hover:text-orange-400 hover:underline"> |
|||
{hardware.firmwareVersion} |
|||
</dd> |
|||
</div> |
|||
</dl> |
|||
<div className="flex justify-between py-1 text-sm font-medium"> |
|||
<dt className="text-gray-500">Bitrate</dt> |
|||
<dd className="whitespace-nowrap text-gray-900"> |
|||
{hardware.bitrate.toFixed(2)} |
|||
<span className="font-mono text-sm text-slate-500 ">bps</span> |
|||
</dd> |
|||
</div> |
|||
</div> |
|||
</Card> |
|||
); |
|||
}; |
|||
|
|||
@ -1,11 +1,55 @@ |
|||
import type React from "react"; |
|||
|
|||
export interface PeersWidgetProps {} |
|||
import { base16 } from "rfc4648"; |
|||
|
|||
export const PeersWidget = ({}: PeersWidgetProps): JSX.Element => { |
|||
import { Hashicon } from "@emeraldpay/hashicon-react"; |
|||
import { EllipsisHorizontalCircleIcon } from "@heroicons/react/24/outline"; |
|||
import type { Protobuf } from "@meshtastic/meshtasticjs"; |
|||
|
|||
import { Card } from "../Card.js"; |
|||
import { IconButton } from "../IconButton.js"; |
|||
import { Mono } from "../Mono.js"; |
|||
|
|||
export interface PeersWidgetProps { |
|||
peers: Protobuf.NodeInfo[]; |
|||
} |
|||
|
|||
export const PeersWidget = ({ peers }: PeersWidgetProps): JSX.Element => { |
|||
return ( |
|||
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> |
|||
Peers |
|||
</div> |
|||
<Card> |
|||
<div className="flex w-full flex-col gap-1"> |
|||
<div className="flex h-8 bg-slate-100"> |
|||
<span className="m-auto text-lg font-medium">Peers</span> |
|||
</div> |
|||
<div className="p-4"> |
|||
{peers.map((peer) => ( |
|||
<div |
|||
className="flex gap-2 rounded-md p-2 hover:bg-slate-100" |
|||
key={peer.num} |
|||
> |
|||
<span className="my-auto shrink-0"> |
|||
<Hashicon value={peer.num.toString()} size={28} /> |
|||
</span> |
|||
<div className="flex flex-col"> |
|||
<span className="font-medium">{peer.user?.longName}</span> |
|||
<Mono> |
|||
{base16 |
|||
.stringify(peer.user?.macaddr ?? []) |
|||
.match(/.{1,2}/g) |
|||
?.join(":") ?? ""} |
|||
</Mono> |
|||
</div> |
|||
<div className="my-auto ml-auto"> |
|||
<IconButton |
|||
variant="secondary" |
|||
size="sm" |
|||
icon={<EllipsisHorizontalCircleIcon className="h-4" />} |
|||
/> |
|||
</div> |
|||
</div> |
|||
))} |
|||
</div> |
|||
</div> |
|||
</Card> |
|||
); |
|||
}; |
|||
|
|||
@ -1,11 +1,25 @@ |
|||
import type React from "react"; |
|||
|
|||
export interface PositionWidgetProps {} |
|||
import { MapPinIcon } from "@heroicons/react/24/outline"; |
|||
|
|||
export const PositionWidget = ({}: PositionWidgetProps): JSX.Element => { |
|||
import { Card } from "../Card.js"; |
|||
|
|||
export interface PositionWidgetProps { |
|||
grid: string; |
|||
} |
|||
|
|||
export const PositionWidget = ({ grid }: PositionWidgetProps): JSX.Element => { |
|||
return ( |
|||
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> |
|||
position |
|||
</div> |
|||
<Card> |
|||
<div className="flex w-20 bg-teal-600 p-3"> |
|||
<MapPinIcon className="m-auto h-12 text-white" /> |
|||
</div> |
|||
<div className="flex w-full flex-col"> |
|||
<div className="flex h-8 bg-slate-100"> |
|||
<span className="m-auto text-lg font-medium">Position</span> |
|||
</div> |
|||
<span className="m-auto text-lg">{grid}</span> |
|||
</div> |
|||
</Card> |
|||
); |
|||
}; |
|||
|
|||
Loading…
Reference in new issue