From 7ad8ba82b6e9c3b7619a347a59b4d5d68d43136e Mon Sep 17 00:00:00 2001 From: byGalax Date: Wed, 22 Apr 2026 21:00:33 +0200 Subject: [PATCH] feat(call): hide soundboard button when the user has no sounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously the music icon always rendered in the in-call bar, opening a popover with a "Keine Sounds gespeichert" empty-state. Matches Discord's pattern better to just drop the button entirely until the user has something to play — otherwise it reads as a broken control. Live- subscribes to soundboardStorage so adding/removing sounds mid-call flips the button in or out without reopening the call. Also closes the popover automatically if the user clears their last sound while it's open. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/desktop/src/components/InCallPanel.tsx | 47 ++++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/components/InCallPanel.tsx b/apps/desktop/src/components/InCallPanel.tsx index ecbd934..36fa3a9 100644 --- a/apps/desktop/src/components/InCallPanel.tsx +++ b/apps/desktop/src/components/InCallPanel.tsx @@ -11,6 +11,10 @@ import { type PttSettings, subscribePttSettings, } from '../lib/pttSettings'; +import { + listSounds, + subscribeSoundboardChanges, +} from '../lib/soundboardStorage'; import { useActiveSpeakers } from '../lib/useActiveSpeakers'; import { CallControls } from './CallControls'; import { CallParticipantTile } from './CallParticipantTile'; @@ -93,6 +97,37 @@ export function InCallPanel({ conversation }: Props) { const [shareMenu, setShareMenu] = useState< { userId: string; displayName: string; hasAudio: boolean; x: number; y: number } | null >(null); + // Soundboard-count so the in-call bar only surfaces the music button when + // the user actually has something to play. Matches Discord's "hide soundboard + // when empty" behaviour — no point dangling a button that opens to a blank + // "Keine Sounds" popover. Subscribes live so a sound added mid-call makes + // the button pop in without reopening the call. + const [soundboardCount, setSoundboardCount] = useState(null); + useEffect(() => { + let cancelled = false; + const refresh = async () => { + try { + const all = await listSounds(); + if (!cancelled) setSoundboardCount(all.length); + } catch { + if (!cancelled) setSoundboardCount(0); + } + }; + void refresh(); + const unsub = subscribeSoundboardChanges(() => { + void refresh(); + }); + return () => { + cancelled = true; + unsub(); + }; + }, []); + // Close the popover if the user just cleared their last sound while it was + // open — keeps the panel from lingering over an empty list. + useEffect(() => { + if (soundboardCount === 0) setSoundboardOpen(false); + }, [soundboardCount]); + // Active-speaker auto-focus uses "who most recently started speaking" // rather than "exactly one speaker" — matches Discord more closely and // handles the case where two people talk briefly without the focus @@ -230,8 +265,16 @@ export function InCallPanel({ conversation }: Props) { onToggleDeafen={toggleDeafen} onOpenParticipants={() => setParticipantsOpen((v) => !v)} participantsOpen={participantsOpen} - onToggleSoundboard={() => setSoundboardOpen((v) => !v)} - soundboardOpen={soundboardOpen} + // Soundboard-Button nur wenn mind. ein Sound existiert. Bis der Count + // aus IndexedDB geladen ist (null), auch nicht rendern — verhindert + // einen Flash des Buttons beim Call-Start wenn der User eh keine + // Sounds hat. + {...(soundboardCount && soundboardCount > 0 + ? { + onToggleSoundboard: () => setSoundboardOpen((v) => !v), + soundboardOpen, + } + : {})} onHangup={() => void hangup()} compact={callMode !== 'fullscreen'} glass={callMode === 'fullscreen'}