fix: show mute slash icon when server-deafened and render badges from effective state

Add isServerDeafened to mic icon slash conditions in VoiceControlBar and
ChannelSidebar. Restructure VoiceUser badge rendering to gate on effective
state (intent OR server enforcement) instead of only LiveKit participant
intent, fixing missing badges in the voice grid for server-muted users.
This commit is contained in:
Jannis Braun
2026-03-09 23:26:51 +01:00
parent 651d7edf9d
commit cfd24c5d58
3 changed files with 37 additions and 36 deletions
@@ -780,7 +780,7 @@ function UserAreaPanel({
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" /> <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" />
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" /> <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" />
{(isMuted || isDeafened || isServerMuted) && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />} {(isMuted || isDeafened || isServerMuted || isServerDeafened) && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />}
</svg> </svg>
</button> </button>
{/* Input chevron */} {/* Input chevron */}
@@ -152,7 +152,7 @@ export function VoiceControlBar() {
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" /> <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3z" />
<path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" /> <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z" />
{(isMuted || isDeafened || isServerMuted) && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />} {(isMuted || isDeafened || isServerMuted || isServerDeafened) && <line x1="3" y1="3" x2="21" y2="21" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />}
</svg> </svg>
</button> </button>
+11 -10
View File
@@ -150,12 +150,15 @@ export function VoiceUser({ tile, large }: VoiceUserProps) {
)} )}
</div> </div>
<div className="flex items-center gap-1 flex-shrink-0"> <div className="flex items-center gap-1 flex-shrink-0">
{participant.isMuted && (() => { {(() => {
const isServerMutedUser = spaceId ? serverMutedUserIds.has(`${spaceId}:${participant.userId}`) : false; const isServerMutedUser = spaceId ? serverMutedUserIds.has(`${spaceId}:${participant.userId}`) : false;
const isServerDeafenedUser = spaceId ? serverDeafenedUserIds.has(`${spaceId}:${participant.userId}`) : false; const isServerDeafenedUser = spaceId ? serverDeafenedUserIds.has(`${spaceId}:${participant.userId}`) : false;
const badgeBg = (isServerMutedUser || isServerDeafenedUser) ? 'bg-accent-amber/90' : 'bg-accent-rose/90'; const effectivelyMuted = participant.isMuted || isServerMutedUser || isServerDeafenedUser;
const effectivelyDeafened = (isLocal ? isDeafened : participant.isDeafened) || isServerDeafenedUser;
return ( return (
<div className={`w-5 h-5 ${badgeBg} rounded-full flex items-center justify-center`}> <>
{effectivelyMuted && (
<div className={`w-5 h-5 ${(isServerMutedUser || isServerDeafenedUser) ? 'bg-accent-amber/90' : 'bg-accent-rose/90'} rounded-full flex items-center justify-center`}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="white"> <svg width="12" height="12" viewBox="0 0 24 24" fill="white">
<path d="M12 2C10.9 2 10 2.9 10 4V12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12V4C14 2.9 13.1 2 12 2Z" /> <path d="M12 2C10.9 2 10 2.9 10 4V12C10 13.1 10.9 14 12 14C13.1 14 14 13.1 14 12V4C14 2.9 13.1 2 12 2Z" />
<line <line
@@ -168,13 +171,9 @@ export function VoiceUser({ tile, large }: VoiceUserProps) {
/> />
</svg> </svg>
</div> </div>
); )}
})()} {effectivelyDeafened && (
{(isLocal ? isDeafened : participant.isDeafened) && (() => { <div className={`w-5 h-5 ${isServerDeafenedUser ? 'bg-accent-amber/90' : 'bg-accent-rose/90'} rounded-full flex items-center justify-center`}>
const isServerDeafenedUser = spaceId ? serverDeafenedUserIds.has(`${spaceId}:${participant.userId}`) : false;
const badgeBg = isServerDeafenedUser ? 'bg-accent-amber/90' : 'bg-accent-rose/90';
return (
<div className={`w-5 h-5 ${badgeBg} rounded-full flex items-center justify-center`}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="white"> <svg width="12" height="12" viewBox="0 0 24 24" fill="white">
<path d="M12 3c-4.97 0-9 4.03-9 9v7c0 1.1.9 2 2 2h2v-7H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-2v7h2c1.1 0 2-.9 2-2v-7c0-4.97-4.03-9-9-9z" /> <path d="M12 3c-4.97 0-9 4.03-9 9v7c0 1.1.9 2 2 2h2v-7H5v-2c0-3.87 3.13-7 7-7s7 3.13 7 7v2h-2v7h2c1.1 0 2-.9 2-2v-7c0-4.97-4.03-9-9-9z" />
<line <line
@@ -187,6 +186,8 @@ export function VoiceUser({ tile, large }: VoiceUserProps) {
/> />
</svg> </svg>
</div> </div>
)}
</>
); );
})()} })()}
</div> </div>