95eff42dfe
Co-authored-by: Lala, Shahd <Shahd.Lala@sybit.de> Co-authored-by: shahdlala66 <shahd.lala66@gmail.com> Reviewed-on: #8
233 lines
7.6 KiB
TypeScript
233 lines
7.6 KiB
TypeScript
import { Component, DestroyRef, OnInit, inject } from '@angular/core';
|
|
import { CommonModule } from '@angular/common';
|
|
import { Router, RouterLink } from '@angular/router';
|
|
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
import { TournamentService } from '../../services/tournament.service';
|
|
import { AuthService } from '../../services/auth.service';
|
|
import { BotService } from '../../services/bot.service';
|
|
import { Bot } from '../../models/bot.models';
|
|
import { Tournament, TournamentResult, RoundPairings } from '../../models/tournament.models';
|
|
import { CurrentUser } from '../../models/auth.models';
|
|
|
|
type StatusTab = 'started' | 'created' | 'finished';
|
|
|
|
@Component({
|
|
selector: 'app-tournaments',
|
|
standalone: true,
|
|
imports: [CommonModule, RouterLink, ReactiveFormsModule],
|
|
templateUrl: './tournaments.component.html',
|
|
styleUrl: './tournaments.component.css'
|
|
})
|
|
export class TournamentsComponent implements OnInit {
|
|
private readonly destroyRef = inject(DestroyRef);
|
|
private readonly tournamentService = inject(TournamentService);
|
|
private readonly authService = inject(AuthService);
|
|
private readonly fb = inject(FormBuilder);
|
|
private readonly botService = inject(BotService);
|
|
private readonly router = inject(Router);
|
|
|
|
loading = true;
|
|
tab: StatusTab = 'started';
|
|
currentUser: CurrentUser | null = null;
|
|
|
|
started: Tournament[] = [];
|
|
created: Tournament[] = [];
|
|
finished: Tournament[] = [];
|
|
|
|
selectedTournament: Tournament | null = null;
|
|
pairings: RoundPairings | null = null;
|
|
pairingsLoading = false;
|
|
|
|
showCreateDialog = false;
|
|
createForm: FormGroup = this.fb.group({
|
|
name: ['', [Validators.required, Validators.minLength(3)]],
|
|
nbRounds: [4, [Validators.required, Validators.min(1), Validators.max(20)]],
|
|
clockLimitMinutes: [3, [Validators.required, Validators.min(1), Validators.max(60)]],
|
|
clockIncrement: [0, [Validators.required, Validators.min(0), Validators.max(60)]],
|
|
rated: [false]
|
|
});
|
|
createLoading = false;
|
|
createError: string | null = null;
|
|
|
|
startingId: string | null = null;
|
|
|
|
joinDialogTournamentId: string | null = null;
|
|
userBots: Bot[] = [];
|
|
botsLoading = false;
|
|
joiningBotId: string | null = null;
|
|
joinError: string | null = null;
|
|
|
|
ngOnInit(): void {
|
|
this.authService.currentUser$
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe(u => { this.currentUser = u; });
|
|
this.loadTournaments();
|
|
}
|
|
|
|
openCreateDialog(): void {
|
|
this.createForm.reset({ name: '', nbRounds: 4, clockLimitMinutes: 3, clockIncrement: 0, rated: false });
|
|
this.createError = null;
|
|
this.showCreateDialog = true;
|
|
}
|
|
|
|
closeCreateDialog(): void {
|
|
this.showCreateDialog = false;
|
|
}
|
|
|
|
submitCreate(): void {
|
|
if (this.createForm.invalid) return;
|
|
this.createLoading = true;
|
|
this.createError = null;
|
|
this.tournamentService.create(this.createForm.value).subscribe({
|
|
next: t => {
|
|
this.createLoading = false;
|
|
this.showCreateDialog = false;
|
|
this.created = [t, ...this.created];
|
|
this.tab = 'created';
|
|
this.selectedTournament = null;
|
|
},
|
|
error: err => {
|
|
this.createLoading = false;
|
|
this.createError = err.error?.message ?? err.error?.error ?? 'Failed to create tournament.';
|
|
}
|
|
});
|
|
}
|
|
|
|
setTab(tab: StatusTab): void {
|
|
this.tab = tab;
|
|
this.selectedTournament = null;
|
|
this.pairings = null;
|
|
}
|
|
|
|
selectTournament(t: Tournament): void {
|
|
if (this.selectedTournament?.id === t.id) {
|
|
this.selectedTournament = null;
|
|
this.pairings = null;
|
|
return;
|
|
}
|
|
this.selectedTournament = t;
|
|
this.pairings = null;
|
|
if (t.round > 0) {
|
|
this.loadPairings(t.id, t.round);
|
|
}
|
|
}
|
|
|
|
get activeList(): Tournament[] {
|
|
return this[this.tab];
|
|
}
|
|
|
|
clockDisplay(t: Tournament): string {
|
|
const min = Math.floor(t.clock.limit / 60);
|
|
return `${min}+${t.clock.increment}`;
|
|
}
|
|
|
|
rankMedal(rank: number): string {
|
|
if (rank === 1) return '🥇';
|
|
if (rank === 2) return '🥈';
|
|
if (rank === 3) return '🥉';
|
|
return `${rank}.`;
|
|
}
|
|
|
|
scoreDisplay(r: TournamentResult): string {
|
|
return r.points % 1 === 0 ? `${r.points}` : `${r.points}`;
|
|
}
|
|
|
|
startTournament(event: MouseEvent, t: Tournament): void {
|
|
event.stopPropagation();
|
|
this.startingId = t.id;
|
|
this.tournamentService.start(t.id).subscribe({
|
|
next: updated => {
|
|
this.startingId = null;
|
|
const list = this.created.map(x => x.id === t.id ? updated : x);
|
|
this.created = list.filter(x => x.status === 'created');
|
|
if (!this.started.find(x => x.id === updated.id)) this.started = [updated, ...this.started];
|
|
this.selectedTournament = updated;
|
|
this.tab = 'started';
|
|
},
|
|
error: () => { this.startingId = null; }
|
|
});
|
|
}
|
|
|
|
watchGame(gameId: string): void {
|
|
void this.router.navigate(['/game', gameId]);
|
|
}
|
|
|
|
openJoinDialog(event: MouseEvent, tournamentId: string): void {
|
|
event.stopPropagation();
|
|
this.joinDialogTournamentId = tournamentId;
|
|
this.joinError = null;
|
|
this.botsLoading = true;
|
|
this.botService.list()
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe({
|
|
next: bots => { this.userBots = bots; this.botsLoading = false; },
|
|
error: () => { this.botsLoading = false; }
|
|
});
|
|
}
|
|
|
|
closeJoinDialog(): void {
|
|
this.joinDialogTournamentId = null;
|
|
this.joiningBotId = null;
|
|
this.joinError = null;
|
|
}
|
|
|
|
joinWithBot(bot: Bot): void {
|
|
if (!this.joinDialogTournamentId || this.joiningBotId) return;
|
|
this.joiningBotId = bot.id;
|
|
this.joinError = null;
|
|
this.botService.rotateToken(bot.id).subscribe({
|
|
next: token => {
|
|
this.tournamentService.joinWithBotToken(this.joinDialogTournamentId!, token).subscribe({
|
|
next: () => {
|
|
this.joiningBotId = null;
|
|
const tid = this.joinDialogTournamentId!;
|
|
this.closeJoinDialog();
|
|
this.tournamentService.get(tid)
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe(updated => {
|
|
this.created = this.created.map(x => x.id === tid ? updated : x);
|
|
this.started = this.started.map(x => x.id === tid ? updated : x);
|
|
if (this.selectedTournament?.id === tid) this.selectedTournament = updated;
|
|
});
|
|
},
|
|
error: err => {
|
|
this.joiningBotId = null;
|
|
this.joinError = err.error?.message ?? err.error?.error ?? 'Failed to join tournament.';
|
|
}
|
|
});
|
|
},
|
|
error: () => {
|
|
this.joiningBotId = null;
|
|
this.joinError = 'Failed to get bot token.';
|
|
}
|
|
});
|
|
}
|
|
|
|
private loadTournaments(): void {
|
|
this.tournamentService.list()
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe({
|
|
next: list => {
|
|
this.started = list.started;
|
|
this.created = list.created;
|
|
this.finished = list.finished;
|
|
this.loading = false;
|
|
if (this.started.length === 0 && this.created.length > 0) this.tab = 'created';
|
|
else if (this.started.length === 0 && this.finished.length > 0) this.tab = 'finished';
|
|
},
|
|
error: () => { this.loading = false; }
|
|
});
|
|
}
|
|
|
|
private loadPairings(id: string, round: number): void {
|
|
this.pairingsLoading = true;
|
|
this.tournamentService.roundPairings(id, round)
|
|
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
.subscribe({
|
|
next: p => { this.pairings = p; this.pairingsLoading = false; },
|
|
error: () => { this.pairingsLoading = false; }
|
|
});
|
|
}
|
|
}
|