Browse Source

Persist message history to local storage

pull/346/head
Grant Colegate 2 years ago
parent
commit
6691c9ae10
  1. 56
      src/core/stores/deviceStore.ts
  2. 101
      src/core/stores/messageStore.ts
  3. 33
      src/pages/Messages.tsx

56
src/core/stores/deviceStore.ts

@ -3,6 +3,8 @@ import { createContext, useContext } from "react";
import { produce } from "immer";
import { create } from "zustand";
import MessageStore from "@core/stores/messageStore.ts";
import { Protobuf, Types } from "@meshtastic/js";
export type Page = "messages" | "map" | "config" | "channels" | "nodes";
@ -38,10 +40,11 @@ export interface Device {
hardware: Protobuf.Mesh.MyNodeInfo;
nodes: Map<number, Protobuf.Mesh.NodeInfo>;
metadata: Map<number, Protobuf.Mesh.DeviceMetadata>;
messages: {
direct: Map<number, MessageWithState[]>;
broadcast: Map<Types.ChannelNumber, MessageWithState[]>;
};
messageStores: {
direct: Map<number, MessageStore>;
broadcast: Map<Types.ChannelNumber, MessageStore>;
semaphore: number;
}
traceroutes: Map<
number,
Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[]
@ -125,9 +128,10 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
hardware: new Protobuf.Mesh.MyNodeInfo(),
nodes: new Map(),
metadata: new Map(),
messages: {
messageStores: {
direct: new Map(),
broadcast: new Map(),
semaphore: 0,
},
traceroutes: new Map(),
connection: undefined,
@ -392,6 +396,9 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
return;
}
device.channels.set(channel.index, channel);
const messageStoreGroup = device.messageStores.broadcast;
messageStoreGroup.set(channel.index, new MessageStore(device.hardware.myNodeNum, channel.index))
}),
);
},
@ -421,6 +428,9 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
return;
}
device.nodes.set(nodeInfo.num, nodeInfo);
const messageStoreGroup = device.messageStores.direct;
messageStoreGroup.set(nodeInfo.num, new MessageStore(device.hardware.myNodeNum, nodeInfo.num))
}),
);
},
@ -480,21 +490,19 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
if (!device) {
return;
}
const messageGroup = device.messages[message.type];
const messageIndex =
const messageStoreGroup = device.messageStores[message.type];
const messageStoreIndex =
message.type === "direct"
? message.from === device.hardware.myNodeNum
? message.to
: message.from
: message.channel;
const messages = messageGroup.get(messageIndex);
const messageStore = messageStoreGroup.get(messageStoreIndex);
if (messages) {
messages.push(message);
messageGroup.set(messageIndex, messages);
} else {
messageGroup.set(messageIndex, [message]);
}
messageStore.addMessage(message);
device.messageStores.semaphore++;
}),
);
},
@ -557,30 +565,24 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
console.log("no device found for id");
return;
}
const messageGroup = device.messages[type];
const messageIndex =
const messageStoreGroup = device.messageStores[type];
const messageStoreIndex =
type === "direct"
? from === device.hardware.myNodeNum
? to
: from
: channelIndex;
const messages = messageGroup.get(messageIndex);
const messageStore = messageStoreGroup.get(messageStoreIndex);
if (!messages) {
if (!messageStore) {
console.log("no messages found for id");
return;
}
messageGroup.set(
messageIndex,
messages.map((msg) => {
if (msg.id === messageId) {
msg.state = state;
}
return msg;
}),
);
messageStore.setMessageState(messageId, state);
device.messageStores.semaphore++;
}),
);
},

101
src/core/stores/messageStore.ts

