Remove no-text rooms, highlight active room, fix custom voice room icons

This commit is contained in:
mplorentz
2026-03-11 16:08:40 -04:00
parent 1ae2c7a182
commit de174a0c85
7 changed files with 75 additions and 61 deletions
+10 -18
View File
@@ -6,6 +6,7 @@
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"
import Volume from "@assets/icons/volume.svg?dataurl"
import UploadMinimalistic from "@assets/icons/upload-minimalistic.svg?dataurl"
import {preventDefault} from "@lib/html"
import FieldInline from "@lib/components/FieldInline.svelte"
@@ -16,15 +17,7 @@
import ModalBody from "@lib/components/ModalBody.svelte"
import {pushToast} from "@app/util/toast"
import {uploadFile} from "@app/core/commands"
import {deriveHasLivekit} from "@app/core/state"
type RoomMode = "text" | "voice" | "both"
const getRoomMode = (room: RoomMeta): RoomMode => {
if (room.livekit && room.noText) return "voice"
if (room.livekit) return "both"
return "text"
}
import {deriveHasLivekit, getRoomType, RoomType} from "@app/core/state"
type Props = {
url: string
@@ -37,13 +30,13 @@
const {url, header, footer, onsubmit, initialValues = makeRoomMeta()}: Props = $props()
const values = $state(initialValues)
let roomMode = $state<RoomMode>(getRoomMode(initialValues))
let roomType = $state<RoomType>(getRoomType(initialValues))
const relayHasLivekit = deriveHasLivekit(url)
const submit = async () => {
const room = $state.snapshot(values)
if ((roomMode === "voice" || roomMode === "both") && !get(relayHasLivekit)) {
if (roomType === RoomType.Voice && !get(relayHasLivekit)) {
return pushToast({
theme: "error",
message: "This relay does not support voice rooms.",
@@ -71,8 +64,8 @@
}
if (get(relayHasLivekit)) {
room.livekit = roomMode === "both" || roomMode === "voice"
room.noText = roomMode === "voice"
room.livekit = roomType === RoomType.Voice
room.noText = false
}
const editMessage = await waitForThunkError(editRoom(url, room))
@@ -171,7 +164,7 @@
{#if imagePreview}
<ImageIcon src={imagePreview} alt="" class="rounded-lg" />
{:else}
<Icon icon={Hashtag} />
<Icon icon={roomType === RoomType.Voice ? Volume : Hashtag} />
{/if}
<input bind:value={values.name} class="grow" type="text" />
</label>
@@ -195,11 +188,10 @@
{#snippet input()}
<select
class="select select-bordered w-full"
bind:value={roomMode}
bind:value={roomType}
aria-label="Room type">
<option value="text">Text only</option>
<option value="both">Text and voice</option>
<option value="voice">Voice only</option>
<option value={RoomType.Text}>Text</option>
<option value={RoomType.Voice}>Voice</option>
</select>
{/snippet}
</FieldInline>
+20 -1
View File
@@ -1,8 +1,11 @@
<script lang="ts">
import Hashtag from "@assets/icons/hashtag.svg?dataurl"
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 ImageIcon from "@lib/components/ImageIcon.svelte"
import {deriveRoom} from "@app/core/state"
import {currentVoiceSession} from "@app/voice"
interface Props {
h: string
@@ -14,9 +17,25 @@
const {url, h, size = 5, fallbackIcon = Hashtag}: Props = $props()
const room = deriveRoom(url, h)
const isVoiceRoom = $derived($room.livekit)
const isVoiceRoomActive = $derived(
$currentVoiceSession?.url === url && $currentVoiceSession?.h === h,
)
const typeIconSrc = $derived(isVoiceRoomActive ? VolumeLoud : Volume)
</script>
{#if $room.picture}
{#if isVoiceRoom}
<div class="flex items-center gap-1 shrink-0">
<Icon
icon={typeIconSrc}
size={size + 1}
class={isVoiceRoomActive ? "text-primary -translate-x-0.5" : ""} />
{#if $room.picture}
<span class="text-base">/</span>
<ImageIcon src={$room.picture} {size} alt="" class="rounded-lg" />
{/if}
</div>
{:else if $room.picture}
<ImageIcon src={$room.picture} {size} alt="" class="rounded-lg" />
{:else}
<Icon icon={fallbackIcon} {size} />
+6 -20
View File
@@ -38,7 +38,6 @@
import SpaceReports from "@app/components/SpaceReports.svelte"
import RoomCreate from "@app/components/RoomCreate.svelte"
import SpaceMenuRoomItem from "@app/components/SpaceMenuRoomItem.svelte"
import VoiceRoomItem from "@app/components/VoiceRoomItem.svelte"
import VoiceWidget from "@app/components/VoiceWidget.svelte"
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
import {
@@ -47,8 +46,6 @@
deriveSpaceMembers,
deriveUserRooms,
deriveOtherRooms,
deriveRoomsWithLivekit,
deriveRoomsNoText,
deriveOtherVoiceRooms,
userSpaceUrls,
hasNip29,
@@ -73,8 +70,6 @@
const calendarPath = makeSpacePath(url, "calendar")
const userRooms = deriveUserRooms(url)
const otherRooms = deriveOtherRooms(url)
const roomsWithLivekit = deriveRoomsWithLivekit(url)
const roomsNoText = deriveRoomsNoText(url)
const otherVoiceRooms = deriveOtherVoiceRooms(url)
const members = deriveSpaceMembers(url)
const userIsAdmin = deriveUserIsSpaceAdmin(url)
@@ -264,14 +259,7 @@
<SecondaryNavHeader>Your Rooms</SecondaryNavHeader>
{/if}
{#each $userRooms as h (h)}
{#if !$roomsNoText.has(h)}
<SpaceMenuRoomItem notify {replaceState} {url} {h} />
{/if}
{#if $roomsWithLivekit.has(h)}
<div class="hidden md:block">
<VoiceRoomItem {url} {h} />
</div>
{/if}
<SpaceMenuRoomItem notify {replaceState} {url} {h} />
{/each}
{#if $otherRooms.length > 0}
<div class="h-2"></div>
@@ -293,13 +281,11 @@
<SpaceMenuRoomItem {replaceState} {url} {h} />
{/each}
{#if $otherVoiceRooms.length > 0}
<div class="hidden md:block">
<div class="h-2"></div>
<SecondaryNavHeader>Voice Rooms</SecondaryNavHeader>
{#each $otherVoiceRooms as h (h)}
<VoiceRoomItem {url} {h} />
{/each}
</div>
<div class="h-2"></div>
<SecondaryNavHeader>Voice Rooms</SecondaryNavHeader>
{#each $otherVoiceRooms as h (h)}
<SpaceMenuRoomItem {replaceState} {url} {h} />
{/each}
{/if}
{#if $canCreateRoom}
<SecondaryNavItem {replaceState} onclick={addRoom}>
+11 -1
View File
@@ -4,9 +4,10 @@
import Icon from "@lib/components/Icon.svelte"
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import RoomNameWithImage from "@app/components/RoomNameWithImage.svelte"
import {deriveShouldNotify} from "@app/core/state"
import {deriveRoom, deriveShouldNotify, getRoomType, RoomType} from "@app/core/state"
import {notifications} from "@app/util/notifications"
import {makeRoomPath} from "@app/util/routes"
import {joinVoiceRoom, currentVoiceSession} from "@app/voice"
interface Props {
url: any
@@ -17,15 +18,24 @@
const {url, h, notify = false, replaceState = false}: Props = $props()
const room = deriveRoom(url, h)
const roomType = $derived(getRoomType($room))
const path = makeRoomPath(url, h)
const shouldNotifyForSpace = deriveShouldNotify(url)
const shouldNotifyForRoom = deriveShouldNotify(url, h)
const showDifferenceIcon = $derived($shouldNotifyForRoom !== $shouldNotifyForSpace)
const handleClick = () => {
if (roomType !== RoomType.Voice) return
if ($currentVoiceSession?.url === url && $currentVoiceSession?.h === h) return
void joinVoiceRoom(url, h)
}
</script>
<SecondaryNavItem
href={path}
{replaceState}
onclick={handleClick}
notification={notify ? $notifications.has(path) : false}>
<RoomNameWithImage {url} {h} />
{#if showDifferenceIcon}
+1 -3
View File
@@ -1,8 +1,6 @@
<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 SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte"
import RoomImage from "@app/components/RoomImage.svelte"
@@ -62,7 +60,7 @@
{#if joinAbortController}
<span class="loading loading-spinner loading-sm"></span>
{:else}
<RoomImage {url} {h} size={4} fallbackIcon={isActive ? VolumeLoud : Volume} />
<RoomImage {url} {h} size={4} />
{/if}
<RoomName {url} {h} />
</div>
+20 -15
View File
@@ -568,11 +568,19 @@ export const chatSearch = derived(throttled(800, chatsById), $chatsByPubkey => {
// Rooms
export enum RoomType {
Text = "text",
Voice = "voice",
}
export type Room = PublishedRoomMeta & {
id: string
url: string
}
export const getRoomType = (room: {livekit?: boolean}): RoomType =>
room.livekit ? RoomType.Voice : RoomType.Text
export const makeRoomId = (url: string, h: string) => `${url}'${h}`
export const splitRoomId = (id: string) => id.split("'")
@@ -664,7 +672,7 @@ export const displayRoom = (url: string, h: string) => getRoom(makeRoomId(url, h
export const roomComparator = (url: string) => (h: string) => displayRoom(url, h).toLowerCase()
export const deriveRoomsWithLivekit = (url: string) =>
export const deriveVoiceRooms = (url: string) =>
derived(roomsById, $roomsById => {
const set = new Set<string>()
for (const room of $roomsById.values()) {
@@ -687,20 +695,17 @@ export const deriveRoomsNoText = (url: string) =>
})
export const deriveOtherVoiceRooms = (url: string) =>
derived(
[deriveRoomsWithLivekit(url), deriveUserRooms(url)],
([$roomsWithLivekit, $userRooms]) => {
const rooms: string[] = []
derived([deriveVoiceRooms(url), deriveUserRooms(url)], ([$roomsWithLivekit, $userRooms]) => {
const rooms: string[] = []
for (const h of $roomsWithLivekit) {
if (!$userRooms.includes(h)) {
rooms.push(h)
}
for (const h of $roomsWithLivekit) {
if (!$userRooms.includes(h)) {
rooms.push(h)
}
}
return sortBy(roomComparator(url), uniq(rooms))
},
)
return sortBy(roomComparator(url), uniq(rooms))
})
// User space/room lists
@@ -792,12 +797,12 @@ export const deriveUserRooms = (url: string) =>
export const deriveOtherRooms = (url: string) =>
derived(
[deriveUserRooms(url), deriveRoomsNoText(url), roomsByUrl],
([$userRooms, $roomsNoText, $roomsByUrl]) => {
[deriveUserRooms(url), deriveVoiceRooms(url), roomsByUrl],
([$userRooms, voiceRooms, $roomsByUrl]) => {
const rooms: string[] = []
for (const {h} of $roomsByUrl.get(url) || []) {
if (!$userRooms.includes(h) && !$roomsNoText.has(h)) {
if (!$userRooms.includes(h) && !voiceRooms.has(h)) {
rooms.push(h)
}
}
+7 -3
View File
@@ -17,9 +17,13 @@
<div data-component="PageBar" class="cw top-sai fixed z-nav p-2 {props.class}">
<div class="rounded-xl bg-base-100 p-4 shadow-md h-20 md:h-12 flex flex-col justify-center">
<div class="flex items-center justify-between gap-4">
<div class="ellipsize flex items-center gap-4 whitespace-nowrap">
{@render props.icon?.()}
{@render props.title?.()}
<div class="flex min-w-0 flex-1 items-center gap-4 py-0.5 pl-1">
<div class="shrink-0 flex items-center">
{@render props.icon?.()}
</div>
<div class="ellipsize whitespace-nowrap">
{@render props.title?.()}
</div>
</div>
{@render props.action?.()}
</div>