Files
singlechat/client/src/components/VideoDock.vue
Torsten Schulz (local) c46e64367d
All checks were successful
Deploy SingleChat / deploy (push) Successful in 23s
More fixes
2026-06-17 15:44:22 +02:00

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>