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

2
src/components/PageLayout.tsx

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

8
src/core/stores/deviceStore.ts

@ -43,7 +43,7 @@ export interface Device {
messageStores: {
direct: Map<number, MessageStore>;
broadcast: Map<Types.ChannelNumber, MessageStore>;
semaphore: number;
semaphore: boolean;
}
traceroutes: Map<
number,
@ -131,7 +131,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
messageStores: {
direct: new Map(),
broadcast: new Map(),
semaphore: 0,
semaphore: false,
},
traceroutes: new Map(),
connection: undefined,
@ -502,7 +502,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
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);
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) {
this.deviceNumber = deviceNumber;
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.loadMessageIds();
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;
messageKey(messageId: number): string {
return `device/${this.deviceNumber}/message/${messageId}`;
}
addMessage(message: MessageWithState): void {
const messageIndex = this.nextIndex();
const messageKey = this.messageKey(messageIndex);
this.setMessage(message);
localStorage.setItem(messageKey, JSON.stringify(message));
this.messageIds.unshift(message.id);
localStorage.setItem(this.messageIdsKey, JSON.stringify(this.messageIds));
this.messages.push(message);
}
getMessage(messageIndex: number): MessageWithState {
const messageKey = this.messageKey(messageIndex);
getMessage(messageId: number): MessageWithState {
const messageKey = this.messageKey(messageId);
const messageJSON = localStorage.getItem(messageKey);
if (messageJSON === null) {
return;
return null;
}
const message = JSON.parse(messageJSON);
@ -57,45 +48,70 @@ export default class MessageStore {
return message;
}
setMessage(message: MessageWithState): void {
const messageKey = this.messageKey(message.id);
localStorage.setItem(messageKey, JSON.stringify(message));
}
setMessageState(messageId: number, state: MessageState): void {
this.messages.forEach((message, i) => {
if (message.id === messageId) {
const messageKey = this.messageKey(i + 1);
const message = this.getMessage(messageId);
message.state = state;
message.state = state;
localStorage.setItem(messageKey, JSON.stringify(message));
this.setMessage(message);
this.messages.forEach((m, i) => {
if (m.id === messageId) {
this.messages[i] = message
return;
}
});
}
deleteMessage(messageIndex: number): void {
const messageKey = this.messageKey(messageIndex);
deleteMessage(messageId: number): void {
const messageKey = this.messageKey(messageId);
localStorage.removeItem(messageKey);
}
loadMessages(): MessageWithState[] {
if (this.currentIndex === 0) {
loadMessageIds(): void {
const messageIdsJSON = localStorage.getItem(this.messageIdsKey);
if (messageIdsJSON === null) {
this.messageIds = [];
localStorage.setItem(this.messageIdsKey, JSON.stringify(this.messageIds));
return;
}
for (let i = 1; i <= this.currentIndex; i++) {
this.messages.push(this.getMessage(i));
}
this.messageIds = JSON.parse(messageIdsJSON);
}
clear() {
if (this.currentIndex === 0) {
return;
}
loadMessages(): void {
if (this.loaded) return;
for (let i = 1; i <= this.currentIndex; i++) {
this.deleteMessage(i);
}
let message;
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 = [];
}
}

19
src/pages/Messages.tsx

@ -32,6 +32,20 @@ export const MessagesPage = (): JSX.Element => {
const node = nodes.get(activeChat);
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 (
<>
<Sidebar>
@ -72,6 +86,7 @@ export const MessagesPage = (): JSX.Element => {
</Sidebar>
<div className="flex flex-col flex-grow">
<PageLayout
onScroll={handleScroll}
label={`Messages: ${
chatType === "broadcast" && currentChannel
? getChannelName(currentChannel)
@ -147,7 +162,7 @@ export const MessagesPage = (): JSX.Element => {
<ChannelChat
key={channel.index}
to="broadcast"
messages={messageStores.broadcast.get(channel.index).messages}
messageStore={messageStores.broadcast.get(channel.index)}
channel={channel.index}
/>
),
@ -158,7 +173,7 @@ export const MessagesPage = (): JSX.Element => {
<ChannelChat
key={node.num}
to={activeChat}
messages={messageStores.direct.get(node.num).messages}
messageStore={messageStores.direct.get(node.num)}
channel={Types.ChannelNumber.Primary}
traceroutes={traceroutes.get(node.num)}
/>

Loading…
Cancel
Save