217 lines
5.0 KiB
Vue
217 lines
5.0 KiB
Vue
<template>
|
|
<aside v-if="chatStore.hasVideoSessions" class="video-dock">
|
|
<section class="video-dock-card video-dock-card-self">
|
|
<div class="video-card-frame video-card-frame-self">
|
|
<video ref="selfVideoRef" autoplay muted playsinline></video>
|
|
<div v-if="!chatStore.selfPreviewStream" class="video-card-placeholder">
|
|
<strong>Eigene Vorschau</strong>
|
|
<span>Kamera nicht aktiv</span>
|
|
</div>
|
|
</div>
|
|
<div class="video-card-meta">
|
|
<strong>Du</strong>
|
|
<span>{{ chatStore.selfMuted ? 'Mikro aus' : 'Mikro an' }}</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section
|
|
v-for="session in chatStore.dockVideoSessions"
|
|
:key="session.callId"
|
|
class="video-dock-card"
|
|
>
|
|
<div
|
|
class="video-card-frame video-card-frame-clickable"
|
|
role="button"
|
|
tabindex="0"
|
|
:title="`${session.withUserName} in den Vordergrund holen`"
|
|
@click="chatStore.bringVideoSessionToFront(session.callId)"
|
|
@keyup.enter="chatStore.bringVideoSessionToFront(session.callId)"
|
|
@keyup.space.prevent="chatStore.bringVideoSessionToFront(session.callId)"
|
|
>
|
|
<VideoSessionSurface :session="session" :muted="true" />
|
|
</div>
|
|
<div class="video-card-meta">
|
|
<strong>{{ session.withUserName }}</strong>
|
|
<span>{{ session.remoteMuted ? 'Mikro aus' : 'Mikro an' }}</span>
|
|
</div>
|
|
<div class="video-card-actions">
|
|
<button type="button" @click="chatStore.bringVideoSessionToFront(session.callId)">
|
|
In den Vordergrund
|
|
</button>
|
|
<button
|
|
v-if="session.status === 'ringing' && session.initiatedBy !== chatStore.userName"
|
|
type="button"
|
|
class="secondary"
|
|
@click="chatStore.acceptVideoCall(session.callId)"
|
|
>
|
|
Annehmen
|
|
</button>
|
|
<button
|
|
v-else-if="session.status === 'ringing' && session.initiatedBy === chatStore.userName"
|
|
type="button"
|
|
class="secondary"
|
|
@click="chatStore.cancelVideoCall(session.callId)"
|
|
>
|
|
Abbrechen
|
|
</button>
|
|
<button
|
|
v-else-if="session.status === 'connecting' || session.status === 'active'"
|
|
type="button"
|
|
class="secondary"
|
|
@click="chatStore.endVideoCall(session.callId)"
|
|
>
|
|
Beenden
|
|
</button>
|
|
<button
|
|
v-if="session.status === 'ringing' && session.initiatedBy !== chatStore.userName"
|
|
type="button"
|
|
class="danger"
|
|
@click="chatStore.rejectVideoCall(session.callId)"
|
|
>
|
|
Ablehnen
|
|
</button>
|
|
</div>
|
|
</section>
|
|
</aside>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
import { useChatStore } from '../stores/chat';
|
|
import VideoSessionSurface from './VideoSessionSurface.vue';
|
|
|
|
const chatStore = useChatStore();
|
|
const selfVideoRef = ref(null);
|
|
|
|
watch(
|
|
() => chatStore.selfPreviewStream,
|
|
(stream) => {
|
|
if (selfVideoRef.value) {
|
|
selfVideoRef.value.srcObject = stream || null;
|
|
}
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
onBeforeUnmount(() => {
|
|
if (selfVideoRef.value) {
|
|
selfVideoRef.value.srcObject = null;
|
|
}
|
|
});
|
|
|
|
function statusLabel(status) {
|
|
switch (status) {
|
|
case 'ringing':
|
|
return 'Klingelt';
|
|
case 'connecting':
|
|
return 'Verbindet';
|
|
case 'active':
|
|
return 'Aktiv';
|
|
default:
|
|
return status;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.video-dock {
|
|
width: min(16vw, 240px);
|
|
min-width: 180px;
|
|
max-width: 240px;
|
|
padding: 12px;
|
|
border-left: 1px solid #dfe6e1;
|
|
background: linear-gradient(180deg, #f5f8f6 0%, #edf3ef 100%);
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.video-dock-card {
|
|
border: 1px solid #d5dfd8;
|
|
border-radius: 12px;
|
|
background: #ffffff;
|
|
overflow: hidden;
|
|
box-shadow: 0 10px 20px rgba(25, 39, 31, 0.07);
|
|
}
|
|
|
|
.video-card-frame {
|
|
aspect-ratio: 16 / 10;
|
|
background: #0e1511;
|
|
position: relative;
|
|
}
|
|
|
|
.video-card-frame-clickable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.video-card-frame video {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
transform: scaleX(-1);
|
|
}
|
|
|
|
.video-card-frame-self video {
|
|
object-fit: cover;
|
|
}
|
|
|
|
.video-card-meta {
|
|
padding: 10px 12px 6px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 3px;
|
|
}
|
|
|
|
.video-card-meta strong {
|
|
color: #1d2821;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.video-card-meta span {
|
|
color: #59685e;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.video-card-actions {
|
|
padding: 0 12px 12px;
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
.video-card-actions button {
|
|
min-height: 34px;
|
|
border: 0;
|
|
border-radius: 8px;
|
|
padding: 0 10px;
|
|
background: #1d6a42;
|
|
color: #ffffff;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.video-card-actions button.secondary {
|
|
background: #edf2ee;
|
|
color: #1d6a42;
|
|
border: 1px solid #c9d7cd;
|
|
}
|
|
|
|
.video-card-actions button.danger {
|
|
background: #a23333;
|
|
}
|
|
|
|
@media (max-width: 1100px) {
|
|
.video-dock {
|
|
width: 200px;
|
|
min-width: 200px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 860px) {
|
|
.video-dock {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|