Browse Source

Load messages on scroll

pull/346/head
Grant Colegate 2 years ago
parent
commit
3fe0333723
  1. 12
      src/components/PageComponents/Messages/ChannelChat.tsx
  2. 2
      src/components/PageLayout.tsx
  3. 8
      src/core/stores/deviceStore.ts
  4. 100
      src/core/stores/messageStore.ts
  5. 19
      src/pages/Messages.tsx

12
src/components/PageComponents/Messages/ChannelChat.tsx

@ -1,28 +1,28 @@
import { Subtle } from "@app/components/UI/Typography/Subtle.tsx"; import { Subtle } from "@app/components/UI/Typography/Subtle.tsx";
import { import { useDevice } from "@app/core/stores/deviceStore.ts";
type MessageWithState, import { MessageStore } from "@app/core/stores/messageStore.ts";
useDevice,
} from "@app/core/stores/deviceStore.ts";
import { Message } from "@components/PageComponents/Messages/Message.tsx"; import { Message } from "@components/PageComponents/Messages/Message.tsx";
import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx"; import { MessageInput } from "@components/PageComponents/Messages/MessageInput.tsx";
import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx"; import { TraceRoute } from "@components/PageComponents/Messages/TraceRoute.tsx";
import type { Protobuf, Types } from "@meshtastic/js"; import type { Protobuf, Types } from "@meshtastic/js";
import { InboxIcon } from "lucide-react"; import { InboxIcon } from "lucide-react";
import { useState, useEffect, useRef } from 'react';
export interface ChannelChatProps { export interface ChannelChatProps {
messages?: MessageWithState[]; messageStore: MessageStore;
channel: Types.ChannelNumber; channel: Types.ChannelNumber;
to: Types.Destination; to: Types.Destination;
traceroutes?: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[]; traceroutes?: Types.PacketMetadata<Protobuf.Mesh.RouteDiscovery>[];
} }
export const ChannelChat = ({ export const ChannelChat = ({
messages, messageStore,
channel, channel,
to, to,
traceroutes, traceroutes,
}: ChannelChatProps): JSX.Element => { }: ChannelChatProps): JSX.Element => {
const { nodes } = useDevice(); const { nodes } = useDevice();
const messages = messageStore.messages;
return ( return (
<div className="flex flex-grow flex-col"> <div className="flex flex-grow flex-col">

2
src/components/PageLayout.tsx

@ -18,6 +18,7 @@ export const PageLayout = ({
noPadding, noPadding,
actions, actions,
children, children,
onScroll,
}: PageLayoutProps): JSX.Element => { }: PageLayoutProps): JSX.Element => {
return ( return (
<> <>
@ -52,6 +53,7 @@ export const PageLayout = ({
"flex h-full w-full flex-col overflow-y-auto", "flex h-full w-full flex-col overflow-y-auto",
!noPadding && "pl-3 pr-3 ", !noPadding && "pl-3 pr-3 ",
)} )}
onScroll={onScroll}
> >
{children} {children}
<Footer /> <Footer />

8
src/core/stores/deviceStore.ts

@ -43,7 +43,7 @@ export interface Device {
messageStores: { messageStores: {
direct: Map<number, MessageStore>; direct: Map<number, MessageStore>;
broadcast: Map<Types.ChannelNumber, MessageStore>; broadcast: Map<Types.ChannelNumber, MessageStore>;
semaphore: number; semaphore: boolean;
} }
traceroutes: Map< traceroutes: Map<
number, number,
@ -131,7 +131,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
messageStores: { messageStores: {
direct: new Map(), direct: new Map(),
broadcast: new Map(), broadcast: new Map(),
semaphore: 0, semaphore: false,
}, },
traceroutes: new Map(), traceroutes: new Map(),
connection: undefined, connection: undefined,
@ -502,7 +502,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
messageStore.addMessage(message); messageStore.addMessage(message);
device.messageStores.semaphore++; device.messageStores.semaphore = !device.messageStores.semaphore;
}), }),
); );
}, },
@ -582,7 +582,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
messageStore.setMessageState(messageId, state); messageStore.setMessageState(messageId, state);
device.messageStores.semaphore++; device.messageStores.semaphore = !device.messageStores.semaphore;
}), }),
); );
}, },

100
src/core/stores/messageStore.ts

