Simplify how video call layout is stored
This commit is contained in:
+1
-1
@@ -410,7 +410,7 @@ progress[value]::-webkit-progress-value {
|
|||||||
transition: width 0.5s;
|
transition: width 0.5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Anchors for fixed overlays (compose, search, reply) — main scroll lives in Page / PageContent flow */
|
/* content width for fixed elements */
|
||||||
|
|
||||||
.left-content {
|
.left-content {
|
||||||
@apply md:left-[calc(18.5rem+var(--sail))];
|
@apply md:left-[calc(18.5rem+var(--sail))];
|
||||||
|
|||||||
@@ -11,23 +11,22 @@
|
|||||||
import {
|
import {
|
||||||
currentVoiceSession,
|
currentVoiceSession,
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
|
VideoCallLayout,
|
||||||
videoCallLayoutRevision,
|
videoCallLayoutRevision,
|
||||||
videoPrimaryTileKey,
|
videoPrimaryTileKey,
|
||||||
toggleVideoPrimaryTile,
|
toggleVideoPrimaryTile,
|
||||||
pubkeyFromLiveKitIdentity,
|
pubkeyFromLiveKitIdentity,
|
||||||
} from "@app/voice"
|
} from "@app/voice"
|
||||||
|
|
||||||
type Variant = "mobile" | "desktop-split" | "desktop-full"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
variant: Variant
|
layout: VideoCallLayout
|
||||||
|
mobile?: boolean
|
||||||
url: string
|
url: string
|
||||||
h: string
|
h: string
|
||||||
visible?: boolean
|
|
||||||
class?: string
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Tile = {
|
type VideoTile = {
|
||||||
identity: string
|
identity: string
|
||||||
isLocal: boolean
|
isLocal: boolean
|
||||||
trackSid: string
|
trackSid: string
|
||||||
@@ -37,11 +36,18 @@
|
|||||||
|
|
||||||
type TileLayout = "spotlight" | "default" | "strip"
|
type TileLayout = "spotlight" | "default" | "strip"
|
||||||
|
|
||||||
const {variant, url, h, visible = true, class: className = ""}: Props = $props()
|
const {layout, mobile = false, url, h, class: className = ""}: Props = $props()
|
||||||
|
|
||||||
const roomMatches = $derived($currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h)
|
const isViewingCurrentCallRoom = $derived(
|
||||||
|
$currentVoiceRoom?.url === url && $currentVoiceRoom?.h === h,
|
||||||
|
)
|
||||||
|
|
||||||
const showPanel = $derived(visible && roomMatches)
|
const showPanel = $derived(
|
||||||
|
isViewingCurrentCallRoom &&
|
||||||
|
(mobile
|
||||||
|
? layout === VideoCallLayout.Video
|
||||||
|
: layout === VideoCallLayout.Split || layout === VideoCallLayout.Video),
|
||||||
|
)
|
||||||
|
|
||||||
const tiles = $derived.by(() => {
|
const tiles = $derived.by(() => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- re-run when remote video subscribes
|
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- re-run when remote video subscribes
|
||||||
@@ -52,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const room = session.room
|
const room = session.room
|
||||||
const out: Tile[] = []
|
const out: VideoTile[] = []
|
||||||
const lp = room.localParticipant
|
const lp = room.localParticipant
|
||||||
|
|
||||||
if (session.cameraOn) {
|
if (session.cameraOn) {
|
||||||
@@ -104,7 +110,7 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
/** Identity + source only — LiveKit can change trackSid after publish, which broke spotlight + stale-key effect. */
|
/** Identity + source only — LiveKit can change trackSid after publish, which broke spotlight + stale-key effect. */
|
||||||
const tileKey = (t: Tile) => `${t.identity}\x1f${t.source}`
|
const tileKey = (t: VideoTile) => `${t.identity}\x1f${t.source}`
|
||||||
|
|
||||||
const primaryTile = $derived.by(() => {
|
const primaryTile = $derived.by(() => {
|
||||||
const k = $videoPrimaryTileKey
|
const k = $videoPrimaryTileKey
|
||||||
@@ -137,7 +143,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const labelFor = (identity: string, source: Tile["source"]) => {
|
const labelFor = (identity: string, source: VideoTile["source"]) => {
|
||||||
const pk = pubkeyFromLiveKitIdentity(identity)
|
const pk = pubkeyFromLiveKitIdentity(identity)
|
||||||
const name = pk ? displayProfileByPubkey(pk) : "Unknown"
|
const name = pk ? displayProfileByPubkey(pk) : "Unknown"
|
||||||
return source === Track.Source.ScreenShare ? `${name} · screen` : name
|
return source === Track.Source.ScreenShare ? `${name} · screen` : name
|
||||||
@@ -151,18 +157,16 @@
|
|||||||
|
|
||||||
const panelChrome = $derived(
|
const panelChrome = $derived(
|
||||||
cx(
|
cx(
|
||||||
variant === "mobile" &&
|
mobile &&
|
||||||
"flex min-h-0 w-full flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden bg-base-200 px-2 pt-4 md:hidden pb-[calc(3.5rem+var(--saib))]",
|
"flex min-h-0 w-full flex-1 flex-col gap-2 overflow-y-auto overflow-x-hidden bg-base-200 px-2 pt-4 md:hidden pb-[calc(3.5rem+var(--saib))]",
|
||||||
variant === "desktop-split" &&
|
!mobile &&
|
||||||
"flex min-h-0 w-full min-w-0 flex-1 flex-col gap-2 overflow-hidden bg-base-200 px-2 pb-2 pt-4",
|
|
||||||
variant === "desktop-full" &&
|
|
||||||
"flex min-h-0 w-full min-w-0 flex-1 flex-col gap-2 overflow-hidden bg-base-200 px-2 pb-2 pt-4",
|
"flex min-h-0 w-full min-w-0 flex-1 flex-col gap-2 overflow-hidden bg-base-200 px-2 pb-2 pt-4",
|
||||||
className,
|
className,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet videoTile(tile: Tile, layout: TileLayout)}
|
{#snippet videoTile(tile: VideoTile, layout: TileLayout)}
|
||||||
<div
|
<div
|
||||||
class={cx(
|
class={cx(
|
||||||
"relative isolate overflow-hidden rounded-box shadow-sm",
|
"relative isolate overflow-hidden rounded-box shadow-sm",
|
||||||
@@ -241,7 +245,7 @@
|
|||||||
|
|
||||||
{#if showPanel}
|
{#if showPanel}
|
||||||
<div class={panelChrome}>
|
<div class={panelChrome}>
|
||||||
{#if variant === "mobile"}
|
{#if mobile}
|
||||||
<div class="flex min-h-0 flex-1 flex-col gap-2">
|
<div class="flex min-h-0 flex-1 flex-col gap-2">
|
||||||
<div class="min-h-0 flex-1 overflow-hidden">
|
<div class="min-h-0 flex-1 overflow-hidden">
|
||||||
{@render videoPanelBody()}
|
{@render videoPanelBody()}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<script module lang="ts">
|
||||||
|
import {MediaQuery} from "svelte/reactivity"
|
||||||
|
|
||||||
|
export const isDesktopLayout = new MediaQuery("min-width: 768px", false)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import {get} from "svelte/store"
|
||||||
|
import {VideoCallLayout, videoCallLayout} from "@app/voice"
|
||||||
|
|
||||||
|
let prevLayout = $state<boolean | undefined>(undefined)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
const currentLayout = isDesktopLayout.current
|
||||||
|
if (prevLayout === undefined) {
|
||||||
|
prevLayout = currentLayout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (prevLayout === currentLayout) return
|
||||||
|
const p = get(videoCallLayout)
|
||||||
|
if (prevLayout && !currentLayout) {
|
||||||
|
if (p === VideoCallLayout.Split) videoCallLayout.set(VideoCallLayout.Video)
|
||||||
|
} else if (!prevLayout && currentLayout) {
|
||||||
|
if (p === VideoCallLayout.Chat) videoCallLayout.set(VideoCallLayout.Split)
|
||||||
|
}
|
||||||
|
prevLayout = currentLayout
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {readable} from "svelte/store"
|
import {readable} from "svelte/store"
|
||||||
import {fade, fly} from "svelte/transition"
|
import {fade, fly} from "svelte/transition"
|
||||||
import {browser} from "$app/environment"
|
|
||||||
import {goto} from "$app/navigation"
|
import {goto} from "$app/navigation"
|
||||||
import {page} from "$app/stores"
|
import {page} from "$app/stores"
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
@@ -20,6 +19,7 @@
|
|||||||
import Button from "@lib/components/Button.svelte"
|
import Button from "@lib/components/Button.svelte"
|
||||||
import VoiceCallAudioSettingsDialog from "@app/components/VoiceCallAudioSettingsDialog.svelte"
|
import VoiceCallAudioSettingsDialog from "@app/components/VoiceCallAudioSettingsDialog.svelte"
|
||||||
import VoiceRoomJoinDialog from "@app/components/VoiceRoomJoinDialog.svelte"
|
import VoiceRoomJoinDialog from "@app/components/VoiceRoomJoinDialog.svelte"
|
||||||
|
import {isDesktopLayout} from "@app/components/VideoCallLayoutViewportSync.svelte"
|
||||||
import {
|
import {
|
||||||
decodeRelay,
|
decodeRelay,
|
||||||
deriveRoom,
|
deriveRoom,
|
||||||
@@ -33,11 +33,11 @@
|
|||||||
import {makeRoomPath} from "@app/util/routes"
|
import {makeRoomPath} from "@app/util/routes"
|
||||||
import {
|
import {
|
||||||
VoiceState,
|
VoiceState,
|
||||||
|
VideoCallLayout,
|
||||||
currentVoiceSession,
|
currentVoiceSession,
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
voiceState,
|
voiceState,
|
||||||
voiceMobileRoomPanel,
|
videoCallLayout,
|
||||||
voiceDesktopRoomPanel,
|
|
||||||
isLocalSpeaking,
|
isLocalSpeaking,
|
||||||
leaveVoiceRoom,
|
leaveVoiceRoom,
|
||||||
toggleMute,
|
toggleMute,
|
||||||
@@ -90,21 +90,6 @@
|
|||||||
pushModal(VoiceCallAudioSettingsDialog)
|
pushModal(VoiceCallAudioSettingsDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
let isMd = $state(
|
|
||||||
typeof window !== "undefined" && window.matchMedia("(min-width: 768px)").matches,
|
|
||||||
)
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (!browser) return
|
|
||||||
const mq = window.matchMedia("(min-width: 768px)")
|
|
||||||
const sync = () => {
|
|
||||||
isMd = mq.matches
|
|
||||||
}
|
|
||||||
sync()
|
|
||||||
mq.addEventListener("change", sync)
|
|
||||||
return () => mq.removeEventListener("change", sync)
|
|
||||||
})
|
|
||||||
|
|
||||||
const showVoiceLayoutToggle = $derived(
|
const showVoiceLayoutToggle = $derived(
|
||||||
$voiceState === VoiceState.Connected &&
|
$voiceState === VoiceState.Connected &&
|
||||||
targetRoom !== undefined &&
|
targetRoom !== undefined &&
|
||||||
@@ -115,17 +100,24 @@
|
|||||||
h === targetRoom.h,
|
h === targetRoom.h,
|
||||||
)
|
)
|
||||||
|
|
||||||
const layoutToggleActive = $derived(
|
const isChatPanelActive = $derived(
|
||||||
showVoiceLayoutToggle &&
|
showVoiceLayoutToggle &&
|
||||||
((!isMd && $voiceMobileRoomPanel === "chat") || (isMd && $voiceDesktopRoomPanel === "split")),
|
((!isDesktopLayout.current &&
|
||||||
|
($videoCallLayout === VideoCallLayout.Chat ||
|
||||||
|
$videoCallLayout === VideoCallLayout.Split)) ||
|
||||||
|
(isDesktopLayout.current && $videoCallLayout === VideoCallLayout.Split)),
|
||||||
)
|
)
|
||||||
|
|
||||||
const onLayoutToggle = () => {
|
const onChatToggle = () => {
|
||||||
if (!showVoiceLayoutToggle) return
|
if (!showVoiceLayoutToggle) return
|
||||||
if (isMd) {
|
if (isDesktopLayout.current) {
|
||||||
voiceDesktopRoomPanel.update(p => (p === "split" ? "video" : "split"))
|
videoCallLayout.update(p =>
|
||||||
|
p === VideoCallLayout.Split ? VideoCallLayout.Video : VideoCallLayout.Split,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
voiceMobileRoomPanel.update(p => (p === "chat" ? "video" : "chat"))
|
videoCallLayout.update(p =>
|
||||||
|
p === VideoCallLayout.Video ? VideoCallLayout.Chat : VideoCallLayout.Video,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,9 +169,9 @@
|
|||||||
class={cx(
|
class={cx(
|
||||||
mediaToggleClass,
|
mediaToggleClass,
|
||||||
"relative shrink-0 overflow-visible",
|
"relative shrink-0 overflow-visible",
|
||||||
layoutToggleActive && "text-primary",
|
isChatPanelActive && "text-primary",
|
||||||
)}
|
)}
|
||||||
onclick={onLayoutToggle}>
|
onclick={onChatToggle}>
|
||||||
<span class="relative inline-flex">
|
<span class="relative inline-flex">
|
||||||
<Icon icon={ChatRound} size={4} />
|
<Icon icon={ChatRound} size={4} />
|
||||||
{#if chatUnread}
|
{#if chatUnread}
|
||||||
|
|||||||
+11
-9
@@ -87,15 +87,17 @@ export const voiceState = writable<VoiceState>(VoiceState.Disconnected)
|
|||||||
|
|
||||||
export const currentVoiceRoom = writable<Room | undefined>(undefined)
|
export const currentVoiceRoom = writable<Room | undefined>(undefined)
|
||||||
|
|
||||||
/** Mobile room UI: full-screen chat vs video (see VoiceWidget layout toggle). */
|
/** Chat-only, full-width video, or split (desktop). On narrow viewports, `split` shows as chat until resize remaps it. */
|
||||||
export const voiceMobileRoomPanel = writable<"chat" | "video">("chat")
|
export enum VideoCallLayout {
|
||||||
|
Chat = "chat",
|
||||||
|
Video = "video",
|
||||||
|
Split = "split",
|
||||||
|
}
|
||||||
|
|
||||||
/** Desktop room UI: messages only, video only, or split (see VoiceWidget layout toggle). */
|
export const videoCallLayout = writable<VideoCallLayout>(VideoCallLayout.Split)
|
||||||
export const voiceDesktopRoomPanel = writable<"chat" | "video" | "split">("split")
|
|
||||||
|
|
||||||
const resetVoiceRoomPanels = () => {
|
const resetVideoCallLayout = () => {
|
||||||
voiceMobileRoomPanel.set("chat")
|
videoCallLayout.set(VideoCallLayout.Chat)
|
||||||
voiceDesktopRoomPanel.set("chat")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const participantPubkeyMap = writable<Map<string, Pubkey>>(new Map())
|
export const participantPubkeyMap = writable<Map<string, Pubkey>>(new Map())
|
||||||
@@ -240,7 +242,7 @@ const onRoomDisconnected = (reason?: DisconnectReason) => {
|
|||||||
videoCallLayoutRevision.set(0)
|
videoCallLayoutRevision.set(0)
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
currentVoiceSession.set(undefined)
|
currentVoiceSession.set(undefined)
|
||||||
resetVoiceRoomPanels()
|
resetVideoCallLayout()
|
||||||
if (reason !== undefined && reason !== DisconnectReason.CLIENT_INITIATED) {
|
if (reason !== undefined && reason !== DisconnectReason.CLIENT_INITIATED) {
|
||||||
voiceState.set(VoiceState.Disconnected)
|
voiceState.set(VoiceState.Disconnected)
|
||||||
const message =
|
const message =
|
||||||
@@ -408,7 +410,7 @@ export const leaveVoiceRoom = async () => {
|
|||||||
videoCallLayoutRevision.set(0)
|
videoCallLayoutRevision.set(0)
|
||||||
videoPrimaryTileKey.set(undefined)
|
videoPrimaryTileKey.set(undefined)
|
||||||
currentVoiceSession.set(undefined)
|
currentVoiceSession.set(undefined)
|
||||||
resetVoiceRoomPanels()
|
resetVideoCallLayout()
|
||||||
session.room.disconnect()
|
session.room.disconnect()
|
||||||
speakingParticipants.set([])
|
speakingParticipants.set([])
|
||||||
participantPubkeyMap.set(new Map())
|
participantPubkeyMap.set(new Map())
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
import {syncKeyboard} from "@app/util/keyboard"
|
import {syncKeyboard} from "@app/util/keyboard"
|
||||||
import {getPageTitle} from "@app/util/title"
|
import {getPageTitle} from "@app/util/title"
|
||||||
import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte"
|
import NewNotificationSound from "@src/app/components/NewNotificationSound.svelte"
|
||||||
|
import VideoCallLayoutViewportSync from "@app/components/VideoCallLayoutViewportSync.svelte"
|
||||||
|
|
||||||
const {children} = $props()
|
const {children} = $props()
|
||||||
|
|
||||||
@@ -229,5 +230,6 @@
|
|||||||
<ModalContainer />
|
<ModalContainer />
|
||||||
<div class="tippy-target"></div>
|
<div class="tippy-target"></div>
|
||||||
<NewNotificationSound />
|
<NewNotificationSound />
|
||||||
|
<VideoCallLayoutViewportSync />
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
|
|||||||
@@ -47,10 +47,10 @@
|
|||||||
import VideoCallContent from "@app/components/VideoCallContent.svelte"
|
import VideoCallContent from "@app/components/VideoCallContent.svelte"
|
||||||
import {
|
import {
|
||||||
VoiceState,
|
VoiceState,
|
||||||
|
VideoCallLayout,
|
||||||
currentVoiceRoom,
|
currentVoiceRoom,
|
||||||
videoTileCount,
|
videoTileCount,
|
||||||
voiceMobileRoomPanel,
|
videoCallLayout,
|
||||||
voiceDesktopRoomPanel,
|
|
||||||
voiceState,
|
voiceState,
|
||||||
} from "@app/voice"
|
} from "@app/voice"
|
||||||
import {makeFeed} from "@app/core/requests"
|
import {makeFeed} from "@app/core/requests"
|
||||||
@@ -74,19 +74,20 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
const showMobileVideoPanel = $derived(
|
const showMobileVideoPanel = $derived(
|
||||||
isVoiceRoom && $voiceState === VoiceState.Connected && $voiceMobileRoomPanel === "video",
|
isVoiceRoom &&
|
||||||
|
$voiceState === VoiceState.Connected &&
|
||||||
|
$videoCallLayout === VideoCallLayout.Video,
|
||||||
)
|
)
|
||||||
|
|
||||||
const pageContentHiddenDesktopVideoOnly = $derived(
|
const pageContentHiddenDesktopVideoOnly = $derived(
|
||||||
voiceConnectedHere && $voiceDesktopRoomPanel === "video",
|
voiceConnectedHere && $videoCallLayout === VideoCallLayout.Video,
|
||||||
)
|
)
|
||||||
|
|
||||||
let prevVideoTileCount = $state(0)
|
let prevVideoTileCount = $state(0)
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if ($voiceState !== VoiceState.Connected) {
|
if ($voiceState !== VoiceState.Connected) {
|
||||||
voiceMobileRoomPanel.set("chat")
|
videoCallLayout.set(VideoCallLayout.Chat)
|
||||||
voiceDesktopRoomPanel.set("chat")
|
|
||||||
prevVideoTileCount = 0
|
prevVideoTileCount = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -100,8 +101,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (prevVideoTileCount === 0 && n >= 1) {
|
if (prevVideoTileCount === 0 && n >= 1) {
|
||||||
voiceDesktopRoomPanel.set("video")
|
videoCallLayout.set(VideoCallLayout.Video)
|
||||||
voiceMobileRoomPanel.set("video")
|
|
||||||
}
|
}
|
||||||
prevVideoTileCount = n
|
prevVideoTileCount = n
|
||||||
})
|
})
|
||||||
@@ -416,35 +416,23 @@
|
|||||||
<div
|
<div
|
||||||
class={cx(
|
class={cx(
|
||||||
"flex min-h-0 flex-1 flex-col",
|
"flex min-h-0 flex-1 flex-col",
|
||||||
voiceConnectedHere && $voiceDesktopRoomPanel === "split" && "md:flex-row",
|
voiceConnectedHere && $videoCallLayout === VideoCallLayout.Split && "md:flex-row",
|
||||||
)}>
|
)}>
|
||||||
{#if voiceConnectedHere}
|
{#if voiceConnectedHere}
|
||||||
<VideoCallContent
|
<VideoCallContent
|
||||||
variant="desktop-split"
|
layout={$videoCallLayout}
|
||||||
{url}
|
{url}
|
||||||
{h}
|
{h}
|
||||||
visible={$voiceDesktopRoomPanel === "split"}
|
|
||||||
class="hidden min-h-0 w-full min-w-0 flex-1 flex-col md:flex" />
|
|
||||||
<VideoCallContent
|
|
||||||
variant="desktop-full"
|
|
||||||
{url}
|
|
||||||
{h}
|
|
||||||
visible={$voiceDesktopRoomPanel === "video"}
|
|
||||||
class="hidden min-h-0 w-full min-w-0 flex-1 flex-col md:flex" />
|
class="hidden min-h-0 w-full min-w-0 flex-1 flex-col md:flex" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={cx(
|
class={cx(
|
||||||
"flex min-h-0 min-w-0 flex-1 flex-col",
|
"flex min-h-0 min-w-0 flex-1 flex-col",
|
||||||
voiceConnectedHere && $voiceDesktopRoomPanel === "video" && "md:hidden",
|
voiceConnectedHere && $videoCallLayout === VideoCallLayout.Video && "md:hidden",
|
||||||
)}>
|
)}>
|
||||||
{#if isVoiceRoom && $voiceState === VoiceState.Connected}
|
{#if isVoiceRoom && $voiceState === VoiceState.Connected}
|
||||||
<VideoCallContent
|
<VideoCallContent layout={$videoCallLayout} mobile {url} {h} class="md:hidden" />
|
||||||
variant="mobile"
|
|
||||||
{url}
|
|
||||||
{h}
|
|
||||||
visible={$voiceMobileRoomPanel === "video"}
|
|
||||||
class="md:hidden" />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<PageContent
|
<PageContent
|
||||||
|
|||||||
Reference in New Issue
Block a user