feat: server image
This commit is contained in:
@@ -18,6 +18,8 @@ export interface ServerInfo {
|
||||
ownerPublicKey?: string;
|
||||
userCount: number;
|
||||
maxUsers: number;
|
||||
icon?: string;
|
||||
iconUpdatedAt?: number;
|
||||
hasPassword?: boolean;
|
||||
isPrivate: boolean;
|
||||
tags?: string[];
|
||||
|
||||
@@ -101,8 +101,16 @@
|
||||
(dblclick)="openServerCard(server)"
|
||||
>
|
||||
<div class="flex min-w-0 items-start gap-3">
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center rounded-lg bg-secondary text-sm font-semibold text-foreground">
|
||||
{{ server.name[0] || '?' }}
|
||||
<div class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg bg-secondary text-sm font-semibold text-foreground">
|
||||
@if (server.icon) {
|
||||
<img
|
||||
[src]="server.icon"
|
||||
[alt]="server.name + ' icon'"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
} @else {
|
||||
{{ server.name[0] || '?' }}
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
|
||||
@@ -1,64 +1,30 @@
|
||||
/* eslint-disable @typescript-eslint/member-ordering */
|
||||
import {
|
||||
Component,
|
||||
effect,
|
||||
inject,
|
||||
OnInit,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
import { Component, effect, inject, OnInit, signal } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { Store } from '@ngrx/store';
|
||||
import {
|
||||
debounceTime,
|
||||
distinctUntilChanged,
|
||||
firstValueFrom,
|
||||
Subject
|
||||
} from 'rxjs';
|
||||
import { debounceTime, distinctUntilChanged, firstValueFrom, Subject } from 'rxjs';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import {
|
||||
lucideSearch,
|
||||
lucideUsers,
|
||||
lucideLock,
|
||||
lucideGlobe,
|
||||
lucidePlus,
|
||||
lucideSettings,
|
||||
lucideChevronDown
|
||||
} from '@ng-icons/lucide';
|
||||
import { lucideSearch, lucideUsers, lucideLock, lucideGlobe, lucidePlus, lucideSettings, lucideChevronDown } from '@ng-icons/lucide';
|
||||
|
||||
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
|
||||
import {
|
||||
selectSearchResults,
|
||||
selectIsSearching,
|
||||
selectRoomsError,
|
||||
selectSavedRooms
|
||||
} from '../../../../store/rooms/rooms.selectors';
|
||||
import { selectSearchResults, selectIsSearching, selectRoomsError, selectSavedRooms } from '../../../../store/rooms/rooms.selectors';
|
||||
import { Room, User } from '../../../../shared-kernel';
|
||||
import { SettingsModalService } from '../../../../core/services/settings-modal.service';
|
||||
import { DatabaseService } from '../../../../infrastructure/persistence';
|
||||
import { type ServerInfo } from '../../domain/models/server-directory.model';
|
||||
import { ServerDirectoryFacade } from '../../application/facades/server-directory.facade';
|
||||
import { selectCurrentUser } from '../../../../store/users/users.selectors';
|
||||
import {
|
||||
ConfirmDialogComponent,
|
||||
LeaveServerDialogComponent,
|
||||
type LeaveServerDialogResult
|
||||
} from '../../../../shared';
|
||||
import { ConfirmDialogComponent, LeaveServerDialogComponent, type LeaveServerDialogResult } from '../../../../shared';
|
||||
import { hasRoomBanForUser } from '../../../access-control';
|
||||
import { UserSearchListComponent } from '../../../direct-message/feature/user-search-list/user-search-list.component';
|
||||
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-search',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
ConfirmDialogComponent,
|
||||
LeaveServerDialogComponent,
|
||||
UserSearchListComponent
|
||||
],
|
||||
imports: [CommonModule, FormsModule, NgIcon, ConfirmDialogComponent, LeaveServerDialogComponent, UserSearchListComponent],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
lucideSearch,
|
||||
@@ -82,6 +48,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
private settingsModal = inject(SettingsModalService);
|
||||
private db = inject(DatabaseService);
|
||||
private serverDirectory = inject(ServerDirectoryFacade);
|
||||
private webrtc = inject(RealtimeSessionFacade);
|
||||
private searchSubject = new Subject<string>();
|
||||
private banLookupRequestVersion = 0;
|
||||
|
||||
@@ -118,6 +85,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
const currentUser = this.currentUser();
|
||||
|
||||
void this.refreshBannedLookup(servers, currentUser ?? null);
|
||||
void this.requestMissingServerIcons(servers, currentUser ?? null);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -170,8 +138,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
|
||||
/** Submit the new server creation form and dispatch the create action. */
|
||||
createServer(): void {
|
||||
if (!this.newServerName())
|
||||
return;
|
||||
if (!this.newServerName()) return;
|
||||
|
||||
const currentUserId = localStorage.getItem('metoyou_currentUserId');
|
||||
|
||||
@@ -225,7 +192,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
|
||||
toggleJoinedServerMenu(event: Event, server: ServerInfo): void {
|
||||
event.stopPropagation();
|
||||
this.joinedServerMenuId.update((currentId) => currentId === server.id ? null : server.id);
|
||||
this.joinedServerMenuId.update((currentId) => (currentId === server.id ? null : server.id));
|
||||
}
|
||||
|
||||
closeJoinedServerMenu(): void {
|
||||
@@ -255,10 +222,12 @@ export class ServerSearchComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(RoomsActions.forgetRoom({
|
||||
roomId: room.id,
|
||||
nextOwnerKey: result.nextOwnerKey
|
||||
}));
|
||||
this.store.dispatch(
|
||||
RoomsActions.forgetRoom({
|
||||
roomId: room.id,
|
||||
nextOwnerKey: result.nextOwnerKey
|
||||
})
|
||||
);
|
||||
|
||||
this.leaveDialogRoom.set(null);
|
||||
}
|
||||
@@ -278,8 +247,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
async confirmPasswordJoin(): Promise<void> {
|
||||
const server = this.passwordPromptServer();
|
||||
|
||||
if (!server)
|
||||
return;
|
||||
if (!server) return;
|
||||
|
||||
await this.attemptJoinServer(server, this.joinPassword());
|
||||
}
|
||||
@@ -291,8 +259,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
getServerUserCount(server: ServerInfo): number {
|
||||
const candidate = server as ServerInfo & { currentUsers?: number };
|
||||
|
||||
if (typeof server.userCount === 'number')
|
||||
return server.userCount;
|
||||
if (typeof server.userCount === 'number') return server.userCount;
|
||||
|
||||
return typeof candidate.currentUsers === 'number' ? candidate.currentUsers : 0;
|
||||
}
|
||||
@@ -304,9 +271,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
getServerOwnerLabel(server: ServerInfo): string {
|
||||
const joinedRoom = this.joinedRoomForServer(server);
|
||||
const ownerKey = server.ownerId || joinedRoom?.hostId || '';
|
||||
const ownerMember = joinedRoom?.members?.find((member) =>
|
||||
member.id === ownerKey || member.oderId === ownerKey
|
||||
);
|
||||
const ownerMember = joinedRoom?.members?.find((member) => member.id === ownerKey || member.oderId === ownerKey);
|
||||
|
||||
return server.ownerName || ownerMember?.displayName || server.ownerId || joinedRoom?.hostId || 'Unknown owner';
|
||||
}
|
||||
@@ -324,6 +289,8 @@ export class ServerSearchComponent implements OnInit {
|
||||
hostName: room.hostId || 'Unknown',
|
||||
userCount: room.userCount ?? 0,
|
||||
maxUsers: room.maxUsers ?? 50,
|
||||
icon: room.icon,
|
||||
iconUpdatedAt: room.iconUpdatedAt,
|
||||
hasPassword: typeof room.hasPassword === 'boolean' ? room.hasPassword : !!room.password,
|
||||
isPrivate: room.isPrivate,
|
||||
channels: room.channels,
|
||||
@@ -348,32 +315,37 @@ export class ServerSearchComponent implements OnInit {
|
||||
this.joinPasswordError.set(null);
|
||||
|
||||
try {
|
||||
const response = await firstValueFrom(this.serverDirectory.requestJoin({
|
||||
roomId: server.id,
|
||||
userId: currentUserId,
|
||||
userPublicKey: currentUser?.oderId || currentUserId,
|
||||
displayName: currentUser?.displayName || 'Anonymous',
|
||||
password: password?.trim() || undefined
|
||||
}, {
|
||||
sourceId: server.sourceId,
|
||||
sourceUrl: server.sourceUrl
|
||||
}));
|
||||
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource({
|
||||
sourceId: response.server.sourceId ?? server.sourceId,
|
||||
sourceName: response.server.sourceName ?? server.sourceName,
|
||||
sourceUrl: response.server.sourceUrl ?? server.sourceUrl,
|
||||
signalingUrl: response.signalingUrl,
|
||||
fallbackName: response.server.sourceName ?? server.sourceName ?? server.name
|
||||
}, {
|
||||
ensureEndpoint: true
|
||||
});
|
||||
const response = await firstValueFrom(
|
||||
this.serverDirectory.requestJoin(
|
||||
{
|
||||
roomId: server.id,
|
||||
userId: currentUserId,
|
||||
userPublicKey: currentUser?.oderId || currentUserId,
|
||||
displayName: currentUser?.displayName || 'Anonymous',
|
||||
password: password?.trim() || undefined
|
||||
},
|
||||
{
|
||||
sourceId: server.sourceId,
|
||||
sourceUrl: server.sourceUrl
|
||||
}
|
||||
)
|
||||
);
|
||||
const resolvedSource = this.serverDirectory.normaliseRoomSignalSource(
|
||||
{
|
||||
sourceId: response.server.sourceId ?? server.sourceId,
|
||||
sourceName: response.server.sourceName ?? server.sourceName,
|
||||
sourceUrl: response.server.sourceUrl ?? server.sourceUrl,
|
||||
signalingUrl: response.signalingUrl,
|
||||
fallbackName: response.server.sourceName ?? server.sourceName ?? server.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: true
|
||||
}
|
||||
);
|
||||
const resolvedServer = {
|
||||
...server,
|
||||
...response.server,
|
||||
channels:
|
||||
Array.isArray(response.server.channels) && response.server.channels.length > 0
|
||||
? response.server.channels
|
||||
: server.channels,
|
||||
channels: Array.isArray(response.server.channels) && response.server.channels.length > 0 ? response.server.channels : server.channels,
|
||||
...resolvedSource,
|
||||
signalingUrl: response.signalingUrl
|
||||
};
|
||||
@@ -409,6 +381,53 @@ export class ServerSearchComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
private async requestMissingServerIcons(servers: ServerInfo[], currentUser: User | null): Promise<void> {
|
||||
if (!currentUser) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const server of servers) {
|
||||
if (server.icon) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const selector = this.serverDirectory.buildRoomSignalSelector(
|
||||
{
|
||||
sourceId: server.sourceId,
|
||||
sourceName: server.sourceName,
|
||||
sourceUrl: server.sourceUrl,
|
||||
fallbackName: server.sourceName ?? server.name
|
||||
},
|
||||
{
|
||||
ensureEndpoint: !!server.sourceUrl
|
||||
}
|
||||
);
|
||||
|
||||
if (!selector) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const wsUrl = this.serverDirectory.getWebSocketUrl(selector);
|
||||
|
||||
try {
|
||||
await firstValueFrom(this.webrtc.connectToSignalingServer(wsUrl));
|
||||
this.webrtc.identify(currentUser.oderId || currentUser.id, currentUser.displayName || 'User', wsUrl, {
|
||||
description: currentUser.description,
|
||||
profileUpdatedAt: currentUser.profileUpdatedAt
|
||||
});
|
||||
this.webrtc.joinRoom(server.id, currentUser.oderId || currentUser.id, wsUrl);
|
||||
this.webrtc.sendRawMessage({
|
||||
type: 'server_icon_sync_request',
|
||||
serverId: server.id,
|
||||
iconUpdatedAt: 0
|
||||
});
|
||||
window.setTimeout(() => this.webrtc.leaveRoom(server.id), 15_000);
|
||||
} catch {
|
||||
/* discovery icons are best-effort */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshBannedLookup(servers: ServerInfo[], currentUser: User | null): Promise<void> {
|
||||
const requestVersion = ++this.banLookupRequestVersion;
|
||||
|
||||
@@ -427,8 +446,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
})
|
||||
);
|
||||
|
||||
if (requestVersion !== this.banLookupRequestVersion)
|
||||
return;
|
||||
if (requestVersion !== this.banLookupRequestVersion) return;
|
||||
|
||||
this.bannedServerLookup.set(Object.fromEntries(entries));
|
||||
}
|
||||
@@ -437,8 +455,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
const currentUser = this.currentUser();
|
||||
const currentUserId = localStorage.getItem('metoyou_currentUserId');
|
||||
|
||||
if (!currentUser && !currentUserId)
|
||||
return false;
|
||||
if (!currentUser && !currentUserId) return false;
|
||||
|
||||
const bans = await this.db.getBansForRoom(server.id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user