Now formatted correctly with eslint

This commit is contained in:
2026-03-04 00:41:02 +01:00
parent ad0e28bf84
commit 4e95ae77c5
99 changed files with 3231 additions and 1464 deletions

View File

@@ -5,9 +5,7 @@
} @else {
@for (ban of bannedUsers(); track ban.oderId) {
<div class="flex items-center gap-3 p-3 bg-secondary/50 rounded-lg">
<div
class="w-8 h-8 rounded-full bg-destructive/20 flex items-center justify-center text-destructive font-semibold text-sm"
>
<div class="w-8 h-8 rounded-full bg-destructive/20 flex items-center justify-center text-destructive font-semibold text-sm">
{{ ban.displayName?.charAt(0)?.toUpperCase() || '?' }}
</div>
<div class="flex-1 min-w-0">
@@ -18,19 +16,21 @@
<p class="text-xs text-muted-foreground truncate">Reason: {{ ban.reason }}</p>
}
@if (ban.expiresAt) {
<p class="text-xs text-muted-foreground">
Expires: {{ formatExpiry(ban.expiresAt) }}
</p>
<p class="text-xs text-muted-foreground">Expires: {{ formatExpiry(ban.expiresAt) }}</p>
} @else {
<p class="text-xs text-destructive">Permanent</p>
}
</div>
@if (isAdmin()) {
<button
type="button"
(click)="unbanUser(ban)"
class="p-2 hover:bg-secondary rounded-lg transition-colors text-muted-foreground hover:text-foreground"
>
<ng-icon name="lucideX" class="w-4 h-4" />
<ng-icon
name="lucideX"
class="w-4 h-4"
/>
</button>
}
</div>
@@ -38,7 +38,5 @@
}
</div>
} @else {
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">
Select a server from the sidebar to manage
</div>
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">Select a server from the sidebar to manage</div>
}

View File

@@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, input } from '@angular/core';
import {
Component,
inject,
input
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { Store } from '@ngrx/store';
@@ -40,7 +44,8 @@ export class BansSettingsComponent {
return (
date.toLocaleDateString() +
' ' +
date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
date.toLocaleTimeString([], { hour: '2-digit',
minute: '2-digit' })
);
}
}

View File

@@ -5,24 +5,21 @@
} @else {
@for (user of membersFiltered(); track user.id) {
<div class="flex items-center gap-3 p-3 bg-secondary/50 rounded-lg">
<app-user-avatar [name]="user.displayName || '?'" size="sm" />
<app-user-avatar
[name]="user.displayName || '?'"
size="sm"
/>
<div class="flex-1 min-w-0">
<div class="flex items-center gap-1.5">
<p class="text-sm font-medium text-foreground truncate">
{{ user.displayName }}
</p>
@if (user.role === 'host') {
<span class="text-[10px] bg-yellow-500/20 text-yellow-400 px-1 py-0.5 rounded"
>Owner</span
>
<span class="text-[10px] bg-yellow-500/20 text-yellow-400 px-1 py-0.5 rounded">Owner</span>
} @else if (user.role === 'admin') {
<span class="text-[10px] bg-blue-500/20 text-blue-400 px-1 py-0.5 rounded"
>Admin</span
>
<span class="text-[10px] bg-blue-500/20 text-blue-400 px-1 py-0.5 rounded">Admin</span>
} @else if (user.role === 'moderator') {
<span class="text-[10px] bg-green-500/20 text-green-400 px-1 py-0.5 rounded"
>Mod</span
>
<span class="text-[10px] bg-green-500/20 text-green-400 px-1 py-0.5 rounded">Mod</span>
}
</div>
</div>
@@ -42,14 +39,20 @@
class="p-1 rounded hover:bg-destructive/20 text-muted-foreground hover:text-destructive transition-colors"
title="Kick"
>
<ng-icon name="lucideUserX" class="w-4 h-4" />
<ng-icon
name="lucideUserX"
class="w-4 h-4"
/>
</button>
<button
(click)="banMember(user)"
class="p-1 rounded hover:bg-destructive/20 text-muted-foreground hover:text-destructive transition-colors"
title="Ban"
>
<ng-icon name="lucideBan" class="w-4 h-4" />
<ng-icon
name="lucideBan"
class="w-4 h-4"
/>
</button>
</div>
}
@@ -58,7 +61,5 @@
}
</div>
} @else {
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">
Select a server from the sidebar to manage
</div>
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">Select a server from the sidebar to manage</div>
}

