Address PR comments on RoomForm

This commit is contained in:
mplorentz
2026-03-04 16:42:49 -05:00
committed by hodlbod
parent ea3be89341
commit f4e4ca1038
4 changed files with 65 additions and 61 deletions
+34 -40
View File
@@ -1,7 +1,9 @@
<script lang="ts">
import {get} from "svelte/store"
import type {Snippet} from "svelte"
import {assoc, append, nth, type Maybe, uniqBy} from "@welshman/lib"
import type {RoomMeta} from "@welshman/util"
import {makeRoomMeta} from "@welshman/util"
import {getTag, makeRoomMeta} from "@welshman/util"
import {waitForThunkError, createRoom, editRoom, joinRoom} from "@welshman/app"
import StickerSmileSquare from "@assets/icons/sticker-smile-square.svg?dataurl"
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
@@ -15,14 +17,14 @@
import ModalBody from "@lib/components/ModalBody.svelte"
import {pushToast} from "@app/util/toast"
import {uploadFile} from "@app/core/commands"
import {checkRelayHasLivekit} from "@app/voice"
import {deriveHasLivekit} from "@app/core/state"
type RoomMode = "text" | "voice" | "both"
const getRoomModeFromEvent = (event?: {tags?: string[][]}): RoomMode => {
const getRoomModeFromEvent = (event: Maybe<{tags: Maybe<string[][]>}>): RoomMode => {
const tags = event?.tags ?? []
const hasLivekit = tags.some(t => t[0] === "livekit")
const hasNoText = tags.some(t => t[0] === "no-text")
const hasLivekit = !!getTag("livekit", tags)
const hasNoText = !!getTag("no-text", tags)
if (hasLivekit && hasNoText) return "voice"
if (hasLivekit) return "both"
return "text"
@@ -30,8 +32,9 @@
const buildTagsWithRoomMode = (existingTags: string[][], roomMode: RoomMode): string[][] => {
const filtered = existingTags.filter(t => t[0] !== "livekit" && t[0] !== "no-text")
if (roomMode === "both") return [...filtered, ["livekit"]]
if (roomMode === "voice") return [...filtered, ["livekit"], ["no-text"]]
if (roomMode === "both") return uniqBy(nth(0), append(["livekit"], filtered))
if (roomMode === "voice")
return uniqBy(nth(0), append(["no-text"], append(["livekit"], filtered)))
return filtered
}
@@ -47,23 +50,12 @@
const values = $state(initialValues)
let roomMode = $state<RoomMode>(getRoomModeFromEvent(initialValues.event))
let relayHasLivekit = $state<boolean | undefined>(undefined)
$effect(() => {
const u = url
let cancelled = false
checkRelayHasLivekit(u).then(has => {
if (!cancelled) relayHasLivekit = has
})
return () => {
cancelled = true
}
})
const relayHasLivekit = deriveHasLivekit(url)
const submit = async () => {
const room = $state.snapshot(values)
if ((roomMode === "voice" || roomMode === "both") && !relayHasLivekit) {
if ((roomMode === "voice" || roomMode === "both") && !get(relayHasLivekit)) {
return pushToast({
theme: "error",
message: "This relay does not support voice rooms.",
@@ -84,16 +76,17 @@
room.pictureMeta = result.tags
}
const existingTags = room.event?.tags ?? []
const tags = buildTagsWithRoomMode(existingTags, roomMode)
room.event = room.event ? {...room.event, tags} : ({tags} as RoomMeta["event"])
const createMessage = await waitForThunkError(createRoom(url, room))
if (createMessage && !createMessage.includes("already")) {
return pushToast({theme: "error", message: createMessage})
}
if (room.event && get(relayHasLivekit)) {
const existingTags = room.event.tags ?? []
const tags = buildTagsWithRoomMode(existingTags, roomMode)
room.event = assoc("tags", tags)(room.event) as RoomMeta["event"]
}
const editMessage = await waitForThunkError(editRoom(url, room))
if (editMessage) {
@@ -207,22 +200,23 @@
</label>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>Room type</p>
{/snippet}
{#snippet input()}
<select class="select select-bordered w-full" bind:value={roomMode} aria-label="Room type">
<option value="text">Text only</option>
<option value="both" disabled={relayHasLivekit === false}>
Text and voice{relayHasLivekit === false ? " (not setup)" : ""}
</option>
<option value="voice" disabled={relayHasLivekit === false}>
Voice only{relayHasLivekit === false ? " (not setup)" : ""}
</option>
</select>
{/snippet}
</FieldInline>
{#if $relayHasLivekit}
<FieldInline>
{#snippet label()}
<p>Room type</p>
{/snippet}
{#snippet input()}
<select
class="select select-bordered w-full"
bind:value={roomMode}
aria-label="Room type">
<option value="text">Text only</option>
<option value="both">Text and voice</option>
<option value="voice">Voice only</option>
</select>
{/snippet}
</FieldInline>
{/if}
<strong class="md:hidden">Permissions</strong>
<div class="flex items-center gap-2">
<input type="checkbox" class="checkbox" bind:checked={values.isRestricted} />
+7
View File
@@ -146,6 +146,7 @@ import {
displayProfileByPubkey,
getProfile,
} from "@welshman/app"
import {checkRelayHasLivekit} from "$lib/livekit"
import {readFeed} from "@lib/feeds"
export const fromCsv = (s: string) => (s || "").split(",").filter(identity)
@@ -1197,6 +1198,12 @@ export const deriveSupportedMethods = simpleCache(([url]: [string]) => {
})
})
export const deriveHasLivekit = simpleCache(([url]: [string]) =>
readable<boolean | undefined>(undefined, set => {
checkRelayHasLivekit(url).then(has => set(has))
}),
)
export const deriveTimeout = (timeout: number) => {
const store = writable<boolean>(false)
+3 -21
View File
@@ -8,31 +8,13 @@ import {derived, get, writable} from "svelte/store"
import {now} from "@welshman/lib"
import {makeEvent, getTagValue} from "@welshman/util"
import {signer, publishThunk} from "@welshman/app"
import {getLivekitEndpoint} from "$lib/livekit"
import {deriveEventsForUrl} from "@app/core/state"
import {pushToast} from "@app/util/toast"
export const ROOM_PRESENCE = 10312
const livekitEndpoint = (url: string, groupId: string) => {
const httpUrl = url
.replace(/^wss:\/\//, "https://")
.replace(/^ws:\/\//, "http://")
.replace(/\/$/, "")
return `${httpUrl}/.well-known/nip29/livekit/${groupId}`
}
export const checkRelayHasLivekit = async (url: string): Promise<boolean> => {
const endpoint = livekitEndpoint(url, "nop")
try {
// Currently we are hitting the API with no auth because zooid returns a 401 livekit
// is configured and 404 if it is not. But we need a standardized solution in the NIP.
const response = await fetch(endpoint)
return response.status === 401
} catch {
return false
}
}
export {checkRelayHasLivekit} from "$lib/livekit"
const PRESENCE_INTERVAL_MS = 60_000
const PRESENCE_EXPIRY_S = 300
@@ -53,7 +35,7 @@ const fetchLivekitToken = async (
groupId: string,
signal?: AbortSignal,
): Promise<{server_url: string; participant_token: string}> => {
const endpoint = livekitEndpoint(url, groupId)
const endpoint = getLivekitEndpoint(url, groupId)
const $signer = signer.get()
if (!$signer) throw new Error("No signer available")
+21
View File
@@ -0,0 +1,21 @@
const livekitEndpoint = (url: string, groupId: string) => {
const httpUrl = url
.replace(/^wss:\/\//, "https://")
.replace(/^ws:\/\//, "http://")
.replace(/\/$/, "")
return `${httpUrl}/.well-known/nip29/livekit/${groupId}`
}
export const checkRelayHasLivekit = async (url: string): Promise<boolean> => {
const endpoint = livekitEndpoint(url, "nop")
try {
// Zooid returns 401 when livekit is configured and 404 if it is not.
const response = await fetch(endpoint)
return response.status === 401
} catch {
return false
}
}
export const getLivekitEndpoint = (url: string, groupId: string) => livekitEndpoint(url, groupId)