153 lines
5.4 KiB
TypeScript
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);
|
|
}
|
|
|
|
}
|