wip: optimizations

This commit is contained in:
2026-05-23 15:28:40 +02:00
parent 5bf506af03
commit 155fe20862
89 changed files with 7431 additions and 392 deletions

View File

@@ -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>

View File

@@ -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');

View File

@@ -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]);
}
}