bandwidth meter
This commit is contained in:
@@ -219,136 +219,145 @@
|
||||
placeholder="Write a text message to your peer"
|
||||
></textarea>
|
||||
|
||||
<div class="composer-toolbar">
|
||||
@if (peer(); as selectedPeer) {
|
||||
<button
|
||||
class="composer-call"
|
||||
type="button"
|
||||
[disabled]="!canStartSelectedVoiceCall()"
|
||||
(click)="openCallChoice(selectedPeer.id)"
|
||||
title="Start call"
|
||||
aria-label="Start call"
|
||||
>
|
||||
📞
|
||||
</button>
|
||||
<div class="composer-toolbar">
|
||||
<div class="composer-actions">
|
||||
@if (peer(); as selectedPeer) {
|
||||
<button
|
||||
class="composer-call"
|
||||
type="button"
|
||||
[disabled]="!canStartSelectedVoiceCall()"
|
||||
(click)="openCallChoice(selectedPeer.id)"
|
||||
title="Start call"
|
||||
aria-label="Start call"
|
||||
>
|
||||
📞
|
||||
</button>
|
||||
|
||||
@if (canEndSelectedVoiceCall()) {
|
||||
<button
|
||||
class="composer-hangup"
|
||||
type="button"
|
||||
(click)="endVoiceCall(selectedPeer.id)"
|
||||
title="End call"
|
||||
aria-label="End call"
|
||||
>
|
||||
🛑
|
||||
</button>
|
||||
}
|
||||
@if (canEndSelectedVoiceCall()) {
|
||||
<button
|
||||
class="composer-hangup"
|
||||
type="button"
|
||||
(click)="endVoiceCall(selectedPeer.id)"
|
||||
title="End call"
|
||||
aria-label="End call"
|
||||
>
|
||||
🛑
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<button
|
||||
class="composer-dictation"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady() || session.signalingState() !== 'connected' || isTranscribingDictation()"
|
||||
(click)="toggleDictation(composerTextarea)"
|
||||
[title]="
|
||||
isDictating()
|
||||
? 'Stop dictation and transcribe'
|
||||
: isTranscribingDictation()
|
||||
? 'Transcribing dictated audio'
|
||||
: 'Start dictation'
|
||||
"
|
||||
[attr.aria-label]="
|
||||
isDictating()
|
||||
? 'Stop dictation and transcribe'
|
||||
: isTranscribingDictation()
|
||||
? 'Transcribing dictated audio'
|
||||
: 'Start dictation'
|
||||
"
|
||||
[class.composer-dictation-active]="isDictating() || isTranscribingDictation()"
|
||||
>
|
||||
{{ isDictating() ? '🛑' : isTranscribingDictation() ? '⏳' : '🗣️' }}
|
||||
</button>
|
||||
<button
|
||||
class="composer-dictation"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady() || session.signalingState() !== 'connected' || isTranscribingDictation()"
|
||||
(click)="toggleDictation(composerTextarea)"
|
||||
[title]="
|
||||
isDictating()
|
||||
? 'Stop dictation and transcribe'
|
||||
: isTranscribingDictation()
|
||||
? 'Transcribing dictated audio'
|
||||
: 'Start dictation'
|
||||
"
|
||||
[attr.aria-label]="
|
||||
isDictating()
|
||||
? 'Stop dictation and transcribe'
|
||||
: isTranscribingDictation()
|
||||
? 'Transcribing dictated audio'
|
||||
: 'Start dictation'
|
||||
"
|
||||
[class.composer-dictation-active]="isDictating() || isTranscribingDictation()"
|
||||
>
|
||||
{{ isDictating() ? '🛑' : isTranscribingDictation() ? '⏳' : '🗣️' }}
|
||||
</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>
|
||||
}
|
||||
<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"
|
||||
[disabled]="!peer() || session.signalingState() !== 'connected' || !messageText.trim()"
|
||||
(click)="requestGeneratedImage()"
|
||||
title="Generate image from prompt"
|
||||
aria-label="Generate image from prompt"
|
||||
>
|
||||
🖼️
|
||||
</button>
|
||||
<button
|
||||
class="composer-image-generate"
|
||||
type="button"
|
||||
[disabled]="!peer() || session.signalingState() !== 'connected' || !messageText.trim()"
|
||||
(click)="requestGeneratedImage()"
|
||||
title="Generate image from prompt"
|
||||
aria-label="Generate image from prompt"
|
||||
>
|
||||
🖼️
|
||||
</button>
|
||||
|
||||
<div class="composer-emoji-picker-shell">
|
||||
@if (emojiPickerOpen()) {
|
||||
<div class="composer-emoji-picker">
|
||||
@for (emoji of emojiOptions; track emoji) {
|
||||
<button
|
||||
class="composer-emoji-option"
|
||||
type="button"
|
||||
(click)="insertEmoji(emoji, composerTextarea)"
|
||||
[attr.aria-label]="'Insert ' + emoji"
|
||||
[title]="'Insert ' + emoji"
|
||||
>
|
||||
{{ emoji }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<button
|
||||
class="composer-emoji-trigger"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
(click)="toggleEmojiPicker($event)"
|
||||
title="Insert emoji"
|
||||
aria-label="Insert emoji"
|
||||
>
|
||||
😀
|
||||
</button>
|
||||
</div>
|
||||
<div class="composer-emoji-picker-shell">
|
||||
@if (emojiPickerOpen()) {
|
||||
<div class="composer-emoji-picker">
|
||||
@for (emoji of emojiOptions; track emoji) {
|
||||
<button
|
||||
class="composer-emoji-option"
|
||||
type="button"
|
||||
(click)="insertEmoji(emoji, composerTextarea)"
|
||||
[attr.aria-label]="'Insert ' + emoji"
|
||||
[title]="'Insert ' + emoji"
|
||||
>
|
||||
{{ emoji }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<button
|
||||
class="composer-emoji-trigger"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
(click)="toggleEmojiPicker($event)"
|
||||
title="Insert emoji"
|
||||
aria-label="Insert emoji"
|
||||
>
|
||||
😀
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="send-emoji"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
(click)="sendMessage()"
|
||||
title="Send message"
|
||||
aria-label="Send message"
|
||||
>
|
||||
✅
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="send-emoji"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
(click)="sendMessage()"
|
||||
title="Send message"
|
||||
aria-label="Send message"
|
||||
>
|
||||
✅
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (lastIncomingReceiveMetric(); as receiveMetric) {
|
||||
<div class="composer-receive-speed" title="Receive speed of the last completed incoming WebRTC message">
|
||||
<span class="composer-receive-speed-label">Rx</span>
|
||||
<span class="composer-receive-speed-value">{{ receiveMetric.mbps | number: '1.2-2' }} Mbit/s</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user