Files
Toju/src/app/features/servers/servers-rail.component.ts
2026-03-09 23:02:52 +01:00

230 lines
6.1 KiB
TypeScript

/* eslint-disable @typescript-eslint/member-ordering */
import {
Component,
computed,
effect,
inject,
signal
} from '@angular/core';
import { CommonModule, NgOptimizedImage } from '@angular/common';
import { Store } from '@ngrx/store';
import { Router } from '@angular/router';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucidePlus } from '@ng-icons/lucide';
import { Room, User } from '../../core/models/index';
import { selectSavedRooms, selectCurrentRoom } from '../../store/rooms/rooms.selectors';
import { selectCurrentUser } from '../../store/users/users.selectors';
import { VoiceSessionService } from '../../core/services/voice-session.service';
import { WebRTCService } from '../../core/services/webrtc.service';
import { RoomsActions } from '../../store/rooms/rooms.actions';
import { DatabaseService } from '../../core/services/database.service';
import { hasRoomBanForUser } from '../../core/helpers/room-ban.helpers';
import {
ConfirmDialogComponent,
ContextMenuComponent,
LeaveServerDialogComponent
} from '../../shared';
@Component({
selector: 'app-servers-rail',
standalone: true,
imports: [
CommonModule,
NgIcon,
ConfirmDialogComponent,
ContextMenuComponent,
LeaveServerDialogComponent,
NgOptimizedImage
],
viewProviders: [provideIcons({ lucidePlus })],
templateUrl: './servers-rail.component.html'
})
export class ServersRailComponent {
private store = inject(Store);
private router = inject(Router);
private voiceSession = inject(VoiceSessionService);
private webrtc = inject(WebRTCService);
private db = inject(DatabaseService);
private banLookupRequestVersion = 0;
savedRooms = this.store.selectSignal(selectSavedRooms);
currentRoom = this.store.selectSignal(selectCurrentRoom);
showMenu = signal(false);
menuX = signal(72);
menuY = signal(100);
contextRoom = signal<Room | null>(null);
showLeaveConfirm = signal(false);
currentUser = this.store.selectSignal(selectCurrentUser);
bannedRoomLookup = signal<Record<string, boolean>>({});
bannedServerName = signal('');
showBannedDialog = signal(false);
visibleSavedRooms = computed(() => this.savedRooms().filter((room) => !this.isRoomMarkedBanned(room)));
constructor() {
effect(() => {
const rooms = this.savedRooms();
const currentUser = this.currentUser();
void this.refreshBannedLookup(rooms, currentUser ?? null);
});
}
initial(name?: string): string {
if (!name)
return '?';
const ch = name.trim()[0]?.toUpperCase();
return ch || '?';
}
trackRoomId = (index: number, room: Room) => room.id;
createServer(): void {
const voiceServerId = this.voiceSession.getVoiceServerId();
if (voiceServerId) {
this.voiceSession.setViewingVoiceServer(false);
}
this.router.navigate(['/search']);
}
async joinSavedRoom(room: Room): Promise<void> {
const currentUserId = localStorage.getItem('metoyou_currentUserId');
if (!currentUserId) {
this.router.navigate(['/login']);
return;
}
if (await this.isRoomBanned(room)) {
this.bannedServerName.set(room.name);
this.showBannedDialog.set(true);
return;
}
const voiceServerId = this.voiceSession.getVoiceServerId();
if (voiceServerId && voiceServerId !== room.id) {
this.voiceSession.setViewingVoiceServer(false);
} else if (voiceServerId === room.id) {
this.voiceSession.setViewingVoiceServer(true);
}
if (this.webrtc.hasJoinedServer(room.id)) {
this.store.dispatch(RoomsActions.viewServer({ room }));
} else {
this.store.dispatch(
RoomsActions.joinRoom({
roomId: room.id,
serverInfo: {
name: room.name,
description: room.description,
hostName: room.hostId || 'Unknown'
}
})
);
}
}
closeBannedDialog(): void {
this.showBannedDialog.set(false);
this.bannedServerName.set('');
}
isRoomMarkedBanned(room: Room): boolean {
return !!this.bannedRoomLookup()[room.id];
}
openContextMenu(evt: MouseEvent, room: Room): void {
evt.preventDefault();
this.contextRoom.set(room);
this.menuX.set(Math.max(evt.clientX + 8, 72));
this.menuY.set(evt.clientY);
this.showMenu.set(true);
}
closeMenu(): void {
this.showMenu.set(false);
}
isCurrentContextRoom(): boolean {
const ctx = this.contextRoom();
const cur = this.currentRoom();
return !!ctx && !!cur && ctx.id === cur.id;
}
openLeaveConfirm(): void {
this.closeMenu();
if (this.contextRoom()) {
this.showLeaveConfirm.set(true);
}
}
confirmLeave(result: { nextOwnerKey?: string }): void {
const ctx = this.contextRoom();
if (!ctx)
return;
const isCurrentRoom = this.currentRoom()?.id === ctx.id;
this.store.dispatch(RoomsActions.forgetRoom({
roomId: ctx.id,
nextOwnerKey: result.nextOwnerKey
}));
if (isCurrentRoom) {
this.router.navigate(['/search']);
}
this.showLeaveConfirm.set(false);
this.contextRoom.set(null);
}
cancelLeave(): void {
this.showLeaveConfirm.set(false);
}
private async refreshBannedLookup(rooms: Room[], currentUser: User | null): Promise<void> {
const requestVersion = ++this.banLookupRequestVersion;
if (!currentUser || rooms.length === 0) {
this.bannedRoomLookup.set({});
return;
}
const persistedUserId = localStorage.getItem('metoyou_currentUserId');
const entries = await Promise.all(
rooms.map(async (room) => {
const bans = await this.db.getBansForRoom(room.id);
return [room.id, hasRoomBanForUser(bans, currentUser, persistedUserId)] as const;
})
);
if (requestVersion !== this.banLookupRequestVersion) {
return;
}
this.bannedRoomLookup.set(Object.fromEntries(entries));
}
private async isRoomBanned(room: Room): Promise<boolean> {
const currentUser = this.currentUser();
const persistedUserId = localStorage.getItem('metoyou_currentUserId');
if (!currentUser && !persistedUserId) {
return false;
}
const bans = await this.db.getBansForRoom(room.id);
return hasRoomBanForUser(bans, currentUser, persistedUserId);
}
}