chore: enforce lint across codebase and ban "maybe" in identifiers

Remove member-ordering and complexity eslint-disable comments by reordering
class members and applying targeted fixes. Add metoyou/no-maybe-in-naming,
type-safe WebRTC e2e harness helpers, and resolve remaining lint errors so
npm run lint exits cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-11 11:08:26 +02:00
parent b630bacdc6
commit 79c6f91cd6
138 changed files with 4286 additions and 2310 deletions

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
effect,
@@ -35,17 +34,21 @@ import { APP_TRANSLATE_IMPORTS } from '../../../../core/i18n';
templateUrl: './bans-settings.component.html'
})
export class BansSettingsComponent {
private store = inject(Store);
private actions$ = inject(Actions);
private db = inject(DatabaseService);
/** The currently selected server, passed from the parent. */
server = input<Room | null>(null);
/** Whether the current user is admin of this server. */
isAdmin = input(false);
bannedUsers = signal<BanEntry[]>([]);
private store = inject(Store);
private actions$ = inject(Actions);
private db = inject(DatabaseService);
constructor() {
effect(() => {
const roomId = this.server()?.id;
@@ -82,10 +85,6 @@ export class BansSettingsComponent {
oderId: ban.oderId }));
}
private async loadBansForServer(roomId: string): Promise<void> {
this.bannedUsers.set(await this.db.getBansForRoom(roomId));
}
formatExpiry(timestamp: number): string {
const date = new Date(timestamp);
@@ -96,4 +95,9 @@ export class BansSettingsComponent {
minute: '2-digit' })
);
}
private async loadBansForServer(roomId: string): Promise<void> {
this.bannedUsers.set(await this.db.getBansForRoom(roomId));
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -41,16 +40,23 @@ type DataAction = 'open' | 'export' | 'import' | 'erase' | 'restart';
templateUrl: './data-settings.component.html'
})
export class DataSettingsComponent {
private readonly electron = inject(ElectronBridgeService);
private readonly appI18n = inject(AppI18nService);
readonly isElectron = this.electron.isAvailable;
readonly dataPath = signal<string | null>(null);
readonly busyAction = signal<DataAction | null>(null);
readonly statusMessage = signal<string | null>(null);
readonly errorMessage = signal<string | null>(null);
readonly restartRequired = signal(false);
private readonly electron = inject(ElectronBridgeService);
private readonly appI18n = inject(AppI18nService);
constructor() {
void this.loadDataPath();
}
@@ -145,4 +151,5 @@ export class DataSettingsComponent {
this.busyAction.set(null);
}
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
DestroyRef,
@@ -49,35 +48,46 @@ const APP_METRICS_POLL_INTERVAL_MS = 2_000;
templateUrl: './debugging-settings.component.html'
})
export class DebuggingSettingsComponent {
private readonly destroyRef = inject(DestroyRef);
private readonly platform = inject(PlatformService);
private readonly electronBridge = inject(ElectronBridgeService);
readonly debugging = inject(DebuggingService);
private readonly appI18n = inject(AppI18nService);
readonly isElectron = this.platform.isElectron;
readonly ramLabel = signal<string | null>(null);
readonly enabled = this.debugging.enabled;
readonly isConsoleOpen = this.debugging.isConsoleOpen;
readonly entryCount = computed(() => {
return this.debugging.entries().reduce((sum, entry) => sum + entry.count, 0);
});
readonly errorCount = computed(() => {
return this.debugging.entries().reduce((sum, entry) => {
return sum + (entry.level === 'error' ? entry.count : 0);
}, 0);
});
readonly warningCount = computed(() => {
return this.debugging.entries().reduce((sum, entry) => {
return sum + (entry.level === 'warn' ? entry.count : 0);
}, 0);
});
readonly lastUpdatedLabel = computed(() => {
const lastEntry = this.debugging.entries().at(-1);
return lastEntry ? lastEntry.timeLabel : this.appI18n.instant('settings.debugging.noLogsYet');
});
private readonly destroyRef = inject(DestroyRef);
private readonly platform = inject(PlatformService);
private readonly electronBridge = inject(ElectronBridgeService);
private readonly appI18n = inject(AppI18nService);
constructor() {
if (this.isElectron)
this.startRamPolling();
@@ -118,4 +128,5 @@ export class DebuggingSettingsComponent {
}
});
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -34,20 +33,30 @@ import { SelectOnFocusDirective, SubmitOnEnterDirective } from '../../../../shar
templateUrl: './general-settings.component.html'
})
export class GeneralSettingsComponent {
private platform = inject(PlatformService);
private electronBridge = inject(ElectronBridgeService);
readonly experimentalMedia = inject(ExperimentalMediaSettingsService);
readonly isElectron = this.platform.isElectron;
reopenLastViewedChat = signal(true);
autoStart = signal(false);
closeToTray = signal(true);
savingAutoStart = signal(false);
savingCloseToTray = signal(false);
ignoredGameProcesses = signal<string[]>([]);
ignoredProcessDraft = signal('');
savingIgnoredGameProcesses = signal(false);
private platform = inject(PlatformService);
private electronBridge = inject(ElectronBridgeService);
constructor() {
this.loadGeneralSettings();
@@ -119,31 +128,6 @@ export class GeneralSettingsComponent {
input.checked = this.experimentalMedia.vlcJsPlaybackEnabled();
}
private async loadDesktopSettings(): Promise<void> {
const api = this.electronBridge.getApi();
if (!api) {
return;
}
try {
const snapshot = await api.getDesktopSettings();
this.applyDesktopSettings(snapshot);
} catch {}
}
private loadGeneralSettings(): void {
const settings = loadGeneralSettingsFromStorage();
this.reopenLastViewedChat.set(settings.reopenLastViewedChat);
}
private applyDesktopSettings(snapshot: DesktopSettingsSnapshot): void {
this.autoStart.set(snapshot.autoStart);
this.closeToTray.set(snapshot.closeToTray);
}
onIgnoredProcessDraftChange(event: Event): void {
const input = event.target as HTMLInputElement;
@@ -169,6 +153,31 @@ export class GeneralSettingsComponent {
await this.saveIgnoredGameProcesses(next);
}
private async loadDesktopSettings(): Promise<void> {
const api = this.electronBridge.getApi();
if (!api) {
return;
}
try {
const snapshot = await api.getDesktopSettings();
this.applyDesktopSettings(snapshot);
} catch {}
}
private loadGeneralSettings(): void {
const settings = loadGeneralSettingsFromStorage();
this.reopenLastViewedChat.set(settings.reopenLastViewedChat);
}
private applyDesktopSettings(snapshot: DesktopSettingsSnapshot): void {
this.autoStart.set(snapshot.autoStart);
this.closeToTray.set(snapshot.closeToTray);
}
private async loadIgnoredGameProcesses(): Promise<void> {
const api = this.electronBridge.getApi();
@@ -200,4 +209,5 @@ export class GeneralSettingsComponent {
this.savingIgnoredGameProcesses.set(false);
}
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -52,17 +51,23 @@ import {
templateUrl: './ice-server-settings.component.html'
})
export class IceServerSettingsComponent {
private iceSettings = inject(IceServerSettingsService);
private readonly appI18n = inject(AppI18nService);
entries = this.iceSettings.entries;
addError = signal<string | null>(null);
newType: 'stun' | 'turn' = 'stun';
newUrl = '';
newUsername = '';
newCredential = '';
private iceSettings = inject(IceServerSettingsService);
private readonly appI18n = inject(AppI18nService);
addEntry(): void {
this.addError.set(null);
@@ -134,4 +139,5 @@ export class IceServerSettingsComponent {
trackEntry(_index: number, entry: IceServerEntry): string {
return entry.id;
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
OnDestroy,
@@ -29,8 +28,6 @@ import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../../core/i18n';
templateUrl: './local-api-settings.component.html'
})
export class LocalApiSettingsComponent implements OnInit, OnDestroy {
private readonly bridge = inject(ElectronBridgeService);
private readonly appI18n = inject(AppI18nService);
readonly isElectron = this.bridge.isAvailable;
@@ -55,10 +52,15 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
});
readonly allowedServersText = signal('');
readonly hasPendingAllowedServersChanges = signal(false);
readonly portText = signal('17878');
readonly hasPendingPortChange = signal(false);
readonly busy = signal(false);
readonly errorMessage = signal<string | null>(null);
readonly statusLabel = computed(() => {
@@ -81,6 +83,10 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
}
});
private readonly bridge = inject(ElectronBridgeService);
private readonly appI18n = inject(AppI18nService);
private statusPollHandle: ReturnType<typeof setInterval> | null = null;
async ngOnInit(): Promise<void> {
@@ -278,4 +284,5 @@ export class LocalApiSettingsComponent implements OnInit, OnDestroy {
this.busy.set(false);
}
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
computed,
@@ -56,23 +55,27 @@ interface ServerMemberView extends RoomMember {
templateUrl: './members-settings.component.html'
})
export class MembersSettingsComponent {
private store = inject(Store);
private readonly i18n = inject(AppI18nService);
/** The currently selected server, passed from the parent. */
server = input<Room | null>(null);
/** Whether the current user is admin of this server. */
isAdmin = input(false);
accessRole = input<string | null>(null);
currentUser = this.store.selectSignal(selectCurrentUser);
currentRoom = this.store.selectSignal(selectCurrentRoom);
usersEntities = this.store.selectSignal(selectUsersEntities);
normalizedServer = computed(() => {
const room = this.server();
return room ? normalizeRoomAccessControl(room) : null;
});
assignableRoles = computed<RoomRole[]>(() => findAssignableRoles(this.normalizedServer()?.roles ?? []));
members = computed<ServerMemberView[]>(() => {
@@ -104,6 +107,10 @@ export class MembersSettingsComponent {
});
});
private store = inject(Store);
private readonly i18n = inject(AppI18nService);
canChangeRoles(member: ServerMemberView): boolean {
const room = this.normalizedServer();
const currentUser = this.currentUser();
@@ -163,4 +170,5 @@ export class MembersSettingsComponent {
roomId: room.id,
displayName: member.displayName }));
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -53,25 +52,40 @@ import { SelectOnFocusDirective, SubmitOnEnterDirective } from '../../../../shar
templateUrl: './network-settings.component.html'
})
export class NetworkSettingsComponent {
private serverDirectory = inject(ServerDirectoryFacade);
private readonly appI18n = inject(AppI18nService);
readonly signalServerAuth = inject(SignalServerAuthService);
private readonly signalServerAuthorize = inject(SignalServerAuthorizeService);
private readonly provisionNoticeService = inject(SignalServerProvisionNoticeService);
readonly provisionNotice = this.provisionNoticeService.notice;
servers = this.serverDirectory.servers;
activeServers = this.serverDirectory.activeServers;
hasMissingDefaultServers = this.serverDirectory.hasMissingDefaultServers;
hasMultipleServers = computed(() => this.servers().length > 1);
hasMultipleActiveServers = computed(() => this.activeServers().length > 1);
isTesting = signal(false);
addError = signal<string | null>(null);
newServerName = '';
newServerUrl = '';
autoReconnect = true;
searchAllServers = true;
private serverDirectory = inject(ServerDirectoryFacade);
private readonly appI18n = inject(AppI18nService);
private readonly signalServerAuthorize = inject(SignalServerAuthorizeService);
private readonly provisionNoticeService = inject(SignalServerProvisionNoticeService);
constructor() {
this.loadConnectionSettings();
}
@@ -166,4 +180,5 @@ export class NetworkSettingsComponent {
this.serverDirectory.setSearchAllServers(this.searchAllServers);
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
computed,
@@ -92,12 +91,13 @@ function upsertRoleChannelOverride(
templateUrl: './permissions-settings.component.html'
})
export class PermissionsSettingsComponent {
private store = inject(Store);
private readonly appI18n = inject(AppI18nService);
server = input<Room | null>(null);
isAdmin = input(false);
currentUser = this.store.selectSignal(selectCurrentUser);
permissionDefinitions = computed(() =>
ROOM_PERMISSION_DEFINITIONS.map((definition) => ({
key: definition.key,
@@ -105,24 +105,30 @@ export class PermissionsSettingsComponent {
description: this.appI18n.instant(`permissions.${definition.key}.description`)
}))
);
permissionStates: PermissionState[] = [
'inherit',
'allow',
'deny'
];
normalizedServer = computed(() => {
const room = this.server();
return room ? normalizeRoomAccessControl(room) : null;
});
roles = computed<RoomRole[]>(() => sortRolesForDisplay(this.normalizedServer()?.roles ?? []));
channels = computed(() => this.normalizedServer()?.channels ?? []);
canManageRoles = computed(() => {
const room = this.normalizedServer();
const user = this.currentUser();
return !!room && !!user && (room.hostId === user.id || room.hostId === user.oderId || resolveRoomPermission(room, user, 'manageRoles'));
});
canManageServer = computed(() => {
const room = this.normalizedServer();
const user = this.currentUser();
@@ -131,10 +137,17 @@ export class PermissionsSettingsComponent {
});
selectedRoleKey: string | null = null;
selectedChannelKey = '';
roleName = '';
roleColor = '#94a3b8';
private store = inject(Store);
private readonly appI18n = inject(AppI18nService);
constructor() {
effect(() => {
const room = this.normalizedServer();
@@ -406,4 +419,5 @@ export class PermissionsSettingsComponent {
trackRole(_: number, role: RoomRole): string {
return role.id;
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
effect,
@@ -54,37 +53,54 @@ import { SelectOnFocusDirective, SubmitOnEnterDirective } from '../../../../shar
templateUrl: './server-settings.component.html'
})
export class ServerSettingsComponent {
private store = inject(Store);
private modal = inject(SettingsModalService);
private serverIconImages = inject(ServerIconImageService);
private readonly appI18n = inject(AppI18nService);
/** The currently selected server, passed from the parent. */
server = input<Room | null>(null);
/** Whether the current user is admin of this server. */
isAdmin = input(false);
/** Whether the current user can manage this server's icon. */
canManageIcon = input(false);
/** Whether the current user can delete this server. */
canDeleteServer = input(false);
roomName = '';
roomDescription = '';
isPrivate = signal(false);
hasPassword = signal(false);
passwordAction = signal<'keep' | 'update' | 'remove'>('keep');
passwordError = signal<string | null>(null);
roomPassword = '';
maxUsers = 0;
showDeleteConfirm = signal(false);
iconError = signal<string | null>(null);
saveSuccess = signal<string | null>(null);
private saveTimeout: ReturnType<typeof setTimeout> | null = null;
/** Reload form fields whenever the server input changes. */
readonly serverData = this.server;
private store = inject(Store);
private modal = inject(SettingsModalService);
private serverIconImages = inject(ServerIconImageService);
private readonly appI18n = inject(AppI18nService);
private saveTimeout: ReturnType<typeof setTimeout> | null = null;
constructor() {
effect(() => {
const room = this.server();
@@ -243,4 +259,5 @@ export class ServerSettingsComponent {
this.saveTimeout = setTimeout(() => this.saveSuccess.set(null), 2000);
}
}

View File

@@ -1,4 +1,3 @@
<!-- eslint-disable @angular-eslint/template/cyclomatic-complexity -->
@if (isOpen() && !isThemeStudioFullscreen()) {
<!-- Backdrop (hidden on mobile where the modal is full-screen) -->
<div

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -104,14 +103,8 @@ import { APP_TRANSLATE_IMPORTS, AppI18nService } from '../../../core/i18n';
})
export class SettingsModalComponent {
readonly modal = inject(SettingsModalService);
private store = inject(Store);
private webrtc = inject(RealtimeSessionFacade);
private theme = inject(ThemeService);
private themeLibrary = inject(ThemeLibraryService);
private viewport = inject(ViewportService);
private readonly appI18n = inject(AppI18nService);
readonly thirdPartyLicenses: readonly ThirdPartyLicense[] = THIRD_PARTY_LICENSES;
private lastRequestedServerId: string | null = null;
/** True on mobile breakpoints. Drives the full-screen, page-stack layout. */
readonly isMobile = this.viewport.isMobile;
@@ -124,21 +117,30 @@ export class SettingsModalComponent {
*/
readonly mobilePage = signal<'menu' | 'detail'>('menu');
private permissionsComponent = viewChild<PermissionsSettingsComponent>('permissionsComp');
savedRooms = this.store.selectSignal(selectSavedRooms);
currentRoom = this.store.selectSignal(selectCurrentRoom);
currentUser = this.store.selectSignal(selectCurrentUser);
isOpen = this.modal.isOpen;
activePage = this.modal.activePage;
themeStudioFullscreen = this.modal.themeStudioFullscreen;
themeStudioMinimized = this.modal.themeStudioMinimized;
isThemeStudioFullscreen = computed(() => this.activePage() === 'theme' && this.themeStudioFullscreen());
activeThemeName = this.theme.activeThemeName;
savedThemesAvailable = this.themeLibrary.isAvailable;
savedThemes = this.themeLibrary.entries;
savedThemesBusy = this.themeLibrary.isBusy;
selectedSavedTheme = this.themeLibrary.selectedEntry;
readonly globalPages = computed<{ id: SettingsPage; label: string; icon: string }[]>(() => [
@@ -153,6 +155,7 @@ export class SettingsModalComponent {
{ id: 'data', label: this.appI18n.instant('settings.nav.data'), icon: 'lucideDownload' },
{ id: 'debugging', label: this.appI18n.instant('settings.nav.debugging'), icon: 'lucideBug' }
]);
readonly serverPages = computed<{ id: SettingsPage; label: string; icon: string }[]>(() => [
{ id: 'server', label: this.appI18n.instant('settings.nav.server'), icon: 'lucideSettings' },
{ id: 'serverPlugins', label: this.appI18n.instant('settings.nav.serverPlugins'), icon: 'lucidePackage' },
@@ -192,6 +195,7 @@ export class SettingsModalComponent {
});
selectedServerId = signal<string | null>(null);
selectedServer = computed<Room | null>(() => {
const id = this.selectedServerId();
const currentRoom = this.currentRoom();
@@ -294,8 +298,25 @@ export class SettingsModalComponent {
});
animating = signal(false);
showThirdPartyLicenses = signal(false);
private store = inject(Store);
private webrtc = inject(RealtimeSessionFacade);
private theme = inject(ThemeService);
private themeLibrary = inject(ThemeLibraryService);
private viewport = inject(ViewportService);
private readonly appI18n = inject(AppI18nService);
private lastRequestedServerId: string | null = null;
private permissionsComponent = viewChild<PermissionsSettingsComponent>('permissionsComp');
constructor() {
effect(() => {
if (!this.isOpen()) {
@@ -482,4 +503,5 @@ export class SettingsModalComponent {
this.themeLibrary.select(matchingTheme?.fileName ?? null);
}
}

View File

@@ -31,23 +31,34 @@ type DesktopUpdateStatus =
templateUrl: './updates-settings.component.html'
})
export class UpdatesSettingsComponent {
private readonly appI18n = inject(AppI18nService);
readonly desktopUpdates = inject(DesktopAppUpdateService);
readonly mobileUpdates = inject(MobileAppUpdateService);
readonly isElectron = this.desktopUpdates.isElectron;
readonly isCapacitor = this.mobileUpdates.isCapacitor;
readonly state = this.desktopUpdates.state;
readonly mobileState = this.mobileUpdates.state;
readonly mobileStatusLabel = computed(() =>
this.getMobileStatusLabel(this.mobileState().status)
);
readonly hasPendingManifestUrlChanges = signal(false);
readonly manifestUrlsText = signal('');
readonly statusLabel = computed(() => this.getStatusLabel(this.state().status));
readonly isUsingConnectedServerDefaults = computed(() => {
return this.state().configuredManifestUrls.length === 0;
});
private readonly appI18n = inject(AppI18nService);
constructor() {
effect(() => {
if (this.hasPendingManifestUrlChanges()) {
@@ -178,4 +189,5 @@ export class UpdatesSettingsComponent {
return this.appI18n.instant(keyMap[status] ?? keyMap['idle']);
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -53,13 +52,10 @@ interface AudioDevice {
templateUrl: './voice-settings.component.html'
})
export class VoiceSettingsComponent {
private voiceConnection = inject(VoiceConnectionFacade);
private voicePlayback = inject(VoicePlaybackService);
private electronBridge = inject(ElectronBridgeService);
private platform = inject(PlatformService);
private readonly appI18n = inject(AppI18nService);
readonly audioService = inject(NotificationAudioService);
readonly isElectron = this.platform.isElectron;
readonly screenShareQualityOptions = computed(() =>
SCREEN_SHARE_QUALITY_OPTIONS.map((option) => ({
id: option.id,
@@ -69,26 +65,51 @@ export class VoiceSettingsComponent {
);
inputDevices = signal<AudioDevice[]>([]);
outputDevices = signal<AudioDevice[]>([]);
selectedInputDevice = signal<string>('');
selectedOutputDevice = signal<string>('');
inputVolume = signal(100);
outputVolume = signal(100);
audioBitrate = signal(96);
latencyProfile = signal<'low' | 'balanced' | 'high'>('balanced');
includeSystemAudio = signal(false);
noiseReduction = signal(true);
screenShareQuality = signal<ScreenShareQuality>('balanced');
askScreenShareQuality = signal(true);
hardwareAcceleration = signal(true);
hardwareAccelerationRestartRequired = signal(false);
readonly selectedScreenShareQualityDescription = computed(
() => this.screenShareQualityOptions().find((option) => option.id === this.screenShareQuality())?.description ?? ''
);
readonly notificationVolumePercent = computed(() =>
String(Math.round(this.audioService.notificationVolume() * 100))
);
private voiceConnection = inject(VoiceConnectionFacade);
private voicePlayback = inject(VoicePlaybackService);
private electronBridge = inject(ElectronBridgeService);
private platform = inject(PlatformService);
private readonly appI18n = inject(AppI18nService);
constructor() {
this.loadVoiceSettings();
this.loadAudioDevices();
@@ -291,4 +312,5 @@ export class VoiceSettingsComponent {
this.hardwareAcceleration.set(snapshot.hardwareAcceleration);
this.hardwareAccelerationRestartRequired.set(snapshot.restartRequired);
}
}

View File

@@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
inject,
@@ -63,26 +62,40 @@ import { SelectOnFocusDirective, SubmitOnEnterDirective } from '../../shared/dir
* Settings page for managing signaling servers and connection preferences.
*/
export class SettingsComponent implements OnInit {
private serverDirectory = inject(ServerDirectoryFacade);
private voiceConnection = inject(VoiceConnectionFacade);
private router = inject(Router);
private readonly appI18n = inject(AppI18nService);
audioService = inject(NotificationAudioService);
servers = this.serverDirectory.servers;
activeServers = this.serverDirectory.activeServers;
hasMissingDefaultServers = this.serverDirectory.hasMissingDefaultServers;
hasMultipleServers = computed(() => this.servers().length > 1);
hasMultipleActiveServers = computed(() => this.activeServers().length > 1);
isTesting = signal(false);
addError = signal<string | null>(null);
newServerName = '';
newServerUrl = '';
autoReconnect = true;
searchAllServers = true;
noiseReduction = true;
private serverDirectory = inject(ServerDirectoryFacade);
private voiceConnection = inject(VoiceConnectionFacade);
private router = inject(Router);
private readonly appI18n = inject(AppI18nService);
/** Load persisted connection settings on component init. */
ngOnInit(): void {
this.loadConnectionSettings();
@@ -233,4 +246,5 @@ export class SettingsComponent implements OnInit {
await this.voiceConnection.toggleNoiseReduction(this.noiseReduction);
}
}