Updated structure
This commit is contained in:
+30
-27
@@ -4,65 +4,68 @@ import MessageBox from "@/components/channel/MessageBox";
|
||||
import AppLayout from "@/components/app/AppLayout";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import useApp from "@/hooks/use-app";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { NodeClient } from "@/lib/node";
|
||||
import { useEffect } from "react";
|
||||
import useAsync from "@/hooks/use-async";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import useIndicators from "@/hooks/use-indicators";
|
||||
import { useEffectOnceWhenReady } from "@/hooks/use-once";
|
||||
import { ClientMessage } from "@/types/protocol";
|
||||
import { getNode, useNullNode } from "@/hooks/use-node";
|
||||
import { useEffectOnceWhenReady } from "@/hooks/use-once";
|
||||
|
||||
export default function DMs() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const app = useApp();
|
||||
const targetNode = useRef<WebSocket | null>(null);
|
||||
const { value: target, loading } = useAsync(() => app.getUserById(id));
|
||||
const router = useRouter();
|
||||
const { indicators, addIndicator } = useIndicators();
|
||||
|
||||
const sendRequest = async (req: ClientMessage) => {
|
||||
console.log("Sending request: ", req);
|
||||
await waitForOpen(targetNode.current);
|
||||
await waitForOpen(app.node.current);
|
||||
console.log("Sending request:", req);
|
||||
|
||||
switch (req.type) {
|
||||
case "send_message":
|
||||
targetNode.current?.send(JSON.stringify(req));
|
||||
targetNode.current?.socket?.send(JSON.stringify(req));
|
||||
if (target?.node_address !== app.profile?.node_address)
|
||||
app.node.current?.socket?.send(JSON.stringify(req));
|
||||
break;
|
||||
case "load_chunk":
|
||||
targetNode.current?.send(JSON.stringify(req));
|
||||
app.node.current?.send(JSON.stringify(req));
|
||||
targetNode.current?.socket?.send(JSON.stringify(req));
|
||||
if (target?.node_address !== app.profile?.node_address)
|
||||
app.node.current?.socket?.send(JSON.stringify(req));
|
||||
break;
|
||||
default:
|
||||
app.node.current?.send(JSON.stringify(req));
|
||||
app.node.current?.socket?.send(JSON.stringify(req));
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const targetNode = useNullNode();
|
||||
|
||||
useEffectOnceWhenReady(
|
||||
() => {
|
||||
async function connectTargetNode() {
|
||||
if (!target) return;
|
||||
await auth({
|
||||
id: target.node_address,
|
||||
wsRef: targetNode,
|
||||
messageStore: app.privateMessages,
|
||||
onIndicator: addIndicator,
|
||||
});
|
||||
}
|
||||
if (!target) return;
|
||||
|
||||
connectTargetNode().then(() => {
|
||||
app.setCurrentChannel({
|
||||
id: id,
|
||||
name: target?.display_name || id,
|
||||
kind: "text",
|
||||
});
|
||||
});
|
||||
return getNode(
|
||||
target?.node_address,
|
||||
app.privateMessages,
|
||||
{
|
||||
onIndicator: addIndicator,
|
||||
},
|
||||
targetNode
|
||||
);
|
||||
},
|
||||
[target],
|
||||
[(v) => v]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
app.setCurrentChannel({
|
||||
id: id,
|
||||
name: target?.display_name || id,
|
||||
kind: "text",
|
||||
});
|
||||
}, [targetNode]);
|
||||
|
||||
return loading || target ? (
|
||||
<AppLayout app={app}>
|
||||
<MessageBox
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import AppLayout from "@/components/app/AppLayout";
|
||||
import useMessages from "@/hooks/use-messages";
|
||||
import useApp from "@/hooks/use-app";
|
||||
import ChannelView from "@/components/channel/ChannelView";
|
||||
import useIndicators from "@/hooks/use-indicators";
|
||||
import useNode from "@/hooks/use-node";
|
||||
|
||||
export default function Server() {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const searchParams = useSearchParams();
|
||||
const app = useApp();
|
||||
const messageStore = useMessages();
|
||||
const { indicators, addIndicator } = useIndicators();
|
||||
|
||||
const serverNode = useNode(id, messageStore, {
|
||||
setServer: app.setServer,
|
||||
onIndicator: addIndicator,
|
||||
onVoiceJoin: (userId, channelId, voiceId) =>
|
||||
app.setVoiceConns((prev) => ({
|
||||
...prev,
|
||||
[channelId]: { ...prev[channelId], [userId]: voiceId },
|
||||
})),
|
||||
onVoiceLeave: (userId, channelId) =>
|
||||
app.setVoiceConns((prev) => {
|
||||
const { [userId]: _, ...remainingUsers } = prev[channelId] || {};
|
||||
return {
|
||||
...prev,
|
||||
[channelId]: remainingUsers,
|
||||
};
|
||||
}),
|
||||
onSpeaking: (voiceId) =>
|
||||
app.setSpeaking((speaking) => ({
|
||||
...speaking,
|
||||
[voiceId]: Date.now(),
|
||||
})),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const channelId = searchParams.get("ch");
|
||||
if (channelId) {
|
||||
app.setCurrentChannel(
|
||||
app.server?.channels.find((v) => v.id === channelId) ||
|
||||
app.server?.channels[0] ||
|
||||
null
|
||||
);
|
||||
} else {
|
||||
app.setCurrentChannel(app.server?.channels[0] || null);
|
||||
}
|
||||
}, [app.server]);
|
||||
|
||||
return (
|
||||
<AppLayout app={app}>
|
||||
<ChannelView
|
||||
app={app}
|
||||
node={serverNode}
|
||||
messages={messageStore.messages.filter(
|
||||
(m) => m.channel_id === app.currentChannel?.id
|
||||
)}
|
||||
ip={id}
|
||||
indicators={indicators}
|
||||
/>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useParams, useSearchParams } from "next/navigation";
|
||||
import AppLayout from "@/components/app/AppLayout";
|
||||
import auth from "@/lib/node";
|
||||
import useMessages from "@/hooks/use-messages";
|
||||
import useApp from "@/hooks/use-app";
|
||||
import { useEffectOnceWhenReady } from "@/hooks/use-once";
|
||||
import ChannelView from "@/components/channel/ChannelView";
|
||||
import { IndicatorContext } from "@/types/protocol";
|
||||
import useIndicators from "@/hooks/use-indicators";
|
||||
|
||||
export default function Server() {
|
||||
const { ip } = useParams<{ ip: string }>();
|
||||
const searchParams = useSearchParams();
|
||||
const serverRef = useRef<WebSocket | null>(null);
|
||||
const app = useApp();
|
||||
const messageStore = useMessages();
|
||||
const { indicators, addIndicator } = useIndicators();
|
||||
|
||||
useEffectOnceWhenReady(
|
||||
() => {
|
||||
if (!ip) return;
|
||||
auth({
|
||||
id: ip,
|
||||
wsRef: serverRef,
|
||||
setServer: app.setServer,
|
||||
messageStore: messageStore,
|
||||
onIndicator: addIndicator,
|
||||
onVoiceJoin: (userId, channelId, voiceId) =>
|
||||
app.setVoiceConns((prev) => ({
|
||||
...prev,
|
||||
[channelId]: { ...prev[channelId], [userId]: voiceId },
|
||||
})),
|
||||
onVoiceLeave: (userId, channelId) =>
|
||||
app.setVoiceConns((prev) => {
|
||||
const { [userId]: _, ...remainingUsers } = prev[channelId] || {};
|
||||
return {
|
||||
...prev,
|
||||
[channelId]: remainingUsers,
|
||||
};
|
||||
}),
|
||||
onSpeaking: (voiceId) =>
|
||||
app.setSpeaking((speaking) => ({
|
||||
...speaking,
|
||||
[voiceId]: Date.now(),
|
||||
})),
|
||||
});
|
||||
},
|
||||
[ip, messageStore.messages],
|
||||
[undefined, (v) => v]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const channelId = searchParams.get("ch");
|
||||
if (channelId) {
|
||||
app.setCurrentChannel(
|
||||
app.server?.channels.find((v) => v.id === channelId) ||
|
||||
app.server?.channels[0] ||
|
||||
null
|
||||
);
|
||||
} else {
|
||||
app.setCurrentChannel(app.server?.channels[0] || null);
|
||||
}
|
||||
}, [app.server]);
|
||||
|
||||
return (
|
||||
<AppLayout app={app}>
|
||||
<ChannelView
|
||||
app={app}
|
||||
messages={messageStore.messages.filter(
|
||||
(m) => m.channel_id === app.currentChannel?.id
|
||||
)}
|
||||
ip={ip}
|
||||
serverRef={serverRef}
|
||||
indicators={indicators}
|
||||
/>
|
||||
</AppLayout>
|
||||
);
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import React, { useEffect } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import auth from "@/lib/node";
|
||||
import { useIsMobile } from "@/hooks/is-mobile";
|
||||
import App from "@/types/app";
|
||||
import { Message } from "@/types/types";
|
||||
@@ -14,6 +13,7 @@ import SidebarDMs from "./SidebarDMs";
|
||||
import SidebarChannels from "./SidebarChannels";
|
||||
import DMItem from "./DMItem";
|
||||
import { useEffectOnceWhenReady } from "@/hooks/use-once";
|
||||
import { getNode, useNullNode } from "@/hooks/use-node";
|
||||
|
||||
export default function AppLayout({
|
||||
children,
|
||||
@@ -28,44 +28,46 @@ export default function AppLayout({
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const isMobile = useIsMobile();
|
||||
const node = useNullNode();
|
||||
|
||||
useEffectOnceWhenReady(
|
||||
() => {
|
||||
if (!app.profile?.node_address) return;
|
||||
|
||||
auth({
|
||||
id: app.profile.node_address,
|
||||
wsRef: app.node,
|
||||
onNewMessage: (m) => {
|
||||
if (m.from !== chatWith && m.from !== app.profile?.id)
|
||||
toast(
|
||||
`New message from ${
|
||||
app.profiles[m.from]?.display_name || m.from
|
||||
}`,
|
||||
{
|
||||
description: m.contents.slice(0, 80),
|
||||
action: {
|
||||
label: "View",
|
||||
onClick: () => router.push(`/chat/${m.from}`),
|
||||
},
|
||||
position: "top-right",
|
||||
}
|
||||
);
|
||||
return getNode(
|
||||
app.profile?.node_address,
|
||||
app.privateMessages,
|
||||
{
|
||||
onNewMessage: (m) => {
|
||||
if (m.from !== chatWith && m.from !== app.profile?.id)
|
||||
toast(
|
||||
`New message from ${
|
||||
app.profiles[m.from]?.display_name || m.from
|
||||
}`,
|
||||
{
|
||||
description: m.contents.slice(0, 80),
|
||||
action: {
|
||||
label: "View",
|
||||
onClick: () => router.push(`/chat/${m.from}`),
|
||||
},
|
||||
position: "top-right",
|
||||
}
|
||||
);
|
||||
|
||||
if (onNewMessage) onNewMessage(m);
|
||||
if (onNewMessage) onNewMessage(m);
|
||||
},
|
||||
},
|
||||
messageStore: app.privateMessages,
|
||||
});
|
||||
|
||||
app.setProfiles((prev) => {
|
||||
if (app.profile) prev[app.profile.id] = app.profile;
|
||||
return prev;
|
||||
});
|
||||
node
|
||||
);
|
||||
},
|
||||
[chatWith, app.profile],
|
||||
[undefined, undefined]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
app.node.current = node.current;
|
||||
}, [node]);
|
||||
|
||||
useEffect(() => app.setSidebarOpen(!isMobile), [isMobile]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,29 +1,36 @@
|
||||
import { Message } from "@/types/types";
|
||||
import MessageBox from "./MessageBox";
|
||||
import App from "@/types/app";
|
||||
import React from "react";
|
||||
import React, { RefObject } from "react";
|
||||
import PluginChannel from "./PluginChannel";
|
||||
import { IndicatorContext } from "@/types/protocol";
|
||||
import { ClientMessage, IndicatorContext } from "@/types/protocol";
|
||||
import VoiceBox from "./voice/VoiceBox";
|
||||
import { sendVoice } from "@/lib/audio";
|
||||
import { NodeClient } from "@/lib/node";
|
||||
|
||||
export default function ChannelView({
|
||||
app,
|
||||
messages,
|
||||
ip,
|
||||
serverRef,
|
||||
node,
|
||||
indicators,
|
||||
}: {
|
||||
app: App;
|
||||
messages: Message[];
|
||||
ip: string;
|
||||
serverRef: React.RefObject<WebSocket | null>;
|
||||
indicators: IndicatorContext[];
|
||||
node: RefObject<NodeClient | null>;
|
||||
}) {
|
||||
if (!app.currentChannel)
|
||||
return (
|
||||
<div className="h-svh w-full max-h-svh flex flex-col pb-5 pl-5 gap-5" />
|
||||
);
|
||||
|
||||
const sendRequest = (req: ClientMessage) => {
|
||||
node.current?.socket?.send(JSON.stringify(req));
|
||||
console.log(node.current?.socket);
|
||||
};
|
||||
|
||||
switch (app.currentChannel.kind) {
|
||||
case "text":
|
||||
return (
|
||||
@@ -31,10 +38,7 @@ export default function ChannelView({
|
||||
app={app}
|
||||
channelName={app.currentChannel.name}
|
||||
messages={messages}
|
||||
sendRequest={(req) => {
|
||||
while (serverRef.current?.CONNECTING) {}
|
||||
serverRef.current?.send(JSON.stringify(req));
|
||||
}}
|
||||
sendRequest={sendRequest}
|
||||
indicators={indicators.filter(
|
||||
(indicator) =>
|
||||
indicator.indicator.params.user_id !== app.profile?.id
|
||||
@@ -49,13 +53,13 @@ export default function ChannelView({
|
||||
channelName={app.currentChannel.name}
|
||||
channelId={app.currentChannel.id}
|
||||
messages={messages}
|
||||
sendRequest={(req) => {
|
||||
while (serverRef.current?.CONNECTING) {}
|
||||
serverRef.current?.send(JSON.stringify(req));
|
||||
}}
|
||||
sendRequest={sendRequest}
|
||||
sendVoice={(data) => {
|
||||
while (!serverRef.current || serverRef.current?.CONNECTING) {}
|
||||
sendVoice(serverRef.current, data);
|
||||
if (!node.current?.socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendVoice(node.current?.socket, data);
|
||||
}}
|
||||
indicators={indicators.filter(
|
||||
(indicator) =>
|
||||
@@ -66,12 +70,7 @@ export default function ChannelView({
|
||||
|
||||
default:
|
||||
return (
|
||||
<PluginChannel
|
||||
app={app}
|
||||
messages={messages}
|
||||
ip={ip}
|
||||
serverRef={serverRef}
|
||||
/>
|
||||
<PluginChannel app={app} messages={messages} ip={ip} node={node} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,17 +2,18 @@ import { Message } from "@/types/types";
|
||||
import App from "@/types/app";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { useParentCommunication } from "react-iframes-bridge";
|
||||
import { NodeClient } from "@/lib/node";
|
||||
|
||||
export default function PluginChannel({
|
||||
app,
|
||||
messages,
|
||||
ip,
|
||||
serverRef,
|
||||
node,
|
||||
}: {
|
||||
app: App;
|
||||
messages: Message[];
|
||||
ip: string;
|
||||
serverRef: React.RefObject<WebSocket | null>;
|
||||
node: React.RefObject<NodeClient | null>;
|
||||
}) {
|
||||
if (!app.currentChannel)
|
||||
return (
|
||||
@@ -32,7 +33,7 @@ export default function PluginChannel({
|
||||
});
|
||||
|
||||
parentComm.onMessage("SEND_MESSAGE", (msg: string) => {
|
||||
serverRef.current?.send(
|
||||
node.current?.socket?.send(
|
||||
JSON.stringify({
|
||||
type: "send_message",
|
||||
params: {
|
||||
|
||||
@@ -9,9 +9,10 @@ import Cookies from "js-cookie";
|
||||
import { toast } from "sonner";
|
||||
import { getProfileById } from "@/actions/get-profile";
|
||||
import { getServerById } from "@/actions/get-server";
|
||||
import { NodeClient } from "@/lib/node";
|
||||
|
||||
export default function useApp(): App {
|
||||
const node = useRef<WebSocket | null>(null);
|
||||
const node = useRef<NodeClient | null>(null);
|
||||
const [profiles, setProfiles] = useState<StringMap<UserProfile | null>>({});
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
const profile = useUser();
|
||||
|
||||
+35
-5
@@ -1,17 +1,47 @@
|
||||
import { useEffect } from "react";
|
||||
import { RefObject, useEffect, useRef } from "react";
|
||||
import { MessageStore } from "./use-messages";
|
||||
import { NodeCallbacks, NodeClient } from "@/lib/node";
|
||||
import { asyncWrapProviders } from "async_hooks";
|
||||
|
||||
export default function useNode(
|
||||
nodeId: string,
|
||||
messageStore: MessageStore,
|
||||
callbacks: NodeCallbacks
|
||||
) {
|
||||
const wsClientRef = useRef<NodeClient | null>(null);
|
||||
useEffect(() => getNode(nodeId, messageStore, callbacks, wsClientRef), []);
|
||||
return wsClientRef;
|
||||
}
|
||||
|
||||
export function useDynNode(
|
||||
nodeId: string | undefined | null,
|
||||
messageStore: MessageStore,
|
||||
callbacks: NodeCallbacks
|
||||
) {
|
||||
const wsClientRef = useRef<NodeClient | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const client = new NodeClient(nodeId, messageStore, callbacks);
|
||||
if (!nodeId) return;
|
||||
return getNode(nodeId, messageStore, callbacks, wsClientRef);
|
||||
}, [nodeId]);
|
||||
|
||||
return wsClientRef;
|
||||
}
|
||||
|
||||
client.connect();
|
||||
export function getNode(
|
||||
nodeId: string,
|
||||
messageStore: MessageStore,
|
||||
callbacks: NodeCallbacks,
|
||||
ref: RefObject<NodeClient | null>
|
||||
) {
|
||||
const client = new NodeClient(nodeId, messageStore, callbacks);
|
||||
|
||||
return () => client.disconnect();
|
||||
}, [nodeId]);
|
||||
ref.current = client;
|
||||
client.connect();
|
||||
|
||||
return () => client.disconnect();
|
||||
}
|
||||
|
||||
export function useNullNode() {
|
||||
return useRef<NodeClient | null>(null);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export function useEffectOnceWhenReady(
|
||||
effect: () => void,
|
||||
effect: () => any,
|
||||
deps: any[],
|
||||
testers: (undefined | ((m: any) => boolean))[]
|
||||
) {
|
||||
@@ -13,6 +13,7 @@ export function useEffectOnceWhenReady(
|
||||
);
|
||||
if (!allReady || hasRun.current) return;
|
||||
hasRun.current = true;
|
||||
effect();
|
||||
|
||||
return effect();
|
||||
}, deps);
|
||||
}
|
||||
|
||||
+4
-1
@@ -13,7 +13,7 @@ export interface NodeCallbacks {
|
||||
onVoiceJoin?: (userId: string, channelId: string, voiceId: number) => void;
|
||||
onVoiceLeave?: (userId: string, channelId: string, voiceId: number) => void;
|
||||
onSpeaking?: (voiceId: number) => void;
|
||||
};
|
||||
}
|
||||
|
||||
export class NodeClient {
|
||||
private ws: WebSocket | null = null;
|
||||
@@ -57,6 +57,7 @@ export class NodeClient {
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
console.log("Disconnecting");
|
||||
this.ws?.close();
|
||||
this.ws = null;
|
||||
}
|
||||
@@ -78,6 +79,8 @@ export class NodeClient {
|
||||
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
console.log("Message received:", data);
|
||||
|
||||
if (data.error) {
|
||||
toast.error(`Error from ${this.name} (${data.error})`, {
|
||||
description: data.message,
|
||||
|
||||
+3
-2
@@ -1,14 +1,15 @@
|
||||
import { Dispatch, RefObject, SetStateAction } from "react";
|
||||
import { StringMap } from "./typeUtils";
|
||||
import { UserProfile } from "@/hooks/get-user";
|
||||
import { Channel, Message, Server } from "./types";
|
||||
import { Channel, Server } from "./types";
|
||||
import { ClientSettings } from "./settings";
|
||||
import { MessageStore } from "@/hooks/use-messages";
|
||||
import { NodeClient } from "@/lib/node";
|
||||
|
||||
export type SetState<T> = Dispatch<SetStateAction<T>>;
|
||||
|
||||
export default interface App {
|
||||
node: RefObject<WebSocket | null>;
|
||||
node: RefObject<NodeClient | null>;
|
||||
profile: UserProfile | null;
|
||||
|
||||
server: Server | undefined;
|
||||
|
||||
Reference in New Issue
Block a user