forked from coracle/flotilla
c5fcf12165
Co-authored-by: Matt Lorentz <mplorentz@noreply.coracle.social> Co-committed-by: Matt Lorentz <mplorentz@noreply.coracle.social>
128 lines
4.1 KiB
Svelte
128 lines
4.1 KiB
Svelte
<script lang="ts">
|
|
import {displayRelayUrl} from "@welshman/util"
|
|
import AltArrowLeft from "@assets/icons/alt-arrow-left.svg?dataurl"
|
|
import AltArrowRight from "@assets/icons/alt-arrow-right.svg?dataurl"
|
|
import Volume from "@assets/icons/volume.svg?dataurl"
|
|
import Button from "@lib/components/Button.svelte"
|
|
import FieldInline from "@lib/components/FieldInline.svelte"
|
|
import Icon from "@lib/components/Icon.svelte"
|
|
import Modal from "@lib/components/Modal.svelte"
|
|
import ModalBody from "@lib/components/ModalBody.svelte"
|
|
import ModalFooter from "@lib/components/ModalFooter.svelte"
|
|
import ModalHeader from "@lib/components/ModalHeader.svelte"
|
|
import ModalSubtitle from "@lib/components/ModalSubtitle.svelte"
|
|
import ModalTitle from "@lib/components/ModalTitle.svelte"
|
|
import {AbortError, TimeoutError} from "$lib/util"
|
|
import {displayRoom} from "@app/core/state"
|
|
import {joinVoiceRoom} from "@app/voice"
|
|
import {popModal} from "@app/util/modal"
|
|
import {pushToast} from "@app/util/toast"
|
|
|
|
type Props = {
|
|
url: string
|
|
h: string
|
|
}
|
|
|
|
const {url, h}: Props = $props()
|
|
|
|
const spaceLabel = $derived(displayRelayUrl(url))
|
|
|
|
let audioInputs = $state<MediaDeviceInfo[]>([])
|
|
let selectedDeviceId = $state("")
|
|
let startWithoutMic = $state(false)
|
|
|
|
const loadDevices = async () => {
|
|
if (!navigator.mediaDevices?.enumerateDevices) return
|
|
try {
|
|
const devices = await navigator.mediaDevices.enumerateDevices()
|
|
audioInputs = devices.filter(d => d.kind === "audioinput")
|
|
} catch {
|
|
audioInputs = []
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
void loadDevices()
|
|
})
|
|
|
|
const goBack = () => history.back()
|
|
|
|
const handleJoinError = (e: unknown) => {
|
|
if (e instanceof AbortError) return
|
|
console.error("Failed to join voice room", e)
|
|
let message = "Failed to join voice room"
|
|
if (e instanceof TimeoutError)
|
|
message = "Connection timed out. Please check your network and try again."
|
|
else if (e instanceof Error) message = e.message
|
|
pushToast({theme: "error", message})
|
|
}
|
|
|
|
const joinVoice = async () => {
|
|
popModal()
|
|
await joinVoiceRoom(
|
|
url,
|
|
h,
|
|
startWithoutMic,
|
|
startWithoutMic ? undefined : selectedDeviceId || undefined,
|
|
).catch(handleJoinError)
|
|
}
|
|
</script>
|
|
|
|
<Modal>
|
|
<ModalBody>
|
|
<ModalHeader>
|
|
<ModalTitle>Join voice room?</ModalTitle>
|
|
<ModalSubtitle>
|
|
<span class="inline-flex flex-wrap items-center justify-center gap-x-1.5 gap-y-1">
|
|
<Icon icon={Volume} size={4} class="shrink-0" />
|
|
<span class="ellipsize min-w-0">{displayRoom(url, h)}</span>
|
|
<span>·</span>
|
|
<span>{spaceLabel}</span>
|
|
</span>
|
|
</ModalSubtitle>
|
|
</ModalHeader>
|
|
<p class="text-sm opacity-80">Select a microphone to join the call:</p>
|
|
<div class="flex flex-col gap-4 pt-2">
|
|
<div class="flex items-center gap-2">
|
|
<input
|
|
id="voice-start-without-mic"
|
|
type="checkbox"
|
|
class="checkbox"
|
|
bind:checked={startWithoutMic} />
|
|
<label for="voice-start-without-mic" class="text-sm cursor-pointer">
|
|
Join without microphone (you can unmute later)
|
|
</label>
|
|
</div>
|
|
<FieldInline>
|
|
{#snippet label()}
|
|
<p>Microphone</p>
|
|
{/snippet}
|
|
{#snippet input()}
|
|
<select
|
|
class="select select-bordered w-full"
|
|
bind:value={selectedDeviceId}
|
|
disabled={startWithoutMic}
|
|
aria-label="Microphone">
|
|
<option value="">Default microphone</option>
|
|
{#each audioInputs as d (d.deviceId)}
|
|
<option value={d.deviceId}>
|
|
{d.label || `Microphone ${d.deviceId.slice(0, 8)}…`}
|
|
</option>
|
|
{/each}
|
|
</select>
|
|
{/snippet}
|
|
</FieldInline>
|
|
</div>
|
|
</ModalBody>
|
|
<ModalFooter>
|
|
<Button class="btn btn-link" onclick={goBack}>
|
|
<Icon icon={AltArrowLeft} />
|
|
Don't join
|
|
</Button>
|
|
<Button class="btn btn-primary" onclick={joinVoice}>
|
|
Join voice
|
|
<Icon icon={AltArrowRight} />
|
|
</Button>
|
|
</ModalFooter>
|
|
</Modal>
|