Skip to content

Commit

Permalink
Added team-wide ready for external game admin.
Browse files Browse the repository at this point in the history
  • Loading branch information
sei-bstein committed Nov 21, 2023
1 parent 46a5f29 commit 5bc1261
Show file tree
Hide file tree
Showing 13 changed files with 93 additions and 43 deletions.
2 changes: 2 additions & 0 deletions projects/gameboard-ui/src/app/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { TeamObserverComponent } from './team-observer/team-observer.component';
import { UserApiKeysComponent } from './user-api-keys/user-api-keys.component';
import { UserRegistrarComponent } from './user-registrar/user-registrar.component';
import { UserReportComponent } from './user-report/user-report.component';
import { DeploymentAdminTeamContextMenuComponent } from './components/deployment-admin-team-context-menu/deployment-admin-team-context-menu.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -90,6 +91,7 @@ import { UserReportComponent } from './user-report/user-report.component';
UserApiKeysComponent,
UserRegistrarComponent,
UserReportComponent,
DeploymentAdminTeamContextMenuComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="btn-group" *ngIf="team" dropdown>
<button [id]="'external-game-admin-context-menu-' + team.id" dropdownToggle type="button"
class="btn ctx-menu-button rounded-circle" aria-controls="dropdown-basic"
tooltip="Additional actions for this team">
<fa-icon [icon]="fa.ellipsisVertical"></fa-icon>
</button>
<ul id="dropdown-basic" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic">
<li role="menuitem">
<button class="dropdown-item btn" (click)="handleTeamReadyStatusToggled(team, isReady)">
{{ (isReady ? "Unready" : "Ready") + " Team \"" + team.name + "\"" }}
</button>
</li>
<li role="menuitem">
<button class="dropdown-item btn" (click)="handleCopyClicked(team.id)">Copy team ID</button>
</li>
</ul>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { fa } from '@/services/font-awesome.service';
import { SimpleEntity } from '@/api/models';
import { SyncStartService } from '@/services/sync-start.service';
import { firstValueFrom } from 'rxjs';
import { ClipboardService } from '@/utility/services/clipboard.service';
import { ToastService } from '@/utility/services/toast.service';

