Browse Source

Update widgets

pull/39/head
Sacha Weatherstone 4 years ago
parent
commit
781d782a70
No known key found for this signature in database GPG Key ID: 7AB2D7E206124B31
  1. 18
      src/components/Card.tsx
  2. 6
      src/components/DeviceSelector.tsx
  3. 5
      src/components/IconButton.tsx
  4. 16
      src/components/Mono.tsx
  5. 51
      src/components/Sidebar.tsx
  6. 30
      src/components/Widgets/BatteryWidget.tsx
  7. 74
      src/components/Widgets/ConfiguringWidget.tsx
  8. 5
      src/components/Widgets/DeviceWidget.tsx
  9. 37
      src/components/Widgets/NodeInfoWidget.tsx
  10. 54
      src/components/Widgets/PeersWidget.tsx
  11. 24
      src/components/Widgets/PositionWidget.tsx

18
src/components/Card.tsx

@ -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>
);
};

6
src/components/DeviceSelector.tsx

@ -5,13 +5,15 @@ import { useDeviceStore } from "@app/core/stores/deviceStore.js";
import { Hashicon } from "@emeraldpay/hashicon-react"; import { Hashicon } from "@emeraldpay/hashicon-react";
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
import { Mono } from "./Mono.js";
export const DeviceSelector = (): JSX.Element => { export const DeviceSelector = (): JSX.Element => {
const { getDevices } = useDeviceStore(); const { getDevices } = useDeviceStore();
const { selectedDevice, setSelectedDevice } = useAppStore(); const { selectedDevice, setSelectedDevice } = useAppStore();
return ( return (
<div className="flex h-full w-16 items-center whitespace-nowrap bg-slate-50 py-12 text-sm [writing-mode:vertical-rl]"> <div className="flex h-full w-16 items-center whitespace-nowrap bg-slate-50 py-12 [writing-mode:vertical-rl]">
<span className="font-mono text-slate-500">Connected Devices</span> <Mono>Connected Devices</Mono>
<span className="mt-6 flex gap-4 font-bold text-slate-900"> <span className="mt-6 flex gap-4 font-bold text-slate-900">
{getDevices().map((device) => ( {getDevices().map((device) => (
<div <div

5
src/components/IconButton.tsx

@ -13,6 +13,7 @@ export const IconButton = ({
variant = "primary", variant = "primary",
icon, icon,
disabled, disabled,
className,
...rest ...rest
}: IconButtonProps): JSX.Element => { }: IconButtonProps): JSX.Element => {
return ( return (
@ -23,7 +24,9 @@ export const IconButton = ({
: "bg-orange-100 text-orange-700 hover:bg-orange-200" : "bg-orange-100 text-orange-700 hover:bg-orange-200"
} ${ } ${
size === "sm" ? "h-8 w-8" : size === "md" ? "h-10 w-10" : "h-12 w-12" size === "sm" ? "h-8 w-8" : size === "md" ? "h-10 w-10" : "h-12 w-12"
} ${disabled ? "cursor-not-allowed bg-red-400 focus:ring-red-500" : ""}`} } ${disabled ? "cursor-not-allowed bg-red-400 focus:ring-red-500" : ""} ${
className ?? ""
}`}
disabled={disabled} disabled={disabled}
{...rest} {...rest}
> >

16
src/components/Mono.tsx

@ -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>
);
};

51
src/components/Sidebar.tsx

@ -1,10 +1,13 @@
import type React from "react"; import type React from "react";
import { useEffect, useState } from "react";
import { useDevice } from "@app/core/providers/useDevice.js"; import { useDevice } from "@app/core/providers/useDevice.js";
import { toMGRS } from "@app/core/utils/toMGRS.js";
import { useAppStore } from "@core/stores/appStore.js"; import { useAppStore } from "@core/stores/appStore.js";
import { useDeviceStore } from "@core/stores/deviceStore.js"; import { useDeviceStore } from "@core/stores/deviceStore.js";
import { Types } from "@meshtastic/meshtasticjs"; import { Protobuf, Types } from "@meshtastic/meshtasticjs";
import { BatteryWidget } from "./Widgets/BatteryWidget.js";
import { ConfiguringWidget } from "./Widgets/ConfiguringWidget.js"; import { ConfiguringWidget } from "./Widgets/ConfiguringWidget.js";
import { DeviceWidget } from "./Widgets/DeviceWidget.js"; import { DeviceWidget } from "./Widgets/DeviceWidget.js";
import { NodeInfoWidget } from "./Widgets/NodeInfoWidget.js"; import { NodeInfoWidget } from "./Widgets/NodeInfoWidget.js";
@ -15,6 +18,21 @@ export const Sidebar = (): JSX.Element => {
const { removeDevice } = useDeviceStore(); const { removeDevice } = useDeviceStore();
const { connection, hardware, nodes, status } = useDevice(); const { connection, hardware, nodes, status } = useDevice();
const { selectedDevice, setSelectedDevice } = useAppStore(); const { selectedDevice, setSelectedDevice } = useAppStore();
const [telemtery, setTelemetry] = useState<Protobuf.DeviceMetrics>();
const [grid, setGrid] = useState<string>("");
const [batteryHistory, setBatteryHistory] = useState<number[]>([]);
useEffect(() => {
const device = nodes.find((n) => n.data.num === hardware.myNodeNum);
if (device?.deviceMetrics?.length) {
setTelemetry(device.deviceMetrics[device.deviceMetrics?.length]);
}
if (device?.data.position) {
setGrid(
toMGRS(device.data.position.latitudeI, device.data.position.longitudeI)
);
}
}, [nodes, hardware.myNodeNum]);
return ( return (
<div className="relative flex w-80 flex-shrink-0 flex-col gap-2 border-x border-slate-200 bg-slate-50 p-2"> <div className="relative flex w-80 flex-shrink-0 flex-col gap-2 border-x border-slate-200 bg-slate-50 p-2">
@ -49,29 +67,14 @@ export const Sidebar = (): JSX.Element => {
{/* */} {/* */}
{/* */} {/* */}
{/* */} {/* */}
<div className="space-y-6"> <div className="flex flex-col gap-3">
<div> <NodeInfoWidget hardware={hardware} />
<h3 className="font-medium text-gray-900">Information</h3> <BatteryWidget
<dl className="mt-2 divide-y divide-gray-200 border-t border-b border-gray-200"> batteryLevel={telemtery?.batteryLevel ?? 0}
<div className="flex justify-between py-3 text-sm font-medium"> voltage={telemtery?.voltage ?? 0}
<dt className="text-gray-500">Firmware version</dt> />
<dd className="cursor-pointer whitespace-nowrap text-gray-900 hover:text-orange-400 hover:underline"> <PeersWidget peers={nodes.map((n) => n.data)} />
{hardware.firmwareVersion} <PositionWidget grid={grid} />
</dd>
</div>
</dl>
<div className="flex justify-between py-3 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>
<NodeInfoWidget />
{/* <BatteryWidget /> */}
<PeersWidget />
<PositionWidget />
<ConfiguringWidget /> <ConfiguringWidget />
</div> </div>

30
src/components/Widgets/BatteryWidget.tsx

@ -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>
);
};

74
src/components/Widgets/ConfiguringWidget.tsx

@ -2,6 +2,9 @@ import React, { useEffect } from "react";
import { useDevice } from "@core/providers/useDevice.js"; import { useDevice } from "@core/providers/useDevice.js";
import { Button } from "../Button.js";
import { Card } from "../Card.js";
export const ConfiguringWidget = (): JSX.Element => { export const ConfiguringWidget = (): JSX.Element => {
const { const {
hardware, hardware,
@ -32,40 +35,45 @@ export const ConfiguringWidget = (): JSX.Element => {
]); ]);
return ( return (
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> <Card className="flex-col">
<p className="text-xl font-bold">Connecting to device</p> <div className="flex h-8 bg-slate-100">
<ol className="flex flex-col gap-3 overflow-hidden"> <span className="m-auto text-lg font-medium">Power</span>
<StatusIndicator </div>
title="Device Info" <div className="flex flex-col gap-2 p-3">
current={hardware.myNodeNum ? 1 : 0} <ol className="flex flex-col gap-3 overflow-hidden">
total={0} <StatusIndicator
/> title="Device Info"
<StatusIndicator title="Peers" current={nodes.length} total={0} /> current={hardware.myNodeNum ? 1 : 0}
<StatusIndicator total={0}
title="Device Config" />
current={Object.keys(config).length - 1} <StatusIndicator title="Peers" current={nodes.length} total={0} />
total={6} <StatusIndicator
/> title="Device Config"
<StatusIndicator current={Object.keys(config).length - 1}
title="Module Config" total={6}
current={Object.keys(moduleConfig).length - 1} />
total={6} <StatusIndicator
/> title="Module Config"
<StatusIndicator current={Object.keys(moduleConfig).length - 1}
title="Channels" total={6}
current={channels.length} />
total={hardware.maxChannels ?? 0} <StatusIndicator
/> title="Channels"
</ol> current={channels.length}
<div total={hardware.maxChannels ?? 0}
className="mt-2 cursor-pointer rounded-md bg-[#dabb6b] p-1 text-center ring-[#f9e3aa]" />
onClick={() => { </ol>
void connection?.configure(); <Button
}} variant="secondary"
> size="sm"
Retry onClick={() => {
void connection?.configure();
}}
>
Retry
</Button>
</div> </div>
</div> </Card>
); );
}; };

5
src/components/Widgets/DeviceWidget.tsx

@ -4,6 +4,7 @@ import { Hashicon } from "@emeraldpay/hashicon-react";
import { XCircleIcon } from "@heroicons/react/24/outline"; import { XCircleIcon } from "@heroicons/react/24/outline";
import { Button } from "../Button.js"; import { Button } from "../Button.js";
import { Card } from "../Card.js";
export interface DeviceWidgetProps { export interface DeviceWidgetProps {
name: string; name: string;
@ -21,7 +22,7 @@ export const DeviceWidget = ({
reconnect, reconnect,
}: DeviceWidgetProps): JSX.Element => { }: DeviceWidgetProps): JSX.Element => {
return ( return (
<div className="relative overflow-hidden rounded-xl bg-emerald-400"> <Card className="relative flex-col">
<div className="absolute bottom-20 h-full w-full"> <div className="absolute bottom-20 h-full w-full">
<Hashicon size={350} value={nodeNum} /> <Hashicon size={350} value={nodeNum} />
</div> </div>
@ -45,6 +46,6 @@ export const DeviceWidget = ({
</div> </div>
</div> </div>
</div> </div>
</div> </Card>
); );
}; };

37
src/components/Widgets/NodeInfoWidget.tsx

@ -1,11 +1,38 @@
import type React from "react"; 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 ( return (
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> <Card className="flex-col">
node info <div className="flex h-8 bg-slate-100">
</div> <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>
); );
}; };

54
src/components/Widgets/PeersWidget.tsx

@ -1,11 +1,55 @@
import type React from "react"; 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 ( return (
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> <Card>
Peers <div className="flex w-full flex-col gap-1">
</div> <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>
); );
}; };

24
src/components/Widgets/PositionWidget.tsx

@ -1,11 +1,25 @@
import type React from "react"; 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 ( return (
<div className="mb-4 flex flex-col space-y-3 rounded-2xl bg-[#f9e3aa] p-6 text-sm text-black"> <Card>
position <div className="flex w-20 bg-teal-600 p-3">
</div> <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…
Cancel
Save