@ -10,44 +10,35 @@ export default class MessageStore {
constructor(deviceNumber: number, channelNumber: number) { constructor(deviceNumber: number, channelNumber: number) {
this.deviceNumber = deviceNumber; this.deviceNumber = deviceNumber;
this.channelNumber = channelNumber; this.channelNumber = channelNumber;
this.indexKey = `device/${this.deviceNumber}/group/${this.channelNumber}/index`; this.messageIdsKey = `device/${this.deviceNumber}/group/${this.channelNumber}/message_ids`;
this.cursor = 0;
this.loaded = false;
this.messageIds = [];
this.messages = []; this.messages = [];
this.loadMessageIds();
this.loadMessages(); this.loadMessages();
} }
get currentIndex(): number { messageKey(messageId: number): string {
return parseInt(localStorage.getItem(this.indexKey)) || 0; return `device/${this.deviceNumber}/message/${messageId}`;
}
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 { addMessage(message: MessageWithState): void {
const messageIndex = this.nextIndex(); this.setMessage(message);
const messageKey = this.messageKey(messageIndex);
localStorage.setItem(messageKey, JSON.stringify(message)); this.messageIds.unshift(message.id);
localStorage.setItem(this.messageIdsKey, JSON.stringify(this.messageIds));
this.messages.push(message); this.messages.push(message);
} }
getMessage(messageIndex: number): MessageWithState { getMessage(messageId: number): MessageWithState {
const messageKey = this.messageKey(messageIndex); const messageKey = this.messageKey(messageId);
const messageJSON = localStorage.getItem(messageKey); const messageJSON = localStorage.getItem(messageKey);
if (messageJSON === null) { if (messageJSON === null) {
return; return null;
} }
const message = JSON.parse(messageJSON); const message = JSON.parse(messageJSON);
@ -57,45 +48,70 @@ export default class MessageStore {
return message; return message;
} }
setMessage(message: MessageWithState): void {
const messageKey = this.messageKey(message.id);
localStorage.setItem(messageKey, JSON.stringify(message));
}
setMessageState(messageId: number, state: MessageState): void { setMessageState(messageId: number, state: MessageState): void {
this.messages.forEach((message, i) => { const message = this.getMessage(messageId);
if (message.id === messageId) {
const messageKey = this.messageKey(i + 1); message.state = state;
message.state = state; this.setMessage(message);
localStorage.setItem(messageKey, JSON.stringify(message));
this.messages.forEach((m, i) => {
if (m.id === messageId) {
this.messages[i] = message
return; return;
} }
}); });
} }
deleteMessage(messageIndex: number): void { deleteMessage(messageId: number): void {
const messageKey = this.messageKey(messageIndex); const messageKey = this.messageKey(messageId);
localStorage.removeItem(messageKey); localStorage.removeItem(messageKey);
} }
loadMessages(): MessageWithState[] { loadMessageIds(): void {
if (this.currentIndex === 0) { const messageIdsJSON = localStorage.getItem(this.messageIdsKey);
if (messageIdsJSON === null) {
this.messageIds = [];
localStorage.setItem(this.messageIdsKey, JSON.stringify(this.messageIds));
return; return;
} }
for (let i = 1; i <= this.currentIndex; i++) { this.messageIds = JSON.parse(messageIdsJSON);
this.messages.push(this.getMessage(i));
}
} }
clear() { loadMessages(): void {
if (this.currentIndex === 0) { if (this.loaded) return;
return;
}
for (let i = 1; i <= this.currentIndex; i++) { let message;
this.deleteMessage(i);
} for (let i = this.cursor; i <= 50 + this.cursor; i++) {
message = this.getMessage(this.messageIds[i]);
if (message === null) {
this.loaded = true;
return;
}
this.cursor = i;
this.messages.unshift(message);
};
}
clear() {
this.messageIds.forEach((messageId) => {
this.deleteMessage(messageId);
});
localStorage.removeItem(this.indexKey); localStorage.removeItem(this.messageIdsKey);
this.messageIds = [];
this.messages = []; this.messages = [];
} }
} }

19
src/pages/Messages.tsx

@ -32,6 +32,20 @@ export const MessagesPage = (): JSX.Element => {
const node = nodes.get(activeChat); const node = nodes.get(activeChat);
const nodeHex = node?.num ? numberToHexUnpadded(node.num) : "Unknown"; const nodeHex = node?.num ? numberToHexUnpadded(node.num) : "Unknown";
const [isLoading, setIsLoading] = useState(false);
const handleScroll = (event) => {
if (!isLoading && activeChat && event.currentTarget.scrollTop < 10) {
setIsLoading(true);
const messageStore = messageStores[chatType].get(activeChat);
messageStore.loadMessages();
setIsLoading(false);
}
};
return ( return (
<> <>
<Sidebar> <Sidebar>
@ -72,6 +86,7 @@ export const MessagesPage = (): JSX.Element => {
</Sidebar> </Sidebar>
<div className="flex flex-col flex-grow"> <div className="flex flex-col flex-grow">
<PageLayout <PageLayout
onScroll={handleScroll}
label={`Messages: ${ label={`Messages: ${
chatType === "broadcast" && currentChannel chatType === "broadcast" && currentChannel
? getChannelName(currentChannel) ? getChannelName(currentChannel)
@ -147,7 +162,7 @@ export const MessagesPage = (): JSX.Element => {
<ChannelChat <ChannelChat
key={channel.index} key={channel.index}
to="broadcast" to="broadcast"
messages={messageStores.broadcast.get(channel.index).messages} messageStore={messageStores.broadcast.get(channel.index)}
channel={channel.index} channel={channel.index}
/> />
), ),
@ -158,7 +173,7 @@ export const MessagesPage = (): JSX.Element => {
<ChannelChat <ChannelChat
key={node.num} key={node.num}
to={activeChat} to={activeChat}
messages={messageStores.direct.get(node.num).messages} messageStore={messageStores.direct.get(node.num)}
channel={Types.ChannelNumber.Primary} channel={Types.ChannelNumber.Primary}
traceroutes={traceroutes.get(node.num)} traceroutes={traceroutes.get(node.num)}
/> />

Loading…
Cancel
Save