offline users and messages
This commit is contained in:
@@ -46,7 +46,7 @@
|
||||
<header class="conversation-modal-header">
|
||||
<div>
|
||||
<p class="conversation-modal-eyebrow mb-1">Fullscreen conversation</p>
|
||||
<h2 class="h5 mb-0">{{ peer()?.displayName ?? 'Conversation' }}</h2>
|
||||
<h2 class="h5 mb-0">{{ displayedPeer()?.displayName ?? 'Conversation' }}</h2>
|
||||
</div>
|
||||
<button
|
||||
class="conversation-modal-close"
|
||||
@@ -113,21 +113,21 @@
|
||||
|
||||
@if (peerDropdownOpen()) {
|
||||
<div class="peer-dropdown-menu" role="listbox">
|
||||
@for (connectedPeer of session.peers(); track connectedPeer.id) {
|
||||
@for (dropdownPeer of dropdownPeers(); track dropdownPeer.id) {
|
||||
<article
|
||||
class="peer-tile"
|
||||
[class.peer-tile-active]="connectedPeer.id === peerId()"
|
||||
[class.peer-tile-unread]="isPeerUnread(connectedPeer.id)"
|
||||
[class.peer-tile-active]="dropdownPeer.id === peerId()"
|
||||
[class.peer-tile-unread]="isPeerUnread(dropdownPeer.id)"
|
||||
>
|
||||
<button
|
||||
class="peer-tile-main text-start"
|
||||
type="button"
|
||||
(click)="selectPeerFromDropdown(connectedPeer.id)"
|
||||
(click)="selectPeerFromDropdown(dropdownPeer.id)"
|
||||
>
|
||||
<div class="peer-tile-row">
|
||||
<span class="peer-tile-title">
|
||||
<span class="fw-semibold">{{ connectedPeer.displayName }}</span>
|
||||
@if (isPeerTyping(connectedPeer.id)) {
|
||||
<span class="fw-semibold">{{ dropdownPeer.displayName }}</span>
|
||||
@if (isPeerTyping(dropdownPeer.id)) {
|
||||
<span class="peer-typing-dots" aria-label="Typing">
|
||||
<span></span>
|
||||
<span></span>
|
||||
@@ -137,10 +137,10 @@
|
||||
</span>
|
||||
<span
|
||||
class="status-led peer-tile-status"
|
||||
[class.status-led-ok]="connectedPeer.channelState === 'open' || connectedPeer.connectionState === 'connected'"
|
||||
[class.status-led-offline]="connectedPeer.channelState !== 'open' && connectedPeer.connectionState !== 'connected'"
|
||||
[class.status-led-ok]="dropdownPeer.channelState === 'open' || dropdownPeer.connectionState === 'connected'"
|
||||
[class.status-led-offline]="dropdownPeer.channelState !== 'open' && dropdownPeer.connectionState !== 'connected'"
|
||||
[attr.aria-label]="
|
||||
connectedPeer.channelState === 'open' || connectedPeer.connectionState === 'connected'
|
||||
dropdownPeer.channelState === 'open' || dropdownPeer.connectionState === 'connected'
|
||||
? 'Connected'
|
||||
: 'Disconnected'
|
||||
"
|
||||
@@ -152,7 +152,7 @@
|
||||
type="button"
|
||||
title="Delete conversation"
|
||||
aria-label="Delete conversation"
|
||||
(click)="deleteConversation(connectedPeer.id, $event)"
|
||||
(click)="deleteConversation(dropdownPeer.id, $event)"
|
||||
>
|
||||
🗑️
|
||||
</button>
|
||||
@@ -215,83 +215,85 @@
|
||||
(click)="trackComposerSelection(composerTextarea)"
|
||||
(keyup)="trackComposerSelection(composerTextarea)"
|
||||
(select)="trackComposerSelection(composerTextarea)"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
placeholder="Write a text message to your peer"
|
||||
[disabled]="!peerId()"
|
||||
placeholder="Write a text message to your peer, even if they are offline"
|
||||
></textarea>
|
||||
|
||||
<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()) {
|
||||
@if (peerId(); as selectedPeerId) {
|
||||
@if (peer(); as livePeer) {
|
||||
<button
|
||||
class="composer-hangup"
|
||||
class="composer-call"
|
||||
type="button"
|
||||
(click)="endVoiceCall(selectedPeer.id)"
|
||||
title="End call"
|
||||
aria-label="End call"
|
||||
[disabled]="!canStartSelectedVoiceCall()"
|
||||
(click)="openCallChoice(livePeer.id)"
|
||||
title="Start call"
|
||||
aria-label="Start call"
|
||||
>
|
||||
🛑
|
||||
📞
|
||||
</button>
|
||||
|
||||
@if (canEndSelectedVoiceCall()) {
|
||||
<button
|
||||
class="composer-hangup"
|
||||
type="button"
|
||||
(click)="endVoiceCall(livePeer.id)"
|
||||
title="End call"
|
||||
aria-label="End call"
|
||||
>
|
||||
🛑
|
||||
</button>
|
||||
}
|
||||
|
||||
<button
|
||||
class="composer-voice"
|
||||
type="button"
|
||||
[disabled]="livePeer.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-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>
|
||||
|
||||
<input
|
||||
#fileInput
|
||||
class="composer-file-input"
|
||||
type="file"
|
||||
[disabled]="selectedPeer.channelState !== 'open'"
|
||||
(change)="sendFile(selectedPeer.id, fileInput)"
|
||||
[disabled]="!selectedPeerId"
|
||||
(change)="sendFile(selectedPeerId, fileInput)"
|
||||
/>
|
||||
<button
|
||||
class="composer-plus"
|
||||
type="button"
|
||||
[disabled]="selectedPeer.channelState !== 'open'"
|
||||
[disabled]="!selectedPeerId"
|
||||
(click)="fileInput.click()"
|
||||
title="Send file"
|
||||
aria-label="Send file"
|
||||
@@ -330,7 +332,7 @@
|
||||
<button
|
||||
class="composer-emoji-trigger"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
[disabled]="!peerId()"
|
||||
(click)="toggleEmojiPicker($event)"
|
||||
title="Insert emoji"
|
||||
aria-label="Insert emoji"
|
||||
@@ -342,7 +344,7 @@
|
||||
<button
|
||||
class="send-emoji"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
[disabled]="!peerId()"
|
||||
(click)="sendMessage()"
|
||||
title="Send message"
|
||||
aria-label="Send message"
|
||||
@@ -368,7 +370,7 @@
|
||||
<ng-template #conversationBubbles>
|
||||
@if (conversation().length === 0) {
|
||||
<div class="empty-chat">
|
||||
No text messages yet. The chat page is ready as soon as the peer channel opens.
|
||||
No text messages yet. Messages and files can be queued here and will send when the peer reconnects.
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -377,6 +379,7 @@
|
||||
class="bubble"
|
||||
[class.bubble-incoming]="entry.direction === 'incoming'"
|
||||
[class.bubble-outgoing]="entry.direction === 'outgoing'"
|
||||
[class.bubble-pending]="isPendingOutgoingEntry(entry)"
|
||||
[class.bubble-system]="entry.direction === 'system'"
|
||||
[class.bubble-emoji-only]="isEmojiOnlyEntry(entry)"
|
||||
>
|
||||
@@ -427,6 +430,9 @@
|
||||
<div class="bubble-meta">
|
||||
<span class="bubble-author">{{ entry.authorLabel }}</span>
|
||||
<time class="bubble-time">{{ entry.createdAt | date: 'shortTime' }}</time>
|
||||
@if (isPendingOutgoingEntry(entry)) {
|
||||
<span class="bubble-delivery-state">Queued</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user