feat(soundboard): shared room/space packs (like emoji/stickers), grid picker, management
Soundboard v2 — a near-parallel of the custom-emoji image-pack system for in-call audio clips. - Data model: 3-tier packs mirroring MSC2545 — room/space pack (state event io.lotus.soundboard, inherited by child rooms via parent-space aggregation), global refs (io.lotus.soundboard_rooms), and the personal pack (io.lotus.soundboard account data; the v1 flat-list content is migrated to the pack shape on read). New plugins/soundboard/ (readers, SoundboardPack, utils) + hooks/useSoundboardPacks (useRelevantSoundboardPacks = user U global U room, deduped). Unit-tested (migration + slug). - Management: reusable SoundboardPackEditor (name + emoji + per-clip volume + delete + upload + batched save), power-level-gated for room packs like emoji packs; a Soundboard page wired into Room + Space settings. - In-call: CallSoundboard rewritten as a Discord-style grid grouped by pack (emoji + name tiles), sourcing room+parent-space U personal clips; a Manage toggle embeds the editors; per-clip volume x master volume on playback. - Spam guard: host gates on a playing key (fork enforces one clip at a time). - Control bar: Mute-Screenshare moved next to the Screenshare button. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -351,10 +351,6 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
|
||||
<MicrophoneButton enabled={microphone} onToggle={handleMicrophoneToggle} />
|
||||
<SoundButton enabled={sound} onToggle={() => callEmbed.control.toggleSound()} />
|
||||
<ScreenshareAudioButton
|
||||
muted={screenshareAudioMuted}
|
||||
onToggle={() => callEmbed.control.toggleScreenshareAudio()}
|
||||
/>
|
||||
</Box>
|
||||
{!compact && showVideoGroup && <ControlDivider />}
|
||||
{showVideoGroup && (
|
||||
@@ -363,12 +359,20 @@ export function CallControls({ callEmbed }: CallControlsProps) {
|
||||
user can stop it; once stopped it hides and can't be restarted. */}
|
||||
{showCamera && <VideoButton enabled={video} onToggle={handleVideoToggle} />}
|
||||
{showScreenshare && (
|
||||
<ScreenShareButton
|
||||
enabled={screenshare}
|
||||
onToggle={() =>
|
||||
screenshare ? callEmbed.control.toggleScreenshare() : setShareConfirm(true)
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<ScreenShareButton
|
||||
enabled={screenshare}
|
||||
onToggle={() =>
|
||||
screenshare ? callEmbed.control.toggleScreenshare() : setShareConfirm(true)
|
||||
}
|
||||
/>
|
||||
{/* Mute-screenshare-audio sits directly next to the screenshare
|
||||
control since they're the same concern. */}
|
||||
<ScreenshareAudioButton
|
||||
muted={screenshareAudioMuted}
|
||||
onToggle={() => callEmbed.control.toggleScreenshareAudio()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!!document.fullscreenEnabled && (
|
||||
<FullscreenButton isFullscreen={isFullscreen} onToggle={handleFullscreen} />
|
||||
|
||||
Reference in New Issue
Block a user