From f460f34ea89e75e4028b0271723a5ef87fed7743 Mon Sep 17 00:00:00 2001 From: Dan Ditomaso Date: Wed, 30 Apr 2025 15:25:26 -0400 Subject: [PATCH] fix: prevent left sidebar collapse, added battery component --- src/components/BatteryStatus.tsx | 86 +++++++++++++++++++ .../PageComponents/Map/NodeDetail.tsx | 24 +----- src/components/Sidebar.tsx | 14 +-- src/components/UI/Dialog.tsx | 2 +- src/components/UI/Sidebar/sidebarButton.tsx | 15 ++-- src/components/generic/Table/index.tsx | 65 +++++++------- src/pages/Config/index.tsx | 2 +- src/pages/Messages.tsx | 3 +- src/pages/Nodes.tsx | 3 +- 9 files changed, 137 insertions(+), 77 deletions(-) create mode 100644 src/components/BatteryStatus.tsx diff --git a/src/components/BatteryStatus.tsx b/src/components/BatteryStatus.tsx new file mode 100644 index 00000000..a84df7e8 --- /dev/null +++ b/src/components/BatteryStatus.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { + PlugZapIcon, + BatteryFullIcon, + BatteryMediumIcon, + BatteryLowIcon, +} from 'lucide-react'; +import { Subtle } from "@components/UI/Typography/Subtle.tsx"; + +interface DeviceMetrics { + batteryLevel?: number | null; + voltage?: number | null; +} + +interface BatteryStatusProps { + deviceMetrics?: DeviceMetrics | null; +} + +interface BatteryStateConfig { + condition: (level: number) => boolean; + Icon: React.ElementType; + className: string; + text: (level: number) => string; +} + +const batteryStates: BatteryStateConfig[] = [ + { + condition: level => level > 100, + Icon: PlugZapIcon, + className: 'text-gray-600', + text: () => 'Plugged in', + }, + { + condition: level => level > 80, + Icon: BatteryFullIcon, + className: 'text-green-500', + text: level => `${level}% charging`, + }, + { + condition: level => level > 20, + Icon: BatteryMediumIcon, + className: 'text-yellow-400', + text: level => `${level}% charging`, + }, + { + condition: () => true, + Icon: BatteryLowIcon, + className: 'text-red-500', + text: level => `${level}% charging`, + }, +]; + +const getBatteryState = (level: number) => { + return batteryStates.find(state => state.condition(level)); +}; + + +const BatteryStatus: React.FC = ({ deviceMetrics }) => { + if (deviceMetrics?.batteryLevel === undefined || deviceMetrics?.batteryLevel === null) { + return null; + } + + const { batteryLevel, voltage } = deviceMetrics; + const currentState = getBatteryState(batteryLevel) ?? batteryStates[batteryStates.length - 1]; + + + const BatteryIcon = currentState.Icon; + const iconClassName = currentState.className; + const statusText = currentState.text(batteryLevel); + + const voltageTitle = `${voltage?.toPrecision(3) ?? 'Unknown'} volts`; + + return ( +
+ + + {statusText} + +
+ ); +}; + +export default BatteryStatus; \ No newline at end of file diff --git a/src/components/PageComponents/Map/NodeDetail.tsx b/src/components/PageComponents/Map/NodeDetail.tsx index 214a037c..d4d5b1cf 100644 --- a/src/components/PageComponents/Map/NodeDetail.tsx +++ b/src/components/PageComponents/Map/NodeDetail.tsx @@ -8,10 +8,6 @@ import { TimeAgo } from "@components/generic/TimeAgo.tsx"; import { Protobuf } from "@meshtastic/core"; import type { Protobuf as ProtobufType } from "@meshtastic/core"; import { - BatteryChargingIcon, - BatteryFullIcon, - BatteryLowIcon, - BatteryMediumIcon, Dot, LockIcon, LockOpenIcon, @@ -28,6 +24,7 @@ import { } from "@radix-ui/react-tooltip"; import { useDevice } from "@core/stores/deviceStore.ts"; import { MessageType, useMessageStore } from "@core/stores/messageStore.ts"; +import BatteryStatus from "@components/BatteryStatus.tsx"; export interface NodeDetailProps { node: ProtobufType.Mesh.NodeInfo; @@ -112,24 +109,7 @@ export const NodeDetail = ({ node }: NodeDetailProps) => { {hardwareType !== "UNSET" && {hardwareType}} {!!node.deviceMetrics?.batteryLevel && ( -
- {node.deviceMetrics?.batteryLevel > 100 - ? - : node.deviceMetrics?.batteryLevel > 80 - ? - : node.deviceMetrics?.batteryLevel > 20 - ? - : } - - {node.deviceMetrics?.batteryLevel > 100 - ? "Charging" - : `${node.deviceMetrics?.batteryLevel}%`} - -
+ )}
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 9e14b077..2d2bef89 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -1,6 +1,6 @@ import React from "react"; import { SidebarSection } from "@components/UI/Sidebar/SidebarSection.tsx"; -import { SidebarButton } from "@components/UI/Sidebar/sidebarButton.tsx"; +import { SidebarButton } from "./UI/Sidebar/sidebarButton.tsx"; import { Subtle } from "@components/UI/Typography/Subtle.tsx"; import { useDevice } from "@core/stores/deviceStore.ts"; import type { Page } from "@core/stores/deviceStore.ts"; @@ -8,7 +8,6 @@ import { Spinner } from "@components/UI/Spinner.tsx"; import { Avatar } from "@components/UI/Avatar.tsx"; import { - BatteryMediumIcon, CircleChevronLeft, CpuIcon, LayersIcon, @@ -25,6 +24,7 @@ import { cn } from "@core/utils/cn.ts"; import { useSidebar } from "@core/stores/sidebarStore.tsx"; import ThemeSwitcher from "@components/ThemeSwitcher.tsx"; import { useAppStore } from "@core/stores/appStore.ts"; +import BatteryStatus from "@components/BatteryStatus.tsx"; export interface SidebarProps { children?: React.ReactNode; @@ -64,13 +64,6 @@ const CollapseToggleButton = () => { ); } -const getBatteryStatus = (level: number | undefined): string => { - if (level === undefined) return "UNK"; - if (level > 100) return "Charging"; - return `${level}%`; -}; - - export const Sidebar = ({ children }: SidebarProps) => { const { hardware, getNode, getNodesLength, metadata, activePage, setActivePage, setDialogOpen } = useDevice(); const { setCommandPaletteOpen } = useAppStore(); @@ -207,8 +200,7 @@ export const Sidebar = ({ children }: SidebarProps) => { )} >
- - {getBatteryStatus(myNode.deviceMetrics?.batteryLevel)} +
diff --git a/src/components/UI/Dialog.tsx b/src/components/UI/Dialog.tsx index a92474d5..acff11a2 100644 --- a/src/components/UI/Dialog.tsx +++ b/src/components/UI/Dialog.tsx @@ -44,7 +44,7 @@ const DialogContent = React.forwardRef< void; disabled?: boolean; + preventCollapse?: boolean; } export const SidebarButton = ({ @@ -22,8 +23,10 @@ export const SidebarButton = ({ children, onClick, disabled = false, + preventCollapse = false, }: SidebarButtonProps) => { - const { isCollapsed } = useSidebar(); + const { isCollapsed: isSidebarCollapsed } = useSidebar(); + const isButtonCollapsed = isSidebarCollapsed && !preventCollapse; return (