Files
Toju/src/app/features/servers/servers-rail.component.ts
2026-03-02 03:30:22 +01:00

153 lines
5.4 KiB
TypeScript

import { Component, inject, signal } from '@angular/core';
import { CommonModule } 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 } from '../../core/models';
import { selectSavedRooms, selectCurrentRoom } from '../../store/rooms/rooms.selectors';
import { VoiceSessionService } from '../../core/services/voice-session.service';
import { WebRTCService } from '../../core/services/webrtc.service';
import { RoomsActions } from '../../store/rooms/rooms.actions';
import { ContextMenuComponent, ConfirmDialogComponent } from '../../shared';
@Component({
selector: 'app-servers-rail',
standalone: true,
imports: [CommonModule, NgIcon, ContextMenuComponent, ConfirmDialogComponent],
viewProviders: [provideIcons({ lucidePlus })],
templateUrl: './servers-rail.component.html',
})
/**
* Vertical rail of saved server icons with context-menu actions for leaving/forgetting.
*/
export class ServersRailComponent {
private store = inject(Store);
private router = inject(Router);
private voiceSession = inject(VoiceSessionService);
private webrtc = inject(WebRTCService);
savedRooms = this.store.selectSignal(selectSavedRooms);
currentRoom = this.store.selectSignal(selectCurrentRoom);
// Context menu state
showMenu = signal(false);
menuX = signal(72); // default X: rail width (~64px) + padding
menuY = signal(100); // default Y: arbitrary initial offset
contextRoom = signal<Room | null>(null);
// Confirmation dialog state
showConfirm = signal(false);
/** Return the first character of a server name as its icon initial. */
initial(name?: string): string {
if (!name) return '?';
const ch = name.trim()[0]?.toUpperCase();
return ch || '?';
}
trackRoomId = (index: number, room: Room) => room.id;
/** Navigate to the server search view. Updates voice session state if applicable. */
createServer(): void {
// Navigate to server list (has create button)
// Update voice session state if connected to voice
const voiceServerId = this.voiceSession.getVoiceServerId();
if (voiceServerId) {
this.voiceSession.setViewingVoiceServer(false);
}
this.router.navigate(['/search']);
}
/** Join or switch to a saved room. Manages voice session and authentication state. */
joinSavedRoom(room: Room): void {
// Require auth: if no current user, go to login
const currentUserId = localStorage.getItem('metoyou_currentUserId');
if (!currentUserId) {
this.router.navigate(['/login']);
return;
}
// Check if we're navigating to a different server while in voice
const voiceServerId = this.voiceSession.getVoiceServerId();
if (voiceServerId && voiceServerId !== room.id) {
// User is switching to a different server while connected to voice
// Update voice session to show floating controls (voice stays connected)
this.voiceSession.setViewingVoiceServer(false);
} else if (voiceServerId === room.id) {
// Navigating back to the voice-connected server
this.voiceSession.setViewingVoiceServer(true);
}
// If we've already joined this server, just switch the view
// (no user_joined broadcast, no leave from other servers)
if (this.webrtc.hasJoinedServer(room.id)) {
this.store.dispatch(RoomsActions.viewServer({ room }));
} else {
// First time joining this server
this.store.dispatch(RoomsActions.joinRoom({
roomId: room.id,
serverInfo: {
name: room.name,
description: room.description,
hostName: room.hostId || 'Unknown',
},
}));
}
}
/** Open the context menu positioned near the cursor for a given room. */
openContextMenu(evt: MouseEvent, room: Room): void {
evt.preventDefault();
this.contextRoom.set(room);
// Offset 8px right to avoid overlapping the rail; floor at rail width (72px)
this.menuX.set(Math.max((evt.clientX + 8), 72));
this.menuY.set(evt.clientY);
this.showMenu.set(true);
}
/** Close the context menu (keeps contextRoom for potential confirmation). */
closeMenu(): void {
this.showMenu.set(false);
// keep contextRoom for potential confirmation dialog
}
/** Check whether the context-menu room is the currently active room. */
isCurrentContextRoom(): boolean {
const ctx = this.contextRoom();
const cur = this.currentRoom();
return !!ctx && !!cur && ctx.id === cur.id;
}
/** Leave the current server and navigate to the servers list. */
leaveServer(): void {
this.closeMenu();
this.store.dispatch(RoomsActions.leaveRoom());
window.dispatchEvent(new CustomEvent('navigate:servers'));
}
/** Show the forget-server confirmation dialog. */
openForgetConfirm(): void {
this.showConfirm.set(true);
this.closeMenu();
}
/** Forget (remove) a server from the saved list, leaving if it is the current room. */
confirmForget(): void {
const ctx = this.contextRoom();
if (!ctx) return;
if (this.currentRoom()?.id === ctx.id) {
this.store.dispatch(RoomsActions.leaveRoom());
window.dispatchEvent(new CustomEvent('navigate:servers'));
}
this.store.dispatch(RoomsActions.forgetRoom({ roomId: ctx.id }));
this.showConfirm.set(false);
this.contextRoom.set(null);
}
/** Cancel the forget-server confirmation dialog. */
cancelForget(): void {
this.showConfirm.set(false);
}
}