Files
flotilla/src/app/components/VoiceRoomItem.svelte
T
mplorentz 82245d895c Voice Room Membership Error (#106)
Before this we were showing "Failed to join voice room" if the relay rejected our request for a livekit token because we aren't a member of the room. Now it shows the error "Failed to join voice room: you must be a member."

Co-authored-by: mplorentz <mplorentz@noreply.gitea.coracle.social>
Reviewed-on: coracle/flotilla#106
Co-authored-by: Matt Lorentz <mplorentz@noreply.coracle.social>
Co-committed-by: Matt Lorentz <mplorentz@noreply.coracle.social>
2026-03-27 17:45:42 +00:00

87 lines
2.6 KiB
Svelte

<script lang="ts">
import cx from "classnames"
import {loadProfile, displayProfileByPubkey} from "@welshman/app"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import RoomImage from "@app/components/RoomImage.svelte"
import RoomName from "@app/components/RoomName.svelte"
import {handleJoinError} from "@app/components/VoiceWidget.svelte"
import {makeRoomPath} from "@app/util/routes"
import {
deriveVoiceParticipants,
joinVoiceRoom,
cancelJoinVoiceRoom,
currentVoiceRoom,
voiceState,
isParticipantSpeaking,
participantKey,
type VoiceParticipant,
} from "@app/voice"
interface Props {
url: string
h: string
replaceState?: boolean
}
const {url, h, replaceState = false}: Props = $props()
const participants = deriveVoiceParticipants(url, h)
const isActive = $derived(
$voiceState === "connected" && $currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
)
const isJoining = $derived(
$voiceState === "joining" && $currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
)
const handleClick = async () => {
if (isActive) return
if (isJoining) {
cancelJoinVoiceRoom()
return
}
await joinVoiceRoom(url, h).catch(handleJoinError)
}
$effect(() => {
for (const p of $participants) {
if (p.pubkey) loadProfile(p.pubkey)
}
})
</script>
<SecondaryNavItem
href={makeRoomPath(url, h)}
{replaceState}
onclick={handleClick}
class={cx("!items-start", isActive && "!bg-base-100 !text-base-content")}>
<div class="flex w-full min-w-0 flex-col gap-2">
<div class="flex gap-2 items-center">
{#if isJoining}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<RoomImage {url} {h} size={4} />
{/if}
<RoomName {url} {h} />
</div>
{#if $participants.length > 0}
{#each $participants as p (participantKey(p as VoiceParticipant))}
<div class="flex items-center gap-2 ml-6">
<div
class={cx(
"inline-flex shrink-0 items-center justify-center rounded-full transition-shadow",
isActive && $isParticipantSpeaking(p) && "ring-2 ring-success",
)}>
<ProfileCircle pubkey={p.pubkey} size={5} class="h-5 w-5" />
</div>
<span class="ellipsize text-xs opacity-70">
{p.pubkey ? displayProfileByPubkey(p.pubkey) : "Unknown"}
</span>
</div>
{/each}
{/if}
</div>
</SecondaryNavItem>