@Component({
selector: 'app-deployment-admin-team-context-menu',
templateUrl: './deployment-admin-team-context-menu.component.html',
styleUrls: ['./deployment-admin-team-context-menu.component.scss']
})
export class DeploymentAdminTeamContextMenuComponent {
@Input() team?: SimpleEntity;
@Input() isReady = false;
@Output() teamReadyStateToggled = new EventEmitter<{ teamId: string, isReady: boolean }>();

protected fa = fa;

constructor(
private clipboardService: ClipboardService,
private syncStartService: SyncStartService,
private toastService: ToastService) { }

protected handleCopyClicked(text: string) {
this.clipboardService.copy(text);
this.toastService.showMessage(`Copied team ID "${text}" to your clipboard.`);
}

protected async handleTeamReadyStatusToggled(team: SimpleEntity, isReady: boolean) {
await firstValueFrom(this.syncStartService.updateTeamReadyState(team.id, { isReady: !isReady }));
this.teamReadyStateToggled.emit({ teamId: team.id, isReady: !isReady });
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<div class="btn-group" *ngIf="player" dropdown>
<button [id]="'external-game-admin-context-menu-' + player.id" dropdownToggle type="button"
class="btn ctx-menu-button rounded-circle" aria-controls="dropdown-basic">
class="btn ctx-menu-button rounded-circle" aria-controls="dropdown-basic"
tooltip="{{ 'Additional actions for ' + player.name }}">
<fa-icon [icon]="fa.ellipsisVertical"></fa-icon>
</button>
<ul id="dropdown-basic" *dropdownMenu class="dropdown-menu" role="menu" aria-labelledby="button-basic">
Expand All @@ -13,31 +14,5 @@
<li role="menuitem">
<button class="dropdown-item btn" (click)="copy(player.id, 'player ID')">Copy player ID</button>
</li>
<!-- <li role="menuitem">
<button class="dropdown-item btn" (click)="copy(player.teamId, 'team ID')">Copy team ID</button>
</li>
<li role="menuitem">
<button class="dropdown-item btn" (click)="onBonusManageRequest.emit(player)">
Manage Challenge Bonuses
</button>
</li>
<li class="divider dropdown-divider"></li>
<li role="menuitem" *ngIf="isResettingSession; else unenroll">
<button class="dropdown-item btn btn-danger"
(click)="onSessionResetRequest.emit({ player, unenrollTeam: false})">
Reset Session
</button>
<button class="dropdown-item btn btn-danger"
(click)="onSessionResetRequest.emit({ player, unenrollTeam: true})">
Reset Session &amp; Unenroll
</button>
</li>
<ng-template #unenroll>
<li role="menuitem">
<button type="button" class="dropdown-item btn btn-danger"
(click)="onUnenrollRequest.emit(player)">Unenroll</button>
</li>
</ng-template> -->
</ul>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { PlayerService } from '@/api/player.service';
import { firstValueFrom } from 'rxjs';
import { ClipboardService } from '@/utility/services/clipboard.service';
import { ToastService } from '@/utility/services/toast.service';
import { SyncStartService } from '@/services/sync-start.service';

export interface ExternalGameAdminPlayerContextMenuData {
id: string;
Expand All @@ -24,7 +25,7 @@ export class ExternalGameAdminPlayerContextMenuComponent implements OnChanges {

constructor(
private clipboardService: ClipboardService,
private playerService: PlayerService,
private syncStartService: SyncStartService,
private toastService: ToastService) { }

ngOnChanges(changes: SimpleChanges): void {
Expand All @@ -39,7 +40,7 @@ export class ExternalGameAdminPlayerContextMenuComponent implements OnChanges {

protected async togglePlayerReadyStatusClicked(playerId: string, currentIsReady: boolean) {
const isReady = !currentIsReady;
await firstValueFrom(this.playerService.updateIsSyncStartReady(playerId, { isReady }));
await firstValueFrom(this.syncStartService.updatePlayerReadyState(playerId, { isReady }));
this.playerReadyStateChanged.emit({ playerId, isReady });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,25 @@ <h3>Warning</h3>
<app-player-avatar-list [players]="team.players"></app-player-avatar-list>
</div>
<div class="card-body">
<h4 class="card-title">{{team.name}}</h4>
<div class="d-flex align-items-center">
<h4 class="card-title flex-grow w-100">{{team.name}}</h4>
<app-deployment-admin-team-context-menu class="d-block" [team]="team" [isReady]="team.isReady"
(teamReadyStateToggled)="handleTeamReadyStateChanged(team.id)"></app-deployment-admin-team-context-menu>
</div>

<div class="card-text mb-4">
<div class="card-section mb-4">
<h5>Players</h5>

<ul class="player-list">
<li *ngFor="let player of team.players"
class="d-flex align-items-center justify-content-begin">
<li *ngFor="let player of team.players" class="d-flex align-items-center w-100">
<app-player-avatar [player]="player" size="tiny"></app-player-avatar>
<app-status-light [state]="player.status | externalGamePlayerStatusToStatusLight"
[tooltip]="player.status | externalGamePlayerStatusToFriendly"></app-status-light>
<div class="flex-grow-1">{{player.name}}</div>
<app-external-game-admin-player-context-menu
(playerReadyStateChanged)="handlePlayerReadyStateChanged($event.playerId)"
[player]="{ id: player.id, name: player.name, isSyncStartReady: player.status == 'ready'}"></app-external-game-admin-player-context-menu>

<div [tooltip]="player.user.name">{{player.name}}</div>
</li>
</ul>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ h3.sync-session-date {
}

.team-avatars-container {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
Expand All @@ -34,3 +33,8 @@ td, th {
.not-deployed {
color: #888888;
}

app-deployment-admin-team-context-menu, app-external-game-admin-player-context-menu {
display: block;
margin-top: -12px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { DateTime } from 'luxon';
import { fa } from '@/services/font-awesome.service';
import { AppTitleService } from '@/services/app-title.service';
import { UnsubscriberService } from '@/services/unsubscriber.service';
import { Observable, Subject, catchError, combineLatest, filter, first, firstValueFrom, map, startWith, switchMap, tap, timer } from 'rxjs';
import { Observable, Subject, catchError, combineLatest, filter, firstValueFrom, map, tap, timer } from 'rxjs';
import { ExternalGameService } from '@/services/external-game.service';
import { ActivatedRoute } from '@angular/router';
import { FriendlyDatesService } from '@/services/friendly-dates.service';
Expand All @@ -17,6 +17,7 @@ export interface ExternalGameAdminTeam {
name: string;
sponsors: SimpleSponsor[];
deployStatus: DeployStatus;
isReady: boolean;
players: {
id: string;
name: string;
Expand Down Expand Up @@ -53,7 +54,7 @@ export interface ExternalGameAdminContext {
styleUrls: ['./external-game-admin.component.scss']
})
export class ExternalGameAdminComponent implements OnInit {
private autoUpdateInterval = 15000;
private autoUpdateInterval = 60000;
private forceRefresh$ = new Subject<void>();

protected ctx$?: Observable<ExternalGameAdminContext>;
Expand Down Expand Up @@ -110,6 +111,10 @@ export class ExternalGameAdminComponent implements OnInit {
this.forceRefresh$.next();
}

protected async handleTeamReadyStateChanged(teamId: string) {
this.forceRefresh$.next();
}

private bindOverallDeployStatus(overallDeployStatus: DeployStatus) {
this.canDeploy = overallDeployStatus == "notStarted";

Expand Down
4 changes: 0 additions & 4 deletions projects/gameboard-ui/src/app/api/player.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ export class PlayerService {
return this.http.put<any>(`${this.url}/player`, model);
}

public updateIsSyncStartReady(playerId: string, model: { isReady: boolean }) {
return this.http.put(`${this.url}/player/${playerId}/ready`, model);
}

public start(player: Player): Observable<Player> {
return this.http.put<Player>(`${this.url}/player/${player.id}/start`, {}).pipe(
map(p => this.transform(p) as Player),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export class SessionStartControlsComponent implements OnInit {
protected async handleReadyUpdated(player: Player) {
this.isReadyingUp = true;
this.logService.logInfo(`Player ${player.id} (user ${player.userId}) updating ready (${player.isReady})...`);
await firstValueFrom(this.playerService.updateIsSyncStartReady(player.id, { isReady: player.isReady }));
await firstValueFrom(this.syncStartService.updatePlayerReadyState(player.id, { isReady: player.isReady }));
this.isReadyingUp = false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ApiUrlService } from './api-url.service';
import { HttpClient } from '@angular/common/http';
import { GetExternalTeamDataResponse } from '@/api/game-models';
import { ExternalGameAdminContext } from '@/admin/components/external-game-admin/external-game-admin.component';
import { DateTime } from 'luxon';
import { ApiDateService } from './api-date.service';

export interface ExternalGameActive {
Expand Down
14 changes: 14 additions & 0 deletions projects/gameboard-ui/src/app/services/sync-start.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Injectable } from '@angular/core';
import { SimpleEntity } from '../api/models';
import { SyncStartPlayer, SyncStartGameState } from '../game/game.models';
import { ApiUrlService } from './api-url.service';
import { HttpClient } from '@angular/common/http';

@Injectable({ providedIn: 'root' })
export class SyncStartService {
constructor(
private apiUrl: ApiUrlService,
private http: HttpClient) { }

public getReadyPlayers(state: SyncStartGameState): SimpleEntity[] {
return this.getPlayersWithReadyState(state, true);
}
Expand All @@ -29,4 +35,12 @@ export class SyncStartService {

return this.getAllPlayers(state).filter(p => p.isReady == readyState) || [];
}

public updatePlayerReadyState(playerId: string, model: { isReady: boolean }) {
return this.http.put(this.apiUrl.build(`player/${playerId}/ready`), model);
}

public updateTeamReadyState(teamId: string, model: { isReady: boolean }) {
return this.http.put(this.apiUrl.build(`team/${teamId}/ready`), model);
}
}

0 comments on commit 5bc1261

Please sign in to comment.