Files
flotilla/src/app/components/VoiceRoomItem.svelte
T

94 lines
2.8 KiB
Svelte

<script lang="ts">
import cx from "classnames"
import {loadProfile, displayProfileByPubkey} from "@welshman/app"
import Volume from "@assets/icons/volume.svg?dataurl"
import VolumeLoud from "@assets/icons/volume-loud.svg?dataurl"
import Icon from "@lib/components/Icon.svelte"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import {isMobileViewport} from "@lib/html"
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,
speakingPubkeys,
} 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 (isMobileViewport()) {
pushToast({theme: "error", message: "Voice rooms are not yet supported on mobile."})
return
}
if (isActive) {
await leaveVoiceRoom()
return
}
if (isJoining) {
joinAbortController?.abort()
return
}
joinAbortController = new AbortController()
isJoining = true
try {
await joinVoiceRoom(url, h, joinAbortController.signal)
} catch (e) {
pushToast({theme: "error", message: "Failed to join voice room"})
} finally {
isJoining = false
joinAbortController = undefined
}
}
$effect(() => {
for (const pk of $participants) {
loadProfile(pk)
}
})
</script>
<SecondaryNavItem
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}
<Icon icon={isActive ? VolumeLoud : Volume} size={4} class="opacity-70" />
{/if}
<RoomName {url} {h} />
</div>
{#if $participants.length > 0}
{#each $participants as pk (pk)}
<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 && $speakingPubkeys.has(pk) && "ring-2 ring-success",
)}>
<ProfileCircle pubkey={pk} size={5} class="h-5 w-5" />
</div>
<span class="ellipsize text-xs opacity-70">
{displayProfileByPubkey(pk)}
</span>
</div>
{/each}
{/if}
</div>
</SecondaryNavItem>