Phase 2 frontend: voice store, VoiceChannel, VoicePanel, VoiceControls
Frontend:
- stores/voice.ts: Zustand store for voice state (join, leave, mute, deafen, video, screen share)
- VoiceChannel.tsx: sidebar item showing 🔊 channel name with participant count
- VoicePanel.tsx: box-drawn frame with participant list and controls
- VoiceControls.tsx: [m] mute, [d] deafen, [cam] video, [share] screen, [x] disconnect
- ChannelList.tsx: integrated VoiceChannel for voice-type channels
- Layout.tsx: added VoicePanel above chat area
Deps:
- Added livekit-client, @livekit/components-react
- NODE_ENV fix for dev deps installation
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import { useVoiceStore } from '../stores/voice.ts';
|
||||
|
||||
interface VoiceChannelProps {
|
||||
channelId: string;
|
||||
channelName: string;
|
||||
}
|
||||
|
||||
export function VoiceChannel({ channelId, channelName }: VoiceChannelProps) {
|
||||
const currentRoom = useVoiceStore((state) => state.currentRoom);
|
||||
const isConnected = useVoiceStore((state) => state.isConnected);
|
||||
const joinVoice = useVoiceStore((state) => state.joinVoice);
|
||||
const participants = useVoiceStore((state) => state.participants);
|
||||
|
||||
const isActive = currentRoom === channelId;
|
||||
|
||||
const handleClick = () => {
|
||||
if (!isActive) {
|
||||
joinVoice(channelId, channelName);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-0.5">
|
||||
<button
|
||||
onClick={handleClick}
|
||||
className={`w-full text-left px-2 py-1 rounded-sm flex items-center gap-2 ${
|
||||
isActive ? 'terminal-active' : 'hover:bg-gb-bg-t text-gb-fg-s'
|
||||
}`}
|
||||
>
|
||||
<span className={isActive ? 'text-gb-orange' : 'text-gb-fg-f'}>
|
||||
{isActive && isConnected ? '●' : isActive ? '◐' : '○'}
|
||||
</span>
|
||||
<span className={isActive ? 'text-gb-orange' : 'text-gb-fg-f'}>
|
||||
🔊
|
||||
</span>
|
||||
<span className="truncate">{channelName}</span>
|
||||
{isActive && participants.length > 0 && (
|
||||
<span className="ml-auto text-xxs text-gb-fg-f">
|
||||
[{participants.length}]
|
||||
</span>
|
||||
)}
|
||||
</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>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user