217 lines
5.4 KiB
Vue
217 lines
5.4 KiB
Vue
<template>
|
|
<div class="grid grid-cols-[360px_1fr] gap-2 p-2 h-screen grid-rows-[auto_1fr] max-w-full">
|
|
<div
|
|
class="flex items-center justify-between gap-2 rounded-xl p-3 bg-surface-950"
|
|
>
|
|
<div class="inline-flex items-center gap-3">
|
|
<PrimeBadge class="opacity-50" severity="secondary" :value="version" size="small" />
|
|
<PrimeBadge :severity="connected ? 'success' : 'danger' " />
|
|
</div>
|
|
|
|
<PrimeButtonGroup class="ml-auto">
|
|
<PrimeButton :severity="inputMuted ? 'info' : undefined" outlined @click="toggleInput">
|
|
<template #icon>
|
|
<Component :is="inputMuted ? MicOff : Mic" />
|
|
</template>
|
|
</PrimeButton>
|
|
<PrimeButton :severity="outputMuted ? 'info' : undefined" outlined @click="toggleOutput">
|
|
<template #icon>
|
|
<Component :is="outputMuted ? VolumeOff : Volume2" />
|
|
</template>
|
|
</PrimeButton>
|
|
</PrimeButtonGroup>
|
|
|
|
<PrimeButton :severity="videoEnabled ? 'success' : undefined" outlined @click="toggleVideo">
|
|
<template #icon>
|
|
<Component :is="videoEnabled ? CameraOff : Camera" />
|
|
</template>
|
|
</PrimeButton>
|
|
|
|
<PrimeButton :severity="sharingEnabled ? 'success' : undefined" outlined @click="toggleShare">
|
|
<template #icon>
|
|
<Component :is="sharingEnabled ? ScreenShareOff : ScreenShare" />
|
|
</template>
|
|
</PrimeButton>
|
|
</div>
|
|
|
|
<div
|
|
class="flex items-center justify-center rounded-xl p-3 bg-surface-950"
|
|
>
|
|
<PrimeSelectButton
|
|
v-model="activeTab"
|
|
:options="tabs"
|
|
option-label="id"
|
|
:allow-empty="false"
|
|
style="--p-togglebutton-content-padding: 0.25rem 0.5rem"
|
|
>
|
|
<template #option="{ option }">
|
|
<Component :is="option.icon" size="24" />
|
|
</template>
|
|
</PrimeSelectButton>
|
|
</div>
|
|
|
|
<PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0">
|
|
<div v-auto-animate class="p-3 space-y-1">
|
|
<template v-for="channel in channels" :key="channel.id">
|
|
<PrimeDivider>
|
|
<PrimeButton size="small" variant="text" @click="joinChannel(channel)">
|
|
{{ channel.name }}
|
|
</PrimeButton>
|
|
</PrimeDivider>
|
|
<ClientRow v-for="client in clients.filter(_client => _client.channelId === channel.id)" :key="client.socketId" :client="client">
|
|
{{ client.userId }}
|
|
</ClientRow>
|
|
</template>
|
|
<!-- <ClientRow v-for="client of clients" :key="client.userId" :client="client" /> -->
|
|
</div>
|
|
</PrimeScrollPanel>
|
|
|
|
<div class="bg-surface-900 rounded-xl overflow-hidden p-3 flex flex-col min-h-full">
|
|
<dl>
|
|
<dt>Socket ID</dt>
|
|
<dd>{{ socket?.id }}</dd>
|
|
|
|
<br>
|
|
<dt>Producers</dt>
|
|
<dd v-for="producer in producersArray" :key="producer.id">
|
|
{{ producer.id }}
|
|
{{ producer.appData }}
|
|
</dd>
|
|
|
|
<br>
|
|
<dl>Consumers</dl>
|
|
<dd v-for="consumer in consumersArray" :key="consumer.id">
|
|
{{ consumer.id }}
|
|
{{ consumer.appData }}
|
|
</dd>
|
|
</dl>
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
|
|
<FullscreenGallery />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import chadApi from '#shared/chad-api'
|
|
import {
|
|
Camera,
|
|
CameraOff,
|
|
MessageCircle,
|
|
Mic,
|
|
MicOff,
|
|
ScreenShare,
|
|
ScreenShareOff,
|
|
Settings,
|
|
TvMinimalPlay,
|
|
UserPen,
|
|
Volume2,
|
|
VolumeOff,
|
|
} from 'lucide-vue-next'
|
|
|
|
const channels = shallowRef<any[]>([])
|
|
|
|
;(async () => {
|
|
channels.value = await chadApi<any[]>('/channels', { method: 'GET' })
|
|
})()
|
|
|
|
const { me } = useClients()
|
|
const {
|
|
version,
|
|
clients,
|
|
inputMuted,
|
|
outputMuted,
|
|
videoEnabled,
|
|
sharingEnabled,
|
|
somebodyStreamingVideo,
|
|
toggleInput,
|
|
toggleOutput,
|
|
toggleVideo,
|
|
toggleShare,
|
|
} = useApp()
|
|
const { connect, connected, socket } = useSignaling()
|
|
const { consumersArray, producersArray } = useMediasoup()
|
|
|
|
interface Tab {
|
|
id: string
|
|
icon: Component
|
|
onClick: () => void | Promise<void>
|
|
}
|
|
|
|
const route = useRoute()
|
|
|
|
const tabs = computed<Tab[]>(() => {
|
|
const result = []
|
|
|
|
if (somebodyStreamingVideo.value) {
|
|
result.push({
|
|
id: 'Gallery',
|
|
icon: TvMinimalPlay,
|
|
onClick: () => {
|
|
navigateTo({ name: 'Gallery' })
|
|
},
|
|
})
|
|
}
|
|
|
|
result.push(
|
|
{
|
|
id: 'Index',
|
|
icon: MessageCircle,
|
|
onClick: () => {
|
|
navigateTo({ name: 'Index' })
|
|
},
|
|
},
|
|
{
|
|
id: 'Profile',
|
|
icon: UserPen,
|
|
onClick: () => {
|
|
navigateTo({ name: 'Profile' })
|
|
},
|
|
},
|
|
{
|
|
id: 'Preferences',
|
|
icon: Settings,
|
|
onClick: () => {
|
|
navigateTo({ name: 'Preferences' })
|
|
},
|
|
},
|
|
)
|
|
|
|
return result
|
|
})
|
|
|
|
const activeTab = ref<Tab>(tabs.value.find(tab => tab.id === route.name) ?? tabs.value.find(tab => tab.id === 'Index')!)
|
|
|
|
watch(activeTab, (activeTab) => {
|
|
activeTab.onClick()
|
|
})
|
|
|
|
connect()
|
|
|
|
async function joinChannel(channel) {
|
|
socket.value?.emit('join-channel', { channelId: channel.id })
|
|
}
|
|
|
|
watch(socket, (socket) => {
|
|
if (!socket)
|
|
return
|
|
|
|
socket.on('channel-removed', (channelId) => {
|
|
const idx = channels.value.findIndex(channel => channel.id === channelId)
|
|
|
|
if (idx === -1)
|
|
return
|
|
|
|
channels.value.splice(idx, 1)
|
|
|
|
triggerRef(channels)
|
|
})
|
|
|
|
socket.on('channel-created', (channel) => {
|
|
channels.value.push(channel)
|
|
|
|
triggerRef(channels)
|
|
})
|
|
}, { immediate: true })
|
|
</script>
|