import { Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipTrigger, } from "@app/components/UI/Tooltip"; import { type MessageWithState, useDeviceStore, } from "@app/core/stores/deviceStore.ts"; import { cn } from "@app/core/utils/cn"; import { Avatar } from "@components/UI/Avatar"; import type { Protobuf } from "@meshtastic/core"; import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import type { LucideIcon } from "lucide-react"; import { useMemo } from "react"; const MESSAGE_STATES = { ACK: "ack", WAITING: "waiting", FAILED: "failed", } as const; type MessageState = MessageWithState["state"]; interface MessageProps { lastMsgSameUser: boolean; message: MessageWithState; sender: Protobuf.Mesh.NodeInfo; } interface StatusTooltipProps { state: MessageState; children: React.ReactNode; } interface StatusIconProps { state: MessageState; className?: string; } const STATUS_TEXT_MAP: Record = { [MESSAGE_STATES.ACK]: "Message delivered", [MESSAGE_STATES.WAITING]: "Waiting for delivery", [MESSAGE_STATES.FAILED]: "Delivery failed", } as const; const STATUS_ICON_MAP: Record = { [MESSAGE_STATES.ACK]: CheckCircle2, [MESSAGE_STATES.WAITING]: CircleEllipsis, [MESSAGE_STATES.FAILED]: AlertCircle, } as const; const getStatusText = (state: MessageState): string => STATUS_TEXT_MAP[state]; const StatusTooltip = ({ state, children }: StatusTooltipProps) => ( {children} {getStatusText(state)} ); const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => { const isFailed = state === MESSAGE_STATES.FAILED; const iconClass = cn( className, "text-slate-500 dark:text-slate-400 w-4 h-4 shrink-0", ); const Icon = STATUS_ICON_MAP[state]; return ( ); }; const getMessageTextStyles = (state: MessageState) => { const isAcknowledged = state === MESSAGE_STATES.ACK; const isFailed = state === MESSAGE_STATES.FAILED; const isWaiting = state === MESSAGE_STATES.WAITING; return cn( "break-words overflow-hidden", isAcknowledged ? "text-slate-900 dark:text-white" : "text-slate-900 dark:text-slate-400", isFailed && "text-red-500 dark:text-red-500", ); }; const TimeDisplay = ({ date, className, }: { date: Date; className?: string }) => (
{date.toLocaleDateString()} {date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", })}
); export const Message = ({ lastMsgSameUser, message, sender }: MessageProps) => { const { getDevices } = useDeviceStore(); const isDeviceUser = useMemo( () => getDevices() .map((device) => device.nodes.get(device.hardware.myNodeNum)?.num) .includes(message.from), [getDevices, message.from], ); const messageUser = sender?.user; const messageTextClass = getMessageTextStyles(message.state); return (
{!lastMsgSameUser ? (
{messageUser?.longName}
) : null}
{message.data}
); };