Browse Source

fix: easy linting errors & auto formatting

Remove unused
Add linting ignore on true parameter
Autoformatting
pull/603/head
Jeremy Gallant 1 year ago
parent
commit
d8130f8a4e
  1. 14
      src/components/PageComponents/Config/Security/Security.tsx
  2. 37
      src/components/PageComponents/Connect/HTTP.tsx
  3. 2
      src/components/ThemeSwitcher.tsx
  4. 91
      src/core/stores/messageStore/index.ts
  5. 17
      src/core/subscriptions.ts
  6. 244
      src/pages/Messages.tsx

14
src/components/PageComponents/Config/Security/Security.tsx

@ -1,16 +1,12 @@
import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx"; import { PkiRegenerateDialog } from "@components/Dialog/PkiRegenerateDialog.tsx";
import { DynamicForm } from "@components/Form/DynamicForm.tsx"; import { DynamicForm } from "@components/Form/DynamicForm.tsx";
import { useAppStore } from "@core/stores/appStore.ts"; import { useAppStore } from "@core/stores/appStore.ts";
import { import { getX25519PrivateKey, getX25519PublicKey } from "@core/utils/x25519.ts";
getX25519PrivateKey,
getX25519PublicKey,
} from "@core/utils/x25519.ts";
import type { SecurityValidation } from "@app/validation/config/security.ts"; import type { SecurityValidation } from "@app/validation/config/security.ts";
import { create } from "@bufbuild/protobuf"; import { create } from "@bufbuild/protobuf";
import { useDevice } from "@core/stores/deviceStore.ts"; import { useDevice } from "@core/stores/deviceStore.ts";
import { Protobuf } from "@meshtastic/core"; import { Protobuf } from "@meshtastic/core";
import { fromByteArray, toByteArray } from "base64-js"; import { fromByteArray, toByteArray } from "base64-js";
import { Eye, EyeOff } from "lucide-react";
import { useReducer } from "react"; import { useReducer } from "react";
import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx"; import { securityReducer } from "@components/PageComponents/Config/Security/securityReducer.tsx";
@ -58,7 +54,8 @@ export const Security = () => {
if (input.length % 4 !== 0) { if (input.length % 4 !== 0) {
addError( addError(
fieldName, fieldName,
`${fieldName === "privateKey" ? "Private" : "Admin" `${
fieldName === "privateKey" ? "Private" : "Admin"
} Key is required to be a 256 bit pre-shared key (PSK)`, } Key is required to be a 256 bit pre-shared key (PSK)`,
); );
return; return;
@ -73,7 +70,8 @@ export const Security = () => {
console.error(e); console.error(e);
addError( addError(
fieldName, fieldName,
`Invalid ${fieldName === "privateKey" ? "Private" : "Admin" `Invalid ${
fieldName === "privateKey" ? "Private" : "Admin"
} Key format`, } Key format`,
); );
} }
@ -242,7 +240,7 @@ export const Security = () => {
? getErrorMessage("adminKey") ? getErrorMessage("adminKey")
: "", : "",
inputChange: adminKeyInputChangeEvent, inputChange: adminKeyInputChangeEvent,
selectChange: () => { }, selectChange: () => {},
bits: [{ text: "256 bit", value: "32", key: "bit256" }], bits: [{ text: "256 bit", value: "32", key: "bit256" }],
devicePSKBitCount: state.privateKeyBitCount, devicePSKBitCount: state.privateKeyBitCount,
hide: !state.adminKeyVisible, hide: !state.adminKeyVisible,

37
src/components/PageComponents/Connect/HTTP.tsx

@ -11,7 +11,7 @@ import { randId } from "@core/utils/randId.ts";
import { MeshDevice } from "@meshtastic/core"; import { MeshDevice } from "@meshtastic/core";
import { TransportHTTP } from "@meshtastic/transport-http"; import { TransportHTTP } from "@meshtastic/transport-http";
import { useState } from "react"; import { useState } from "react";
import { useForm, useController } from "react-hook-form"; import { useController, useForm } from "react-hook-form";
import { AlertTriangle } from "lucide-react"; import { AlertTriangle } from "lucide-react";
import { useMessageStore } from "../../../core/stores/messageStore/index.ts"; import { useMessageStore } from "../../../core/stores/messageStore/index.ts";
@ -20,7 +20,10 @@ interface FormData {
tls: boolean; tls: boolean;
} }
export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgress }: TabElementProps) => { export const HTTP = (
{ closeDialog, setConnectionInProgress, connectionInProgress }:
TabElementProps,
) => {
const isURLHTTPS = location.protocol === "https:"; const isURLHTTPS = location.protocol === "https:";
const { addDevice } = useDeviceStore(); const { addDevice } = useDeviceStore();
@ -30,8 +33,8 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
const { control, handleSubmit, register } = useForm<FormData>({ const { control, handleSubmit, register } = useForm<FormData>({
defaultValues: { defaultValues: {
ip: ["client.meshtastic.org", "localhost"].includes( ip: ["client.meshtastic.org", "localhost"].includes(
globalThis.location.hostname, globalThis.location.hostname,
) )
? "meshtastic.local" ? "meshtastic.local"
: globalThis.location.host, : globalThis.location.host,
tls: isURLHTTPS ? true : false, tls: isURLHTTPS ? true : false,
@ -42,7 +45,9 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
field: { value: tlsValue, onChange: setTLS }, field: { value: tlsValue, onChange: setTLS },
} = useController({ name: "tls", control }); } = useController({ name: "tls", control });
const [connectionError, setConnectionError] = useState<{ host: string; secure: boolean } | null>(null); const [connectionError, setConnectionError] = useState<
{ host: string; secure: boolean } | null
>(null);
const onSubmit = handleSubmit(async (data) => { const onSubmit = handleSubmit(async (data) => {
setConnectionInProgress(true); setConnectionInProgress(true);
@ -91,21 +96,31 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
{connectionError && ( {connectionError && (
<div className="mt-2 mb-2 p-3 rounded-md bg-amber-100 border border-amber-300 dark:bg-amber-100 dark:border-amber-300"> <div className="mt-2 mb-2 p-3 rounded-md bg-amber-100 border border-amber-300 dark:bg-amber-100 dark:border-amber-300">
<div className="flex gap-2 items-start"> <div className="flex gap-2 items-start">
<AlertTriangle className="shrink-0 mt-0.5 text-amber-600 dark:text-amber-600" size={20} /> <AlertTriangle
className="shrink-0 mt-0.5 text-amber-600 dark:text-amber-600"
size={20}
/>
<div> <div>
<p className="text-sm font-medium text-amber-800 dark:text-amber-800"> <p className="text-sm font-medium text-amber-800 dark:text-amber-800">
Connection Failed Connection Failed
</p> </p>
<p className="text-xs mt-1 text-amber-700 dark:text-amber-700"> <p className="text-xs mt-1 text-amber-700 dark:text-amber-700">
Could not connect to the device. {connectionError.secure && "If using HTTPS, you may need to accept a self-signed certificate first. "} Could not connect to the device. {connectionError.secure &&
"If using HTTPS, you may need to accept a self-signed certificate first. "}
Please open{" "} Please open{" "}
<Link <Link
href={`${connectionError.secure ? "https" : "http"}://${connectionError.host}`} href={`${
connectionError.secure ? "https" : "http"
}://${connectionError.host}`}
className="underline font-medium text-amber-800 dark:text-amber-800" className="underline font-medium text-amber-800 dark:text-amber-800"
> >
{`${connectionError.secure ? "https" : "http"}://${connectionError.host}`} {`${
connectionError.secure ? "https" : "http"
}://${connectionError.host}`}
</Link>{" "} </Link>{" "}
in a new tab{connectionError.secure ? ", accept any TLS warnings if prompted, then try again" : ""}.{" "} in a new tab{connectionError.secure
? ", accept any TLS warnings if prompted, then try again"
: ""}.{" "}
<Link <Link
href="https://meshtastic.org/docs/software/web-client/#http" href="https://meshtastic.org/docs/software/web-client/#http"
className="underline font-medium text-amber-800 dark:text-amber-800" className="underline font-medium text-amber-800 dark:text-amber-800"
@ -120,7 +135,7 @@ export const HTTP = ({ closeDialog, setConnectionInProgress, connectionInProgres
</div> </div>
<Button <Button
type="submit" type="submit"
variant={"default"} variant="default"
> >
<span>{connectionInProgress ? "Connecting..." : "Connect"}</span> <span>{connectionInProgress ? "Connecting..." : "Connect"}</span>
</Button> </Button>

2
src/components/ThemeSwitcher.tsx

@ -9,7 +9,7 @@ export default function ThemeSwitcher({
}: { }: {
className?: string; className?: string;
}) { }) {
const { theme, preference, setPreference } = useTheme(); const { preference, setPreference } = useTheme();
const themeIcons = { const themeIcons = {
light: <Sun className="size-6" />, light: <Sun className="size-6" />,

91
src/core/stores/messageStore/index.ts

@ -1,9 +1,19 @@
import { create } from 'zustand'; import { create } from "zustand";
import { persist } from 'zustand/middleware'; // import { persist } from 'zustand/middleware';
import { produce } from 'immer'; import { produce } from "immer";
import { Types } from '@meshtastic/core'; import { Types } from "@meshtastic/core";
import { storageWithMapSupport } from "../storage/indexDB.ts"; // import { storageWithMapSupport } from "../storage/indexDB.ts";
import { ChannelId, ClearMessageParams, ConversationId, GetMessagesParams, Message, MessageId, MessageLogMap, NodeNum, SetMessageStateParams } from "@core/stores/messageStore/types.ts"; import {
ChannelId,
ClearMessageParams,
ConversationId,
GetMessagesParams,
Message,
MessageId,
MessageLogMap,
NodeNum,
SetMessageStateParams,
} from "@core/stores/messageStore/types.ts";
export enum MessageState { export enum MessageState {
Ack = "ack", Ack = "ack",
@ -16,8 +26,11 @@ export enum MessageType {
Broadcast = "broadcast", Broadcast = "broadcast",
} }
export function getConversationId(node1: NodeNum, node2: NodeNum): ConversationId { export function getConversationId(
return [node1, node2].sort((a, b) => a - b).join(':'); node1: NodeNum,
node2: NodeNum,
): ConversationId {
return [node1, node2].sort((a, b) => a - b).join(":");
} }
export interface MessageStore { export interface MessageStore {
@ -25,9 +38,9 @@ export interface MessageStore {
direct: Map<ConversationId, MessageLogMap>; direct: Map<ConversationId, MessageLogMap>;
broadcast: Map<ChannelId, MessageLogMap>; broadcast: Map<ChannelId, MessageLogMap>;
}; };
}; }
export interface MessageStore { export interface MessageStore {
messages: MessageStore['messages']; messages: MessageStore["messages"];
draft: Map<Types.Destination, string>; draft: Map<Types.Destination, string>;
nodeNum: number; // This device's node number nodeNum: number; // This device's node number
activeChat: number; // Represents otherNodeNum for Direct, or channel for Broadcast activeChat: number; // Represents otherNodeNum for Direct, or channel for Broadcast
@ -47,7 +60,7 @@ export interface MessageStore {
clearDraft: (key: Types.Destination) => void; clearDraft: (key: Types.Destination) => void;
} }
const CURRENT_STORE_VERSION = 0; // const CURRENT_STORE_VERSION = 0;
export const useMessageStore = create<MessageStore>()( export const useMessageStore = create<MessageStore>()(
// persist( // persist(
@ -82,17 +95,29 @@ export const useMessageStore = create<MessageStore>()(
if (message.type === MessageType.Direct) { if (message.type === MessageType.Direct) {
const conversationId = getConversationId(message.from, message.to); const conversationId = getConversationId(message.from, message.to);
if (!state.messages.direct.has(conversationId)) { if (!state.messages.direct.has(conversationId)) {
state.messages.direct.set(conversationId, new Map<MessageId, Message>()); state.messages.direct.set(
conversationId,
new Map<MessageId, Message>(),
);
} }
state.messages.direct.get(conversationId)!.set(message.messageId, message); state.messages.direct.get(conversationId)!.set(
message.messageId,
message,
);
} else if (message.type === MessageType.Broadcast) { } else if (message.type === MessageType.Broadcast) {
const channelId = message.channel as ChannelId; const channelId = message.channel as ChannelId;
if (!state.messages.broadcast.has(channelId)) { if (!state.messages.broadcast.has(channelId)) {
state.messages.broadcast.set(channelId, new Map<MessageId, Message>()); state.messages.broadcast.set(
channelId,
new Map<MessageId, Message>(),
);
} }
state.messages.broadcast.get(channelId)!.set(message.messageId, message); state.messages.broadcast.get(channelId)!.set(
message.messageId,
message,
);
} }
}) }),
); );
}, },
@ -103,7 +128,10 @@ export const useMessageStore = create<MessageStore>()(
let targetMessage: Message | undefined; let targetMessage: Message | undefined;
if (params.type === MessageType.Direct) { if (params.type === MessageType.Direct) {
const conversationId = getConversationId(params.nodeA, params.nodeB); const conversationId = getConversationId(
params.nodeA,
params.nodeB,
);
messageLog = state.messages.direct.get(conversationId); messageLog = state.messages.direct.get(conversationId);
if (messageLog) { if (messageLog) {
targetMessage = messageLog.get(params.messageId); targetMessage = messageLog.get(params.messageId);
@ -118,9 +146,13 @@ export const useMessageStore = create<MessageStore>()(
if (targetMessage) { if (targetMessage) {
targetMessage.state = params.newState ?? MessageState.Ack; targetMessage.state = params.newState ?? MessageState.Ack;
} else { } else {
console.warn(`Message or conversation/channel not found for state update. Params: ${JSON.stringify(params)}`); console.warn(
`Message or conversation/channel not found for state update. Params: ${
JSON.stringify(params)
}`,
);
} }
}) }),
); );
}, },
getMessages: (params: GetMessagesParams): Message[] => { getMessages: (params: GetMessagesParams): Message[] => {
@ -130,7 +162,6 @@ export const useMessageStore = create<MessageStore>()(
if (params.type === MessageType.Direct) { if (params.type === MessageType.Direct) {
const conversationId = getConversationId(params.nodeA, params.nodeB); const conversationId = getConversationId(params.nodeA, params.nodeB);
messageMap = state.messages.direct.get(conversationId); messageMap = state.messages.direct.get(conversationId);
} else { } else {
messageMap = state.messages.broadcast.get(params.channelId); messageMap = state.messages.broadcast.get(params.channelId);
} }
@ -165,23 +196,29 @@ export const useMessageStore = create<MessageStore>()(
const deleted = messageLog.delete(params.messageId); const deleted = messageLog.delete(params.messageId);
if (deleted) { if (deleted) {
console.log(`Deleted message ${params.messageId} from ${params.type} message ${parentKey}`); console.log(
`Deleted message ${params.messageId} from ${params.type} message ${parentKey}`,
);
// Clean up empty MessageLogMap and its entry in the parent map // Clean up empty MessageLogMap and its entry in the parent map
if (messageLog.size === 0) { if (messageLog.size === 0) {
parentMap.delete(parentKey); parentMap.delete(parentKey);
console.log(`Cleaned up empty message entry for ${parentKey}`); console.log(`Cleaned up empty message entry for ${parentKey}`);
} }
} else { } else {
console.warn(`Message ${params.messageId} not found in ${params.type} chat ${parentKey} for deletion.`); console.warn(
`Message ${params.messageId} not found in ${params.type} chat ${parentKey} for deletion.`,
);
} }
} else { } else {
console.warn(`Message entry ${parentKey} not found for message deletion.`); console.warn(
`Message entry ${parentKey} not found for message deletion.`,
);
} }
}) }),
); );
}, },
getDraft: (key) => { getDraft: (key) => {
return get().draft.get(key) ?? ''; return get().draft.get(key) ?? "";
}, },
setDraft: (key, message) => { setDraft: (key, message) => {
set(produce((state: MessageStore) => { set(produce((state: MessageStore) => {
@ -198,7 +235,7 @@ export const useMessageStore = create<MessageStore>()(
state.messages.direct = new Map<ConversationId, MessageLogMap>(); state.messages.direct = new Map<ConversationId, MessageLogMap>();
state.messages.broadcast = new Map<ChannelId, MessageLogMap>(); state.messages.broadcast = new Map<ChannelId, MessageLogMap>();
})); }));
} },
}), }),
// { // {
// name: 'meshtastic-message-store', // name: 'meshtastic-message-store',
@ -209,4 +246,4 @@ export const useMessageStore = create<MessageStore>()(
// nodeNum: state.nodeNum, // nodeNum: state.nodeNum,
// }), // }),
// }) // })
) );

17
src/core/subscriptions.ts

@ -1,13 +1,13 @@
import type { Device } from "@core/stores/deviceStore.ts"; import type { Device } from "@core/stores/deviceStore.ts";
import { MeshDevice, Protobuf } from "@meshtastic/core"; import { MeshDevice, Protobuf } from "@meshtastic/core";
import { MessageState, MessageType, type MessageStore } from "./stores/messageStore/index.ts"; import { type MessageStore, MessageType } from "./stores/messageStore/index.ts";
import PacketToMessageDTO from "@core/dto/PacketToMessageDTO.ts"; import PacketToMessageDTO from "@core/dto/PacketToMessageDTO.ts";
import NodeInfoFactory from "@core/dto/NodeNumToNodeInfoDTO.ts"; import NodeInfoFactory from "@core/dto/NodeNumToNodeInfoDTO.ts";
export const subscribeAll = ( export const subscribeAll = (
device: Device, device: Device,
connection: MeshDevice, connection: MeshDevice,
messageStore: MessageStore messageStore: MessageStore,
) => { ) => {
let myNodeNum = 0; let myNodeNum = 0;
@ -79,7 +79,6 @@ export const subscribeAll = (
device.setModuleConfig(moduleConfig); device.setModuleConfig(moduleConfig);
}); });
connection.events.onMessagePacket.subscribe((messagePacket) => { connection.events.onMessagePacket.subscribe((messagePacket) => {
// incoming and outgoing messages are handled by this event listener // incoming and outgoing messages are handled by this event listener
console.log("Message Packet", messagePacket); console.log("Message Packet", messagePacket);
@ -117,7 +116,6 @@ export const subscribeAll = (
}); });
}); });
connection.events.onRoutingPacket.subscribe((routingPacket) => { connection.events.onRoutingPacket.subscribe((routingPacket) => {
if (routingPacket.data.variant.case === "errorReason") { if (routingPacket.data.variant.case === "errorReason") {
switch (routingPacket.data.variant.value) { switch (routingPacket.data.variant.value) {
@ -126,19 +124,24 @@ export const subscribeAll = (
break; break;
case Protobuf.Mesh.Routing_Error.NO_CHANNEL: case Protobuf.Mesh.Routing_Error.NO_CHANNEL:
console.error(`Routing Error: ${routingPacket.data.variant.value}`); console.error(`Routing Error: ${routingPacket.data.variant.value}`);
device.setNodeError(routingPacket.from, Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value]); device.setNodeError(
routingPacket.from,
Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value],
);
device.setDialogOpen("refreshKeys", true); device.setDialogOpen("refreshKeys", true);
break; break;
case Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY: case Protobuf.Mesh.Routing_Error.PKI_UNKNOWN_PUBKEY:
console.error(`Routing Error: ${routingPacket.data.variant.value}`); console.error(`Routing Error: ${routingPacket.data.variant.value}`);
device.setNodeError(routingPacket.from, Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value]); device.setNodeError(
routingPacket.from,
Protobuf.Mesh.Routing_Error[routingPacket?.data?.variant?.value],
);
device.setDialogOpen("refreshKeys", true); device.setDialogOpen("refreshKeys", true);
break; break;
default: { default: {
break; break;
} }
} }
} }
}); });
}; };

