220 lines
5.1 KiB
Vue
220 lines
5.1 KiB
Vue
<template>
|
|
<v-container fluid class="px-0">
|
|
<div class="message-list">
|
|
<chat-list :loader="readMore" :messages="channels.messages" />
|
|
</div>
|
|
</v-container>
|
|
|
|
<v-footer
|
|
app
|
|
class="footer-section flex-col gap-2 min-h-[64px]"
|
|
:style="`padding-bottom: max(${safeAreaBottom}, 6px)`"
|
|
>
|
|
<v-expand-transition>
|
|
<v-expansion-panels v-show="channels.call">
|
|
<v-expansion-panel
|
|
eager
|
|
icon="mdi-phone"
|
|
title="Call is ongoing"
|
|
elevation="1"
|
|
class="call-expansion"
|
|
@group:selected="(val) => val && mountJitsi()"
|
|
>
|
|
<template #text>
|
|
<v-expand-transition>
|
|
<v-progress-linear v-show="joining" indeterminate />
|
|
</v-expand-transition>
|
|
|
|
<div class="call-container w-full h-[360px]">
|
|
<div id="call" class="h-full w-full" v-if="channels.call"></div>
|
|
</div>
|
|
</template>
|
|
</v-expansion-panel>
|
|
</v-expansion-panels>
|
|
</v-expand-transition>
|
|
|
|
<chat-editor class="w-full" @sent="scrollTop" />
|
|
</v-footer>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useChannels } from "@/stores/channels"
|
|
import { request } from "@/scripts/request"
|
|
import { useUI } from "@/stores/ui"
|
|
import { computed, onUnmounted, reactive, ref, watch } from "vue"
|
|
import { useRoute } from "vue-router"
|
|
import { useUserinfo } from "@/stores/userinfo"
|
|
import ChatList from "@/components/chat/ChatList.vue"
|
|
import ChatEditor from "@/components/chat/ChatEditor.vue"
|
|
|
|
const ui = useUI()
|
|
const id = useUserinfo()
|
|
const route = useRoute()
|
|
const channels = useChannels()
|
|
|
|
const safeAreaBottom = computed(() => {
|
|
return `${ui.safeArea.bottom}px`
|
|
})
|
|
|
|
const chatList = ref<HTMLDivElement>()
|
|
|
|
const { showErrorSnackbar } = useUI()
|
|
const loading = ref(false)
|
|
const joining = ref(false)
|
|
|
|
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
|
|
|
async function readCall() {
|
|
loading.value = true
|
|
const res = await request(
|
|
"messaging",
|
|
`/api/channels/${route.params.channel}/calls/ongoing`
|
|
)
|
|
if (res.status !== 200 && res.status !== 404) {
|
|
showErrorSnackbar(await res.text())
|
|
} else if (res.status !== 404) {
|
|
channels.call = await res.json()
|
|
}
|
|
loading.value = false
|
|
}
|
|
|
|
async function readHistory() {
|
|
loading.value = true
|
|
const res = await request(
|
|
"messaging",
|
|
`/api/channels/${route.params.channel}/messages?` + new URLSearchParams({
|
|
take: pagination.pageSize.toString(),
|
|
offset: ((pagination.page - 1) * pagination.pageSize).toString()
|
|
})
|
|
)
|
|
if (res.status !== 200) {
|
|
showErrorSnackbar(await res.text())
|
|
throw new Error()
|
|
} else {
|
|
const data = await res.json()
|
|
pagination.total = data["count"]
|
|
channels.messages.push(...(data["data"] ?? []))
|
|
}
|
|
loading.value = false
|
|
}
|
|
|
|
async function readMore({ done }: any) {
|
|
// Reach the end of data
|
|
if (pagination.total === 0) {
|
|
done("ok")
|
|
return
|
|
}
|
|
if (pagination.total <= pagination.page * pagination.pageSize) {
|
|
done("empty")
|
|
return
|
|
}
|
|
|
|
pagination.page++
|
|
|
|
try {
|
|
await readHistory()
|
|
} catch {
|
|
done("error")
|
|
}
|
|
|
|
if (pagination.total > 0) done("ok")
|
|
else done("empty")
|
|
}
|
|
|
|
watch(
|
|
() => channels.current,
|
|
(val) => {
|
|
if (val) {
|
|
unmountJitsi()
|
|
pagination.page = 1
|
|
pagination.total = 0
|
|
readHistory()
|
|
readCall()
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
function scrollTop() {
|
|
window.scroll({ top: 0 })
|
|
}
|
|
|
|
watch(
|
|
() => channels.call,
|
|
(val) => {
|
|
if (!val) {
|
|
if (jitsiInstance) {
|
|
jitsiInstance.executeCommand("endConference")
|
|
jitsiInstance.executeCommand("hangup")
|
|
}
|
|
unmountJitsi()
|
|
}
|
|
}
|
|
)
|
|
|
|
let mounted = false
|
|
let jitsiInstance: any
|
|
|
|
async function mountJitsi() {
|
|
if (mounted) return false
|
|
if (!channels.call) return
|
|
|
|
joining.value = true
|
|
const tk = await channels.exchangeCallToken()
|
|
joining.value = false
|
|
if (!tk) return
|
|
|
|
const domain = tk.endpoint.replace("http://", "").replace("https://", "")
|
|
const options = {
|
|
roomName: channels.call.external_id,
|
|
parentNode: document.querySelector("#call"),
|
|
jwt: tk.token,
|
|
userInfo: {
|
|
avatar: id.userinfo.data?.picture,
|
|
displayName: id.userinfo.displayName
|
|
},
|
|
configOverwrite: {
|
|
prejoinPageEnabled: true,
|
|
startWithAudioMuted: false,
|
|
startWithVideoMuted: true
|
|
}
|
|
}
|
|
// This class imported by the script tag in index.html
|
|
// @ts-ignore
|
|
jitsiInstance = new JitsiMeetExternalAPI(domain, options)
|
|
mounted = true
|
|
}
|
|
|
|
function unmountJitsi() {
|
|
mounted = false
|
|
if (jitsiInstance) {
|
|
jitsiInstance.dispose()
|
|
jitsiInstance = null
|
|
}
|
|
}
|
|
|
|
onUnmounted(() => unmountJitsi())
|
|
</script>
|
|
|
|
<style scoped>
|
|
.footer-section {
|
|
background: rgba(255, 255, 255, 0.65);
|
|
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
|
backdrop-filter: blur(5px);
|
|
-webkit-backdrop-filter: blur(5px);
|
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
|
|
|
padding-top: 8px !important;
|
|
padding-left: 8px !important;
|
|
padding-right: 8px !important;
|
|
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
</style>
|
|
|
|
<style>
|
|
.call-expansion .v-expansion-panel-text__wrapper {
|
|
padding: 0 !important;
|
|
}
|
|
</style> |