View File

@@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, input } from '@angular/core';
import {
Component,
inject,
input
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
@@ -15,7 +19,12 @@ import { UserAvatarComponent } from '../../../../shared';
@Component({
selector: 'app-members-settings',
standalone: true,
imports: [CommonModule, FormsModule, NgIcon, UserAvatarComponent],
imports: [
CommonModule,
FormsModule,
NgIcon,
UserAvatarComponent
],
viewProviders: [
provideIcons({
lucideUserX,
@@ -43,7 +52,9 @@ export class MembersSettingsComponent {
}
changeRole(user: User, role: 'admin' | 'moderator' | 'member'): void {
this.store.dispatch(UsersActions.updateUserRole({ userId: user.id, role }));
this.store.dispatch(UsersActions.updateUserRole({ userId: user.id,
role }));
this.webrtcService.broadcastMessage({
type: 'role-change',
targetUserId: user.id,

View File

@@ -3,7 +3,10 @@
<section>
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<ng-icon name="lucideGlobe" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideGlobe"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Server Endpoints</h4>
</div>
<button
@@ -11,14 +14,16 @@
[disabled]="isTesting()"
class="flex items-center gap-1.5 px-2.5 py-1 text-xs bg-secondary text-foreground rounded-lg hover:bg-secondary/80 transition-colors disabled:opacity-50"
>
<ng-icon name="lucideRefreshCw" class="w-3.5 h-3.5" [class.animate-spin]="isTesting()" />
<ng-icon
name="lucideRefreshCw"
class="w-3.5 h-3.5"
[class.animate-spin]="isTesting()"
/>
Test All
</button>
</div>
<p class="text-xs text-muted-foreground mb-3">
Server directories to search for rooms. The active server is used for creating new rooms.
</p>
<p class="text-xs text-muted-foreground mb-3">Server directories to search for rooms. The active server is used for creating new rooms.</p>
<!-- Server List -->
<div class="space-y-2 mb-3">
@@ -41,10 +46,7 @@
<div class="flex items-center gap-2">
<span class="text-sm font-medium text-foreground truncate">{{ server.name }}</span>
@if (server.isActive) {
<span
class="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-full"
>Active</span
>
<span class="text-[10px] bg-primary text-primary-foreground px-1.5 py-0.5 rounded-full">Active</span>
}
</div>
<p class="text-xs text-muted-foreground truncate">{{ server.url }}</p>
@@ -105,7 +107,10 @@
[disabled]="!newServerName || !newServerUrl"
class="px-3 py-1.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed self-end"
>
<ng-icon name="lucidePlus" class="w-4 h-4" />
<ng-icon
name="lucidePlus"
class="w-4 h-4"
/>
</button>
</div>
@if (addError()) {
@@ -117,7 +122,10 @@
<!-- Connection Settings -->
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon name="lucideServer" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideServer"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Connection</h4>
</div>
<div class="space-y-3">

View File

@@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, signal } from '@angular/core';
import {
Component,
inject,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
@@ -18,7 +22,11 @@ import { STORAGE_KEY_CONNECTION_SETTINGS } from '../../../../core/constants';
@Component({
selector: 'app-network-settings',
standalone: true,
imports: [CommonModule, FormsModule, NgIcon],
imports: [
CommonModule,
FormsModule,
NgIcon
],
viewProviders: [
provideIcons({
lucideGlobe,
@@ -65,6 +73,7 @@ export class NetworkSettingsComponent {
name: this.newServerName.trim(),
url: this.newServerUrl.trim().replace(/\/$/, '')
});
this.newServerName = '';
this.newServerUrl = '';
const servers = this.servers();
@@ -108,6 +117,7 @@ export class NetworkSettingsComponent {
searchAllServers: this.searchAllServers
})
);
this.serverDirectory.setSearchAllServers(this.searchAllServers);
}
}

View File

@@ -1,9 +1,7 @@
@if (server()) {
<div class="space-y-4 max-w-xl">
@if (!isAdmin()) {
<p class="text-xs text-muted-foreground mb-1">
You are viewing this server's permissions. Only the server owner can make changes.
</p>
<p class="text-xs text-muted-foreground mb-1">You are viewing this server's permissions. Only the server owner can make changes.</p>
}
<div class="space-y-2.5">
<div class="flex items-center justify-between p-3 bg-secondary/50 rounded-lg">
@@ -118,13 +116,14 @@
[class.bg-green-600]="saveSuccess() === 'permissions'"
[class.hover:bg-green-600]="saveSuccess() === 'permissions'"
>
<ng-icon name="lucideCheck" class="w-4 h-4" />
<ng-icon
name="lucideCheck"
class="w-4 h-4"
/>
{{ saveSuccess() === 'permissions' ? 'Saved!' : 'Save Permissions' }}
</button>
}
</div>
} @else {
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">
Select a server from the sidebar to manage
</div>
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">Select a server from the sidebar to manage</div>
}

View File

@@ -1,5 +1,10 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, input, signal } from '@angular/core';
import {
Component,
inject,
input,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
@@ -12,7 +17,11 @@ import { RoomsActions } from '../../../../store/rooms/rooms.actions';
@Component({
selector: 'app-permissions-settings',
standalone: true,
imports: [CommonModule, FormsModule, NgIcon],
imports: [
CommonModule,
FormsModule,
NgIcon
],
viewProviders: [
provideIcons({
lucideCheck
@@ -75,6 +84,7 @@ export class PermissionsSettingsComponent {
}
})
);
this.showSaveSuccess('permissions');
}

View File

@@ -4,13 +4,16 @@
<h4 class="text-sm font-semibold text-foreground mb-3">Room Settings</h4>
@if (!isAdmin()) {
<p class="text-xs text-muted-foreground mb-3">
You are viewing this server's settings as a non-admin. Only the server owner can make
changes.
You are viewing this server's settings as a non-admin. Only the server owner can make changes.
</p>
}
<div class="space-y-4">
<div>
<label for="room-name" class="block text-xs font-medium text-muted-foreground mb-1">Room Name</label>
<label
for="room-name"
class="block text-xs font-medium text-muted-foreground mb-1"
>Room Name</label
>
<input
type="text"
[(ngModel)]="roomName"
@@ -22,7 +25,11 @@
/>
</div>
<div>
<label for="room-description" class="block text-xs font-medium text-muted-foreground mb-1">Description</label>
<label
for="room-description"
class="block text-xs font-medium text-muted-foreground mb-1"
>Description</label
>
<textarea
[(ngModel)]="roomDescription"
[readOnly]="!isAdmin()"
@@ -49,9 +56,15 @@
[class.text-muted-foreground]="!isPrivate()"
>
@if (isPrivate()) {
<ng-icon name="lucideLock" class="w-4 h-4" />
<ng-icon
name="lucideLock"
class="w-4 h-4"
/>
} @else {
<ng-icon name="lucideUnlock" class="w-4 h-4" />
<ng-icon
name="lucideUnlock"
class="w-4 h-4"
/>
}
</button>
</div>
@@ -65,7 +78,10 @@
</div>
}
<div>
<label for="room-max-users" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="room-max-users"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Max Users (0 = unlimited)
</label>
<input
@@ -90,7 +106,10 @@
[class.bg-green-600]="saveSuccess() === 'server'"
[class.hover:bg-green-600]="saveSuccess() === 'server'"
>
<ng-icon name="lucideCheck" class="w-4 h-4" />
<ng-icon
name="lucideCheck"
class="w-4 h-4"
/>
{{ saveSuccess() === 'server' ? 'Saved!' : 'Save Settings' }}
</button>
@@ -102,7 +121,10 @@
type="button"
class="w-full px-4 py-2 bg-destructive/10 text-destructive border border-destructive/20 rounded-lg hover:bg-destructive/20 transition-colors flex items-center justify-center gap-2 text-sm"
>
<ng-icon name="lucideTrash2" class="w-4 h-4" />
<ng-icon
name="lucideTrash2"
class="w-4 h-4"
/>
Delete Room
</button>
</div>
@@ -123,7 +145,5 @@
</app-confirm-dialog>
}
} @else {
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">
Select a server from the sidebar to manage
</div>
<div class="flex items-center justify-center h-40 text-muted-foreground text-sm">Select a server from the sidebar to manage</div>
}

View File

@@ -1,10 +1,21 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, input, signal, computed } from '@angular/core';
import {
Component,
inject,
input,
signal,
computed
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { Store } from '@ngrx/store';
import { lucideCheck, lucideTrash2, lucideLock, lucideUnlock } from '@ng-icons/lucide';
import {
lucideCheck,
lucideTrash2,
lucideLock,
lucideUnlock
} from '@ng-icons/lucide';
import { Room } from '../../../../core/models';
import { RoomsActions } from '../../../../store/rooms/rooms.actions';
@@ -14,7 +25,12 @@ import { SettingsModalService } from '../../../../core/services/settings-modal.s
@Component({
selector: 'app-server-settings',
standalone: true,
imports: [CommonModule, FormsModule, NgIcon, ConfirmDialogComponent],
imports: [
CommonModule,
FormsModule,
NgIcon,
ConfirmDialogComponent
],
viewProviders: [
provideIcons({
lucideCheck,
@@ -78,6 +94,7 @@ export class ServerSettingsComponent {
}
})
);
this.showSaveSuccess('server');
}

View File

@@ -35,11 +35,7 @@
<div class="flex-1 overflow-y-auto py-2">
<!-- Global section -->
<p
class="px-4 py-1.5 text-[11px] font-semibold text-muted-foreground/70 uppercase tracking-wider"
>
General
</p>
<p class="px-4 py-1.5 text-[11px] font-semibold text-muted-foreground/70 uppercase tracking-wider">General</p>
@for (page of globalPages; track page.id) {
<button
(click)="navigate(page.id)"
@@ -51,7 +47,10 @@
[class.text-foreground]="activePage() !== page.id"
[class.hover:bg-secondary]="activePage() !== page.id"
>
<ng-icon [name]="page.icon" class="w-4 h-4" />
<ng-icon
[name]="page.icon"
class="w-4 h-4"
/>
{{ page.label }}
</button>
}
@@ -59,11 +58,7 @@
<!-- Server section -->
@if (savedRooms().length > 0) {
<div class="mt-3 pt-3 border-t border-border">
<p
class="px-4 py-1.5 text-[11px] font-semibold text-muted-foreground/70 uppercase tracking-wider"
>
Server
</p>
<p class="px-4 py-1.5 text-[11px] font-semibold text-muted-foreground/70 uppercase tracking-wider">Server</p>
<!-- Server selector -->
<div class="px-3 pb-2">
@@ -91,7 +86,10 @@
[class.text-foreground]="activePage() !== page.id"
[class.hover:bg-secondary]="activePage() !== page.id"
>
<ng-icon [name]="page.icon" class="w-4 h-4" />
<ng-icon
[name]="page.icon"
class="w-4 h-4"
/>
{{ page.label }}
</button>
}
@@ -104,9 +102,7 @@
<!-- Content -->
<div class="flex-1 flex flex-col min-w-0">
<!-- Header -->
<div
class="flex items-center justify-between px-6 py-4 border-b border-border flex-shrink-0"
>
<div class="flex items-center justify-between px-6 py-4 border-b border-border flex-shrink-0">
<h3 class="text-lg font-semibold text-foreground">
@switch (activePage()) {
@case ('network') {
@@ -134,7 +130,10 @@
type="button"
class="p-2 hover:bg-secondary rounded-lg transition-colors text-muted-foreground hover:text-foreground"
>
<ng-icon name="lucideX" class="w-5 h-5" />
<ng-icon
name="lucideX"
class="w-5 h-5"
/>
</button>
</div>

View File

@@ -24,9 +24,7 @@ import {
import { SettingsModalService, SettingsPage } from '../../../core/services/settings-modal.service';
import { selectSavedRooms, selectCurrentRoom } from '../../../store/rooms/rooms.selectors';
import {
selectCurrentUser
} from '../../../store/users/users.selectors';
import { selectCurrentUser } from '../../../store/users/users.selectors';
import { Room } from '../../../core/models';
import { NetworkSettingsComponent } from './network-settings/network-settings.component';
@@ -80,14 +78,25 @@ export class SettingsModalComponent {
// --- Side-nav items ---
readonly globalPages: { id: SettingsPage; label: string; icon: string }[] = [
{ id: 'network', label: 'Network', icon: 'lucideGlobe' },
{ id: 'voice', label: 'Voice & Audio', icon: 'lucideAudioLines' }
{ id: 'network',
label: 'Network',
icon: 'lucideGlobe' }, { id: 'voice',
label: 'Voice & Audio',
icon: 'lucideAudioLines' }
];
readonly serverPages: { id: SettingsPage; label: string; icon: string }[] = [
{ id: 'server', label: 'Server', icon: 'lucideSettings' },
{ id: 'members', label: 'Members', icon: 'lucideUsers' },
{ id: 'bans', label: 'Bans', icon: 'lucideBan' },
{ id: 'permissions', label: 'Permissions', icon: 'lucideShield' }
{ id: 'server',
label: 'Server',
icon: 'lucideSettings' },
{ id: 'members',
label: 'Members',
icon: 'lucideUsers' },
{ id: 'bans',
label: 'Bans',
icon: 'lucideBan' },
{ id: 'permissions',
label: 'Permissions',
icon: 'lucideShield' }
];
// ===== SERVER SELECTOR =====

View File

@@ -2,12 +2,19 @@
<!-- Devices -->
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon name="lucideMic" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideMic"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Devices</h4>
</div>
<div class="space-y-3">
<div>
<label for="input-device-select" class="block text-xs font-medium text-muted-foreground mb-1">Microphone</label>
<label
for="input-device-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>Microphone</label
>
<select
(change)="onInputDeviceChange($event)"
id="input-device-select"
@@ -24,7 +31,11 @@
</select>
</div>
<div>
<label for="output-device-select" class="block text-xs font-medium text-muted-foreground mb-1">Speaker</label>
<label
for="output-device-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>Speaker</label
>
<select
(change)="onOutputDeviceChange($event)"
id="output-device-select"
@@ -46,12 +57,18 @@
<!-- Volume -->
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon name="lucideHeadphones" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideHeadphones"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Volume</h4>
</div>
<div class="space-y-3">
<div>
<label for="input-volume-slider" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="input-volume-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Input Volume: {{ inputVolume() }}%
</label>
<input
@@ -65,7 +82,10 @@
/>
</div>
<div>
<label for="output-volume-slider" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="output-volume-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Output Volume: {{ outputVolume() }}%
</label>
<input
@@ -79,7 +99,10 @@
/>
</div>
<div>
<label for="notification-volume-slider" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="notification-volume-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Notification Volume: {{ audioService.notificationVolume() * 100 | number: '1.0-0' }}%
</label>
<div class="flex items-center gap-2">
@@ -102,9 +125,7 @@
Test
</button>
</div>
<p class="text-[10px] text-muted-foreground/60 mt-1">
Controls join, leave &amp; notification sounds
</p>
<p class="text-[10px] text-muted-foreground/60 mt-1">Controls join, leave &amp; notification sounds</p>
</div>
</div>
</section>
@@ -112,24 +133,49 @@
<!-- Quality & Processing -->
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon name="lucideAudioLines" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideAudioLines"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Quality & Processing</h4>
</div>
<div class="space-y-3">
<div>
<label for="latency-profile-select" class="block text-xs font-medium text-muted-foreground mb-1">Latency Profile</label>
<label
for="latency-profile-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>Latency Profile</label
>
<select
(change)="onLatencyProfileChange($event)"
id="latency-profile-select"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
>
<option value="low" [selected]="latencyProfile() === 'low'">Low (fast)</option>
<option value="balanced" [selected]="latencyProfile() === 'balanced'">Balanced</option>
<option value="high" [selected]="latencyProfile() === 'high'">High (quality)</option>
<option
value="low"
[selected]="latencyProfile() === 'low'"
>
Low (fast)
</option>
<option
value="balanced"
[selected]="latencyProfile() === 'balanced'"
>
Balanced
</option>
<option
value="high"
[selected]="latencyProfile() === 'high'"
>
High (quality)
</option>
</select>
</div>
<div>
<label for="audio-bitrate-slider" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="audio-bitrate-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Audio Bitrate: {{ audioBitrate() }} kbps
</label>
<input
@@ -187,7 +233,10 @@
<!-- Voice Leveling -->
<section>
<div class="flex items-center gap-2 mb-3">
<ng-icon name="lucideActivity" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideActivity"
class="w-5 h-5 text-muted-foreground"
/>
<h4 class="text-sm font-semibold text-foreground">Voice Leveling</h4>
</div>
<div class="space-y-3">
@@ -212,12 +261,15 @@
</label>
</div>
<!-- Advanced controls visible only when enabled -->
<!-- Advanced controls - visible only when enabled -->
@if (voiceLeveling.enabled()) {
<div class="space-y-3 pl-1 border-l-2 border-primary/20 ml-1">
<!-- Target Loudness -->
<div class="pl-3">
<label for="target-loudness-slider" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="target-loudness-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Target Loudness: {{ voiceLeveling.targetDbfs() }} dBFS
</label>
<input
@@ -238,19 +290,32 @@
<!-- AGC Strength -->
<div class="pl-3">
<label for="agc-strength-select" class="block text-xs font-medium text-muted-foreground mb-1">AGC Strength</label>
<label
for="agc-strength-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>AGC Strength</label
>
<select
(change)="onStrengthChange($event)"
id="agc-strength-select"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
>
<option value="low" [selected]="voiceLeveling.strength() === 'low'">
<option
value="low"
[selected]="voiceLeveling.strength() === 'low'"
>
Low (gentle)
</option>
<option value="medium" [selected]="voiceLeveling.strength() === 'medium'">
<option
value="medium"
[selected]="voiceLeveling.strength() === 'medium'"
>
Medium
</option>
<option value="high" [selected]="voiceLeveling.strength() === 'high'">
<option
value="high"
[selected]="voiceLeveling.strength() === 'high'"
>
High (aggressive)
</option>
</select>
@@ -258,7 +323,10 @@
<!-- Max Gain Boost -->
<div class="pl-3">
<label for="max-gain-slider" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="max-gain-slider"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Max Gain Boost: {{ voiceLeveling.maxGainDb() }} dB
</label>
<input
@@ -279,7 +347,10 @@
<!-- Response Speed -->
<div class="pl-3">
<label for="response-speed-select" class="block text-xs font-medium text-muted-foreground mb-1">
<label
for="response-speed-select"
class="block text-xs font-medium text-muted-foreground mb-1"
>
Response Speed
</label>
<select
@@ -287,11 +358,22 @@
id="response-speed-select"
class="w-full px-3 py-2 bg-secondary rounded-lg border border-border text-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary"
>
<option value="slow" [selected]="voiceLeveling.speed() === 'slow'">
<option
value="slow"
[selected]="voiceLeveling.speed() === 'slow'"
>
Slow (natural)
</option>
<option value="medium" [selected]="voiceLeveling.speed() === 'medium'">Medium</option>
<option value="fast" [selected]="voiceLeveling.speed() === 'fast'">
<option
value="medium"
[selected]="voiceLeveling.speed() === 'medium'"
>
Medium
</option>
<option
value="fast"
[selected]="voiceLeveling.speed() === 'fast'"
>
Fast (aggressive)
</option>
</select>

View File

@@ -1,16 +1,22 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, signal } from '@angular/core';
import {
Component,
inject,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideMic, lucideHeadphones, lucideAudioLines, lucideActivity } from '@ng-icons/lucide';
import {
lucideMic,
lucideHeadphones,
lucideAudioLines,
lucideActivity
} from '@ng-icons/lucide';
import { WebRTCService } from '../../../../core/services/webrtc.service';
import { VoiceLevelingService } from '../../../../core/services/voice-leveling.service';
import {
NotificationAudioService,
AppSound
} from '../../../../core/services/notification-audio.service';
import { NotificationAudioService, AppSound } from '../../../../core/services/notification-audio.service';
import { STORAGE_KEY_VOICE_SETTINGS } from '../../../../core/constants';
interface AudioDevice {
@@ -21,7 +27,11 @@ interface AudioDevice {
@Component({
selector: 'app-voice-settings',
standalone: true,
imports: [CommonModule, FormsModule, NgIcon],
imports: [
CommonModule,
FormsModule,
NgIcon
],
viewProviders: [
provideIcons({
lucideMic,
@@ -63,12 +73,15 @@ export class VoiceSettingsComponent {
this.inputDevices.set(
devices
.filter((device) => device.kind === 'audioinput')
.map((device) => ({ deviceId: device.deviceId, label: device.label }))
.map((device) => ({ deviceId: device.deviceId,
label: device.label }))
);
this.outputDevices.set(
devices
.filter((device) => device.kind === 'audiooutput')
.map((device) => ({ deviceId: device.deviceId, label: device.label }))
.map((device) => ({ deviceId: device.deviceId,
label: device.label }))
);
} catch {}
}

View File

@@ -5,9 +5,15 @@
class="p-2 hover:bg-secondary rounded-lg transition-colors"
title="Go back"
>
<ng-icon name="lucideArrowLeft" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideArrowLeft"
class="w-5 h-5 text-muted-foreground"
/>
</button>
<ng-icon name="lucideSettings" class="w-6 h-6 text-primary" />
<ng-icon
name="lucideSettings"
class="w-6 h-6 text-primary"
/>
<h1 class="text-2xl font-bold text-foreground">Settings</h1>
</div>
@@ -15,7 +21,10 @@
<div class="bg-card border border-border rounded-lg p-6 mb-6">
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2">
<ng-icon name="lucideGlobe" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideGlobe"
class="w-5 h-5 text-muted-foreground"
/>
<h2 class="text-lg font-semibold text-foreground">Server Endpoints</h2>
</div>
<button
@@ -23,14 +32,18 @@
[disabled]="isTesting()"
class="flex items-center gap-2 px-3 py-1.5 text-sm bg-secondary text-foreground rounded-lg hover:bg-secondary/80 transition-colors disabled:opacity-50"
>
<ng-icon name="lucideRefreshCw" class="w-4 h-4" [class.animate-spin]="isTesting()" />
<ng-icon
name="lucideRefreshCw"
class="w-4 h-4"
[class.animate-spin]="isTesting()"
/>
Test All
</button>
</div>
<p class="text-sm text-muted-foreground mb-4">
Add multiple server directories to search for rooms across different networks. The active
server will be used for creating and registering new rooms.
Add multiple server directories to search for rooms across different networks. The active server will be used for creating and registering new
rooms.
</p>
<!-- Server List -->
@@ -58,9 +71,7 @@
<div class="flex items-center gap-2">
<span class="font-medium text-foreground truncate">{{ server.name }}</span>
@if (server.isActive) {
<span class="text-xs bg-primary text-primary-foreground px-2 py-0.5 rounded-full"
>Active</span
>
<span class="text-xs bg-primary text-primary-foreground px-2 py-0.5 rounded-full">Active</span>
}
</div>
<p class="text-sm text-muted-foreground truncate">{{ server.url }}</p>
@@ -123,7 +134,10 @@
[disabled]="!newServerName || !newServerUrl"
class="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed self-end"
>
<ng-icon name="lucidePlus" class="w-5 h-5" />
<ng-icon
name="lucidePlus"
class="w-5 h-5"
/>
</button>
</div>
@if (addError()) {
@@ -135,7 +149,10 @@
<!-- Connection Settings -->
<div class="bg-card border border-border rounded-lg p-6 mb-6">
<div class="flex items-center gap-2 mb-4">
<ng-icon name="lucideServer" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideServer"
class="w-5 h-5 text-muted-foreground"
/>
<h2 class="text-lg font-semibold text-foreground">Connection Settings</h2>
</div>
@@ -143,9 +160,7 @@
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-foreground">Auto-reconnect</p>
<p class="text-sm text-muted-foreground">
Automatically reconnect when connection is lost
</p>
<p class="text-sm text-muted-foreground">Automatically reconnect when connection is lost</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
@@ -163,9 +178,7 @@
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-foreground">Search all servers</p>
<p class="text-sm text-muted-foreground">
Search across all configured server directories
</p>
<p class="text-sm text-muted-foreground">Search across all configured server directories</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input
@@ -185,7 +198,10 @@
<!-- Voice Settings -->
<div class="bg-card border border-border rounded-lg p-6">
<div class="flex items-center gap-2 mb-4">
<ng-icon name="lucideAudioLines" class="w-5 h-5 text-muted-foreground" />
<ng-icon
name="lucideAudioLines"
class="w-5 h-5 text-muted-foreground"
/>
<h2 class="text-lg font-semibold text-foreground">Voice Settings</h2>
</div>
@@ -195,9 +211,7 @@
<div class="flex items-center justify-between mb-2">
<div>
<p class="font-medium text-foreground">Notification volume</p>
<p class="text-sm text-muted-foreground">
Volume for join, leave, and notification sounds
</p>
<p class="text-sm text-muted-foreground">Volume for join, leave, and notification sounds</p>
</div>
<span class="text-sm font-medium text-muted-foreground tabular-nums w-10 text-right">
{{ audioService.notificationVolume() * 100 | number: '1.0-0' }}%
@@ -226,9 +240,7 @@
<div class="flex items-center justify-between">
<div>
<p class="font-medium text-foreground">Noise reduction</p>
<p class="text-sm text-muted-foreground">
Use RNNoise to suppress background noise from your microphone
</p>
<p class="text-sm text-muted-foreground">Use RNNoise to suppress background noise from your microphone</p>
</div>
<label class="relative inline-flex items-center cursor-pointer">
<input

View File

@@ -1,5 +1,10 @@
/* eslint-disable @typescript-eslint/member-ordering */
import { Component, inject, signal, OnInit } from '@angular/core';
import {
Component,
inject,
signal,
OnInit
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
@@ -25,7 +30,11 @@ import { STORAGE_KEY_CONNECTION_SETTINGS, STORAGE_KEY_VOICE_SETTINGS } from '../
@Component({
selector: 'app-settings',
standalone: true,
imports: [CommonModule, FormsModule, NgIcon],
imports: [
CommonModule,
FormsModule,
NgIcon
],
viewProviders: [
provideIcons({
lucideServer,
@@ -142,6 +151,7 @@ export class SettingsComponent implements OnInit {
searchAllServers: this.searchAllServers
})
);
this.serverDirectory.setSearchAllServers(this.searchAllServers);
}
@@ -190,8 +200,10 @@ export class SettingsComponent implements OnInit {
localStorage.setItem(
STORAGE_KEY_VOICE_SETTINGS,
JSON.stringify({ ...existing, noiseReduction: this.noiseReduction })
JSON.stringify({ ...existing,
noiseReduction: this.noiseReduction })
);
await this.webrtcService.toggleNoiseReduction(this.noiseReduction);
}
}