Update widgets
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
+27
-24
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -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,8 +35,11 @@ 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">
|
||||||
|
<span className="m-auto text-lg font-medium">Power</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 p-3">
|
||||||
<ol className="flex flex-col gap-3 overflow-hidden">
|
<ol className="flex flex-col gap-3 overflow-hidden">
|
||||||
<StatusIndicator
|
<StatusIndicator
|
||||||
title="Device Info"
|
title="Device Info"
|
||||||
@@ -57,15 +63,17 @@ export const ConfiguringWidget = (): JSX.Element => {
|
|||||||
total={hardware.maxChannels ?? 0}
|
total={hardware.maxChannels ?? 0}
|
||||||
/>
|
/>
|
||||||
</ol>
|
</ol>
|
||||||
<div
|
<Button
|
||||||
className="mt-2 cursor-pointer rounded-md bg-[#dabb6b] p-1 text-center ring-[#f9e3aa]"
|
variant="secondary"
|
||||||
|
size="sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void connection?.configure();
|
void connection?.configure();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Retry
|
Retry
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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">
|
||||||
|
<span className="m-auto text-lg font-medium">Information</span>
|
||||||
</div>
|
</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";
|
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 className="flex h-8 bg-slate-100">
|
||||||
|
<span className="m-auto text-lg font-medium">Peers</span>
|
||||||
</div>
|
</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";
|
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">
|
||||||
|
<MapPinIcon className="m-auto h-12 text-white" />
|
||||||
</div>
|
</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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user