This commit is contained in:
204
client/src/components/VideoDock.vue
Normal file
204
client/src/components/VideoDock.vue
Normal file
@@ -0,0 +1,204 @@
|
||||
<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">
|
||||
<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(20vw, 320px);
|
||||
min-width: 220px;
|
||||
max-width: 320px;
|
||||
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 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: 240px;
|
||||
min-width: 240px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 860px) {
|
||||
.video-dock {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user