Many features

This commit is contained in:
2026-03-10 22:36:21 +01:00
parent df309d088c
commit d2c4152ea7
12 changed files with 1034 additions and 120 deletions

View File

@@ -7,6 +7,36 @@
[title]="(peer()?.displayName ?? 'Peer') + ' webcam'"
(closeRequested)="closeRemoteVideoModal()"
></app-peer-video-modal>
<audio #callAudioElement hidden autoplay playsinline></audio>
@if (incomingVoiceCallPeer(); as callingPeer) {
<div class="call-modal-backdrop">
<section class="panel p-4" style="width:min(100%,24rem)" (click)="$event.stopPropagation()">
<div class="mb-3">
<div>
<h2 class="h5 mb-1">Incoming voice call</h2>
<p class="small mb-0">{{ callingPeer.displayName }} is calling you.</p>
</div>
</div>
<div class="d-flex flex-wrap gap-2 justify-content-end">
<button
class="btn btn-success"
type="button"
(click)="acceptIncomingVoiceCall(callingPeer.id)"
>
Accept
</button>
<button
class="btn btn-outline-secondary"
type="button"
(click)="rejectIncomingVoiceCall(callingPeer.id)"
>
Reject
</button>
</div>
</section>
</div>
}
<div class="chat-header d-flex flex-column flex-lg-row justify-content-between align-items-start align-items-lg-center gap-3 mb-4">
<div>
@@ -49,7 +79,11 @@
}
@for (connectedPeer of session.peers(); track connectedPeer.id) {
<article class="peer-tile" [class.peer-tile-active]="connectedPeer.id === peerId()">
<article
class="peer-tile"
[class.peer-tile-active]="connectedPeer.id === peerId()"
[class.peer-tile-unread]="isPeerUnread(connectedPeer.id)"
>
<button
class="peer-tile-main text-start"
type="button"
@@ -189,6 +223,19 @@
}
</div>
}
@case ('voice') {
<div class="voice-bubble">
<div class="voice-bubble-label">Voice message</div>
@if (entry.downloadUrl) {
<audio
class="voice-player"
[src]="entry.downloadUrl"
controls
preload="metadata"
></audio>
}
</div>
}
@default {
@if (entry.showSpinner) {
<div class="bubble-system-status">
@@ -205,39 +252,6 @@
</div>
<div class="composer">
@if (peer(); as selectedPeer) {
<div class="composer-actions">
<button
class="composer-camera"
type="button"
[disabled]="selectedPeer.channelState !== 'open' && !isStreamingCameraToSelectedPeer()"
(click)="toggleCameraStream(selectedPeer.id)"
[title]="isStreamingCameraToSelectedPeer() ? 'Stop webcam' : 'Start webcam'"
[attr.aria-label]="isStreamingCameraToSelectedPeer() ? 'Stop webcam' : 'Start webcam'"
>
{{ isStreamingCameraToSelectedPeer() ? '🛑' : '📹' }}
</button>
<input
#fileInput
class="composer-file-input"
type="file"
[disabled]="selectedPeer.channelState !== 'open'"
(change)="sendFile(selectedPeer.id, fileInput)"
/>
<button
class="composer-plus"
type="button"
[disabled]="selectedPeer.channelState !== 'open'"
(click)="fileInput.click()"
title="Send file"
aria-label="Send file"
>
+
</button>
</div>
}
<textarea
#composerTextarea
class="form-control composer-textarea"
@@ -252,7 +266,73 @@
placeholder="Write a text message to your peer"
></textarea>
<div class="composer-send">
<div class="composer-toolbar">
@if (peer(); as selectedPeer) {
<button
class="composer-call"
type="button"
[disabled]="!canStartSelectedVoiceCall()"
(click)="startVoiceCall(selectedPeer.id)"
title="Start voice call"
aria-label="Start voice call"
>
📞
</button>
@if (canEndSelectedVoiceCall()) {
<button
class="composer-hangup"
type="button"
(click)="endVoiceCall(selectedPeer.id)"
title="End voice call"
aria-label="End voice call"
>
🛑
</button>
}
<button
class="composer-camera"
type="button"
[disabled]="selectedPeer.channelState !== 'open' && !isStreamingCameraToSelectedPeer()"
(click)="toggleCameraStream(selectedPeer.id)"
[title]="isStreamingCameraToSelectedPeer() ? 'Stop webcam' : 'Start webcam'"
[attr.aria-label]="isStreamingCameraToSelectedPeer() ? 'Stop webcam' : 'Start webcam'"
>
{{ isStreamingCameraToSelectedPeer() ? '🛑' : '📹' }}
</button>
<button
class="composer-voice"
type="button"
[disabled]="selectedPeer.channelState !== 'open' && !isRecordingVoice()"
(click)="toggleVoiceRecording()"
[title]="isRecordingVoice() ? 'Stop and send voice message' : 'Record voice message'"
[attr.aria-label]="isRecordingVoice() ? 'Stop and send voice message' : 'Record voice message'"
[class.composer-voice-recording]="isRecordingVoice()"
>
{{ isRecordingVoice() ? '⏹️' : '🎙️' }}
</button>
<input
#fileInput
class="composer-file-input"
type="file"
[disabled]="selectedPeer.channelState !== 'open'"
(change)="sendFile(selectedPeer.id, fileInput)"
/>
<button
class="composer-plus"
type="button"
[disabled]="selectedPeer.channelState !== 'open'"
(click)="fileInput.click()"
title="Send file"
aria-label="Send file"
>
+
</button>
}
<button
class="composer-image-generate"
type="button"