@ -0,0 +1,101 @@
import { Protobuf, Types } from "@meshtastic/js";
export interface MessageWithState extends Types.PacketMetadata<string> {
state: MessageState;
}
export type MessageState = "ack" | "waiting" | Protobuf.Mesh.Routing_Error;
export default class MessageStore {
constructor(deviceNumber: number, channelNumber: number) {
this.deviceNumber = deviceNumber;
this.channelNumber = channelNumber;
this.indexKey = `device/${this.deviceNumber}/group/${this.channelNumber}/index`;
this.messages = [];
this.loadMessages();
}
get currentIndex(): number {
return parseInt(localStorage.getItem(this.indexKey)) || 0;
}
messageKey(messageIndex: number): string {
return `device/${this.deviceNumber}/group/${this.channelNumber}/message/${messageIndex}`;
}
nextIndex(): number {
const nextIndex = this.currentIndex + 1;
localStorage.setItem(this.indexKey, nextIndex);
return nextIndex;
}
addMessage(message: MessageWithState): void {
const messageIndex = this.nextIndex();
const messageKey = this.messageKey(messageIndex);
localStorage.setItem(messageKey, JSON.stringify(message));
this.messages.push(message);
}
getMessage(messageIndex: number): MessageWithState {
const messageKey = this.messageKey(messageIndex);
const messageJSON = localStorage.getItem(messageKey);
if (messageJSON === null) {
return;
}
const message = JSON.parse(messageJSON);
message.rxTime = new Date(message.rxTime);
return message;
}
setMessageState(messageId: number, state: MessageState): void {
this.messages.forEach((message, i) => {
if (message.id === messageId) {
const messageKey = this.messageKey(i + 1);
message.state = state;
localStorage.setItem(messageKey, JSON.stringify(message));
return;
}
});
}
deleteMessage(messageIndex: number): void {
const messageKey = this.messageKey(messageIndex);
localStorage.removeItem(messageKey);
}
loadMessages(): MessageWithState[] {
if (this.currentIndex === 0) {
return;
}
for (let i = 1; i <= this.currentIndex; i++) {
this.messages.push(this.getMessage(i));
}
}
clear() {
if (this.currentIndex === 0) {
return;
}
for (let i = 1; i <= this.currentIndex; i++) {
this.deleteMessage(i);
}
localStorage.removeItem(this.indexKey);
this.messages = [];
}
}

33
src/pages/Messages.tsx

@ -9,11 +9,11 @@ import { Hashicon } from "@emeraldpay/hashicon-react";
import { Protobuf, Types } from "@meshtastic/js";
import { numberToHexUnpadded } from "@noble/curves/abstract/utils";
import { getChannelName } from "@pages/Channels.tsx";
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon } from "lucide-react";
import { HashIcon, LockIcon, LockOpenIcon, WaypointsIcon, TrashIcon } from "lucide-react";
import { useState } from "react";
export const MessagesPage = (): JSX.Element => {
const { channels, nodes, hardware, messages, traceroutes, connection } =
const { channels, nodes, hardware, messageStores, traceroutes, connection } =
useDevice();
const [chatType, setChatType] =
useState<Types.PacketDestination>("broadcast");
@ -114,8 +114,31 @@ export const MessagesPage = (): JSX.Element => {
);
},
},
{
icon: TrashIcon,
onClick() {
const targetNode = nodes.get(activeChat)?.num;
if (targetNode === undefined) return;
messageStores.direct.get(targetNode).clear()
toast({
title: "Message history cleared.",
});
}
},
]
: [
{
icon: TrashIcon,
onClick() {
const targetChannel = channels.get(activeChat)?.index;
if (targetChannel === undefined) return;
messageStores.broadcast.get(targetChannel).clear()
toast({
title: "Message history cleared.",
});
}
},
]
: []
}
>
{allChannels.map(
@ -124,7 +147,7 @@ export const MessagesPage = (): JSX.Element => {
<ChannelChat
key={channel.index}
to="broadcast"
messages={messages.broadcast.get(channel.index)}
messages={messageStores.broadcast.get(channel.index).messages}
channel={channel.index}
/>
),
@ -135,7 +158,7 @@ export const MessagesPage = (): JSX.Element => {
<ChannelChat
key={node.num}
to={activeChat}
messages={messages.direct.get(node.num)}
messages={messageStores.direct.get(node.num).messages}
channel={Types.ChannelNumber.Primary}
traceroutes={traceroutes.get(node.num)}
/>

Loading…
Cancel
Save