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 { 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 {
|
||||
channelId: string;
|
||||
@@ -10,6 +14,29 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
|
||||
const isConnected = useVoiceStore((state) => state.isConnected);
|
||||
const joinVoice = useVoiceStore((state) => state.joinVoice);
|
||||
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;
|
||||
|
||||
@@ -50,19 +77,43 @@ export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
|
||||
</button>
|
||||
{isActive && (
|
||||
<div className="pl-7 py-0.5">
|
||||
{participants.map((p) => (
|
||||
<div
|
||||
key={p.identity}
|
||||
className="text-xxs text-gb-fg-f flex items-center gap-1"
|
||||
>
|
||||
<span className={p.isSpeaking ? 'text-gb-green' : 'text-gb-fg-f'}>
|
||||
{p.isMuted ? '○' : '●'}
|
||||
</span>
|
||||
<span className={p.isSpeaking ? 'text-gb-green' : ''}>
|
||||
{p.username}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
{participants.map((p) => {
|
||||
const isMe = p.identity === currentUserId;
|
||||
return (
|
||||
<div
|
||||
key={p.identity}
|
||||
className="text-xxs text-gb-fg-f flex items-center gap-1 group"
|
||||
>
|
||||
<span className={p.isSpeaking ? 'text-gb-green' : 'text-gb-fg-f'}>
|
||||
{p.isMuted ? '○' : '●'}
|
||||
</span>
|
||||
<span className={p.isSpeaking ? 'text-gb-green' : ''}>
|
||||
{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>
|
||||
|
||||
@@ -1,42 +1,15 @@
|
||||
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 { VideoGrid } from './VideoGrid.tsx';
|
||||
import { AudioRenderers } from './AudioRenderers.tsx';
|
||||
import { usePermissions } from '../lib/usePermissions.ts';
|
||||
import { useChannelStore } from '../stores/channel.ts';
|
||||
|
||||
export function VoicePanel() {
|
||||
const isConnected = useVoiceStore((state) => state.isConnected);
|
||||
const currentRoom = useVoiceStore((state) => state.currentRoom);
|
||||
const currentRoomName = useVoiceStore((state) => state.currentRoomName);
|
||||
const participants = useVoiceStore((state) => state.participants);
|
||||
const error = useVoiceStore((state) => state.error);
|
||||
const whispers = useVoiceStore((state) => state.whispers);
|
||||
const whisperTo = useVoiceStore((state) => state.whisperTo);
|
||||
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;
|
||||
|
||||
@@ -93,68 +66,6 @@ export function VoicePanel() {
|
||||
</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 */}
|
||||
<VideoGrid />
|
||||
<AudioRenderers />
|
||||
|
||||
Reference in New Issue
Block a user