diff --git a/src/components/PageComponents/Messages/MessageItem.tsx b/src/components/PageComponents/Messages/MessageItem.tsx index efeb68df..ff696007 100644 --- a/src/components/PageComponents/Messages/MessageItem.tsx +++ b/src/components/PageComponents/Messages/MessageItem.tsx @@ -5,100 +5,89 @@ import { TooltipProvider, TooltipTrigger, } from "@components/UI/Tooltip.tsx"; -import { useDevice, useDeviceStore } from "@core/stores/deviceStore.ts"; +import { useDevice } from "@core/stores/deviceStore.ts"; import { cn } from "@core/utils/cn.ts"; import { Avatar } from "@components/UI/Avatar.tsx"; import { AlertCircle, CheckCircle2, CircleEllipsis } from "lucide-react"; import type { LucideIcon } from "lucide-react"; import { ReactNode, useMemo } from "react"; -import { Message, MessageState } from "../../../core/stores/messageStore/index.ts"; -import { Protobuf } from "@meshtastic/js"; -import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; +import { MessageState, useMessageStore } from "@core/stores/messageStore/index.ts"; +import { Protobuf, Types } from "@meshtastic/js"; +import { Message } from "@core/stores/messageStore/types.ts"; +// import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx"; // Uncomment if needed later -interface MessageProps { - message: Message; - // locale?: string; // locale -} - -interface MessageStatus { - state: MessageState; +interface MessageStatusInfo { displayText: string; icon: LucideIcon; ariaLabel: string; iconClassName?: string; } -const MESSAGE_STATUS: Record = { - [MessageState.Ack]: { state: MessageState.Ack, displayText: "Message delivered", icon: CheckCircle2, ariaLabel: "Message delivered", iconClassName: "text-green-500 dark:text-green-500" }, - [MessageState.Waiting]: { state: MessageState.Waiting, displayText: "Waiting for delivery", icon: CircleEllipsis, ariaLabel: "Sending message", iconClassName: "text-slate-400 dark:text-slate-400" }, - [MessageState.Failed]: { state: MessageState.Failed, displayText: "Delivery failed", icon: AlertCircle, ariaLabel: "Message delivery failed", iconClassName: "text-red-500 dark:text-red-400" }, +const MESSAGE_STATUS_MAP: Record = { + [MessageState.Ack]: { displayText: "Message delivered", icon: CheckCircle2, ariaLabel: "Message delivered", iconClassName: "text-green-500" }, + [MessageState.Waiting]: { displayText: "Waiting for delivery", icon: CircleEllipsis, ariaLabel: "Sending message", iconClassName: "text-slate-400" }, + [MessageState.Failed]: { displayText: "Delivery failed", icon: AlertCircle, ariaLabel: "Message delivery failed", iconClassName: "text-red-500 dark:text-red-400" }, }; -const getMessageStatus = (state: MessageState): MessageStatus => - MESSAGE_STATUS[state] ?? { state: MessageState.Failed, displayText: "Unknown state", icon: AlertCircle, ariaLabel: "Message status unknown" }; +const UNKNOWN_STATUS: MessageStatusInfo = { displayText: "Unknown state", icon: AlertCircle, ariaLabel: "Message status unknown", iconClassName: "text-red-500 dark:text-red-400" }; -const StatusTooltip = ({ status, children }: { status: MessageStatus; children: ReactNode }) => ( +const getMessageStatusInfo = (state: MessageState): MessageStatusInfo => + MESSAGE_STATUS_MAP[state] ?? UNKNOWN_STATUS; + +const StatusTooltip = ({ statusInfo, children }: { statusInfo: MessageStatusInfo; children: ReactNode }) => ( {children} - {status.displayText} + {statusInfo.displayText} ); -const StatusIcon = ({ status }: { status: MessageStatus }) => { - const Icon = status.icon; - const iconClass = cn("size-4 shrink-0", status.iconClassName); - return ( - - - - - ); -}; +interface MessageItemProps { + message: Message; +} -const TimeDisplay = ({ date, className }: { date: number; className?: string }) => { - const _date = useMemo(() => new Date(date), [date]); - const locale = 'en-US'; // TODO: Make dynamic - const formattedTime = useMemo(() => _date.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit', hour12: true }), [_date, locale]); - const fullDate = useMemo(() => _date.toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' }), [_date, locale]); +export const MessageItem = ({ message }: MessageItemProps) => { + const { getNode } = useDevice(); + const { getMyNodeNum } = useMessageStore() + + const messageUser: Protobuf.Mesh.NodeInfo | null | undefined = useMemo(() => { + return message.from != null ? getNode(message.from) : null; + }, [getNode, message.from]); - return ( - - ); -}; -export const MessageItem = ({ message }: MessageProps) => { - const { getDevices } = useDeviceStore(); - const { getNode } = useDevice() - - const messageUser: Protobuf.Mesh.NodeInfo | null = useMemo(() => { - if (message?.from === null || message?.from === undefined) return null; - const fromNode = getNode(message.from); - if (fromNode) { - return fromNode ?? null; - } - return null; - }, [getDevices, message.from]); - - const { shortName, displayName, } = useMemo(() => { - const fallbackName = message.from + const myNodeNum = useMemo(() => getMyNodeNum(), [getMyNodeNum]); + const { displayName, shortName } = useMemo(() => { + const userIdHex = message.from.toString(16).toUpperCase().padStart(2, '0'); + const last4 = userIdHex.slice(-4); + const fallbackName = `Meshtastic ${last4}` const longName = messageUser?.user?.longName; - const shortName = messageUser?.user?.shortName ?? fallbackName; - const displayName = longName || fallbackName; - return { shortName, displayName, from: message.from }; + const derivedShortName = messageUser?.user?.shortName || fallbackName; + const derivedDisplayName = longName || derivedShortName; + return { displayName: derivedDisplayName, shortName: derivedShortName }; }, [messageUser, message.from]); - const messageStatus = getMessageStatus(message.state); - const messageText = message?.message ?? ""; - const messageDate = message?.date; + const messageStatusInfo = getMessageStatusInfo(message.state); + const StatusIconComponent = messageStatusInfo.icon; + + const messageDate = useMemo(() => message.date ? new Date(message.date) : null, [message.date]); + const locale = 'en-US'; // TODO: Make dynamic via props or context + + const formattedTime = useMemo(() => + messageDate?.toLocaleTimeString(locale, { hour: 'numeric', minute: '2-digit', hour12: true }) ?? '', + [messageDate, locale]); + + const fullDateTime = useMemo(() => + messageDate?.toLocaleString(locale, { dateStyle: 'medium', timeStyle: 'short' }) ?? '', + [messageDate, locale]); + + const isSender = myNodeNum !== undefined && message.from === myNodeNum; + const isOnPrimaryChannel = message.channel === Types.ChannelNumber.Primary; // Use the enum + const shouldShowStatusIcon = isSender && isOnPrimaryChannel; + const messageItemWrapperClass = cn( "group w-full py-2 relative list-none", @@ -106,39 +95,48 @@ export const MessageItem = ({ message }: MessageProps) => { "hover:bg-slate-300/15 dark:hover:bg-slate-600/20", "transition-colors duration-100 ease-in-out", ); + const dateTextStyle = "text-xs text-slate-500 dark:text-slate-400"; - const gridGapClass = "gap-x-2"; - - const baseTextStyle = "text-sm text-slate-800 dark:text-slate-200"; - const nameTextStyle = "font-medium text-slate-900 dark:text-slate-100 mr-2"; - const dateTextStyle = "text-slate-500 dark:text-slate-400"; return (
  • -
    +
    -
    - {messageDate != null ? ( -
    - - - -
    - ) : null} - -
    - {messageText} +
    +
    + + {displayName} + + {messageDate && ( + + )} + {shouldShowStatusIcon && ( + + + + + )}
    + {message?.message && ( +
    + {message.message} +
    + )}
    - {/* TODO: Restore when reactions/emojis are functional */} - {/* console.log("Reply to message:", message.messageId)} - /> */} + {/* Actions Menu Placeholder */} + {/*
    + console.log("Reply")} /> +
    */}
  • ); }; \ No newline at end of file diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx index f1279eb1..40ae524e 100644 --- a/src/components/PageLayout.tsx +++ b/src/components/PageLayout.tsx @@ -48,7 +48,7 @@ export const PageLayout = ({ {leftBar && (