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}

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 && (