45 changed files with 95 additions and 343 deletions
@ -1,18 +0,0 @@ |
|||
import type React from "react"; |
|||
|
|||
export const Card = ({ |
|||
children, |
|||
className, |
|||
...rest |
|||
}: JSX.IntrinsicElements["div"]): JSX.Element => { |
|||
return ( |
|||
<div |
|||
className={`flex overflow-hidden rounded-md bg-white text-sm text-black shadow-md ${ |
|||
className ?? "" |
|||
}`}
|
|||
{...rest} |
|||
> |
|||
{children} |
|||
</div> |
|||
); |
|||
}; |
|||
@ -1,50 +0,0 @@ |
|||
import type React from "react"; |
|||
|
|||
import { Disclosure } from "@headlessui/react"; |
|||
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/react/24/outline"; |
|||
|
|||
import { Mono } from "./Mono.js"; |
|||
|
|||
export interface DropdownProps { |
|||
title: string; |
|||
stat?: number; |
|||
icon: JSX.Element; |
|||
defaultOpen?: boolean; |
|||
children: React.ReactNode; |
|||
} |
|||
|
|||
export const Dropdown = ({ |
|||
title, |
|||
stat, |
|||
icon, |
|||
defaultOpen, |
|||
children |
|||
}: DropdownProps): JSX.Element => { |
|||
return ( |
|||
<Disclosure defaultOpen={defaultOpen}> |
|||
{({ open }) => ( |
|||
<> |
|||
<Disclosure.Button className="group flex h-8 justify-between bg-slate-100 px-2 hover:bg-slate-200"> |
|||
<div className="my-auto flex gap-2 text-slate-700"> |
|||
<div className="my-auto">{icon}</div> |
|||
<span className="text-lg font-medium">{title}</span> |
|||
{stat !== undefined && ( |
|||
<span className="my-auto flex rounded-full bg-slate-200 px-3 py-0.5 group-hover:bg-slate-300"> |
|||
<Mono>{stat}</Mono> |
|||
</span> |
|||
)} |
|||
</div> |
|||
<div className="my-auto text-slate-600"> |
|||
{open ? ( |
|||
<ChevronUpIcon className="h-5" /> |
|||
) : ( |
|||
<ChevronDownIcon className="h-5" /> |
|||
)} |
|||
</div> |
|||
</Disclosure.Button> |
|||
<Disclosure.Panel>{children}</Disclosure.Panel> |
|||
</> |
|||
)} |
|||
</Disclosure> |
|||
); |
|||
}; |
|||
@ -1,119 +0,0 @@ |
|||
import React, { useEffect } from "react"; |
|||
|
|||
import { useDevice } from "@core/providers/useDevice.js"; |
|||
import { AdjustmentsHorizontalIcon } from "@heroicons/react/24/outline"; |
|||
|
|||
import { Card } from "../Card.js"; |
|||
import { Dropdown } from "../Dropdown.js"; |
|||
|
|||
export const ConfiguringWidget = (): JSX.Element => { |
|||
const { |
|||
hardware, |
|||
channels, |
|||
config, |
|||
moduleConfig, |
|||
setReady, |
|||
nodes, |
|||
connection |
|||
} = useDevice(); |
|||
|
|||
useEffect(() => { |
|||
if ( |
|||
hardware.myNodeNum !== 0 && |
|||
Object.keys(config).length === 7 && |
|||
Object.keys(moduleConfig).length === 7 && |
|||
channels.length === hardware.maxChannels |
|||
) { |
|||
setReady(true); |
|||
} |
|||
}, [ |
|||
config, |
|||
moduleConfig, |
|||
channels, |
|||
hardware.maxChannels, |
|||
hardware.myNodeNum, |
|||
setReady |
|||
]); |
|||
|
|||
return ( |
|||
<Card className="flex-col"> |
|||
<Dropdown |
|||
title="Config Status" |
|||
icon={<AdjustmentsHorizontalIcon className="h-4" />} |
|||
> |
|||
<div className="flex flex-col gap-2 p-3"> |
|||
<ol className="flex flex-col gap-3 overflow-hidden"> |
|||
<StatusIndicator |
|||
title="Device Info" |
|||
current={hardware.myNodeNum ? 1 : 0} |
|||
total={0} |
|||
/> |
|||
<StatusIndicator title="Peers" current={nodes.length} total={0} /> |
|||
<StatusIndicator |
|||
title="Device Config" |
|||
current={Object.keys(config).length - 1} |
|||
total={6} |
|||
/> |
|||
<StatusIndicator |
|||
title="Module Config" |
|||
current={Object.keys(moduleConfig).length - 1} |
|||
total={6} |
|||
/> |
|||
<StatusIndicator |
|||
title="Channels" |
|||
current={channels.length} |
|||
total={hardware.maxChannels ?? 0} |
|||
/> |
|||
</ol> |
|||
</div> |
|||
</Dropdown> |
|||
</Card> |
|||
); |
|||
}; |
|||
|
|||
export interface StatusIndicatorProps { |
|||
title: string; |
|||
current: number; |
|||
total: number; |
|||
} |
|||
|
|||
const StatusIndicator = ({ |
|||
title, |
|||
current, |
|||
total |
|||
}: StatusIndicatorProps): JSX.Element => { |
|||
return ( |
|||
<li className="relative"> |
|||
<div |
|||
className={`absolute top-4 left-2.5 -ml-px h-full w-0.5 ${ |
|||
current >= total ? "bg-green-500" : "bg-[#f9e3aa]" |
|||
}`}
|
|||
/> |
|||
<div className="flex"> |
|||
<div |
|||
className={`relative z-10 flex h-5 w-5 rounded-full border-2 ${ |
|||
current === 0 |
|||
? "border-[#dabb6b] bg-[#f9e3aa]" |
|||
: current >= total |
|||
? "border-green-500 bg-green-500" |
|||
: "border-green-500 bg-[#f9e3aa]" |
|||
}`}
|
|||
> |
|||
<span |
|||
className={`m-auto h-1.5 w-1.5 rounded-full ${ |
|||
current > 0 ? "bg-green-500" : "bg-[#f9e3aa]" |
|||
}`}
|
|||
/> |
|||
</div> |
|||
|
|||
<span className="ml-4 flex gap-1 text-sm"> |
|||
<span className="font-medium">{title}</span> |
|||
<span className="font-mono text-slate-500"> |
|||
({current} |
|||
{total !== 0 && `/${total}`}) |
|||
</span> |
|||
</span> |
|||
</div> |
|||
</li> |
|||
); |
|||
}; |
|||
@ -1,42 +0,0 @@ |
|||
import type React from "react"; |
|||
|
|||
import { InformationCircleIcon } from "@heroicons/react/24/outline"; |
|||
import type { Protobuf } from "@meshtastic/meshtasticjs"; |
|||
|
|||
import { Card } from "../Card.js"; |
|||
import { Dropdown } from "../Dropdown.js"; |
|||
|
|||
export interface NodeInfoWidgetProps { |
|||
hardware: Protobuf.MyNodeInfo; |
|||
} |
|||
|
|||
export const NodeInfoWidget = ({ |
|||
hardware |
|||
}: NodeInfoWidgetProps): JSX.Element => { |
|||
return ( |
|||
<Card className="flex-col"> |
|||
<Dropdown |
|||
title="Information" |
|||
icon={<InformationCircleIcon className="h-4" />} |
|||
> |
|||
<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> |
|||
</Dropdown> |
|||
</Card> |
|||
); |
|||
}; |
|||
Loading…
Reference in new issue