diff --git a/src/app/auth/Login.tsx b/src/app/auth/Login.tsx index b5a1f18..b5419d9 100644 --- a/src/app/auth/Login.tsx +++ b/src/app/auth/Login.tsx @@ -33,8 +33,7 @@ export default function Login() { message: "Login successful! Redirecting...", }); - // small delay to show feedback before redirect - setTimeout(() => router.push("/chat"), 800); + router.push("/chat"); } catch (err: any) { setFeedback({ kind: "error", diff --git a/src/app/chat/[id]/page.tsx b/src/app/chat/[id]/page.tsx index 84d1259..e42dafe 100644 --- a/src/app/chat/[id]/page.tsx +++ b/src/app/chat/[id]/page.tsx @@ -22,13 +22,17 @@ export default function DMs() { auth({ id: target.node_address, wsRef: targetNode, - addMessage: app.addMessage, - deleteMessage: app.deleteMessage, - editMessage: app.editMessage, + messageStore: app.privateMessages, }); } connectTargetNode(); + + app.setCurrentChannel({ + id: id, + name: target?.display_name || id, + kind: "text", + }); }, [target]); return loading || target ? ( @@ -37,16 +41,21 @@ export default function DMs() { app={app} channelName={target?.display_name || id} channelIcon={target?.avatar_url || "_"} - messages={app.messages.filter( + messages={app.privateMessages.messages.filter( (m) => (m.channel_id === id && m.from === app.profile?.id) || (m.from === id && m.channel_id === app.profile?.id) )} sendRequest={(req) => { + console.log("Sending request: ", req); switch (req.type) { case "send_message": targetNode.current?.send(JSON.stringify(req)); break; + case "load_chunk": + targetNode.current?.send(JSON.stringify(req)); + app.node.current?.send(JSON.stringify(req)); + break; default: app.node.current?.send(JSON.stringify(req)); break; diff --git a/src/app/chat/page.tsx b/src/app/chat/page.tsx index f6a639b..38d0569 100644 --- a/src/app/chat/page.tsx +++ b/src/app/chat/page.tsx @@ -52,7 +52,7 @@ export default function ChatHub() { channels: [], }, ]; - const recentActivity = app.messages.slice(0, 3); + const recentActivity = app.privateMessages.messages.slice(0, 3); return ( diff --git a/src/app/server/[ip]/page.tsx b/src/app/server/[ip]/page.tsx index e10152d..fba3c96 100644 --- a/src/app/server/[ip]/page.tsx +++ b/src/app/server/[ip]/page.tsx @@ -6,7 +6,7 @@ import MessageBox from "@/components/channel/MessageBox"; import AppLayout from "@/components/app/AppLayout"; import { Server as ServerType } from "@/types/types"; import auth from "@/lib/auth"; -import { useServerMessages } from "@/hooks/use-messages"; +import useMessages from "@/hooks/use-messages"; import useApp from "@/hooks/use-app"; import { useEffectOnceWhenReady } from "@/hooks/use-once"; import ChannelView from "@/components/channel/ChannelView"; @@ -16,25 +16,19 @@ export default function Server() { const searchParams = useSearchParams(); const serverRef = useRef(null); const app = useApp(); - const { messages, addMessage, editMessage, deleteMessage } = - useServerMessages(); + const messageStore = useMessages(); useEffectOnceWhenReady( () => { if (!ip) return; - const msgs = messages[ip]; auth({ id: ip, wsRef: serverRef, setServer: app.setServer, - lastMessage: msgs ? msgs[msgs.length - 1]?.id : undefined, - - addMessage: (m) => addMessage(ip, m), - editMessage: (msg_id, contents) => editMessage(ip, msg_id, contents), - deleteMessage: (msg_id) => deleteMessage(ip, msg_id), + messageStore: messageStore, }); }, - [ip, messages], + [ip, messageStore.messages], [undefined, (v) => v] ); @@ -55,7 +49,9 @@ export default function Server() { m.channel_id === app.currentChannel?.id + )} ip={ip} serverRef={serverRef} /> diff --git a/src/components/app/AppLayout.tsx b/src/components/app/AppLayout.tsx index 4a2d628..77de524 100644 --- a/src/components/app/AppLayout.tsx +++ b/src/components/app/AppLayout.tsx @@ -50,11 +50,7 @@ export default function AppLayout({ if (onNewMessage) onNewMessage(m); }, - lastMessage: app.messages?.[app.messages.length - 1]?.id, - - addMessage: app.addMessage, - deleteMessage: app.deleteMessage, - editMessage: app.editMessage, + messageStore: app.privateMessages, }); app.setProfiles((prev) => { diff --git a/src/components/app/SidebarDMs.tsx b/src/components/app/SidebarDMs.tsx index 148fe8e..ecee3d3 100644 --- a/src/components/app/SidebarDMs.tsx +++ b/src/components/app/SidebarDMs.tsx @@ -24,7 +24,7 @@ export default function SidebarDMs({ app }: { app: App }) { await Promise.all( [ ...new Set( - app.messages + app.privateMessages.messages .sort((a, b) => a.timestamp - b.timestamp) .flatMap((m) => [m.from, m.channel_id]) .filter((id) => id !== app.profile?.id) @@ -44,7 +44,7 @@ export default function SidebarDMs({ app }: { app: App }) { ); }; load(); - }, [query, app.messages, app.profile]); + }, [query, app.privateMessages.messages, app.profile]); return (
diff --git a/src/components/channel/ChannelView.tsx b/src/components/channel/ChannelView.tsx index 68d563c..ba3cc18 100644 --- a/src/components/channel/ChannelView.tsx +++ b/src/components/channel/ChannelView.tsx @@ -11,7 +11,7 @@ export default function ChannelView({ serverRef, }: { app: App; - messages: Record; + messages: Message[]; ip: string; serverRef: React.RefObject; }) { @@ -25,11 +25,7 @@ export default function ChannelView({ m.channel_id === app.currentChannel?.id - ) || [] - } + messages={messages} sendRequest={(req) => serverRef.current?.send(JSON.stringify(req))} /> ); diff --git a/src/components/channel/MessageBox.tsx b/src/components/channel/MessageBox.tsx index 1ccbb59..7ade402 100644 --- a/src/components/channel/MessageBox.tsx +++ b/src/components/channel/MessageBox.tsx @@ -286,6 +286,37 @@ export default function MessageBox({ }) { const [text, setText] = useState(""); + const ref = useRef(null); + const chunkRef = useRef(0); + + useEffect(() => { + if (!ref.current) return; + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + console.log("Element is in view!"); + if (!app.currentChannel) return; + sendRequest({ + type: "load_chunk", + params: { + channel_id: app.currentChannel.id, + chunk_id: chunkRef.current, + }, + }); + chunkRef.current += 1; + } + }, + { + threshold: 0.5, + } + ); + + observer.observe(ref.current); + + return () => observer.disconnect(); + }, []); + const sendMessage = () => { if (!app.currentChannel) { return; @@ -346,6 +377,7 @@ export default function MessageBox({ /> ))} +
@@ -370,12 +402,7 @@ export default function MessageBox({ -
diff --git a/src/components/channel/PluginChannel.tsx b/src/components/channel/PluginChannel.tsx index 62bb1b8..a1fead5 100644 --- a/src/components/channel/PluginChannel.tsx +++ b/src/components/channel/PluginChannel.tsx @@ -10,7 +10,7 @@ export default function PluginChannel({ serverRef, }: { app: App; - messages: Record; + messages: Message[]; ip: string; serverRef: React.RefObject; }) { diff --git a/src/hooks/use-app.ts b/src/hooks/use-app.ts index 840cafd..141ada0 100644 --- a/src/hooks/use-app.ts +++ b/src/hooks/use-app.ts @@ -1,6 +1,6 @@ import App from "@/types/app"; import { useEffect, useRef, useState } from "react"; -import { useMessages } from "./use-messages"; +import useMessages from "./use-messages"; import { StringMap } from "@/types/typeUtils"; import useUser, { UserProfile } from "./get-user"; import { useClientSettings } from "./use-settings"; @@ -12,7 +12,6 @@ import { toast } from "sonner"; export default function useApp(): App { const node = useRef(null); const [profiles, setProfiles] = useState>({}); - const { messages, addMessage, deleteMessage, editMessage } = useMessages(); const [sidebarOpen, setSidebarOpen] = useState(false); const profile = useUser(); const [clientSettings, setClientSettings] = useClientSettings(); @@ -20,6 +19,7 @@ export default function useApp(): App { const [servers, setServers] = useState>({}); const [currentChannel, setCurrentChannel] = useState(null); const [server, setServer] = useState(); + const privateMessages = useMessages(); useEffect(() => { const stored = Cookies.get("servers"); @@ -38,8 +38,7 @@ export default function useApp(): App { }, []); return { - editMessage, - deleteMessage, + privateMessages, currentChannel, setCurrentChannel, server, @@ -54,8 +53,6 @@ export default function useApp(): App { profile, profiles, setProfiles, - messages, - addMessage, sidebarOpen, setSidebarOpen, getUserById: async (id: string) => { @@ -63,7 +60,6 @@ export default function useApp(): App { const onResponse = (res: Response) => { setProfiles((prev) => { - console.log("test"); prev[id] = res.data as UserProfile; return prev; }); diff --git a/src/hooks/use-messages.ts b/src/hooks/use-messages.ts index 7cf0712..883d84a 100644 --- a/src/hooks/use-messages.ts +++ b/src/hooks/use-messages.ts @@ -1,108 +1,32 @@ import { Message } from "@/types/types"; -import { create } from "zustand"; -import { persist } from "zustand/middleware"; +import { useState } from "react"; -interface MessageStore { +export interface MessageStore { messages: Message[]; setMessages: (msgs: Message[]) => void; addMessage: (msg: Message) => void; deleteMessage: (id: number) => void; editMessage: (id: number, contents: string) => void; clearMessages: () => void; + insertMessages: (msgs: Message[]) => void; } +export default function useMessages(): MessageStore { + const [messages, setMessages] = useState([]); -type ServerMessageStore = { - messages: Record; // server_id → messages - setMessages: (serverId: string, msgs: Message[]) => void; - addMessage: (serverId: string, msg: Message) => void; - deleteMessage: (serverId: string, id: number) => void; - editMessage: (serverId: string, id: number, contents: string) => void; - clearMessages: (serverId?: string) => void; -}; - -export const useMessages = create()( - persist( - (set) => ({ - messages: [], - setMessages: (msgs) => set({ messages: msgs }), - addMessage: (msg) => - set((state) => - state.messages.some((m) => m.id === msg.id) - ? state - : { messages: [...state.messages, msg] } - ), - deleteMessage: (id: number) => - set((state) => ({ - messages: state.messages.filter((m) => m.id !== id), - })), - editMessage: (id: number, contents) => - set((state) => ({ - messages: state.messages.map((m) => - m.id === id ? { ...m, contents } : m - ), - })), - clearMessages: () => set({ messages: [] }), - }), - { name: "chat-messages" } - ) -); - -export const useServerMessages = create()( - persist( - (set) => ({ - messages: {}, - - setMessages: (serverId, msgs) => - set((state) => ({ - messages: { ...state.messages, [serverId]: msgs }, - })), - - addMessage: (serverId, msg) => - set((state) => { - const serverMsgs = state.messages[serverId] || []; - - if (serverMsgs.some((m) => m.id === msg.id)) return state; - - return { - messages: { - ...state.messages, - [serverId]: [...serverMsgs, msg], - }, - }; - }), - - deleteMessage: (serverId, id) => - set((state) => ({ - messages: { - ...state.messages, - [serverId]: (state.messages[serverId] || []).filter( - (m) => m.id !== id - ), - }, - })), - - editMessage: (serverId, id, contents) => - set((state) => ({ - messages: { - ...state.messages, - [serverId]: (state.messages[serverId] || []).map((m) => - m.id === id ? { ...m, contents } : m - ), - }, - })), - - clearMessages: (serverId) => - set((state) => - serverId - ? { - messages: { - ...state.messages, - [serverId]: [], - }, - } - : { messages: {} } - ), - }), - { name: "server-messages" } - ) -); + return { + messages: messages, + setMessages: (msgs) => setMessages(msgs), + addMessage: (msg) => + setMessages((messages) => + messages.some((m) => m.id === msg.id) ? messages : [...messages, msg] + ), + deleteMessage: (id: number) => + setMessages((messages) => messages.filter((m) => m.id !== id)), + editMessage: (id: number, contents) => + setMessages((messages) => + messages.map((m) => (m.id === id ? { ...m, contents } : m)) + ), + clearMessages: () => setMessages([]), + insertMessages: (msgs) => setMessages((m) => [...msgs, ...m]), + }; +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 1dd0bc8..df7c3a4 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -3,28 +3,22 @@ import axios from "axios"; import { Dispatch, RefObject, SetStateAction } from "react"; import { get } from "./request"; import { toast } from "sonner"; +import { MessageStore } from "@/hooks/use-messages"; type AuthParams = { id: string; wsRef: RefObject; setServer?: Dispatch>; onNewMessage?: (m: Message) => void; - lastMessage?: number; - - addMessage: (m: Message) => void; - deleteMessage: (id: number) => void; - editMessage: (id: number, contents: string) => void; + messageStore: MessageStore; }; export default async function auth({ id, wsRef, setServer, - addMessage, onNewMessage, - lastMessage, - deleteMessage, - editMessage, + messageStore, }: AuthParams) { console.log("Authenticating with server id:", id); @@ -64,7 +58,6 @@ export default async function auth({ JSON.stringify({ version: "0.0.1", auth_token: server_auth, - last_message: lastMessage || 0, }) ); name = data.name; @@ -73,20 +66,31 @@ export default async function auth({ switch (data.type) { case "authenticated": - data.params.messages.forEach(addMessage); + // ws.send( + // JSON.stringify({ + // type: "load_chunk", + // params: { + // channel_id: "general", + // chunk_id: 0, + // }, + // }) + // ); break; case "message_create": if (onNewMessage) onNewMessage(data.params as Message); - addMessage(data.params as Message); + messageStore.addMessage(data.params as Message); break; case "message_delete": - deleteMessage(data.params.message_id); + messageStore.deleteMessage(data.params.message_id); break; case "message_update": - editMessage(data.params.message_id, data.params.contents); + messageStore.editMessage(data.params.message_id, data.params.contents); + break; + case "chunk": + messageStore.insertMessages(data.params); break; } }; diff --git a/src/types/app.ts b/src/types/app.ts index 87784da..fd685bd 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -3,6 +3,7 @@ import { StringMap } from "./typeUtils"; import { UserProfile } from "@/hooks/get-user"; import { Channel, Message, Server } from "./types"; import { ClientSettings } from "./settings"; +import { MessageStore } from "@/hooks/use-messages"; export type SetState = Dispatch>; @@ -22,10 +23,7 @@ export default interface App { profiles: StringMap; setProfiles: SetState>; - messages: Message[]; - addMessage: (v: Message) => void; - deleteMessage: (id: number) => void; - editMessage: (id: number, v: string) => void; + privateMessages: MessageStore; sidebarOpen: boolean; setSidebarOpen: SetState; diff --git a/src/types/protocol.ts b/src/types/protocol.ts index fd8cffe..d40a05f 100644 --- a/src/types/protocol.ts +++ b/src/types/protocol.ts @@ -6,4 +6,5 @@ export interface MessageType { export type ClientMessage = | MessageType<"send_message", { channel_id: string; contents: string }> | MessageType<"delete_message", { message_id: number }> - | MessageType<"edit_message", { message_id: number; new_contents: string }>; + | MessageType<"edit_message", { message_id: number; new_contents: string }> + | MessageType<"load_chunk", { channel_id: string; chunk_id: number }>;