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