fix(webrtc): fix typescript errors for participant list migration
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
import { useVoiceStore } from '../stores/voice.ts';
|
import { useVoiceStore } from '../stores/voice.ts';
|
||||||
|
import { useAuthStore } from '../stores/auth.ts';
|
||||||
|
import { usePermissions } from '../lib/usePermissions.ts';
|
||||||
|
import { useChannelStore } from '../stores/channel.ts';
|
||||||
|
import { api } from '../lib/api.ts';
|
||||||
|
|
||||||
interface VoiceChannelProps {
|
interface VoiceChannelProps {
|
||||||
channelId: string;
|
channelId: string;
|
||||||
@@ -11,6 +15,29 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
|
|||||||
const joinVoice = useVoiceStore((state) => state.joinVoice);
|
const joinVoice = useVoiceStore((state) => state.joinVoice);
|
||||||
const participants = useVoiceStore((state) => state.participants);
|
const participants = useVoiceStore((state) => state.participants);
|
||||||
|
|
||||||
|
const currentUserId = useAuthStore((state) => state.user?.id);
|
||||||
|
const activeChannelId = useChannelStore((s) => s.activeChannelId);
|
||||||
|
const channelsByServer = useChannelStore((s) => s.channelsByServer);
|
||||||
|
|
||||||
|
const serverId = activeChannelId
|
||||||
|
? Object.entries(channelsByServer).find(([_, channels]) => channels.some(c => c.id === activeChannelId))?.[0]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const { canMute } = usePermissions(serverId || null);
|
||||||
|
|
||||||
|
const handleMute = async (identity: string) => {
|
||||||
|
if (!currentRoom) return;
|
||||||
|
try {
|
||||||
|
await api.post(`/voice/rooms/${currentRoom}/participants/${identity}/mute`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to mute participant:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const whisperTo = (identity: string, username: string) => {
|
||||||
|
useVoiceStore.getState().whisperTo(identity, username);
|
||||||
|
};
|
||||||
|
|
||||||
const isActive = currentRoom === channelId;
|
const isActive = currentRoom === channelId;
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
@@ -50,19 +77,43 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
|
|||||||
</button>
|
</button>
|
||||||
{isActive && (
|
{isActive && (
|
||||||
<div className="pl-7 py-0.5">
|
<div className="pl-7 py-0.5">
|
||||||
{participants.map((p) => (
|
{participants.map((p) => {
|
||||||
<div
|
const isMe = p.identity === currentUserId;
|
||||||
key={p.identity}
|
return (
|
||||||
className="text-xxs text-gb-fg-f flex items-center gap-1"
|
<div
|
||||||
>
|
key={p.identity}
|
||||||
<span className={p.isSpeaking ? 'text-gb-green' : 'text-gb-fg-f'}>
|
className="text-xxs text-gb-fg-f flex items-center gap-1 group"
|
||||||
{p.isMuted ? '○' : '●'}
|
>
|
||||||
</span>
|
<span className={p.isSpeaking ? 'text-gb-green' : 'text-gb-fg-f'}>
|
||||||
<span className={p.isSpeaking ? 'text-gb-green' : ''}>
|
{p.isMuted ? '○' : '●'}
|
||||||
{p.username}
|
</span>
|
||||||
</span>
|
<span className={p.isSpeaking ? 'text-gb-green' : ''}>
|
||||||
</div>
|
{p.username}
|
||||||
))}
|
</span>
|
||||||
|
|
||||||
|
{!isMe && (
|
||||||
|
<div className="ml-auto opacity-0 group-hover:opacity-100 flex items-center gap-1">
|
||||||
|
{canMute && !p.isMuted && (
|
||||||
|
<button
|
||||||
|
className="text-gb-fg-f hover:text-gb-red"
|
||||||
|
onClick={(e) => { e.stopPropagation(); handleMute(p.identity); }}
|
||||||
|
title={`Mute ${p.username}`}
|
||||||
|
>
|
||||||
|
[mute]
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="text-gb-fg-f hover:text-gb-orange"
|
||||||
|
onClick={(e) => { e.stopPropagation(); whisperTo(p.identity, p.username); }}
|
||||||
|
title={`Whisper to ${p.username}`}
|
||||||
|
>
|
||||||
|
[whisper]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,42 +1,15 @@
|
|||||||
import { useVoiceStore } from '../stores/voice.ts';
|
import { useVoiceStore } from '../stores/voice.ts';
|
||||||
import { useAuthStore } from '../stores/auth.ts';
|
|
||||||
import { useServerStore } from '../stores/server.ts';
|
|
||||||
import { api } from '../lib/api.ts';
|
|
||||||
import { VoiceControls } from './VoiceControls.tsx';
|
import { VoiceControls } from './VoiceControls.tsx';
|
||||||
import { VideoGrid } from './VideoGrid.tsx';
|
import { VideoGrid } from './VideoGrid.tsx';
|
||||||
import { AudioRenderers } from './AudioRenderers.tsx';
|
import { AudioRenderers } from './AudioRenderers.tsx';
|
||||||
import { usePermissions } from '../lib/usePermissions.ts';
|
|
||||||
import { useChannelStore } from '../stores/channel.ts';
|
|
||||||
|
|
||||||
export function VoicePanel() {
|
export function VoicePanel() {
|
||||||
const isConnected = useVoiceStore((state) => state.isConnected);
|
const isConnected = useVoiceStore((state) => state.isConnected);
|
||||||
const currentRoom = useVoiceStore((state) => state.currentRoom);
|
const currentRoom = useVoiceStore((state) => state.currentRoom);
|
||||||
const currentRoomName = useVoiceStore((state) => state.currentRoomName);
|
const currentRoomName = useVoiceStore((state) => state.currentRoomName);
|
||||||
const participants = useVoiceStore((state) => state.participants);
|
|
||||||
const error = useVoiceStore((state) => state.error);
|
const error = useVoiceStore((state) => state.error);
|
||||||
const whispers = useVoiceStore((state) => state.whispers);
|
const whispers = useVoiceStore((state) => state.whispers);
|
||||||
const whisperTo = useVoiceStore((state) => state.whisperTo);
|
|
||||||
const dismissWhisper = useVoiceStore((state) => state.dismissWhisper);
|
const dismissWhisper = useVoiceStore((state) => state.dismissWhisper);
|
||||||
const currentUserId = useAuthStore((state) => state.user?.id);
|
|
||||||
|
|
||||||
const activeChannelId = useChannelStore((s) => s.activeChannelId);
|
|
||||||
const channelsByServer = useChannelStore((s) => s.channelsByServer);
|
|
||||||
const activeServerId = useServerStore((s) => s.activeServerId);
|
|
||||||
|
|
||||||
const serverId = activeChannelId
|
|
||||||
? Object.entries(channelsByServer).find(([_, channels]) => channels.some(c => c.id === activeChannelId))?.[0]
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const { canMute } = usePermissions(serverId || activeServerId);
|
|
||||||
|
|
||||||
const handleMute = async (identity: string) => {
|
|
||||||
if (!currentRoom) return;
|
|
||||||
try {
|
|
||||||
await api.post(`/voice/rooms/${currentRoom}/participants/${identity}/mute`);
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to mute participant', err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!currentRoom) return null;
|
if (!currentRoom) return null;
|
||||||
|
|
||||||
@@ -93,68 +66,6 @@ export function VoicePanel() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Participants */}
|
|
||||||
<div className="px-3 py-1.5 border-b border-gb-bg-t">
|
|
||||||
<div className="text-xxs text-gb-fg-f mb-1">
|
|
||||||
── participants ──
|
|
||||||
</div>
|
|
||||||
{participants.length === 0 && (
|
|
||||||
<div className="text-xxs text-gb-fg-f">[empty channel]</div>
|
|
||||||
)}
|
|
||||||
{participants.map((p) => {
|
|
||||||
const isMe = p.identity === currentUserId;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={p.identity}
|
|
||||||
className="flex items-center gap-2 py-0.5 text-xs"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
p.isSpeaking
|
|
||||||
? 'text-gb-green'
|
|
||||||
: p.isMuted
|
|
||||||
? 'text-gb-red'
|
|
||||||
: 'text-gb-fg-s'
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{p.isMuted ? '○' : '●'}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={p.isSpeaking ? 'text-gb-green' : 'text-gb-fg-s'}
|
|
||||||
>
|
|
||||||
{p.username}
|
|
||||||
</span>
|
|
||||||
{p.isMuted && (
|
|
||||||
<span className="text-xxs text-gb-red">[muted]</span>
|
|
||||||
)}
|
|
||||||
{p.isSpeaking && (
|
|
||||||
<span className="text-xxs text-gb-green">[speaking]</span>
|
|
||||||
)}
|
|
||||||
{!isMe && (
|
|
||||||
<div className="ml-auto flex items-center gap-1">
|
|
||||||
{canMute && !p.isMuted && (
|
|
||||||
<button
|
|
||||||
className="text-xxs text-gb-fg-f hover:text-gb-red"
|
|
||||||
onClick={() => handleMute(p.identity)}
|
|
||||||
title={`Mute ${p.username}`}
|
|
||||||
>
|
|
||||||
[mute]
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
className="text-xxs text-gb-fg-f hover:text-gb-orange"
|
|
||||||
onClick={() => whisperTo(p.identity, p.username)}
|
|
||||||
title={`Whisper to ${p.username}`}
|
|
||||||
>
|
|
||||||
[whisper]
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Video grid */}
|
{/* Video grid */}
|
||||||
<VideoGrid />
|
<VideoGrid />
|
||||||
<AudioRenderers />
|
<AudioRenderers />
|
||||||
|
|||||||
Reference in New Issue
Block a user