refactor: Clean lint errors and organise files

This commit is contained in:
2026-04-17 01:06:01 +02:00
parent 2927a86fbb
commit 35b616fb77
60 changed files with 1161 additions and 728 deletions

View File

@@ -182,15 +182,7 @@
[name]="u.displayName"
[avatarUrl]="u.avatarUrl"
size="xs"
[ringClass]="
u.voiceState?.isDeafened
? 'ring-2 ring-red-500'
: u.voiceState?.isMuted
? 'ring-2 ring-yellow-500'
: voiceActivity.isSpeaking(u.oderId || u.id)()
? 'ring-2 ring-green-400 shadow-[0_0_8px_2px_rgba(74,222,128,0.6)]'
: 'ring-2 ring-green-500/40'
"
[ringClass]="getVoiceUserRingClass(u)"
/>
<span class="text-sm text-foreground/80 truncate flex-1">{{ u.displayName }}</span>
<!-- Ping latency indicator -->
@@ -246,7 +238,11 @@
<h4 class="text-xs uppercase tracking-wide text-muted-foreground font-medium mb-2 px-1">You</h4>
<div
class="flex items-center gap-2 rounded-md bg-secondary/60 px-3 py-2 hover:bg-secondary/80 transition-colors cursor-pointer"
role="button"
tabindex="0"
(click)="openProfileCard($event, currentUser()!, true); $event.stopPropagation()"
(keydown.enter)="openProfileCard($event, currentUser()!, true); $event.stopPropagation()"
(keydown.space)="openProfileCard($event, currentUser()!, true); $event.preventDefault(); $event.stopPropagation()"
>
<app-user-avatar
[name]="currentUser()?.displayName || '?'"
@@ -293,8 +289,12 @@
@for (user of onlineRoomUsers(); track user.id) {
<div
class="group/user flex items-center gap-2 rounded-md px-3 py-2 transition-colors hover:bg-secondary/50 cursor-pointer"
role="button"
tabindex="0"
(contextmenu)="openUserContextMenu($event, user)"
(click)="openProfileCard($event, user, false); $event.stopPropagation()"
(keydown.enter)="openProfileCard($event, user, false); $event.stopPropagation()"
(keydown.space)="openProfileCard($event, user, false); $event.preventDefault(); $event.stopPropagation()"
>
<app-user-avatar
[name]="user.displayName"
@@ -352,7 +352,11 @@
@for (member of offlineRoomMembers(); track member.oderId || member.id) {
<div
class="flex items-center gap-2 rounded-md px-3 py-2 opacity-80 hover:bg-secondary/30 transition-colors cursor-pointer"
role="button"
tabindex="0"
(click)="openProfileCardForMember($event, member); $event.stopPropagation()"
(keydown.enter)="openProfileCardForMember($event, member); $event.stopPropagation()"
(keydown.space)="openProfileCardForMember($event, member); $event.preventDefault(); $event.stopPropagation()"
>
<app-user-avatar
[name]="member.displayName"

View File

@@ -103,7 +103,7 @@ export class RoomsSidePanelComponent {
private voiceWorkspace = inject(VoiceWorkspaceService);
private voicePlayback = inject(VoicePlaybackService);
private profileCard = inject(ProfileCardService);
voiceActivity = inject(VoiceActivityService);
private readonly voiceActivity = inject(VoiceActivityService);
readonly panelMode = input<PanelMode>('channels');
readonly showVoiceControls = input(true);
@@ -186,14 +186,14 @@ export class RoomsSidePanelComponent {
draggedVoiceUserId = signal<string | null>(null);
dragTargetVoiceChannelId = signal<string | null>(null);
openProfileCard(event: MouseEvent, user: User, editable: boolean): void {
openProfileCard(event: Event, user: User, editable: boolean): void {
event.stopPropagation();
const el = event.currentTarget as HTMLElement;
this.profileCard.open(el, user, { placement: 'left', editable });
}
openProfileCardForMember(event: MouseEvent, member: RoomMember): void {
openProfileCardForMember(event: Event, member: RoomMember): void {
const user: User = {
id: member.id,
oderId: member.oderId || member.id,
@@ -886,6 +886,22 @@ export class RoomsSidePanelComponent {
return this.isUserSharing(userId) || this.isUserOnCamera(userId);
}
getVoiceUserRingClass(user: User): string {
if (user.voiceState?.isDeafened) {
return 'ring-2 ring-red-500';
}
if (user.voiceState?.isMuted) {
return 'ring-2 ring-yellow-500';
}
if (this.isVoiceUserSpeaking(user)) {
return 'ring-2 ring-green-400 shadow-[0_0_8px_2px_rgba(74,222,128,0.6)]';
}
return 'ring-2 ring-green-500/40';
}
getUserLiveIconName(userId: string): string {
return this.isUserSharing(userId) ? 'lucideMonitor' : 'lucideVideo';
}
@@ -981,6 +997,12 @@ export class RoomsSidePanelComponent {
return 'bg-red-500';
}
private isVoiceUserSpeaking(user: User): boolean {
const userKey = user.oderId || user.id;
return !!userKey && this.voiceActivity.speakingMap().get(userKey) === true;
}
private findKnownUser(userId: string): User | null {
const current = this.currentUser();

View File

@@ -22,9 +22,9 @@ import {
lucideVolumeX
} from '@ng-icons/lucide';
import { UserAvatarComponent } from '../../../shared';
import { VoiceWorkspacePlaybackService } from './voice-workspace-playback.service';
import { VoiceWorkspaceStreamItem } from './voice-workspace.models';
import { UserAvatarComponent } from '../../../../shared';
import { VoiceWorkspacePlaybackService } from '../voice-workspace-playback.service';
import { VoiceWorkspaceStreamItem } from '../voice-workspace.models';
@Component({
selector: 'app-voice-workspace-stream-tile',
@@ -86,23 +86,20 @@ export class VoiceWorkspaceStreamTileComponent implements OnDestroy {
void video.play().catch(() => {});
});
effect(
() => {
this.workspacePlayback.settings();
effect(() => {
this.workspacePlayback.settings();
const item = this.item();
const item = this.item();
if (item.isLocal || !item.hasAudio) {
this.volume.set(0);
this.muted.set(false);
return;
}
if (item.isLocal || !item.hasAudio) {
this.volume.set(0);
this.muted.set(false);
return;
}
this.volume.set(this.workspacePlayback.getUserVolume(item.peerKey));
this.muted.set(this.workspacePlayback.isUserMuted(item.peerKey));
},
{ allowSignalWrites: true }
);
this.volume.set(this.workspacePlayback.getUserVolume(item.peerKey));
this.muted.set(this.workspacePlayback.isUserMuted(item.peerKey));
});
effect(() => {
const ref = this.videoRef();

View File

@@ -48,7 +48,7 @@ import { UsersActions } from '../../../store/users/users.actions';
import { selectCurrentUser, selectOnlineUsers } from '../../../store/users/users.selectors';
import { ScreenShareQualityDialogComponent, UserAvatarComponent } from '../../../shared';
import { VoiceWorkspacePlaybackService } from './voice-workspace-playback.service';
import { VoiceWorkspaceStreamTileComponent } from './voice-workspace-stream-tile.component';
import { VoiceWorkspaceStreamTileComponent } from './voice-workspace-stream-tile/voice-workspace-stream-tile.component';
import { VoiceWorkspaceStreamItem } from './voice-workspace.models';
import { ThemeNodeDirective } from '../../../domains/theme';
@@ -456,38 +456,35 @@ export class VoiceWorkspaceComponent {
this.pruneObservedRemoteStreams(peerKeys);
});
effect(
() => {
const isExpanded = this.showExpanded();
const shouldAutoHideChrome = this.shouldAutoHideChrome();
effect(() => {
const isExpanded = this.showExpanded();
const shouldAutoHideChrome = this.shouldAutoHideChrome();
if (!isExpanded) {
this.clearHeaderHideTimeout();
this.showWorkspaceHeader.set(true);
this.wasExpanded = false;
this.wasAutoHideChrome = false;
return;
}
if (!shouldAutoHideChrome) {
this.clearHeaderHideTimeout();
this.showWorkspaceHeader.set(true);
this.wasExpanded = true;
this.wasAutoHideChrome = false;
return;
}
const shouldRevealChrome = !this.wasExpanded || !this.wasAutoHideChrome;
if (!isExpanded) {
this.clearHeaderHideTimeout();
this.showWorkspaceHeader.set(true);
this.wasExpanded = false;
this.wasAutoHideChrome = false;
return;
}
if (!shouldAutoHideChrome) {
this.clearHeaderHideTimeout();
this.showWorkspaceHeader.set(true);
this.wasExpanded = true;
this.wasAutoHideChrome = true;
this.wasAutoHideChrome = false;
return;
}
if (shouldRevealChrome) {
this.revealWorkspaceChrome();
}
},
{ allowSignalWrites: true }
);
const shouldRevealChrome = !this.wasExpanded || !this.wasAutoHideChrome;
this.wasExpanded = true;
this.wasAutoHideChrome = true;
if (shouldRevealChrome) {
this.revealWorkspaceChrome();
}
});
}
onWorkspacePointerMove(): void {

View File

@@ -26,21 +26,21 @@ import {
tap
} from 'rxjs';
import { Room, User } from '../../shared-kernel';
import { UserBarComponent } from '../../domains/authentication/feature/user-bar/user-bar.component';
import { VoiceSessionFacade } from '../../domains/voice-session';
import { selectSavedRooms, selectCurrentRoom } from '../../store/rooms/rooms.selectors';
import { selectCurrentUser, selectOnlineUsers } from '../../store/users/users.selectors';
import { RoomsActions } from '../../store/rooms/rooms.actions';
import { DatabaseService } from '../../infrastructure/persistence';
import { NotificationsFacade } from '../../domains/notifications';
import { type ServerInfo, ServerDirectoryFacade } from '../../domains/server-directory';
import { hasRoomBanForUser } from '../../domains/access-control';
import { Room, User } from '../../../shared-kernel';
import { UserBarComponent } from '../../../domains/authentication/feature/user-bar/user-bar.component';
import { VoiceSessionFacade } from '../../../domains/voice-session';
import { selectSavedRooms, selectCurrentRoom } from '../../../store/rooms/rooms.selectors';
import { selectCurrentUser, selectOnlineUsers } from '../../../store/users/users.selectors';
import { RoomsActions } from '../../../store/rooms/rooms.actions';
import { DatabaseService } from '../../../infrastructure/persistence';
import { NotificationsFacade } from '../../../domains/notifications';
import { type ServerInfo, ServerDirectoryFacade } from '../../../domains/server-directory';
import { hasRoomBanForUser } from '../../../domains/access-control';
import {
ConfirmDialogComponent,
ContextMenuComponent,
LeaveServerDialogComponent
} from '../../shared';
} from '../../../shared';
@Component({
selector: 'app-servers-rail',
@@ -81,8 +81,8 @@ export class ServersRailComponent {
bannedRoomLookup = signal<Record<string, boolean>>({});
isOnSearch = toSignal(
this.router.events.pipe(
filter((e): e is NavigationEnd => e instanceof NavigationEnd),
map((e) => e.urlAfterRedirects.startsWith('/search'))
filter((navigationEvent): navigationEvent is NavigationEnd => navigationEvent instanceof NavigationEnd),
map((navigationEvent) => navigationEvent.urlAfterRedirects.startsWith('/search'))
),
{ initialValue: this.router.url.startsWith('/search') }
);

View File

@@ -25,6 +25,7 @@
<div class="flex items-center gap-1">
@if (canKickMembers(member)) {
<button
type="button"
(click)="kickMember(member)"
class="grid h-8 w-8 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-destructive/20 hover:text-destructive"
title="Kick"
@@ -37,6 +38,7 @@
}
@if (canBanMembers(member)) {
<button
type="button"
(click)="banMember(member)"
class="grid h-8 w-8 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-destructive/20 hover:text-destructive"
title="Ban"

View File

@@ -32,7 +32,7 @@ import { RealtimeSessionFacade } from '../../../core/realtime';
import { selectSavedRooms, selectCurrentRoom } from '../../../store/rooms/rooms.selectors';
import { selectCurrentUser } from '../../../store/users/users.selectors';
import { Room, UserRole } from '../../../shared-kernel';
import { NotificationsSettingsComponent } from '../../../domains/notifications/feature/settings/notifications-settings.component';
import { NotificationsSettingsComponent } from '../../../domains/notifications';
import { resolveLegacyRole, resolveRoomPermission } from '../../../domains/access-control';
import { GeneralSettingsComponent } from './general-settings/general-settings.component';

View File

@@ -9,7 +9,8 @@ export interface ThirdPartyLicense {
}
const toLicenseText = (lines: readonly string[]): string => lines.join('\n');
const GROUPED_LICENSE_NOTE = 'Grouped by the license declared in the installed package metadata for the packages below. Some upstream packages include their own copyright notices in addition to this standard license text.';
const GROUPED_LICENSE_NOTE = 'Grouped by the license declared in the installed package metadata for the packages below. '
+ 'Some upstream packages include their own copyright notices in addition to this standard license text.';
const MIT_LICENSE_TEXT = toLicenseText([
'MIT License',
'',

View File

@@ -5,9 +5,9 @@ import {
inject,
signal
} from '@angular/core';
import { ElectronBridgeService } from '../../core/platform/electron/electron-bridge.service';
import { ContextMenuComponent } from '../../shared';
import type { ContextMenuParams } from '../../core/platform/electron/electron-api.models';
import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service';
import { ContextMenuComponent } from '../../../shared';
import type { ContextMenuParams } from '../../../core/platform/electron/electron-api.models';
@Component({
selector: 'app-native-context-menu',

View File

@@ -26,18 +26,18 @@ import {
selectVoiceChannels,
selectIsSignalServerReconnecting,
selectSignalServerCompatibilityError
} from '../../store/rooms/rooms.selectors';
import { RoomsActions } from '../../store/rooms/rooms.actions';
import { selectCurrentUser } from '../../store/users/users.selectors';
import { ElectronBridgeService } from '../../core/platform/electron/electron-bridge.service';
import { RealtimeSessionFacade } from '../../core/realtime';
import { ServerDirectoryFacade } from '../../domains/server-directory';
import { PlatformService } from '../../core/platform';
import { STORAGE_KEY_CURRENT_USER_ID } from '../../core/constants';
import { LeaveServerDialogComponent } from '../../shared';
import { Room } from '../../shared-kernel';
import { VoiceWorkspaceService } from '../../domains/voice-session';
import { ThemeNodeDirective } from '../../domains/theme';
} from '../../../store/rooms/rooms.selectors';
import { RoomsActions } from '../../../store/rooms/rooms.actions';
import { selectCurrentUser } from '../../../store/users/users.selectors';
import { ElectronBridgeService } from '../../../core/platform/electron/electron-bridge.service';
import { RealtimeSessionFacade } from '../../../core/realtime';
import { ServerDirectoryFacade } from '../../../domains/server-directory';
import { PlatformService } from '../../../core/platform';
import { STORAGE_KEY_CURRENT_USER_ID } from '../../../core/constants';
import { LeaveServerDialogComponent } from '../../../shared';
import { Room } from '../../../shared-kernel';
import { VoiceWorkspaceService } from '../../../domains/voice-session';
import { ThemeNodeDirective } from '../../../domains/theme';
@Component({
selector: 'app-title-bar',