- {message.state === "ack" ? (
-
- ) : message.state === "waiting" ? (
-
- ) : (
-
+ {getStatusText(state)}
+
+
+
+
+);
+
+const StatusIcon = ({ state, className, ...otherProps }: StatusIconProps) => {
+ const isFailed = state === MESSAGE_STATES.FAILED;
+ const iconClass = cn(
+ className,
+ "text-gray-500 dark:text-gray-400 w-4 h-4 flex-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-black dark:text-white"
+ : "text-black dark:text-gray-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 (
+
+
- {message.data}
-
+ >
+
+ {!lastMsgSameUser ? (
+
+
+
+
+ {messageUser?.longName}
+
+
+
+ ) : null}
+
+
+
);
diff --git a/src/components/PageComponents/Messages/MessageInput.tsx b/src/components/PageComponents/Messages/MessageInput.tsx
index ae88f9ac..e8b85357 100644
--- a/src/components/PageComponents/Messages/MessageInput.tsx
+++ b/src/components/PageComponents/Messages/MessageInput.tsx
@@ -4,16 +4,24 @@ import { Input } from "@components/UI/Input.tsx";
import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/js";
import { SendIcon } from "lucide-react";
-import { useCallback, useMemo, useState } from "react";
+import {
+ type JSX,
+ startTransition,
+ useCallback,
+ useMemo,
+ useState,
+} from "react";
export interface MessageInputProps {
to: Types.Destination;
channel: Types.ChannelNumber;
+ maxBytes: number;
}
export const MessageInput = ({
to,
channel,
+ maxBytes,
}: MessageInputProps): JSX.Element => {
const {
connection,
@@ -24,6 +32,7 @@ export const MessageInput = ({
} = useDevice();
const myNodeNum = hardware.myNodeNum;
const [localDraft, setLocalDraft] = useState(messageDraft);
+ const [messageBytes, setMessageBytes] = useState(0);
const debouncedSetMessageDraft = useMemo(
() => debounce(setMessageDraft, 300),
@@ -60,19 +69,29 @@ export const MessageInput = ({
const handleInputChange = (e: React.ChangeEvent
) => {
const newValue = e.target.value;
- setLocalDraft(newValue);
- debouncedSetMessageDraft(newValue);
+ const byteLength = new Blob([newValue]).size;
+
+ if (byteLength <= maxBytes) {
+ setLocalDraft(newValue);
+ debouncedSetMessageDraft(newValue);
+ setMessageBytes(byteLength);
+ }
};
return (