95eff42dfe
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de> Co-authored-by: shahdlala66 <shahd.lala66@gmail.com> Reviewed-on: #8
252 lines
7.7 KiB
TypeScript
252 lines
7.7 KiB
TypeScript
import { Component, DestroyRef, HostListener, OnInit, inject } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { Router } from '@angular/router';
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
import { AuthService } from '../../services/auth.service';
|
|
import { AuthDialogService } from '../../services/auth-dialog.service';
|
|
import { CurrentUser } from '../../models/auth.models';
|
|
import { LoginDialogComponent } from '../login-dialog/login-dialog.component';
|
|
import { RegisterDialogComponent } from '../register-dialog/register-dialog.component';
|
|
import { ThemeService } from '../../services/theme.service';
|
|
import { ChallengeEventService } from '../../services/challenge-event.service';
|
|
import { ChallengeService } from '../../services/challenge.service';
|
|
import { ChallengeWebSocketService } from '../../services/challenge-websocket.service';
|
|
import { Challenge } from '../../models/challenge.models';
|
|
|
|
@Component({
|
|
selector: 'app-toolbar',
|
|
standalone: true,
|
|
imports: [CommonModule, LoginDialogComponent, RegisterDialogComponent],
|
|
templateUrl: './toolbar.component.html',
|
|
styleUrl: './toolbar.component.css'
|
|
})
|
|
export class ToolbarComponent implements OnInit {
|
|
private readonly destroyRef = inject(DestroyRef);
|
|
private readonly authService = inject(AuthService);
|
|
private readonly authDialogService = inject(AuthDialogService);
|
|
private readonly themeService = inject(ThemeService);
|
|
private readonly challengeEventService = inject(ChallengeEventService);
|
|
private readonly challengeService = inject(ChallengeService);
|
|
private readonly challengeWs = inject(ChallengeWebSocketService);
|
|
private readonly router = inject(Router);
|
|
|
|
private pollHandle: ReturnType<typeof setInterval> | null = null;
|
|
private readonly navigatedChallengeIds = new Set<string>();
|
|
|
|
currentUser: CurrentUser | null = null;
|
|
showLoginDialog = false;
|
|
showRegisterDialog = false;
|
|
isDarkMode = false;
|
|
profileOpen = false;
|
|
notifOpen = false;
|
|
incomingChallenges: Challenge[] = [];
|
|
acceptingId: string | null = null;
|
|
decliningId: string | null = null;
|
|
|
|
ngOnInit(): void {
|
|
this.destroyRef.onDestroy(() => this.stopPolling());
|
|
|
|
this.authService.currentUser$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe(user => {
|
|
this.currentUser = user;
|
|
if (user) {
|
|
this.challengeWs.connect();
|
|
this.startPolling();
|
|
} else {
|
|
this.challengeWs.disconnect();
|
|
this.stopPolling();
|
|
this.navigatedChallengeIds.clear();
|
|
this.challengeEventService.clear();
|
|
}
|
|
});
|
|
|
|
this.authDialogService.dialogState$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe(state => {
|
|
this.showLoginDialog = state === 'login';
|
|
this.showRegisterDialog = state === 'register';
|
|
});
|
|
|
|
this.themeService.darkMode$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe(isDark => { this.isDarkMode = isDark; });
|
|
|
|
this.challengeEventService.getIncomingChallenges$()
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe(challenges => { this.incomingChallenges = challenges; });
|
|
}
|
|
|
|
private startPolling(): void {
|
|
this.fetchChallenges();
|
|
this.pollHandle = setInterval(() => this.fetchChallenges(), 10_000);
|
|
}
|
|
|
|
private stopPolling(): void {
|
|
if (this.pollHandle !== null) {
|
|
clearInterval(this.pollHandle);
|
|
this.pollHandle = null;
|
|
}
|
|
}
|
|
|
|
private fetchChallenges(): void {
|
|
this.challengeService.listChallenges().subscribe({
|
|
next: response => {
|
|
const incoming = response.in ?? response.incoming ?? [];
|
|
this.challengeEventService.setIncomingChallenges(incoming);
|
|
|
|
const outgoing = response.out ?? response.outgoing ?? [];
|
|
for (const c of outgoing) {
|
|
if (c.status === 'accepted' && c.gameId && !this.navigatedChallengeIds.has(c.id)) {
|
|
this.navigatedChallengeIds.add(c.id);
|
|
if (!this.router.url.includes(`/game/${c.gameId}`)) {
|
|
void this.router.navigate(['/game', c.gameId]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@HostListener('document:click', ['$event'])
|
|
onDocumentClick(event: MouseEvent): void {
|
|
if (!(event.target as HTMLElement).closest('[data-dropdown]')) {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
}
|
|
}
|
|
|
|
toggleProfile(event: MouseEvent): void {
|
|
event.stopPropagation();
|
|
const wasOpen = this.profileOpen;
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
this.profileOpen = !wasOpen;
|
|
}
|
|
|
|
toggleNotif(event: MouseEvent): void {
|
|
event.stopPropagation();
|
|
const wasOpen = this.notifOpen;
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
this.notifOpen = !wasOpen;
|
|
}
|
|
|
|
openLoginDialog(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
this.authDialogService.openLogin();
|
|
}
|
|
|
|
closeLoginDialog(): void {
|
|
this.authDialogService.close();
|
|
}
|
|
|
|
openRegisterDialog(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
this.authDialogService.openRegister();
|
|
}
|
|
|
|
closeRegisterDialog(): void {
|
|
this.authDialogService.close();
|
|
}
|
|
|
|
logout(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
this.authService.logout();
|
|
}
|
|
|
|
toggleTheme(event: MouseEvent): void {
|
|
event.stopPropagation();
|
|
this.themeService.toggleTheme();
|
|
}
|
|
|
|
goToHome(): void {
|
|
void this.router.navigate(['/']);
|
|
}
|
|
|
|
goToProfile(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
void this.router.navigate(['/profile']);
|
|
}
|
|
|
|
goToGames(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
void this.router.navigate(['/games']);
|
|
}
|
|
|
|
goToTournaments(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
void this.router.navigate(['/tournaments']);
|
|
}
|
|
|
|
goToChallenges(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
void this.router.navigate(['/challenges']);
|
|
}
|
|
|
|
goToBots(): void {
|
|
this.profileOpen = false;
|
|
this.notifOpen = false;
|
|
void this.router.navigate(['/bots']);
|
|
}
|
|
|
|
onLoginSuccess(): void {
|
|
this.closeLoginDialog();
|
|
}
|
|
|
|
onRegisterSuccess(): void {
|
|
this.closeRegisterDialog();
|
|
}
|
|
|
|
getInitial(): string {
|
|
return this.currentUser?.username?.charAt(0).toUpperCase() ?? '?';
|
|
}
|
|
|
|
getTimeControlDisplay(challenge: Challenge): string {
|
|
const { limit, increment } = challenge.timeControl;
|
|
if (!limit || !increment) return 'Unlimited';
|
|
return `${Math.floor(limit / 60)}+${increment}`;
|
|
}
|
|
|
|
getExpirationInfo(challenge: Challenge): string {
|
|
const diff = new Date(challenge.expiresAt).getTime() - Date.now();
|
|
if (diff <= 0) return 'Expired';
|
|
const min = Math.floor(diff / 60000);
|
|
return min > 60 ? `${Math.floor(min / 60)}h` : `${min}m`;
|
|
}
|
|
|
|
acceptChallenge(event: MouseEvent, challenge: Challenge): void {
|
|
event.stopPropagation();
|
|
if (this.acceptingId || this.decliningId) return;
|
|
this.acceptingId = challenge.id;
|
|
this.challengeService.acceptChallenge(challenge.id).subscribe({
|
|
next: accepted => {
|
|
this.acceptingId = null;
|
|
this.challengeEventService.onChallengeAccepted(accepted);
|
|
if (accepted.gameId) void this.router.navigate(['/game', accepted.gameId]);
|
|
},
|
|
error: () => { this.acceptingId = null; }
|
|
});
|
|
}
|
|
|
|
declineChallenge(event: MouseEvent, challenge: Challenge): void {
|
|
event.stopPropagation();
|
|
if (this.acceptingId || this.decliningId) return;
|
|
this.decliningId = challenge.id;
|
|
this.challengeService.declineChallenge(challenge.id, { reason: 'generic' }).subscribe({
|
|
next: () => {
|
|
this.decliningId = null;
|
|
this.challengeEventService.removeChallenge(challenge.id);
|
|
},
|
|
error: () => { this.decliningId = null; }
|
|
});
|
|
}
|
|
}
|