wip: optimizations
This commit is contained in:
@@ -153,10 +153,10 @@
|
||||
/>
|
||||
|
||||
<section
|
||||
class="min-h-0 overflow-y-auto"
|
||||
class="flex min-h-0 flex-col"
|
||||
[class.hidden]="isMobile() && mobileTab() !== 'servers'"
|
||||
>
|
||||
<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 class="z-10 flex shrink-0 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>
|
||||
@@ -164,11 +164,11 @@
|
||||
</div>
|
||||
|
||||
@if (isSearching()) {
|
||||
<div class="flex items-center justify-center py-8">
|
||||
<div class="flex flex-1 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">
|
||||
<div class="flex flex-1 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"
|
||||
@@ -176,10 +176,20 @@
|
||||
<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"
|
||||
<app-virtual-list
|
||||
class="block min-h-0 flex-1 p-3"
|
||||
[items]="searchResults()"
|
||||
[estimateSize]="140"
|
||||
[overscan]="4"
|
||||
[trackBy]="trackServerById"
|
||||
>
|
||||
<ng-template
|
||||
#item
|
||||
let-server
|
||||
>
|
||||
<div class="pb-2">
|
||||
<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)"
|
||||
@@ -315,8 +325,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</app-virtual-list>
|
||||
}
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -58,6 +58,7 @@ import {
|
||||
import { ChatMessageMarkdownComponent } from '../../../chat';
|
||||
import { hasRoomBanForUser } from '../../../access-control';
|
||||
import { UserSearchListComponent } from '../../../direct-message/feature/user-search-list/user-search-list.component';
|
||||
import { VirtualListComponent } from '../../../../shared/components/virtual-list';
|
||||
import { RealtimeSessionFacade } from '../../../../core/realtime';
|
||||
import {
|
||||
PluginRequirementService,
|
||||
@@ -82,7 +83,8 @@ interface JoinPluginConsentDialog {
|
||||
ChatMessageMarkdownComponent,
|
||||
ConfirmDialogComponent,
|
||||
LeaveServerDialogComponent,
|
||||
UserSearchListComponent
|
||||
UserSearchListComponent,
|
||||
VirtualListComponent
|
||||
],
|
||||
viewProviders: [
|
||||
provideIcons({
|
||||
@@ -187,6 +189,9 @@ export class ServerSearchComponent implements OnInit {
|
||||
this.searchSubject.next(query);
|
||||
}
|
||||
|
||||
/** Stable trackBy reference for the virtualized server results list. */
|
||||
readonly trackServerById = (_index: number, server: ServerInfo): string => server.id;
|
||||
|
||||
/** Join a server from the search results. Redirects to login if unauthenticated. */
|
||||
async joinServer(server: ServerInfo): Promise<void> {
|
||||
const currentUserId = localStorage.getItem('metoyou_currentUserId');
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { jsonStorage } from '../../../../infrastructure/persistence/json-storage.service';
|
||||
import {
|
||||
DISABLED_DEFAULT_SERVER_KEYS_STORAGE_KEY,
|
||||
REMOVED_DEFAULT_SERVER_KEYS_STORAGE_KEY,
|
||||
@@ -8,26 +9,16 @@ import type { ServerEndpoint } from '../../domain/models/server-directory.model'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ServerEndpointStorageService {
|
||||
private readonly storage = jsonStorage;
|
||||
|
||||
loadEndpoints(): ServerEndpoint[] | null {
|
||||
const stored = localStorage.getItem(SERVER_ENDPOINTS_STORAGE_KEY);
|
||||
const parsed = this.storage.read<unknown>(SERVER_ENDPOINTS_STORAGE_KEY, null);
|
||||
|
||||
if (!stored) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(stored) as unknown;
|
||||
|
||||
return Array.isArray(parsed)
|
||||
? parsed as ServerEndpoint[]
|
||||
: null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return Array.isArray(parsed) ? parsed as ServerEndpoint[] : null;
|
||||
}
|
||||
|
||||
saveEndpoints(endpoints: ServerEndpoint[]): void {
|
||||
localStorage.setItem(SERVER_ENDPOINTS_STORAGE_KEY, JSON.stringify(endpoints));
|
||||
this.storage.write(SERVER_ENDPOINTS_STORAGE_KEY, endpoints);
|
||||
}
|
||||
|
||||
loadDisabledDefaultEndpointKeys(): Set<string> {
|
||||
@@ -39,7 +30,7 @@ export class ServerEndpointStorageService {
|
||||
}
|
||||
|
||||
clearDisabledDefaultEndpointKeys(): void {
|
||||
localStorage.removeItem(DISABLED_DEFAULT_SERVER_KEYS_STORAGE_KEY);
|
||||
this.storage.remove(DISABLED_DEFAULT_SERVER_KEYS_STORAGE_KEY);
|
||||
}
|
||||
|
||||
loadRemovedDefaultEndpointKeys(): Set<string> {
|
||||
@@ -51,35 +42,25 @@ export class ServerEndpointStorageService {
|
||||
}
|
||||
|
||||
clearRemovedDefaultEndpointKeys(): void {
|
||||
localStorage.removeItem(REMOVED_DEFAULT_SERVER_KEYS_STORAGE_KEY);
|
||||
this.storage.remove(REMOVED_DEFAULT_SERVER_KEYS_STORAGE_KEY);
|
||||
}
|
||||
|
||||
private loadStringSet(storageKey: string): Set<string> {
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
const parsed = this.storage.read<unknown>(storageKey, null);
|
||||
|
||||
if (!stored) {
|
||||
if (!Array.isArray(parsed)) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(stored) as unknown;
|
||||
|
||||
if (!Array.isArray(parsed)) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
return new Set(parsed.filter((value): value is string => typeof value === 'string'));
|
||||
} catch {
|
||||
return new Set<string>();
|
||||
}
|
||||
return new Set(parsed.filter((value): value is string => typeof value === 'string'));
|
||||
}
|
||||
|
||||
private saveStringSet(storageKey: string, keys: Set<string>): void {
|
||||
if (keys.size === 0) {
|
||||
localStorage.removeItem(storageKey);
|
||||
this.storage.remove(storageKey);
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(storageKey, JSON.stringify([...keys]));
|
||||
this.storage.write(storageKey, [...keys]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user