import { memo, useMemo } from "react"; import { Tooltip, TooltipArrow, TooltipContent, TooltipProvider, TooltipTrigger, } from "@components/UI/Tooltip.tsx"; import { type MessageWithState, useDeviceStore, } from "@core/stores/deviceStore.ts"; import { cn } from "@core/utils/cn.ts"; import { Avatar } from "@components/UI/Avatar.tsx"; import type { Protobuf } from "@meshtastic/core"; import { AlertCircle, CheckCircle2, CircleEllipsis, LucideIcon } from "lucide-react"; type MessageStateValue = { state: string; icon: LucideIcon; displayText: string; } 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 MESSAGE_STATES: Record = { ACK: { state: 'ack', icon: CheckCircle2, displayText: "Message delivered" }, WAITING: { state: 'waiting', icon: CircleEllipsis, displayText: "Waiting for delivery" }, FAILED: { state: 'failed', icon: AlertCircle, displayText: "Delivery failed" }, }; const getMessageState = (state: MessageState): MessageStateValue => { switch (state) { case MESSAGE_STATES.ACK.state: return MESSAGE_STATES.ACK; case MESSAGE_STATES.WAITING.state: return MESSAGE_STATES.WAITING; case MESSAGE_STATES.FAILED.state: return MESSAGE_STATES.FAILED; default: return MESSAGE_STATES.FAILED; } } const StatusTooltip = ({ state, children }: StatusTooltipProps) => ( {children} {getMessageState(state).displayText ?? "An unknown error occurred"}; ); const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => { const msgState = getMessageState(state); const isFailed = msgState.state === 'failed' const iconClass = cn( className, "text-slate-500 dark:text-slate-400 size-5 shrink-0" ); const Icon = msgState.icon; return ( ); }; const TimeDisplay = memo(({ date, className }: { date: Date; className?: string }) => (
{date.toLocaleDateString()} {date.toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit", })}
)); export const Message = memo(({ 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 getMessageTextStyles = (state: MessageState) => { const msgState = getMessageState(state); const isAcknowledged = msgState.state === 'ack' const isFailed = msgState.state === 'failed' 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 messageTextClass = useMemo(() => getMessageTextStyles(message.state), [message.state]); return (
{!lastMsgSameUser && (
{messageUser?.longName}
)}
{message.data}
); });