fix(webrtc): fix typescript errors for participant list migration

This commit is contained in:
2026-07-06 13:32:10 +00:00
parent 1d37284c25
commit 88e7faaafb
2 changed files with 64 additions and 102 deletions
+64 -13
View File
@@ -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;
@@ -10,6 +14,29 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
const isConnected = useVoiceStore((state) => state.isConnected); const isConnected = useVoiceStore((state) => state.isConnected);
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;
@@ -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>
-89
View File
@@ -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 />