Working UI
This commit is contained in:
@@ -28,6 +28,19 @@ export default function Server() {
|
|||||||
setServer: app.setServer,
|
setServer: app.setServer,
|
||||||
messageStore: messageStore,
|
messageStore: messageStore,
|
||||||
onIndicator: addIndicator,
|
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,
|
||||||
|
};
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[ip, messageStore.messages],
|
[ip, messageStore.messages],
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export default function ChannelView({
|
|||||||
<VoiceBox
|
<VoiceBox
|
||||||
app={app}
|
app={app}
|
||||||
channelName={app.currentChannel.name}
|
channelName={app.currentChannel.name}
|
||||||
|
channelId={app.currentChannel.id}
|
||||||
messages={messages}
|
messages={messages}
|
||||||
sendRequest={(req) => {
|
sendRequest={(req) => {
|
||||||
while (serverRef.current?.CONNECTING) {}
|
while (serverRef.current?.CONNECTING) {}
|
||||||
|
|||||||
@@ -6,13 +6,7 @@ import { Message } from "@/types/types";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { startMic } from "@/lib/audio";
|
import { startMic } from "@/lib/audio";
|
||||||
import { Button } from "../../ui/button";
|
import { Button } from "../../ui/button";
|
||||||
import {
|
import { ChevronLeftIcon, Phone, Volume2Icon } from "lucide-react";
|
||||||
ChevronLeftIcon,
|
|
||||||
Phone,
|
|
||||||
PhoneCall,
|
|
||||||
PhoneOff,
|
|
||||||
Volume2Icon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import VoiceGrid from "./VoiceGrid";
|
import VoiceGrid from "./VoiceGrid";
|
||||||
|
|
||||||
export default function VoiceBox({
|
export default function VoiceBox({
|
||||||
@@ -20,10 +14,12 @@ export default function VoiceBox({
|
|||||||
app,
|
app,
|
||||||
channelName,
|
channelName,
|
||||||
sendRequest,
|
sendRequest,
|
||||||
|
channelId,
|
||||||
}: {
|
}: {
|
||||||
|
channelName?: string;
|
||||||
app: App;
|
app: App;
|
||||||
sendRequest: (req: ClientMessage) => void;
|
sendRequest: (req: ClientMessage) => void;
|
||||||
channelName?: string;
|
channelId: string;
|
||||||
messages: Message[];
|
messages: Message[];
|
||||||
indicators: IndicatorContext[];
|
indicators: IndicatorContext[];
|
||||||
sendVoice: (data: Int16Array) => void;
|
sendVoice: (data: Int16Array) => void;
|
||||||
@@ -54,25 +50,26 @@ export default function VoiceBox({
|
|||||||
</span>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
<VoiceGrid
|
{app.voiceConns[channelId] && (
|
||||||
users={[
|
<VoiceGrid
|
||||||
{ id: "1", name: "Klesty", speaking: true },
|
users={Object.entries(app.voiceConns[channelId]).map(
|
||||||
{ id: "2", name: "Alex", speaking: false },
|
([userId, _]) => ({
|
||||||
{ id: "3", name: "Mia", speaking: true },
|
id: userId,
|
||||||
{ id: "4", name: "John", speaking: false },
|
speaking: false,
|
||||||
]}
|
})
|
||||||
/>
|
)}
|
||||||
|
app={app}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<footer className="mt-auto h-max w-max mx-auto">
|
<footer className="mt-auto h-max w-max mx-auto">
|
||||||
{voiceConn ? (
|
{voiceConn ? (
|
||||||
<Button
|
<Button
|
||||||
className="mx-auto my-auto h-max bg-transparent text-destructive border border-destructive hover:bg-destructive/20 rounded-full aspect-square w-12"
|
className="mx-auto my-auto h-max bg-transparent text-destructive border border-destructive hover:bg-destructive/20 rounded-full aspect-square w-12"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!app.currentChannel) return;
|
|
||||||
|
|
||||||
setVoiceConn(false);
|
setVoiceConn(false);
|
||||||
sendRequest({
|
sendRequest({
|
||||||
type: "leave_voice",
|
type: "leave_voice",
|
||||||
params: { channel_id: app.currentChannel.id },
|
params: { channel_id: channelId },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -82,12 +79,10 @@ export default function VoiceBox({
|
|||||||
<Button
|
<Button
|
||||||
className="mx-auto my-auto h-max bg-transparent text-chart-2 border border-chart-2 hover:bg-chart-2/20 rounded-full aspect-square w-12"
|
className="mx-auto my-auto h-max bg-transparent text-chart-2 border border-chart-2 hover:bg-chart-2/20 rounded-full aspect-square w-12"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!app.currentChannel) return;
|
|
||||||
|
|
||||||
setVoiceConn(true);
|
setVoiceConn(true);
|
||||||
sendRequest({
|
sendRequest({
|
||||||
type: "join_voice",
|
type: "join_voice",
|
||||||
params: { channel_id: app.currentChannel.id },
|
params: { channel_id: channelId },
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
|
import App from "@/types/app";
|
||||||
import { VoiceTile, VoiceUser } from "./VoiceTile";
|
import { VoiceTile, VoiceUser } from "./VoiceTile";
|
||||||
|
|
||||||
export default function VoiceGrid({ users }: { users: VoiceUser[] }) {
|
export default function VoiceGrid({
|
||||||
|
app,
|
||||||
|
users,
|
||||||
|
}: {
|
||||||
|
app: App;
|
||||||
|
users: VoiceUser[];
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full overflow-y-auto p-4">
|
<div className="h-full overflow-y-auto p-4">
|
||||||
<div
|
<div
|
||||||
@@ -13,7 +20,7 @@ export default function VoiceGrid({ users }: { users: VoiceUser[] }) {
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<VoiceTile key={user.id} user={user} />
|
<VoiceTile key={user.id} user={user} app={app} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import AsyncValue from "@/components/AsyncValue";
|
||||||
|
import App from "@/types/app";
|
||||||
|
|
||||||
export type VoiceUser = {
|
export type VoiceUser = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
|
||||||
speaking: boolean;
|
speaking: boolean;
|
||||||
muted?: boolean;
|
muted?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function VoiceTile({ user }: { user: VoiceUser }) {
|
export function VoiceTile({ user, app }: { app: App; user: VoiceUser }) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
@@ -21,20 +23,23 @@ export function VoiceTile({ user }: { user: VoiceUser }) {
|
|||||||
${user.muted ? "opacity-50" : ""}
|
${user.muted ? "opacity-50" : ""}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{/* Avatar */}
|
{/* Avatar and name */}
|
||||||
<div
|
<AsyncValue
|
||||||
className={`
|
func={() => app.getUserById(user.id)}
|
||||||
h-20 w-20 rounded-full
|
render={({ value }) => (
|
||||||
flex items-center justify-center
|
<>
|
||||||
text-2xl font-semibold
|
<img
|
||||||
bg-background
|
src={value?.avatar_url}
|
||||||
`}
|
alt="Pfp"
|
||||||
>
|
className="h-20 w-20 rounded-full"
|
||||||
{user.name[0].toUpperCase()}
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Name */}
|
<div className="mt-3 text-sm font-medium">
|
||||||
<div className="mt-3 text-sm font-medium">{user.name}</div>
|
{value?.display_name}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Speaking indicator */}
|
{/* Speaking indicator */}
|
||||||
{user.speaking && (
|
{user.speaking && (
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import useMessages from "./use-messages";
|
|||||||
import { StringMap } from "@/types/typeUtils";
|
import { StringMap } from "@/types/typeUtils";
|
||||||
import useUser, { UserProfile } from "./get-user";
|
import useUser, { UserProfile } from "./get-user";
|
||||||
import { useClientSettings } from "./use-settings";
|
import { useClientSettings } from "./use-settings";
|
||||||
import { get, Response } from "@/lib/request";
|
|
||||||
import { Channel, Server } from "@/types/types";
|
import { Channel, Server } from "@/types/types";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -22,6 +21,9 @@ export default function useApp(): App {
|
|||||||
const [currentChannel, setCurrentChannel] = useState<Channel | null>(null);
|
const [currentChannel, setCurrentChannel] = useState<Channel | null>(null);
|
||||||
const [server, setServer] = useState<Server>();
|
const [server, setServer] = useState<Server>();
|
||||||
const privateMessages = useMessages();
|
const privateMessages = useMessages();
|
||||||
|
const [voiceConns, SetVoiceConns] = useState<
|
||||||
|
Record<string, Record<string, number>>
|
||||||
|
>({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const stored = Cookies.get("servers");
|
const stored = Cookies.get("servers");
|
||||||
@@ -38,6 +40,8 @@ export default function useApp(): App {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
voiceConns,
|
||||||
|
setVoiceConns: SetVoiceConns,
|
||||||
privateMessages,
|
privateMessages,
|
||||||
currentChannel,
|
currentChannel,
|
||||||
setCurrentChannel,
|
setCurrentChannel,
|
||||||
|
|||||||
+21
-1
@@ -15,6 +15,8 @@ type AuthParams = {
|
|||||||
messageStore: MessageStore;
|
messageStore: MessageStore;
|
||||||
onIndicator?: (i: IndicatorContext) => void;
|
onIndicator?: (i: IndicatorContext) => void;
|
||||||
onVoice?: (userId: string, bytes: number[]) => void;
|
onVoice?: (userId: string, bytes: number[]) => void;
|
||||||
|
onVoiceJoin?: (userId: string, channelId: string, voiceId: number) => void;
|
||||||
|
onVoiceLeave?: (userId: string, channelId: string, voiceId: number) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function auth({
|
export default async function auth({
|
||||||
@@ -24,6 +26,8 @@ export default async function auth({
|
|||||||
onNewMessage,
|
onNewMessage,
|
||||||
messageStore,
|
messageStore,
|
||||||
onIndicator,
|
onIndicator,
|
||||||
|
onVoiceJoin,
|
||||||
|
onVoiceLeave,
|
||||||
}: AuthParams) {
|
}: AuthParams) {
|
||||||
console.log("Authenticating with server id:", id);
|
console.log("Authenticating with server id:", id);
|
||||||
|
|
||||||
@@ -48,7 +52,7 @@ export default async function auth({
|
|||||||
if (m.data instanceof Blob) {
|
if (m.data instanceof Blob) {
|
||||||
const buffer = await m.data.arrayBuffer();
|
const buffer = await m.data.arrayBuffer();
|
||||||
const bytes = new Uint8Array(buffer);
|
const bytes = new Uint8Array(buffer);
|
||||||
playPCM16(bytes)
|
playPCM16(bytes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +105,22 @@ export default async function auth({
|
|||||||
if (!onIndicator) break;
|
if (!onIndicator) break;
|
||||||
onIndicator(msg.params);
|
onIndicator(msg.params);
|
||||||
break;
|
break;
|
||||||
|
case "voice_join":
|
||||||
|
if (!onVoiceJoin) break;
|
||||||
|
onVoiceJoin(
|
||||||
|
msg.params.user_id,
|
||||||
|
msg.params.channel_id,
|
||||||
|
msg.params.voice_id
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "voice_leave":
|
||||||
|
if (!onVoiceLeave) break;
|
||||||
|
onVoiceLeave(
|
||||||
|
msg.params.user_id,
|
||||||
|
msg.params.channel_id,
|
||||||
|
msg.params.voice_id
|
||||||
|
);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -35,4 +35,7 @@ export default interface App {
|
|||||||
|
|
||||||
currentChannel: Channel | null;
|
currentChannel: Channel | null;
|
||||||
setCurrentChannel: SetState<Channel | null>;
|
setCurrentChannel: SetState<Channel | null>;
|
||||||
|
|
||||||
|
voiceConns: Record<string, Record<string, number>>;
|
||||||
|
setVoiceConns: SetState<Record<string, Record<string, number>>>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user