diff --git a/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx b/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx
index 17e48eb1..8041cbd1 100644
--- a/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx
+++ b/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.test.tsx
@@ -1,6 +1,6 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { DeleteMessagesDialog } from "@components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx";
vi.mock('@core/stores/messageStore.ts', () => ({
diff --git a/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx b/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx
index c93f2795..20cf9ade 100644
--- a/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx
+++ b/src/components/Dialog/DeleteMessagesDialog/DeleteMessagesDialog.tsx
@@ -10,7 +10,7 @@ import {
DialogTitle,
} from "@components/UI/Dialog.tsx";
import { AlertTriangleIcon } from "lucide-react";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export interface DeleteMessagesDialogProps {
open: boolean;
diff --git a/src/components/Dialog/NodeOptionsDialog.tsx b/src/components/Dialog/NodeOptionsDialog.tsx
index 6a239902..dcf20f00 100644
--- a/src/components/Dialog/NodeOptionsDialog.tsx
+++ b/src/components/Dialog/NodeOptionsDialog.tsx
@@ -13,7 +13,7 @@ import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { TrashIcon } from "lucide-react";
import { Button } from "../UI/Button.tsx";
-import { MessageType, useMessageStore } from "@core/stores/messageStore.ts";
+import { MessageType, useMessageStore } from "../../core/stores/messageStore/index.ts";
export interface NodeOptionsDialogProps {
node: Protobuf.Mesh.NodeInfo | undefined;
diff --git a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx
index 587a30f5..bf379436 100644
--- a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx
+++ b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.test.tsx
@@ -1,7 +1,7 @@
import { render, screen } from "@testing-library/react";
import { DeviceContext, useDeviceStore } from "@core/stores/deviceStore.ts";
import { RefreshKeysDialog } from "./RefreshKeysDialog.tsx";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { expect, test, vi, beforeEach, afterEach } from 'vitest';
import { Protobuf } from "@meshtastic/core";
diff --git a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx
index d32cbfdc..98625ceb 100644
--- a/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx
+++ b/src/components/Dialog/RefreshKeysDialog/RefreshKeysDialog.tsx
@@ -9,7 +9,7 @@ import { Button } from "@components/UI/Button.tsx";
import { LockKeyholeOpenIcon } from "lucide-react";
import { useRefreshKeysDialog } from "./useRefreshKeysDialog.ts";
import { useDevice } from "@core/stores/deviceStore.ts";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export interface RefreshKeysDialogProps {
open: boolean;
diff --git a/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts b/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts
index 4a291eea..14b421c2 100644
--- a/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts
+++ b/src/components/Dialog/RefreshKeysDialog/useRefreshKeysDialog.ts
@@ -1,6 +1,6 @@
import { useCallback } from "react";
import { useDevice } from "@core/stores/deviceStore.ts";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export function useRefreshKeysDialog() {
const { removeNode, setDialogOpen, clearNodeError, getNodeError } = useDevice();
diff --git a/src/components/Form/FormWrapper.tsx b/src/components/Form/FormWrapper.tsx
index 27cc01ef..c9a4da94 100644
--- a/src/components/Form/FormWrapper.tsx
+++ b/src/components/Form/FormWrapper.tsx
@@ -24,7 +24,7 @@ export const FieldWrapper = ({
-
{description}
+
{description}
{validationText}
diff --git a/src/components/PageComponents/Connect/BLE.tsx b/src/components/PageComponents/Connect/BLE.tsx
index 39c721b9..4e639855 100644
--- a/src/components/PageComponents/Connect/BLE.tsx
+++ b/src/components/PageComponents/Connect/BLE.tsx
@@ -7,7 +7,7 @@ import { subscribeAll } from "@core/subscriptions.ts";
import { randId } from "@core/utils/randId.ts";
import { BleConnection, ServiceUuid } from "@meshtastic/js";
import { useCallback, useEffect, useState } from "react";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export const BLE = ({ setConnectionInProgress, closeDialog }: TabElementProps) => {
const [bleDevices, setBleDevices] = useState
([]);
diff --git a/src/components/PageComponents/Connect/HTTP.tsx b/src/components/PageComponents/Connect/HTTP.tsx
index 8f177811..c50c94b5 100644
--- a/src/components/PageComponents/Connect/HTTP.tsx
+++ b/src/components/PageComponents/Connect/HTTP.tsx
@@ -13,7 +13,7 @@ import { TransportHTTP } from "@meshtastic/transport-http";
import { useState } from "react";
import { useForm, useController } from "react-hook-form";
import { AlertTriangle } from "lucide-react";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
interface FormData {
ip: string;
diff --git a/src/components/PageComponents/Connect/Serial.tsx b/src/components/PageComponents/Connect/Serial.tsx
index c14775c0..b9cd7dd5 100644
--- a/src/components/PageComponents/Connect/Serial.tsx
+++ b/src/components/PageComponents/Connect/Serial.tsx
@@ -8,7 +8,7 @@ import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core";
import { TransportWebSerial } from "@meshtastic/transport-web-serial";
import { useCallback, useEffect, useState } from "react";
-import { useMessageStore } from "@core/stores/messageStore.ts";
+import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
export const Serial = ({ setConnectionInProgress, closeDialog }: TabElementProps) => {
const [serialPorts, setSerialPorts] = useState([]);
diff --git a/src/components/PageComponents/Map/NodeDetail.tsx b/src/components/PageComponents/Map/NodeDetail.tsx
index d4d5b1cf..aa260e3d 100644
--- a/src/components/PageComponents/Map/NodeDetail.tsx
+++ b/src/components/PageComponents/Map/NodeDetail.tsx
@@ -23,7 +23,7 @@ import {
TooltipTrigger,
} from "@radix-ui/react-tooltip";
import { useDevice } from "@core/stores/deviceStore.ts";
-import { MessageType, useMessageStore } from "@core/stores/messageStore.ts";
+import { MessageType, useMessageStore } from "../../../core/stores/messageStore/index.ts";
import BatteryStatus from "@components/BatteryStatus.tsx";
export interface NodeDetailProps {
diff --git a/src/components/PageComponents/Messages/ChannelChat.tsx b/src/components/PageComponents/Messages/ChannelChat.tsx
index bc08b2d9..51bbd4db 100644
--- a/src/components/PageComponents/Messages/ChannelChat.tsx
+++ b/src/components/PageComponents/Messages/ChannelChat.tsx
@@ -1,10 +1,10 @@
import { MessageItem } from "@components/PageComponents/Messages/MessageItem.tsx";
-import type { Message as MessageType } from "@core/stores/messageStore.ts";
import { InboxIcon } from "lucide-react";
import { useCallback, useEffect, useRef } from "react";
+import { Message } from "@core/stores/messageStore/types.ts";
export interface ChannelChatProps {
- messages?: MessageType[];
+ messages?: Message[];
}
const EmptyState = () => (
@@ -28,7 +28,6 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => {
useEffect(() => {
const scrollContainer = scrollContainerRef.current;
if (!scrollContainer) return;
-
const isScrolledToBottom = scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight <= 10;
if (isScrolledToBottom || !userScrolledUpRef.current) {
@@ -43,9 +42,7 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => {
const isAtBottom = scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight <= 10;
userScrolledUpRef.current = !isAtBottom;
};
-
- scrollContainer?.addEventListener('scroll', handleScroll);
-
+ scrollContainer?.addEventListener('scroll', handleScroll, { passive: true });
return () => {
scrollContainer?.removeEventListener('scroll', handleScroll);
};
@@ -63,16 +60,17 @@ export const ChannelChat = ({ messages = [] }: ChannelChatProps) => {
return (
- {messages?.map((message) => {
- return (
-
- );
- })}
+
+
+ {messages?.map((message) => (
+
+ ))}
+
);
diff --git a/src/components/PageComponents/Messages/MessageInput.test.tsx b/src/components/PageComponents/Messages/MessageInput.test.tsx
index 597fc52b..6ca0477c 100644
--- a/src/components/PageComponents/Messages/MessageInput.test.tsx
+++ b/src/components/PageComponents/Messages/MessageInput.test.tsx
@@ -2,7 +2,7 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { MessageInput } from './MessageInput.tsx';
import { useDevice } from '@core/stores/deviceStore.ts';
-import { useMessageStore } from '@core/stores/messageStore.ts';
+import { useMessageStore } from '../../../core/stores/messageStore/index.ts';
import { debounce } from '@core/utils/debounce.ts';
import { Types } from "@meshtastic/core";
diff --git a/src/components/PageComponents/Messages/MessageInput.tsx b/src/components/PageComponents/Messages/MessageInput.tsx
index f29dd25c..e7277c91 100644
--- a/src/components/PageComponents/Messages/MessageInput.tsx
+++ b/src/components/PageComponents/Messages/MessageInput.tsx
@@ -1,57 +1,31 @@
import { Button } from "@components/UI/Button.tsx";
import { Input } from "@components/UI/Input.tsx";
-import { useDevice } from "@core/stores/deviceStore.ts";
import type { Types } from "@meshtastic/core";
import { SendIcon } from "lucide-react";
-import {
- startTransition, useCallback, // @ts-types="react"
- useDeferredValue, useState
-} from "react";
-import { MessageState, MessageType, useMessageStore } from "@core/stores/messageStore.ts";
+import { startTransition, useState } from "react";
+import { useMessageStore } from "@core/stores/messageStore/index.ts";
export interface MessageInputProps {
+ onSend: (message: string) => void;
to: Types.Destination;
- channel: Types.ChannelNumber;
maxBytes: number;
}
export const MessageInput = ({
+ onSend,
to,
- channel,
maxBytes,
}: MessageInputProps) => {
- const { connection } = useDevice();
- const { setMessageState, activeChat, setDraft, getDraft, clearDraft } = useMessageStore();
+ const { setDraft, getDraft, clearDraft } = useMessageStore();
const [localDraft, setLocalDraft] = useState(getDraft(to));
const [messageBytes, setMessageBytes] = useState(0);
const calculateBytes = (text: string) => new Blob([text]).size;
- const deferredBytes = useDeferredValue(calculateBytes(localDraft));
-
-
- const chatType = to === MessageType.Broadcast ? MessageType.Broadcast : MessageType.Direct;
-
- const sendText = useCallback(async (message: string) => {
- try {
- const messageId = await connection?.sendText(message, to, true, channel);
- if (messageId !== undefined) {
- setMessageState({ type: chatType, key: activeChat, messageId, newState: MessageState.Ack });
- }
- // deno-lint-ignore no-explicit-any
- } catch (e: any) {
- setMessageState({
- type: chatType,
- key: activeChat,
- messageId: e?.id,
- newState: MessageState.Failed,
- });
- }
- }, [channel, connection, setMessageState, to, activeChat, chatType]);
const handleInputChange = (e: React.ChangeEvent) => {
const newValue = e.target.value;
- const byteLength = deferredBytes
+ const byteLength = calculateBytes(newValue);
if (byteLength <= maxBytes) {
setLocalDraft(newValue);
@@ -63,12 +37,12 @@ export const MessageInput = ({
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!localDraft.trim()) return;
+ setMessageBytes(0);
startTransition(() => {
- sendText(localDraft.trim());
+ onSend(localDraft.trim());
setLocalDraft("");
clearDraft(to);
- setMessageBytes(0);
});
};
diff --git a/src/components/PageComponents/Messages/MessageItem.tsx b/src/components/PageComponents/Messages/MessageItem.tsx
index ca5a7bba..360f64ea 100644
--- a/src/components/PageComponents/Messages/MessageItem.tsx
+++ b/src/components/PageComponents/Messages/MessageItem.tsx
@@ -11,7 +11,7 @@ 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.ts";
+import { Message, MessageState } from "../../../core/stores/messageStore/index.ts";
import { Protobuf } from "@meshtastic/js";
import { MessageActionsMenu } from "@components/PageComponents/Messages/MessageActionsMenu.tsx";
@@ -25,12 +25,13 @@ interface MessageStatus {
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" },
- [MessageState.Waiting]: { state: MessageState.Waiting, displayText: "Waiting for delivery", icon: CircleEllipsis, ariaLabel: "Sending message" },
- [MessageState.Failed]: { state: MessageState.Failed, displayText: "Delivery failed", icon: AlertCircle, ariaLabel: "Message delivery failed" },
+ [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 getMessageStatus = (state: MessageState): MessageStatus =>
@@ -40,7 +41,7 @@ const StatusTooltip = ({ status, children }: { status: MessageStatus; children:
{children}
-
+
{status.displayText}
@@ -48,9 +49,9 @@ const StatusTooltip = ({ status, children }: { status: MessageStatus; children:
);
-const StatusIcon = ({ status, className }: { status: MessageStatus; className?: string }) => {
+const StatusIcon = ({ status }: { status: MessageStatus }) => {
const Icon = status.icon;
- const iconClass = cn("size-4 shrink-0", className);
+ const iconClass = cn("size-4 shrink-0", status.iconClassName);
return (
@@ -107,14 +108,11 @@ export const MessageItem = ({ message }: MessageProps) => {
"transition-colors duration-100 ease-in-out",
);
- // const avatarSizeClass = "size-11";
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";
- const statusIconBaseColor = "text-slate-400 dark:text-slate-400";
- const statusIconFailedColor = "text-red-500 dark:text-red-400";
return (
@@ -128,10 +126,7 @@ export const MessageItem = ({ message }: MessageProps) => {
{displayName}
-
+
) : null}
diff --git a/src/components/PageLayout.tsx b/src/components/PageLayout.tsx
index 5799f8c1..f1279eb1 100644
--- a/src/components/PageLayout.tsx
+++ b/src/components/PageLayout.tsx
@@ -48,7 +48,7 @@ export const PageLayout = ({
{leftBar && (
)}
- {/* Center column (Header + Main) */}
{/* Header */}
{/* Header Content */}
-
+
{label}
@@ -82,13 +81,15 @@ export const PageLayout = ({
aria-disabled={action.disabled}
aria-busy={action.isLoading}
>
- {action.isLoading ? (
-
- ) : (
-
- )}
+
+ {action.isLoading ? (
+
+ ) : (
+
+ )}
+
))}
@@ -112,7 +113,7 @@ export const PageLayout = ({
{rightBar && (