Fix private calls
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
effect,
|
||||
HostListener,
|
||||
inject,
|
||||
input,
|
||||
signal,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
@@ -15,10 +16,13 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { map } from 'rxjs';
|
||||
import { ElectronBridgeService } from '../../../../core/platform/electron/electron-bridge.service';
|
||||
import { UserAvatarComponent } from '../../../../shared';
|
||||
import { DirectCallService } from '../../../direct-call';
|
||||
import { Attachment, AttachmentFacade } from '../../../attachment';
|
||||
import { ThemeNodeDirective } from '../../../theme';
|
||||
import { DirectMessageService } from '../../application/services/direct-message.service';
|
||||
import { selectAllUsers, selectCurrentUser } from '../../../../store/users/users.selectors';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { lucidePhone, lucidePhoneCall } from '@ng-icons/lucide';
|
||||
import {
|
||||
ChatMessageComposerSubmitEvent,
|
||||
ChatMessageComposerComponent,
|
||||
@@ -57,9 +61,11 @@ interface DmStatusLabel {
|
||||
ChatMessageListComponent,
|
||||
ChatMessageOverlaysComponent,
|
||||
KlipyGifPickerComponent,
|
||||
NgIcon,
|
||||
ThemeNodeDirective,
|
||||
UserAvatarComponent
|
||||
],
|
||||
viewProviders: [provideIcons({ lucidePhone, lucidePhoneCall })],
|
||||
templateUrl: './dm-chat.component.html',
|
||||
host: {
|
||||
class: 'block h-full'
|
||||
@@ -74,10 +80,15 @@ export class DmChatComponent {
|
||||
private readonly attachments = inject(AttachmentFacade);
|
||||
private readonly klipy = inject(KlipyService);
|
||||
private readonly linkMetadata = inject(LinkMetadataService);
|
||||
private readonly metadataRequestKeys = new Set<string>();
|
||||
private openedConversationId: string | null = null;
|
||||
readonly directCalls = inject(DirectCallService);
|
||||
readonly directMessages = inject(DirectMessageService);
|
||||
readonly currentUser = this.store.selectSignal(selectCurrentUser);
|
||||
readonly allUsers = this.store.selectSignal(selectAllUsers);
|
||||
readonly showGifPicker = signal(false);
|
||||
readonly conversationId = input<string | null>(null);
|
||||
readonly showCallButton = input(true);
|
||||
readonly composerBottomPadding = signal(140);
|
||||
readonly gifPickerAnchorRight = signal(16);
|
||||
readonly linkMetadataByMessageId = signal<Record<string, LinkMetadata[]>>({});
|
||||
@@ -87,15 +98,26 @@ export class DmChatComponent {
|
||||
readonly routeConversationId = toSignal(this.route.paramMap.pipe(map((params) => params.get('conversationId'))), {
|
||||
initialValue: this.route.snapshot.paramMap.get('conversationId')
|
||||
});
|
||||
readonly effectiveConversationId = computed(() => this.conversationId() ?? this.routeConversationId());
|
||||
readonly currentUserId = computed(() => this.currentUser()?.oderId || this.currentUser()?.id || '');
|
||||
readonly conversation = this.directMessages.selectedConversation;
|
||||
readonly klipyEnabled = computed(() => this.klipy.isEnabled(null));
|
||||
readonly conversationKey = computed(() => this.conversation()?.id ?? 'dm:none');
|
||||
readonly typingUsers = computed(() => {
|
||||
void this.directMessages.typingEntries();
|
||||
|
||||
return this.directMessages.typingUsers(this.conversation()?.id);
|
||||
});
|
||||
readonly peerUser = computed(() => {
|
||||
const conversation = this.conversation();
|
||||
|
||||
return conversation ? this.peerUserFor(conversation) : null;
|
||||
});
|
||||
readonly isGroupConversation = computed(() => {
|
||||
const conversation = this.conversation();
|
||||
|
||||
return !!conversation && (conversation.kind === 'group' || conversation.participants.length > 2);
|
||||
});
|
||||
readonly participantUsers = computed<User[]>(() => {
|
||||
const conversation = this.conversation();
|
||||
const knownUsers = this.allUsers();
|
||||
@@ -173,22 +195,57 @@ export class DmChatComponent {
|
||||
readonly peerName = computed(() => {
|
||||
const conversation = this.conversation();
|
||||
const currentUserId = this.currentUserId();
|
||||
|
||||
if (conversation && this.isGroupConversation()) {
|
||||
return conversation.title || this.groupConversationTitle(conversation);
|
||||
}
|
||||
|
||||
const peerId = conversation?.participants.find((participantId) => participantId !== currentUserId);
|
||||
|
||||
return peerId ? conversation?.participantProfiles[peerId]?.displayName || peerId : 'Direct Message';
|
||||
});
|
||||
readonly peerCallIcon = computed(() => {
|
||||
const conversation = this.conversation();
|
||||
|
||||
if (conversation && this.isGroupConversation()) {
|
||||
return this.directCalls.isCallingConversation(conversation.id) ? 'lucidePhoneCall' : 'lucidePhone';
|
||||
}
|
||||
|
||||
const peer = this.peerUser();
|
||||
|
||||
return peer && this.directCalls.isCallingUser(peer) ? 'lucidePhoneCall' : 'lucidePhone';
|
||||
});
|
||||
readonly canCallConversation = computed(() => {
|
||||
const conversation = this.conversation();
|
||||
|
||||
if (!conversation) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isGroupConversation()) {
|
||||
return conversation.participants.some((participantId) => participantId !== this.currentUserId());
|
||||
}
|
||||
|
||||
return !!this.peerUser();
|
||||
});
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const conversationId = this.routeConversationId();
|
||||
const conversationId = this.effectiveConversationId();
|
||||
|
||||
if (conversationId) {
|
||||
if (!conversationId) {
|
||||
this.openedConversationId = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (conversationId !== this.openedConversationId) {
|
||||
this.openedConversationId = conversationId;
|
||||
void this.directMessages.openConversation(conversationId);
|
||||
}
|
||||
});
|
||||
|
||||
effect(() => {
|
||||
void this.routeConversationId();
|
||||
void this.effectiveConversationId();
|
||||
void this.klipy.refreshAvailability(null);
|
||||
});
|
||||
|
||||
@@ -226,11 +283,28 @@ export class DmChatComponent {
|
||||
this.replyTo.set(null);
|
||||
|
||||
if (event.pendingFiles.length > 0) {
|
||||
this.attachments.rememberMessageRoom(message.id, `direct-message:${conversation.id}`);
|
||||
this.attachments.publishAttachments(message.id, event.pendingFiles, this.currentUserId() || undefined);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleTypingStarted(): void {
|
||||
const conversationId = this.conversation()?.id;
|
||||
|
||||
if (conversationId) {
|
||||
this.directMessages.sendTyping(conversationId, true);
|
||||
}
|
||||
}
|
||||
|
||||
async callConversation(): Promise<void> {
|
||||
const conversation = this.conversation();
|
||||
|
||||
if (conversation && this.canCallConversation()) {
|
||||
await this.directCalls.startConversationCall(conversation);
|
||||
}
|
||||
}
|
||||
|
||||
setReplyTo(message: ChatMessageReplyEvent): void {
|
||||
this.replyTo.set(message);
|
||||
}
|
||||
@@ -325,6 +399,20 @@ export class DmChatComponent {
|
||||
const electronApi = this.electronBridge.getApi();
|
||||
|
||||
if (electronApi) {
|
||||
const diskPath = this.getAttachmentDiskPath(attachment);
|
||||
|
||||
if (diskPath && electronApi.saveExistingFileAs) {
|
||||
try {
|
||||
const result = await electronApi.saveExistingFileAs(diskPath, attachment.filename);
|
||||
|
||||
if (result.saved || result.cancelled) {
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
/* fall back to blob/browser download */
|
||||
}
|
||||
}
|
||||
|
||||
const blob = await this.getAttachmentBlob(attachment);
|
||||
|
||||
if (blob) {
|
||||
@@ -391,12 +479,16 @@ export class DmChatComponent {
|
||||
continue;
|
||||
}
|
||||
|
||||
const urls = this.linkMetadata.extractUrls(message.content).filter((url) => !hasDedicatedChatEmbed(url));
|
||||
const urls = this.linkMetadata.extractUrls(message.content)
|
||||
.filter((url) => !hasDedicatedChatEmbed(url))
|
||||
.filter((url) => !this.metadataRequestKeys.has(this.metadataRequestKey(message.id, url)));
|
||||
|
||||
if (urls.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
urls.forEach((url) => this.metadataRequestKeys.add(this.metadataRequestKey(message.id, url)));
|
||||
|
||||
const metadata = (await this.linkMetadata.fetchAllMetadata(urls)).filter((entry) => !entry.failed);
|
||||
|
||||
if (metadata.length === 0) {
|
||||
@@ -410,11 +502,19 @@ export class DmChatComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private metadataRequestKey(messageId: string, url: string): string {
|
||||
return `${messageId}:${url}`;
|
||||
}
|
||||
|
||||
private async getAttachmentBlob(attachment: Attachment): Promise<Blob | null> {
|
||||
if (!attachment.objectUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (attachment.objectUrl.startsWith('file:')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(attachment.objectUrl);
|
||||
|
||||
@@ -424,6 +524,10 @@ export class DmChatComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private getAttachmentDiskPath(attachment: Attachment): string | null {
|
||||
return attachment.savedPath || attachment.filePath || null;
|
||||
}
|
||||
|
||||
private blobToBase64(blob: Blob): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@@ -445,6 +549,10 @@ export class DmChatComponent {
|
||||
}
|
||||
|
||||
private peerUserFor(conversation: NonNullable<ReturnType<typeof this.conversation>>): User | null {
|
||||
if (conversation.kind === 'group' || conversation.participants.length > 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const currentUserId = this.currentUserId();
|
||||
const peerId = conversation.participants.find((participantId) => participantId !== currentUserId);
|
||||
|
||||
@@ -454,4 +562,16 @@ export class DmChatComponent {
|
||||
|
||||
return this.participantUsers().find((user) => user.id === peerId || user.oderId === peerId) ?? null;
|
||||
}
|
||||
|
||||
private groupConversationTitle(conversation: NonNullable<ReturnType<typeof this.conversation>>): string {
|
||||
const names = conversation.participants
|
||||
.filter((participantId) => participantId !== this.currentUserId())
|
||||
.map((participantId) => conversation.participantProfiles[participantId]?.displayName || participantId);
|
||||
|
||||
if (names.length <= 3) {
|
||||
return names.join(', ');
|
||||
}
|
||||
|
||||
return `${names.slice(0, 3).join(', ')} +${names.length - 3}`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user