feat: Add pm
This commit is contained in:
@@ -1,16 +1,57 @@
|
||||
<div class="flex flex-col h-full">
|
||||
<!-- My Servers -->
|
||||
<div class="p-4 border-b border-border">
|
||||
<h3 class="font-semibold text-foreground mb-2">My Servers</h3>
|
||||
@if (savedRooms().length === 0) {
|
||||
<p class="text-sm text-muted-foreground">No joined servers yet</p>
|
||||
} @else {
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="flex h-full min-h-0 flex-col">
|
||||
<div class="border-b border-border px-3 py-3">
|
||||
<div class="flex flex-col gap-2 md:flex-row md:items-center">
|
||||
<div class="relative min-w-0 flex-1">
|
||||
<ng-icon
|
||||
name="lucideSearch"
|
||||
class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
aria-label="Search people and servers"
|
||||
class="h-10 w-full rounded-lg border border-border bg-secondary py-2 pl-10 pr-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="Search servers and users..."
|
||||
[(ngModel)]="searchQuery"
|
||||
(ngModelChange)="onSearchChange($event)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex shrink-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="Create New Server"
|
||||
class="inline-flex h-10 items-center justify-center gap-2 rounded-lg bg-primary px-3 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90"
|
||||
(click)="openCreateDialog()"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucidePlus"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
Create
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="grid h-10 w-10 place-items-center rounded-lg border border-border bg-secondary transition-colors hover:bg-secondary/80"
|
||||
title="Settings"
|
||||
(click)="openSettings()"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideSettings"
|
||||
class="h-5 w-5 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (savedRooms().length > 0) {
|
||||
<div class="mt-2 flex items-center gap-2 overflow-x-auto pb-1">
|
||||
<span class="shrink-0 text-xs font-medium text-muted-foreground">My Servers</span>
|
||||
@for (room of savedRooms(); track room.id) {
|
||||
<button
|
||||
(click)="joinSavedRoom(room)"
|
||||
type="button"
|
||||
class="px-3 py-1.5 text-xs rounded-full bg-secondary hover:bg-secondary/80 border border-border text-foreground"
|
||||
class="shrink-0 rounded-md border border-border bg-card px-2.5 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
|
||||
(click)="joinSavedRoom(room)"
|
||||
>
|
||||
{{ room.name }}
|
||||
</button>
|
||||
@@ -18,160 +59,169 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<!-- Search Header -->
|
||||
<div class="p-4 border-b border-border">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="relative flex-1">
|
||||
<ng-icon
|
||||
name="lucideSearch"
|
||||
class="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-4 h-4"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
[(ngModel)]="searchQuery"
|
||||
(ngModelChange)="onSearchChange($event)"
|
||||
placeholder="Search servers..."
|
||||
class="w-full pl-10 pr-4 py-2 bg-secondary rounded-lg border border-border text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
(click)="openSettings()"
|
||||
type="button"
|
||||
class="grid h-9 w-9 place-items-center rounded-lg border border-border bg-secondary transition-colors hover:bg-secondary/80"
|
||||
title="Settings"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideSettings"
|
||||
class="w-5 h-5 text-muted-foreground"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Create Server Button -->
|
||||
<div class="p-4 border-b border-border">
|
||||
<button
|
||||
(click)="openCreateDialog()"
|
||||
type="button"
|
||||
class="w-full flex items-center justify-center gap-2 px-4 py-3 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucidePlus"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
Create New Server
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid min-h-0 flex-1 grid-cols-1 overflow-hidden lg:grid-cols-[minmax(300px,380px)_1fr]">
|
||||
<app-user-search-list
|
||||
class="min-h-0 overflow-y-auto border-b border-border lg:border-b-0 lg:border-r"
|
||||
[searchQuery]="searchQuery"
|
||||
/>
|
||||
|
||||
<!-- Search Results -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
@if (isSearching()) {
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
|
||||
<section class="min-h-0 overflow-y-auto">
|
||||
<div class="sticky top-0 z-10 flex items-center justify-between border-b border-border bg-background/95 px-3 py-2 backdrop-blur">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-foreground">Servers</h3>
|
||||
<p class="text-xs text-muted-foreground">{{ searchResults().length }} found</p>
|
||||
</div>
|
||||
</div>
|
||||
} @else if (searchResults().length === 0) {
|
||||
<div class="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
||||
<ng-icon
|
||||
name="lucideSearch"
|
||||
class="w-12 h-12 mb-4 opacity-50"
|
||||
/>
|
||||
<p class="text-lg">No servers found</p>
|
||||
<p class="text-sm">Try a different search or create your own</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="p-4 space-y-3">
|
||||
@for (server of searchResults(); track server.id) {
|
||||
<button
|
||||
(click)="joinServer(server)"
|
||||
type="button"
|
||||
class="w-full p-4 bg-card rounded-lg border transition-all text-left group"
|
||||
[class.border-border]="!isServerMarkedBanned(server)"
|
||||
[class.hover:border-primary/50]="!isServerMarkedBanned(server)"
|
||||
[class.hover:bg-card/80]="!isServerMarkedBanned(server)"
|
||||
[class.border-destructive/40]="isServerMarkedBanned(server)"
|
||||
[class.bg-destructive/5]="isServerMarkedBanned(server)"
|
||||
[class.hover:border-destructive/60]="isServerMarkedBanned(server)"
|
||||
[attr.aria-label]="isServerMarkedBanned(server) ? 'Banned server' : 'Join server'"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3
|
||||
class="font-semibold transition-colors"
|
||||
[class.text-foreground]="!isServerMarkedBanned(server)"
|
||||
[class.group-hover:text-primary]="!isServerMarkedBanned(server)"
|
||||
[class.text-destructive]="isServerMarkedBanned(server)"
|
||||
>
|
||||
{{ server.name }}
|
||||
</h3>
|
||||
@if (isServerMarkedBanned(server)) {
|
||||
<ng-icon
|
||||
name="lucideLock"
|
||||
class="w-4 h-4 text-destructive"
|
||||
/>
|
||||
<span class="inline-flex items-center rounded-full bg-destructive/10 px-2 py-0.5 text-[11px] font-medium text-destructive"
|
||||
>Banned</span
|
||||
|
||||
@if (isSearching()) {
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="h-8 w-8 animate-spin rounded-full border-b-2 border-primary"></div>
|
||||
</div>
|
||||
} @else if (searchResults().length === 0) {
|
||||
<div class="flex flex-col items-center justify-center px-4 py-10 text-muted-foreground">
|
||||
<ng-icon
|
||||
name="lucideSearch"
|
||||
class="mb-3 h-10 w-10 opacity-50"
|
||||
/>
|
||||
<p class="text-sm font-medium">No servers found</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="space-y-2 p-3">
|
||||
@for (server of searchResults(); track server.id) {
|
||||
<div
|
||||
class="group w-full cursor-pointer rounded-lg border bg-card p-3 text-left transition-colors"
|
||||
[class.border-border]="!isServerMarkedBanned(server)"
|
||||
[class.hover:border-primary/50]="!isServerMarkedBanned(server)"
|
||||
[class.hover:bg-card/80]="!isServerMarkedBanned(server)"
|
||||
[class.border-destructive/40]="isServerMarkedBanned(server)"
|
||||
[class.bg-destructive/5]="isServerMarkedBanned(server)"
|
||||
[class.hover:border-destructive/60]="isServerMarkedBanned(server)"
|
||||
[title]="isJoinedServer(server) ? 'Double-click to open ' + server.name : 'Double-click to join ' + server.name"
|
||||
(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>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex min-w-0 flex-wrap items-center gap-2">
|
||||
<h3
|
||||
class="truncate text-sm font-semibold transition-colors"
|
||||
[class.text-foreground]="!isServerMarkedBanned(server)"
|
||||
[class.group-hover:text-primary]="!isServerMarkedBanned(server)"
|
||||
[class.text-destructive]="isServerMarkedBanned(server)"
|
||||
>
|
||||
} @else if (server.isPrivate) {
|
||||
<ng-icon
|
||||
name="lucideLock"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="inline-flex items-center rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground"
|
||||
>Private</span
|
||||
>
|
||||
} @else if (server.hasPassword) {
|
||||
<ng-icon
|
||||
name="lucideLock"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
/>
|
||||
<span class="inline-flex items-center rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground"
|
||||
>Password</span
|
||||
{{ server.name }}
|
||||
</h3>
|
||||
|
||||
@if (isServerMarkedBanned(server)) {
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full bg-destructive/10 px-2 py-0.5 text-[11px] font-medium text-destructive"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideLock"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
Banned
|
||||
</span>
|
||||
} @else if (server.isPrivate) {
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideLock"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
Private
|
||||
</span>
|
||||
} @else if (server.hasPassword) {
|
||||
<span
|
||||
class="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideLock"
|
||||
class="h-3 w-3"
|
||||
/>
|
||||
Password
|
||||
</span>
|
||||
} @else {
|
||||
<ng-icon
|
||||
name="lucideGlobe"
|
||||
class="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (server.description) {
|
||||
<p class="mt-1 line-clamp-1 text-xs text-muted-foreground">{{ server.description }}</p>
|
||||
}
|
||||
|
||||
<div class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<ng-icon
|
||||
name="lucideUsers"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
{{ getServerUserCount(server) }}/{{ getServerCapacityLabel(server) }}
|
||||
</span>
|
||||
@if (server.topic) {
|
||||
<span class="truncate">{{ server.topic }}</span>
|
||||
}
|
||||
<span class="truncate">Owner: {{ getServerOwnerLabel(server) }}</span>
|
||||
<span class="truncate">{{ server.sourceName || server.hostName || 'Unknown' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="relative shrink-0">
|
||||
@if (isJoinedServer(server)) {
|
||||
<div
|
||||
class="flex items-center overflow-hidden rounded-md border border-emerald-500/30 bg-emerald-500/10 text-xs font-semibold text-emerald-500"
|
||||
>
|
||||
<span class="px-2.5 py-1.5">Joined</span>
|
||||
<button
|
||||
type="button"
|
||||
class="grid h-8 w-8 place-items-center border-l border-emerald-500/20 transition-colors hover:bg-emerald-500/15"
|
||||
[attr.aria-label]="'Server actions for ' + server.name"
|
||||
(click)="toggleJoinedServerMenu($event, server)"
|
||||
>
|
||||
<ng-icon
|
||||
name="lucideChevronDown"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (joinedServerMenuId() === server.id) {
|
||||
<div class="absolute right-0 top-full z-20 mt-1 w-36 rounded-md border border-border bg-card py-1 shadow-lg">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-2 text-left text-xs font-medium text-destructive transition-colors hover:bg-destructive/10"
|
||||
(click)="openLeaveDialog($event, server)"
|
||||
>
|
||||
Leave
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
} @else {
|
||||
<ng-icon
|
||||
name="lucideGlobe"
|
||||
class="w-4 h-4 text-muted-foreground"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="pointer-events-none scale-95 rounded-md bg-primary px-2.5 py-1.5 text-xs font-semibold text-primary-foreground opacity-0 transition-[opacity,transform] duration-75 ease-out hover:scale-100 hover:opacity-100 group-hover:pointer-events-auto group-hover:scale-100 group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:scale-100 group-focus-within:opacity-100"
|
||||
[attr.aria-label]="'Join ' + server.name"
|
||||
(click)="joinServer(server)"
|
||||
>
|
||||
<span class="sr-only">{{ server.name }}</span>
|
||||
Join
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
@if (server.description) {
|
||||
<p class="text-sm text-muted-foreground mt-1 line-clamp-2">
|
||||
{{ server.description }}
|
||||
</p>
|
||||
}
|
||||
@if (server.topic) {
|
||||
<span class="inline-block mt-2 px-2 py-0.5 text-xs bg-secondary rounded-full text-muted-foreground">
|
||||
{{ server.topic }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
<div class="flex items-center gap-1 text-muted-foreground text-sm ml-4">
|
||||
<ng-icon
|
||||
name="lucideUsers"
|
||||
class="w-4 h-4"
|
||||
/>
|
||||
<span>{{ getServerUserCount(server) }}/{{ getServerCapacityLabel(server) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 space-y-1 text-xs">
|
||||
<div class="text-muted-foreground">
|
||||
Users: <span class="text-foreground/80">{{ getServerUserCount(server) }}/{{ getServerCapacityLabel(server) }}</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground">
|
||||
Listed by: <span class="text-foreground/80">{{ server.sourceName || server.hostName || 'Unknown' }}</span>
|
||||
</div>
|
||||
<div class="text-muted-foreground">
|
||||
Owner: <span class="text-foreground/80">{{ server.ownerName || server.ownerId || 'Unknown' }}</span>
|
||||
</div>
|
||||
@if (server.hasPassword && !server.isPrivate && !isServerMarkedBanned(server)) {
|
||||
<div class="text-muted-foreground">Access: <span class="text-foreground/80">Password required</span></div>
|
||||
}
|
||||
</div>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@if (joinErrorMessage() || error()) {
|
||||
@@ -181,6 +231,15 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (leaveDialogRoom()) {
|
||||
<app-leave-server-dialog
|
||||
[room]="leaveDialogRoom()!"
|
||||
[currentUser]="currentUser() ?? null"
|
||||
(confirmed)="confirmLeaveServer($event)"
|
||||
(cancelled)="closeLeaveDialog()"
|
||||
/>
|
||||
}
|
||||
|
||||
@if (showBannedDialog()) {
|
||||
<app-confirm-dialog
|
||||
title="Banned"
|
||||
|
||||
@@ -23,7 +23,8 @@ import {
|
||||
lucideLock,
|
||||
lucideGlobe,
|
||||
lucidePlus,
|
||||
lucideSettings
|
||||
lucideSettings,
|
||||
lucideChevronDown
|
||||
} from '@ng-icons/lucide';
|
||||
|
||||
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
|
||||
@@ -39,8 +40,13 @@ 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 } 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';
|
||||
|
||||
@Component({
|
||||
selector: 'app-server-search',
|
||||
@@ -49,7 +55,9 @@ import { hasRoomBanForUser } from '../../../access-control';
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
NgIcon,
|
||||
ConfirmDialogComponent
|
||||
ConfirmDialogComponent,
|
||||
LeaveServerDialogComponent,
|
||||
UserSearchListComponent
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -58,7 +66,8 @@ import { hasRoomBanForUser } from '../../../access-control';
|
||||
lucideLock,
|
||||
lucideGlobe,
|
||||
lucidePlus,
|
||||
lucideSettings
|
||||
lucideSettings,
|
||||
lucideChevronDown
|
||||
})
|
||||
],
|
||||
templateUrl: './server-search.component.html'
|
||||
@@ -91,6 +100,8 @@ export class ServerSearchComponent implements OnInit {
|
||||
joinPassword = signal('');
|
||||
joinPasswordError = signal<string | null>(null);
|
||||
joinErrorMessage = signal<string | null>(null);
|
||||
joinedServerMenuId = signal<string | null>(null);
|
||||
leaveDialogRoom = signal<Room | null>(null);
|
||||
|
||||
// Create dialog state
|
||||
showCreateDialog = signal(false);
|
||||
@@ -117,7 +128,7 @@ export class ServerSearchComponent implements OnInit {
|
||||
this.store.dispatch(RoomsActions.loadRooms());
|
||||
|
||||
// Setup debounced search
|
||||
this.searchSubject.pipe(debounceTime(300), distinctUntilChanged()).subscribe((query) => {
|
||||
this.searchSubject.pipe(debounceTime(120), distinctUntilChanged()).subscribe((query) => {
|
||||
this.store.dispatch(RoomsActions.searchServers({ query }));
|
||||
});
|
||||
}
|
||||
@@ -190,7 +201,66 @@ export class ServerSearchComponent implements OnInit {
|
||||
|
||||
/** Join a previously saved room by converting it to a ServerInfo payload. */
|
||||
joinSavedRoom(room: Room): void {
|
||||
void this.joinServer(this.toServerInfo(room));
|
||||
this.openJoinedRoom(room);
|
||||
}
|
||||
|
||||
openServerCard(server: ServerInfo): void {
|
||||
const joinedRoom = this.joinedRoomForServer(server);
|
||||
|
||||
if (joinedRoom) {
|
||||
this.openJoinedRoom(joinedRoom);
|
||||
return;
|
||||
}
|
||||
|
||||
void this.joinServer(server);
|
||||
}
|
||||
|
||||
joinedRoomForServer(server: ServerInfo): Room | null {
|
||||
return this.savedRooms().find((room) => room.id === server.id) ?? null;
|
||||
}
|
||||
|
||||
isJoinedServer(server: ServerInfo): boolean {
|
||||
return !!this.joinedRoomForServer(server);
|
||||
}
|
||||
|
||||
toggleJoinedServerMenu(event: Event, server: ServerInfo): void {
|
||||
event.stopPropagation();
|
||||
this.joinedServerMenuId.update((currentId) => currentId === server.id ? null : server.id);
|
||||
}
|
||||
|
||||
closeJoinedServerMenu(): void {
|
||||
this.joinedServerMenuId.set(null);
|
||||
}
|
||||
|
||||
openLeaveDialog(event: Event, server: ServerInfo): void {
|
||||
event.stopPropagation();
|
||||
const room = this.joinedRoomForServer(server);
|
||||
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.joinedServerMenuId.set(null);
|
||||
this.leaveDialogRoom.set(room);
|
||||
}
|
||||
|
||||
closeLeaveDialog(): void {
|
||||
this.leaveDialogRoom.set(null);
|
||||
}
|
||||
|
||||
confirmLeaveServer(result: LeaveServerDialogResult): void {
|
||||
const room = this.leaveDialogRoom();
|
||||
|
||||
if (!room) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.store.dispatch(RoomsActions.forgetRoom({
|
||||
roomId: room.id,
|
||||
nextOwnerKey: result.nextOwnerKey
|
||||
}));
|
||||
|
||||
this.leaveDialogRoom.set(null);
|
||||
}
|
||||
|
||||
closeBannedDialog(): void {
|
||||
@@ -231,6 +301,21 @@ export class ServerSearchComponent implements OnInit {
|
||||
return server.maxUsers > 0 ? String(server.maxUsers) : '∞';
|
||||
}
|
||||
|
||||
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
|
||||
);
|
||||
|
||||
return server.ownerName || ownerMember?.displayName || server.ownerId || joinedRoom?.hostId || 'Unknown owner';
|
||||
}
|
||||
|
||||
private openJoinedRoom(room: Room): void {
|
||||
this.joinedServerMenuId.set(null);
|
||||
this.store.dispatch(RoomsActions.viewServer({ room }));
|
||||
}
|
||||
|
||||
private toServerInfo(room: Room): ServerInfo {
|
||||
return {
|
||||
id: room.id,
|
||||
|
||||
@@ -4,10 +4,11 @@ import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import {
|
||||
Observable,
|
||||
forkJoin,
|
||||
merge,
|
||||
of,
|
||||
throwError
|
||||
} from 'rxjs';
|
||||
import { catchError, map } from 'rxjs/operators';
|
||||
import { catchError, map, scan } from 'rxjs/operators';
|
||||
import {
|
||||
ChannelPermissionOverride,
|
||||
type Channel,
|
||||
@@ -299,9 +300,8 @@ export class ServerDirectoryApiService {
|
||||
return this.searchSingleEndpoint(query, this.getApiBaseUrl(), this.endpointState.activeServer());
|
||||
}
|
||||
|
||||
return forkJoin(onlineEndpoints.map((endpoint) => this.searchSingleEndpoint(query, `${endpoint.url}/api`, endpoint))).pipe(
|
||||
map((resultArrays) => resultArrays.flat()),
|
||||
map((servers) => this.deduplicateById(servers))
|
||||
return merge(...onlineEndpoints.map((endpoint) => this.searchSingleEndpoint(query, `${endpoint.url}/api`, endpoint))).pipe(
|
||||
scan((servers, endpointServers) => this.deduplicateById([...servers, ...endpointServers]), [] as ServerInfo[])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user