244
src/pages/Messages.tsx

@ -12,34 +12,55 @@ import { HashIcon, LockIcon, LockOpenIcon } from "lucide-react";
import { useCallback, useDeferredValue, useMemo, useState } from "react"; import { useCallback, useDeferredValue, useMemo, useState } from "react";
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx";
import { cn } from "@core/utils/cn.ts"; import { cn } from "@core/utils/cn.ts";
import { MessageState, MessageType, useMessageStore } from "@core/stores/messageStore/index.ts"; import {
MessageState,
MessageType,
useMessageStore,
} from "@core/stores/messageStore/index.ts";
import { useSidebar } from "@core/stores/sidebarStore.tsx"; import { useSidebar } from "@core/stores/sidebarStore.tsx";
import { Input } from "@components/UI/Input.tsx"; import { Input } from "@components/UI/Input.tsx";
type NodeInfoWithUnread = Protobuf.Mesh.NodeInfo & { unreadCount: number }; type NodeInfoWithUnread = Protobuf.Mesh.NodeInfo & { unreadCount: number };
export const MessagesPage = () => { export const MessagesPage = () => {
const { channels, getNodes, getNode, hasNodeError, unreadCounts, resetUnread, connection } = useDevice(); const {
const { getMyNodeNum, getMessages, setActiveChat, chatType, activeChat, setChatType, setMessageState, } = useMessageStore() channels,
getNodes,
getNode,
hasNodeError,
unreadCounts,
resetUnread,
connection,
} = useDevice();
const {
getMyNodeNum,
getMessages,
setActiveChat,
chatType,
activeChat,
setChatType,
setMessageState,
} = useMessageStore();
const { toast } = useToast(); const { toast } = useToast();
const { isCollapsed } = useSidebar() const { isCollapsed } = useSidebar();
const [searchTerm, setSearchTerm] = useState<string>(""); const [searchTerm, setSearchTerm] = useState<string>("");
const deferredSearch = useDeferredValue(searchTerm); const deferredSearch = useDeferredValue(searchTerm);
const filteredNodes = (): NodeInfoWithUnread[] => { const filteredNodes = (): NodeInfoWithUnread[] => {
const lowerCaseSearchTerm = deferredSearch.toLowerCase(); const lowerCaseSearchTerm = deferredSearch.toLowerCase();
return getNodes(node => { return getNodes((node) => {
const longName = node.user?.longName?.toLowerCase() ?? ''; const longName = node.user?.longName?.toLowerCase() ?? "";
const shortName = node.user?.shortName?.toLowerCase() ?? ''; const shortName = node.user?.shortName?.toLowerCase() ?? "";
return longName.includes(lowerCaseSearchTerm) || shortName.includes(lowerCaseSearchTerm) return longName.includes(lowerCaseSearchTerm) ||
shortName.includes(lowerCaseSearchTerm);
}) })
.map((node) => ({ .map((node) => ({
...node, ...node,
unreadCount: unreadCounts.get(node.num) ?? 0, unreadCount: unreadCounts.get(node.num) ?? 0,
})) }))
.sort((a, b) => b.unreadCount - a.unreadCount); .sort((a, b) => b.unreadCount - a.unreadCount);
} };
const allChannels = Array.from(channels.values()); const allChannels = Array.from(channels.values());
const filteredChannels = allChannels.filter( const filteredChannels = allChannels.filter(
@ -55,19 +76,39 @@ export const MessagesPage = () => {
const isDirect = chatType === MessageType.Direct; const isDirect = chatType === MessageType.Direct;
const toValue = isDirect ? activeChat : MessageType.Broadcast; const toValue = isDirect ? activeChat : MessageType.Broadcast;
const channelValue = isDirect ? Types.ChannelNumber.Primary : activeChat ?? 0; const channelValue = isDirect
? Types.ChannelNumber.Primary
: activeChat ?? 0;
console.log(`Sending message: "${message}" to: ${toValue}, channel: ${channelValue}, type: ${chatType}`); console.log(
`Sending message: "${message}" to: ${toValue}, channel: ${channelValue}, type: ${chatType}`,
);
let messageId: number | undefined; let messageId: number | undefined;
try { try {
messageId = await connection?.sendText(message, toValue, true, channelValue); messageId = await connection?.sendText(
message,
toValue,
true,
channelValue,
);
if (messageId !== undefined) { if (messageId !== undefined) {
if (chatType === MessageType.Broadcast) { if (chatType === MessageType.Broadcast) {
setMessageState({ type: chatType, channelId: channelValue, messageId, newState: MessageState.Ack }); setMessageState({
type: chatType,
channelId: channelValue,
messageId,
newState: MessageState.Ack,
});
} else { } else {
setMessageState({ type: chatType, nodeA: getMyNodeNum(), nodeB: activeChat, messageId, newState: MessageState.Ack }); setMessageState({
type: chatType,
nodeA: getMyNodeNum(),
nodeB: activeChat,
messageId,
newState: MessageState.Ack,
});
} }
} else { } else {
console.warn("sendText completed but messageId is undefined"); console.warn("sendText completed but messageId is undefined");
@ -78,10 +119,21 @@ export const MessagesPage = () => {
// Note: messageId might be undefined here if the error occurred before it was assigned // Note: messageId might be undefined here if the error occurred before it was assigned
if (chatType === MessageType.Broadcast) { if (chatType === MessageType.Broadcast) {
const failedId = messageId ?? `failed-${Date.now()}`; const failedId = messageId ?? `failed-${Date.now()}`;
setMessageState({ type: chatType, channelId: channelValue, messageId: failedId, newState: MessageState.Failed }); setMessageState({
type: chatType,
channelId: channelValue,
messageId: failedId,
newState: MessageState.Failed,
});
} else { // MessageType.Direct } else { // MessageType.Direct
const failedId = messageId ?? `failed-${Date.now()}`; const failedId = messageId ?? `failed-${Date.now()}`;
setMessageState({ type: chatType, nodeA: getMyNodeNum(), nodeB: activeChat, messageId: failedId, newState: MessageState.Failed }); setMessageState({
type: chatType,
nodeA: getMyNodeNum(),
nodeB: activeChat,
messageId: failedId,
newState: MessageState.Failed,
});
} }
} }
}, [activeChat, chatType, connection, getMyNodeNum, setMessageState]); }, [activeChat, chatType, connection, getMyNodeNum, setMessageState]);
@ -100,7 +152,11 @@ export const MessagesPage = () => {
case MessageType.Direct: case MessageType.Direct:
return ( return (
<ChannelChat <ChannelChat
messages={getMessages({ type: MessageType.Direct, nodeA: getMyNodeNum(), nodeB: activeChat })} messages={getMessages({
type: MessageType.Direct,
nodeA: getMyNodeNum(),
nodeB: activeChat,
})}
/> />
); );
default: default:
@ -110,7 +166,7 @@ export const MessagesPage = () => {
</div> </div>
); );
} }
} };
const leftSidebar = useMemo(() => ( const leftSidebar = useMemo(() => (
<Sidebar> <Sidebar>
@ -119,72 +175,108 @@ export const MessagesPage = () => {
<SidebarButton <SidebarButton
key={channel.index} key={channel.index}
count={unreadCounts.get(channel.index)} count={unreadCounts.get(channel.index)}
label={channel.settings?.name || (channel.index === 0 ? "Primary" : `Ch ${channel.index}`)} label={channel.settings?.name ||
active={activeChat === channel.index && chatType === MessageType.Broadcast} (channel.index === 0 ? "Primary" : `Ch ${channel.index}`)}
active={activeChat === channel.index &&
chatType === MessageType.Broadcast}
onClick={() => { onClick={() => {
setChatType(MessageType.Broadcast); setChatType(MessageType.Broadcast);
setActiveChat(channel.index); setActiveChat(channel.index);
resetUnread(channel.index); resetUnread(channel.index);
}} }}
> >
<HashIcon size={16} className={cn(isCollapsed ? "mr-0 mt-2" : "mr-2")} /> <HashIcon
size={16}
className={cn(isCollapsed ? "mr-0 mt-2" : "mr-2")}
/>
</SidebarButton> </SidebarButton>
))} ))}
</SidebarSection> </SidebarSection>
</Sidebar> </Sidebar>
), [filteredChannels, unreadCounts, activeChat, chatType, isCollapsed, setActiveChat, setChatType, resetUnread]); ), [
filteredChannels,
const rightSidebar = useMemo(() => ( unreadCounts,
<SidebarSection label="" className="px-0 flex flex-col h-full overflow-y-auto"> activeChat,
<label className="p-2 block"> chatType,
<Input isCollapsed,
type="text" setActiveChat,
placeholder="Search nodes..." setChatType,
value={searchTerm} resetUnread,
onChange={(e) => setSearchTerm(e.target.value)} ]);
showClearButton={!!searchTerm}
/> const rightSidebar = useMemo(
</label> () => (
<div className={cn( <SidebarSection
"flex flex-col h-full flex-1 overflow-y-auto gap-2.5 pt-1 ", label=""
)}> className="px-0 flex flex-col h-full overflow-y-auto"
{filteredNodes()?.map((node) => ( >
<SidebarButton <label className="p-2 block">
key={node.num} <Input
preventCollapse={true} type="text"
label={node.user?.longName ?? `UNK`} placeholder="Search nodes..."
count={node.unreadCount > 0 ? node.unreadCount : undefined} value={searchTerm}
active={activeChat === node.num && chatType === MessageType.Direct} onChange={(e) => setSearchTerm(e.target.value)}
onClick={() => { showClearButton={!!searchTerm}
setChatType(MessageType.Direct); />
setActiveChat(node.num); </label>
resetUnread(node.num); <div
}}> className={cn(
<Avatar "flex flex-col h-full flex-1 overflow-y-auto gap-2.5 pt-1 ",
text={node.user?.shortName ?? "UNK"} )}
className={cn(hasNodeError(node.num) && "text-red-500")} >
showError={hasNodeError(node.num)} {filteredNodes()?.map((node) => (
size="sm" <SidebarButton
/> key={node.num}
</SidebarButton> // Default value for preventCollapse is false, hence ignore linting error:
))} // deno-lint-ignore jsx-boolean-value
</div> preventCollapse={true}
</SidebarSection> label={node.user?.longName ?? `UNK`}
), [filteredNodes, searchTerm, activeChat, chatType, setActiveChat, setChatType, resetUnread, hasNodeError]); count={node.unreadCount > 0 ? node.unreadCount : undefined}
active={activeChat === node.num &&
chatType === MessageType.Direct}
onClick={() => {
setChatType(MessageType.Direct);
setActiveChat(node.num);
resetUnread(node.num);
}}
>
<Avatar
text={node.user?.shortName ?? "UNK"}
className={cn(hasNodeError(node.num) && "text-red-500")}
showError={hasNodeError(node.num)}
size="sm"
/>
</SidebarButton>
))}
</div>
</SidebarSection>
),
[
filteredNodes,
searchTerm,
activeChat,
chatType,
setActiveChat,
setChatType,
resetUnread,
hasNodeError,
],
);
return ( return (
<PageLayout <PageLayout
label={`Messages: ${isBroadcast && currentChannel label={`Messages: ${
? getChannelName(currentChannel) isBroadcast && currentChannel
: isDirect && otherNode ? getChannelName(currentChannel)
: isDirect && otherNode
? (otherNode.user?.longName ?? "Unknown") ? (otherNode.user?.longName ?? "Unknown")
: "Select a Chat" : "Select a Chat"
}`} }`}
rightBar={rightSidebar} rightBar={rightSidebar}
leftBar={leftSidebar} leftBar={leftSidebar}
actions={isDirect && otherNode actions={isDirect && otherNode
? [ ? [
{ {
key: 'encryption', key: "encryption",
icon: otherNode.user?.publicKey?.length ? LockIcon : LockOpenIcon, icon: otherNode.user?.publicKey?.length ? LockIcon : LockOpenIcon,
iconClasses: otherNode.user?.publicKey?.length iconClasses: otherNode.user?.publicKey?.length
? "text-green-600" ? "text-green-600"
@ -204,15 +296,19 @@ export const MessagesPage = () => {
{renderChatContent()} {renderChatContent()}
<div className="flex-none dark:bg-slate-900 p-2"> <div className="flex-none dark:bg-slate-900 p-2">
{(isBroadcast || isDirect) ? ( {(isBroadcast || isDirect)
<MessageInput ? (
to={isDirect ? activeChat : MessageType.Broadcast} <MessageInput
onSend={sendText} to={isDirect ? activeChat : MessageType.Broadcast}
maxBytes={200} onSend={sendText}
/> maxBytes={200}
) : ( />
<div className="p-4 text-center text-slate-400 italic">Select a chat to send a message.</div> )
)} : (
<div className="p-4 text-center text-slate-400 italic">
Select a chat to send a message.
</div>
)}
</div> </div>
</div> </div>
</PageLayout> </PageLayout>

Loading…
Cancel
Save