Many new functionalities
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
<main class="chat-shell py-4">
|
||||
<div class="container-lg">
|
||||
<section class="chat-page panel p-3 p-lg-4">
|
||||
<app-peer-video-modal
|
||||
[visible]="remoteVideoModalVisible()"
|
||||
[stream]="remoteVideoStream()"
|
||||
[title]="(peer()?.displayName ?? 'Peer') + ' webcam'"
|
||||
(closeRequested)="closeRemoteVideoModal()"
|
||||
></app-peer-video-modal>
|
||||
|
||||
<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>
|
||||
<a class="back-link" routerLink="/">← Back to dashboard</a>
|
||||
@@ -100,18 +107,41 @@
|
||||
[class.bubble-outgoing]="entry.direction === 'outgoing'"
|
||||
[class.bubble-system]="entry.direction === 'system'"
|
||||
>
|
||||
<button
|
||||
class="bubble-delete"
|
||||
type="button"
|
||||
(click)="deleteMessage(entry)"
|
||||
title="Delete message"
|
||||
aria-label="Delete message"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@if (entry.direction !== 'system') {
|
||||
<div class="bubble-actions">
|
||||
<button
|
||||
class="bubble-action"
|
||||
type="button"
|
||||
(click)="toggleForwardMenu(entry, $event)"
|
||||
title="Forward message"
|
||||
aria-label="Forward message"
|
||||
>
|
||||
⏩
|
||||
</button>
|
||||
<button
|
||||
class="bubble-action bubble-delete"
|
||||
type="button"
|
||||
(click)="deleteMessage(entry)"
|
||||
title="Delete message"
|
||||
aria-label="Delete message"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
@if (isForwardMenuOpen(entry.id)) {
|
||||
<div class="bubble-forward-menu">
|
||||
<select #forwardSelect class="bubble-forward-select" (change)="forwardEntry(entry, forwardSelect.value, forwardSelect)">
|
||||
<option value="">Forward to…</option>
|
||||
@for (targetPeer of forwardTargets(entry); track targetPeer.id) {
|
||||
<option [value]="targetPeer.id">{{ targetPeer.displayName }}</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="bubble-meta">
|
||||
<span>{{ entry.authorLabel }}</span>
|
||||
<time>{{ entry.createdAt | date: 'shortTime' }}</time>
|
||||
<span class="bubble-author">{{ entry.authorLabel }}</span>
|
||||
<time class="bubble-time">{{ entry.createdAt | date: 'shortTime' }}</time>
|
||||
</div>
|
||||
|
||||
@switch (entry.kind) {
|
||||
@@ -131,6 +161,18 @@
|
||||
/>
|
||||
}
|
||||
|
||||
@if (isVideoEntry(entry)) {
|
||||
<video
|
||||
class="bubble-video"
|
||||
[src]="entry.downloadUrl"
|
||||
controls
|
||||
autoplay
|
||||
muted
|
||||
playsinline
|
||||
preload="metadata"
|
||||
></video>
|
||||
}
|
||||
|
||||
@if (isIncomingJsonFileEntry(entry)) {
|
||||
<app-json-file-viewer [entry]="entry"></app-json-file-viewer>
|
||||
}
|
||||
@@ -157,6 +199,18 @@
|
||||
|
||||
<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"
|
||||
@@ -174,27 +228,74 @@
|
||||
>
|
||||
+
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<textarea
|
||||
#composerTextarea
|
||||
class="form-control composer-textarea"
|
||||
rows="3"
|
||||
[(ngModel)]="messageText"
|
||||
(ngModelChange)="handleMessageTextChange($event)"
|
||||
(keydown.enter)="handleComposerEnter($event)"
|
||||
(click)="trackComposerSelection(composerTextarea)"
|
||||
(keyup)="trackComposerSelection(composerTextarea)"
|
||||
(select)="trackComposerSelection(composerTextarea)"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
placeholder="Write a text message to your peer"
|
||||
></textarea>
|
||||
<button
|
||||
class="send-emoji"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
(click)="sendMessage()"
|
||||
title="Send message"
|
||||
aria-label="Send message"
|
||||
>
|
||||
✅
|
||||
</button>
|
||||
|
||||
<div class="composer-send">
|
||||
<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>
|
||||
|
||||
<button
|
||||
class="send-emoji"
|
||||
type="button"
|
||||
[disabled]="!session.isSelectedPeerReady()"
|
||||
(click)="sendMessage()"
|
||||
title="Send message"
|
||||
aria-label="Send message"
|
||||
>
|
||||
✅
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user