81 lines
2.4 KiB
Svelte
81 lines
2.4 KiB
Svelte
<script lang="ts">
|
|
import {loadProfile, displayProfileByPubkey} from "@welshman/app"
|
|
import Volume from "@assets/icons/volume.svg?dataurl"
|
|
import Icon from "@lib/components/Icon.svelte"
|
|
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
|
import RoomName from "@app/components/RoomName.svelte"
|
|
import {pushToast} from "@app/util/toast"
|
|
import {
|
|
deriveVoiceParticipants,
|
|
joinVoiceRoom,
|
|
leaveVoiceRoom,
|
|
currentVoiceSession,
|
|
} from "@app/voice"
|
|
|
|
interface Props {
|
|
url: string
|
|
h: string
|
|
}
|
|
|
|
const {url, h}: Props = $props()
|
|
|
|
const participants = deriveVoiceParticipants(url, h)
|
|
const isActive = $derived($currentVoiceSession?.url === url && $currentVoiceSession?.h === h)
|
|
let isJoining = $state(false)
|
|
let joinAbortController: AbortController | undefined
|
|
|
|
const handleClick = async () => {
|
|
if (isActive) {
|
|
await leaveVoiceRoom()
|
|
return
|
|
}
|
|
if (isJoining) {
|
|
joinAbortController?.abort()
|
|
return
|
|
}
|
|
joinAbortController = new AbortController()
|
|
isJoining = true
|
|
try {
|
|
await joinVoiceRoom(url, h, joinAbortController.signal)
|
|
} catch (e) {
|
|
if (e instanceof Error && e.message === "Join cancelled") return
|
|
if (e instanceof DOMException && e.name === "AbortError") return
|
|
const message = e instanceof Error ? e.message : String(e)
|
|
pushToast({theme: "error", message: `Failed to join voice room: ${message}`})
|
|
} finally {
|
|
isJoining = false
|
|
joinAbortController = undefined
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
for (const pk of $participants) {
|
|
loadProfile(pk)
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<div>
|
|
<SecondaryNavItem onclick={handleClick} class={isActive ? "!bg-base-100 !text-base-content" : ""}>
|
|
{#if isJoining}
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
{:else}
|
|
<Icon icon={Volume} size={4} class="opacity-70" />
|
|
{/if}
|
|
<RoomName {url} {h} />
|
|
</SecondaryNavItem>
|
|
{#if $participants.length > 0}
|
|
<div class="flex flex-col gap-1 pb-1 pl-10">
|
|
{#each $participants as pk (pk)}
|
|
<div class="flex items-center gap-2">
|
|
<ProfileCircle pubkey={pk} size={5} class="h-5 w-5" />
|
|
<span class="ellipsize text-xs opacity-70">
|
|
{displayProfileByPubkey(pk)}
|
|
</span>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|