From 37dc90e031bedf0413778ad9dba3e1ddfd58da6f Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Tue, 5 Mar 2024 11:07:01 -0500 Subject: [PATCH 01/18] Add external game host 'extend' endpoint --- .../game-editor/game-editor.component.html | 48 +++++++++++++------ .../game-editor/game-editor.component.ts | 7 ++- .../gameboard-ui/src/app/api/game-models.ts | 3 +- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html index 111dc727..b6329154 100644 --- a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html +++ b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.html @@ -273,32 +273,50 @@

Does the game use standard VMs, or is it played in Unity or another external host? - -
+
+

External Game Settings

-
-
- - +
+
+ + - When Gameboard boots an external game, it'll post game metadata to this URL. - ({{externalGameServerUrlBase | trim:"/":"end"}}/{{game.externalGameStartupUrl}}) + The URL from which Gameboard will serve the game client
-
- - +
+
+ +
+

Endpoints

+
+ +
+
+ + - The URL from which Gameboard will attempt to serve the game client + When Gameboard boots an external game, it'll post game metadata to this endpoint on the external game + host. + +
+ +
+ + + + If a team's session end time changes, Gameboard will make a request to the external host notifying it of + the change.
- +
diff --git a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts index dfe6098e..8a557cb2 100644 --- a/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts +++ b/projects/gameboard-ui/src/app/admin/game-editor/game-editor.component.ts @@ -93,8 +93,11 @@ export class GameEditorComponent implements AfterViewInit { // the first time we flip to "External" mode, if the external game start url isn't specified, // default it to gamebrain's url in settings - if (this.config.gamebrainhost && !this.game.externalGameStartupUrl && !this.defaultExternalGameStartUrlSuggested) { - this.game.externalGameStartupUrl = `${this.config.gamebrainhost}admin/deploy`; + if (this.config.gamebrainhost && !this.game.externalGameStartupEndpoint && !this.defaultExternalGameStartUrlSuggested) { + // this looks funny because the URL of the only currently-supported external host (Gamebrain) has its base url + // set through helm configuration of the API (so we only care about configuring the endpoint, not the fully-qualified) + // url + this.game.externalGameStartupEndpoint = "admin/deploy"; this.defaultExternalGameStartUrlSuggested = true; } }), diff --git a/projects/gameboard-ui/src/app/api/game-models.ts b/projects/gameboard-ui/src/app/api/game-models.ts index 1697557d..0fb29709 100644 --- a/projects/gameboard-ui/src/app/api/game-models.ts +++ b/projects/gameboard-ui/src/app/api/game-models.ts @@ -38,7 +38,8 @@ export interface GameDetail { requireSponsoredTeam: boolean; allowPreview: boolean; externalGameClientUrl?: string; - externalGameStartupUrl?: string; + externalGameTeamExtendedEndpoint?: string; + externalGameStartupEndpoint?: string; requireSynchronizedStart: boolean; requireTeam: boolean; showOnHomePageInPracticeMode: boolean; From 14f3c8fc94a2df782d0d21995e7ee69cf0032d6d Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Tue, 5 Mar 2024 12:34:49 -0500 Subject: [PATCH 02/18] Change default cancel button color on the default confirm dialog --- .../src/app/core/components/modal/modal-confirm.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/gameboard-ui/src/app/core/components/modal/modal-confirm.component.ts b/projects/gameboard-ui/src/app/core/components/modal/modal-confirm.component.ts index 383de5ab..87f06f22 100644 --- a/projects/gameboard-ui/src/app/core/components/modal/modal-confirm.component.ts +++ b/projects/gameboard-ui/src/app/core/components/modal/modal-confirm.component.ts @@ -17,7 +17,7 @@ import { ModalConfirmConfig } from '@/core/components/modal/modal.models';
From 6f2a4894f882362976462d6f8e69c645c49742d8 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Wed, 6 Mar 2024 13:05:35 -0500 Subject: [PATCH 03/18] Incorporate advanced score into scoreboard. Fix bugs with manual bonuses and scoreboard. --- .../admin-player-session.component.html | 37 ++++++++++++++++--- .../admin-player-session.component.scss | 14 +++++-- .../admin-player-session.component.ts | 6 +-- ...ge-manual-challenge-bonuses.component.html | 17 ++++++++- ...ge-manual-challenge-bonuses.component.scss | 3 +- .../player-registrar.component.html | 37 ++++++++++--------- .../gameboard-ui/src/app/api/player-models.ts | 5 +++ .../app/core/pipes/datetime-to-date.pipe.ts | 1 - ...coreboard-team-detail-modal.component.html | 20 +++++++--- ...coreboard-team-detail-modal.component.scss | 9 +++++ .../scoreboard-page.component.html | 1 - .../app/game/pipes/score-to-tooltip.pipe.ts | 2 +- .../app/services/scoring/scoring.models.ts | 4 +- .../app/services/scoring/scoring.service.ts | 10 ++--- 14 files changed, 117 insertions(+), 49 deletions(-) diff --git a/projects/gameboard-ui/src/app/admin/admin-player-session/admin-player-session.component.html b/projects/gameboard-ui/src/app/admin/admin-player-session/admin-player-session.component.html index 07841871..bc772828 100644 --- a/projects/gameboard-ui/src/app/admin/admin-player-session/admin-player-session.component.html +++ b/projects/gameboard-ui/src/app/admin/admin-player-session/admin-player-session.component.html @@ -2,14 +2,39 @@ +

Timeline

-
Timeline [BETA]
-

Session Extension

+
+

Advancement

+
+
+
Advanced from
+
{{team.advancedFromGame.name}}
+
+ +
+
Played as
+
{{team.advancedFromPlayer?.name}}
+
+ +
+
Team
+
{{team.advancedFromPlayer?.name}}
+
+ +
+
Score
+
{{team.advancedWithScore || 0}}
+
+
+
+ +

Session Extension

@@ -39,12 +64,12 @@

Session Extension

- -
-

Other tools

+

Other tools

+ + -
+
+
+
+ + +
+ +
+ + +
{{ context.gameInfo.name }}

{{ context.score.team.name }}

-
Advanced to next round +
+ Advanced to next round
@@ -51,7 +52,7 @@

Cumulative Time

-
+

Challenges

@@ -100,7 +101,7 @@

Challenges

- + @@ -112,13 +113,16 @@

Challenges

{{ context.score.overallScore.completionScore }} - {{ context.score.overallScore.totalScore - teamManualBonusTotal }} + + {{ context.score.overallScore.totalScore - teamManualBonusTotal - + (context.score.overallScore.advancedScore || 0) }}
-
+

Additional Bonuses

@@ -133,6 +137,10 @@

Additional Bonuses

+ + + +
Previous Game Performance{{ context.score.overallScore.advancedScore }}
{{bonus.description}} @@ -146,7 +154,7 @@

Additional Bonuses

-
+
Grand Total
{{context.score.overallScore.totalScore}}
diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss b/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss index 26f6f0de..a92b89fa 100644 --- a/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss +++ b/projects/gameboard-ui/src/app/game/components/scoreboard-team-detail-modal/scoreboard-team-detail-modal.component.scss @@ -69,6 +69,15 @@ tfoot td { width: 12rem; } +.summed-score-container { + font-weight: bold; + text-transform: uppercase; + + div { + padding: 0.75rem; + } +} + .grand-total-container { background-color: $info; color: #eee; diff --git a/projects/gameboard-ui/src/app/game/pages/scoreboard-page/scoreboard-page.component.html b/projects/gameboard-ui/src/app/game/pages/scoreboard-page/scoreboard-page.component.html index c1088a67..5032e646 100644 --- a/projects/gameboard-ui/src/app/game/pages/scoreboard-page/scoreboard-page.component.html +++ b/projects/gameboard-ui/src/app/game/pages/scoreboard-page/scoreboard-page.component.html @@ -25,7 +25,6 @@

Scoreboard

-
diff --git a/projects/gameboard-ui/src/app/game/pipes/score-to-tooltip.pipe.ts b/projects/gameboard-ui/src/app/game/pipes/score-to-tooltip.pipe.ts index 536237cb..fc51bdbe 100644 --- a/projects/gameboard-ui/src/app/game/pipes/score-to-tooltip.pipe.ts +++ b/projects/gameboard-ui/src/app/game/pipes/score-to-tooltip.pipe.ts @@ -9,6 +9,6 @@ export class ScoreToTooltipPipe implements PipeTransform { return ""; const clickPrompt = gameIsLive ? "" : " (Click for details)"; - return `${value.scoreChallenge} + ${value.scoreAutoBonus + value.scoreManualBonus} bonus${clickPrompt}`; + return `${value.scoreChallenge} + ${(value.scoreAdvanced || 0) + value.scoreAutoBonus + value.scoreManualBonus} bonus${clickPrompt}`; } } diff --git a/projects/gameboard-ui/src/app/services/scoring/scoring.models.ts b/projects/gameboard-ui/src/app/services/scoring/scoring.models.ts index a2594816..50b8f231 100644 --- a/projects/gameboard-ui/src/app/services/scoring/scoring.models.ts +++ b/projects/gameboard-ui/src/app/services/scoring/scoring.models.ts @@ -28,6 +28,7 @@ export interface GameScoringConfigChallengeBonusSolveRank extends GameScoringCon } export interface Score { + advancedScore?: number; completionScore: number; manualBonusScore: number; bonusScore: number; @@ -83,7 +84,6 @@ export interface GameScoreTeam { players: PlayerWithSponsor[]; rank: number; team: SimpleEntity; - remainingTimeMs?: number; totalTimeMs: number; } @@ -112,7 +112,6 @@ export interface TeamScore { isAdvancedToNextRound: boolean; overallScore: Score; cumulativeTimeMs: number; - remainingTimeMs?: number; } export interface AutoChallengeBonus { @@ -163,6 +162,7 @@ export interface DenormalizedTeamScore { teamId: string; teamName: string; scoreOverall: number; + scoreAdvanced?: number; scoreAutoBonus: number; scoreManualBonus: number; scoreChallenge: number; diff --git a/projects/gameboard-ui/src/app/services/scoring/scoring.service.ts b/projects/gameboard-ui/src/app/services/scoring/scoring.service.ts index d4477480..d7e95e9e 100644 --- a/projects/gameboard-ui/src/app/services/scoring/scoring.service.ts +++ b/projects/gameboard-ui/src/app/services/scoring/scoring.service.ts @@ -34,11 +34,6 @@ export class ScoringService { public getScoreboard(gameId: string): Observable { return this.http.get(this.apiUrl.build(`game/${gameId}/scoreboard`)).pipe(map(s => { - if (!s.game.isLiveUntil) - return s; - - s.game.isLiveUntil = this.apiDateTime.toDateTime(s.game.isLiveUntil as any) as DateTime; - for (let t of s.teams) { if (!t.sessionEnds) continue; @@ -46,6 +41,11 @@ export class ScoringService { t.sessionEnds = this.apiDateTime.toDateTime(t.sessionEnds as any) as DateTime; } + if (!s.game.isLiveUntil) + return s; + + s.game.isLiveUntil = this.apiDateTime.toDateTime(s.game.isLiveUntil as any) as DateTime; + return s; })); } From b68e9056f9c379b8498ed36e3562a8dfab98a59d Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Wed, 6 Mar 2024 15:04:02 -0500 Subject: [PATCH 04/18] Start on #392. --- .../team-admin-context-menu.component.html | 5 +++++ .../team-admin-context-menu.component.ts | 1 + 2 files changed, 6 insertions(+) diff --git a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html index ba14ba5e..31644be9 100644 --- a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html +++ b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html @@ -21,6 +21,11 @@ +
  • + +
  • diff --git a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.scss b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.scss index 579bf2f3..2acaeeeb 100644 --- a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.scss +++ b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.scss @@ -1,8 +1,14 @@ +@import "../../../../scss/variables"; + .ctx-menu-button { - aspect-ratio: 1 / 1; + aspect-ratio: 1 / 1; } .btn-danger { - font-weight: bold; - color: red; + color: red; + font-weight: bold; +} + +.btn-warning { + color: $warning; } diff --git a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts index e3263a4c..9ab65cb4 100644 --- a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts +++ b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts @@ -4,6 +4,9 @@ import { fa } from '@/services/font-awesome.service'; import { GameSessionService } from '@/services/game-session.service'; import { ClipboardService } from '@/utility/services/clipboard.service'; import { ToastService } from '@/utility/services/toast.service'; +import { SyncStartService } from '@/services/sync-start.service'; +import { firstValueFrom } from 'rxjs'; +import { PlayerService } from '@/api/player.service'; export interface TeamAdminContextMenuSessionResetRequest { player: Player; @@ -16,14 +19,16 @@ export interface TeamAdminContextMenuSessionResetRequest { styleUrls: ['./team-admin-context-menu.component.scss'] }) export class TeamAdminContextMenuComponent implements OnInit { - @Input() public player!: Player; + @Input() player!: Player; + @Input() hasStartedSession = false; @Input() isViewing = false; @Input() isSyncStartGame = false; - @Output() onBonusManageRequest = new EventEmitter(); - @Output() onSessionResetRequest = new EventEmitter(); - @Output() onUnenrollRequest = new EventEmitter(); - @Output() onViewRequest = new EventEmitter(); + @Output() bonusManageRequest = new EventEmitter(); + @Output() playerChange = new EventEmitter + @Output() sessionResetRequest = new EventEmitter(); + @Output() unenrollRequest = new EventEmitter(); + @Output() viewRequest = new EventEmitter(); protected fa = fa; protected isResettingSession = false; @@ -31,9 +36,14 @@ export class TeamAdminContextMenuComponent implements OnInit { constructor( private clipboardService: ClipboardService, private gameSessionService: GameSessionService, + private playerService: PlayerService, + private syncStartService: SyncStartService, private toastService: ToastService) { } ngOnInit(): void { + if (!this.player) + throw new Error("No player provided"); + this.isResettingSession = !this.gameSessionService.canUnenroll(this.player); } @@ -41,4 +51,21 @@ export class TeamAdminContextMenuComponent implements OnInit { await this.clipboardService.copy(text); this.toastService.showMessage(`Copied ${description} "${text}" to your clipboard.`); } + + async handleUpdatePlayerReady(player: Player, isReady: boolean) { + player.isReady = isReady; + await firstValueFrom(this.syncStartService.updatePlayerReadyState(player.id, { isReady })); + this.playerChange.emit(player); + this.toastService.showMessage( + player.isReady ? + `Player ${player.approvedName} has been readied.` : + `Player ${player.approvedName} is no longer ready.` + ); + } + + async handleStartSession(player: Player) { + await firstValueFrom(this.playerService.start(player)); + this.playerChange.emit(player); + this.toastService.showMessage(`Session started for ${player.approvedName}`); + } } diff --git a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html index 022e328f..a4a94e2d 100644 --- a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html +++ b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html @@ -164,7 +164,10 @@

    Players — {{ctx.game.name}}

    -
    Late Start
    +
    + Not Ready +
    +
    Late Start
    Advanced
    {{player.rank}} | @@ -181,8 +184,9 @@

    Players — {{ctx.game.name}}

    + [isSyncStartGame]="game.requireSynchronizedStart" (bonusManageRequest)="manageManualBonuses(player)" + (playerChange)="handlePlayerChange($event)" (sessionResetRequest)="confirmReset($event)" + (unenrollRequest)="confirmUnenroll($event)" (viewRequest)="view($event)">
    diff --git a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts index 67e52f49..0122b55d 100644 --- a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts +++ b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts @@ -21,6 +21,7 @@ import { AppTitleService } from '@/services/app-title.service'; import { UnsubscriberService } from '@/services/unsubscriber.service'; import { ExtendTeamsModalComponent } from '../components/extend-teams-modal/extend-teams-modal.component'; import { unique } from 'projects/gameboard-ui/src/tools'; +import { ToastService } from '@/utility/services/toast.service'; @Component({ selector: 'app-player-registrar', @@ -70,6 +71,7 @@ export class PlayerRegistrarComponent { private clipboard: ClipboardService, private teamService: TeamService, private title: AppTitleService, + private toastService: ToastService, private unityService: UnityService, private unsub: UnsubscriberService ) { @@ -285,6 +287,10 @@ export class PlayerRegistrarComponent { ); } + protected handlePlayerChange(player: Player) { + this.refresh$.next(true); + } + protected confirmReset(request: TeamAdminContextMenuSessionResetRequest) { this.modalConfirmService.openConfirm({ bodyContent: ` diff --git a/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts b/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts index 3dfaec68..6e90d536 100644 --- a/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts +++ b/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts @@ -51,6 +51,7 @@ export class SessionStartControlsComponent implements OnInit { this.isConnectedToGameHub = gameIds.some(gId => gId == this.ctx.game.id); }), this.gameHub.syncStartGameStateChanged$.subscribe(stateUpdate => { + console.log("state update", stateUpdate); this.handleNewSyncStartState(stateUpdate); }) ); From 2fc67f4c57287601a9c69a6faad059af94c6e51f Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Thu, 7 Mar 2024 11:08:05 -0500 Subject: [PATCH 06/18] Add date filters to challenges report (just filters stat data, not records). --- .../challenges-report/challenges-report.component.html | 2 ++ .../challenges-report/challenges-report.component.ts | 6 ++++++ .../reports/challenges-report/challenges-report.models.ts | 2 ++ .../reports/support-report/support-report.component.html | 1 - 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html b/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html index 5708bb17..95fa3902 100644 --- a/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html +++ b/projects/gameboard-ui/src/app/reports/components/reports/challenges-report/challenges-report.component.html @@ -9,6 +9,8 @@ + - From be64a515d04da1710b0c597c634c871d45d35c95 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Thu, 7 Mar 2024 12:51:38 -0500 Subject: [PATCH 07/18] Try to have external game page scroll to bottom on iframe load. --- .../external-game-page/external-game-page.component.html | 2 +- .../external-game-page/external-game-page.component.ts | 8 +++++++- projects/gameboard-ui/src/app/services/window.service.ts | 4 ++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.html b/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.html index 7bf62a98..71d497a4 100644 --- a/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.html +++ b/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.html @@ -2,7 +2,7 @@
    + scrolling="no" onload="">
    diff --git a/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.ts b/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.ts index 1de872ea..2194c20f 100644 --- a/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.ts +++ b/projects/gameboard-ui/src/app/game/pages/external-game-page/external-game-page.component.ts @@ -9,6 +9,7 @@ import { RouterService } from '@/services/router.service'; import { LogService } from '@/services/log.service'; import { ExternalGameService } from '@/services/external-game.service'; import { ConfigService } from '@/utility/config.service'; +import { WindowService } from '@/services/window.service'; @Component({ selector: 'app-external-game-page', @@ -29,7 +30,8 @@ export class ExternalGamePageComponent implements OnInit, OnDestroy { private layoutService: LayoutService, private log: LogService, private route: ActivatedRoute, - private routerService: RouterService) { } + private routerService: RouterService, + private windowService: WindowService) { } async ngOnInit(): Promise { this.isProduction = environment.production; @@ -60,6 +62,10 @@ export class ExternalGamePageComponent implements OnInit, OnDestroy { this.routerService.reloadOnNextNavigateEnd(); } + protected handleIframeLoad() { + this.windowService.scrollToBottom(); + } + ngOnDestroy(): void { this.layoutService.stickyMenu$.next(true); } diff --git a/projects/gameboard-ui/src/app/services/window.service.ts b/projects/gameboard-ui/src/app/services/window.service.ts index 50051691..6b17e686 100644 --- a/projects/gameboard-ui/src/app/services/window.service.ts +++ b/projects/gameboard-ui/src/app/services/window.service.ts @@ -16,4 +16,8 @@ export class WindowService { print() { this.document.defaultView!.print(); } + + scrollToBottom() { + this.document.defaultView?.scrollTo(0, this.document.body.scrollHeight); + } } From cc7c58ff99ff47a4af171aaf3de068d899a1de82 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Thu, 7 Mar 2024 13:18:56 -0500 Subject: [PATCH 08/18] flex wrap on team active challenges --- .../active-challenges-modal.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.html b/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.html index 7a27641c..8e440710 100644 --- a/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.html +++ b/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.html @@ -17,7 +17,7 @@ -
      +
      • From 022ab20c444b994ca7b9e2fc3834bd20b2edea76 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Thu, 7 Mar 2024 13:20:40 -0500 Subject: [PATCH 09/18] Style scoreboard --- .../app/game/components/scoreboard/scoreboard.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html b/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html index b44079d6..192c816f 100644 --- a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html +++ b/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html @@ -1,5 +1,5 @@ - + This game is still being played. Check back after the round ends ({{ scoreboardData.game.isLiveUntil?.toJSDate() | friendlyDateAndTime }}) to see who came out on top and view complete score breakdowns for From 219bd3daf249d075ba984cbeb2ba5e938feb77ad Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Thu, 7 Mar 2024 16:16:19 -0500 Subject: [PATCH 10/18] Refine ready player/session start in player admin. --- .../team-admin-context-menu.component.html | 30 +++++++++++-------- .../team-admin-context-menu.component.ts | 1 + .../src/app/api/event-horizon.service.ts | 10 +++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html index eb813240..aa8b529e 100644 --- a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html +++ b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.html @@ -20,18 +20,14 @@ Manual Bonuses
      • - -
      • - -
      • -
      • - -
      • + + +
      • + +
      • +
    + + + + diff --git a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts index 9ab65cb4..054feeee 100644 --- a/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts +++ b/projects/gameboard-ui/src/app/admin/components/team-admin-context-menu/team-admin-context-menu.component.ts @@ -44,6 +44,7 @@ export class TeamAdminContextMenuComponent implements OnInit { if (!this.player) throw new Error("No player provided"); + this.hasStartedSession = this.player?.sessionBegin?.getFullYear() !== 0; this.isResettingSession = !this.gameSessionService.canUnenroll(this.player); } diff --git a/projects/gameboard-ui/src/app/api/event-horizon.service.ts b/projects/gameboard-ui/src/app/api/event-horizon.service.ts index 44524498..6c105938 100644 --- a/projects/gameboard-ui/src/app/api/event-horizon.service.ts +++ b/projects/gameboard-ui/src/app/api/event-horizon.service.ts @@ -4,13 +4,15 @@ import { firstValueFrom, map, of } from 'rxjs'; import { EventHorizonEventType, EventHorizonEvent, TeamEventHorizonViewModel, EventHorizonChallengeSpec, EventHorizonGamespaceOnOffEvent } from './event-horizon.models'; import { ApiUrlService } from '@/services/api-url.service'; import { ApiDateTimeService } from '@/services/api-date-time.service'; +import { LogService } from '@/services/log.service'; @Injectable({ providedIn: 'root' }) export class EventHorizonService { constructor( private apiDateTimeService: ApiDateTimeService, private apiUrl: ApiUrlService, - private http: HttpClient) { } + private http: HttpClient, + private log: LogService) { } async getTeamEventHorizon(teamId: string): Promise { return await firstValueFrom(this.http.get(this.apiUrl.build(`team/${teamId}/timeline`)).pipe( @@ -21,8 +23,10 @@ export class EventHorizonService { const start = this.apiDateTimeService.toDateTime(timeline.team.session.start as any); const end = this.apiDateTimeService.toDateTime(timeline.team.session.end as any); - if (!start) - throw new Error(`Couldn't resolve timeline start for team event horizon ${teamId}.`); + if (!start) { + this.log.logInfo("No start date for this team's timeline. Have they started their session?"); + return null; + } timeline.team.session.start = start; timeline.team.session.end = end; From 6a40376621f061926f546fdc4ff9ce39f6dc2551 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Thu, 7 Mar 2024 16:32:18 -0500 Subject: [PATCH 11/18] Style scoreboard --- .../scoreboard/scoreboard.component.html | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html b/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html index 192c816f..4b36688a 100644 --- a/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html +++ b/projects/gameboard-ui/src/app/game/components/scoreboard/scoreboard.component.html @@ -8,7 +8,7 @@ - + @@ -19,8 +19,9 @@ @@ -63,12 +64,11 @@ From 38af49a97706a9079fc1b443c10a2adba80bec35 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Fri, 8 Mar 2024 15:26:05 -0500 Subject: [PATCH 12/18] Remove unused models and properties. Pull logging --- projects/gameboard-ui/src/app/api/player-models.ts | 8 -------- .../session-start-controls.component.ts | 3 ++- .../src/app/game/pages/game-page/game-page.component.ts | 6 ------ 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/projects/gameboard-ui/src/app/api/player-models.ts b/projects/gameboard-ui/src/app/api/player-models.ts index 46f365bb..7bf9c83f 100644 --- a/projects/gameboard-ui/src/app/api/player-models.ts +++ b/projects/gameboard-ui/src/app/api/player-models.ts @@ -245,14 +245,6 @@ export interface TeamPlayer { pendingName: string; } -export interface TeamState { - id: string; - name: string; - sessionBegin: Date, - sessionEnd: Date, - actor: SimpleEntity -} - export interface TeamSummary { id: string; name: string; diff --git a/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts b/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts index 6e90d536..1765b9ee 100644 --- a/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts +++ b/projects/gameboard-ui/src/app/game/components/session-start-controls/session-start-controls.component.ts @@ -51,7 +51,7 @@ export class SessionStartControlsComponent implements OnInit { this.isConnectedToGameHub = gameIds.some(gId => gId == this.ctx.game.id); }), this.gameHub.syncStartGameStateChanged$.subscribe(stateUpdate => { - console.log("state update", stateUpdate); + this.logService.logInfo("State update", stateUpdate); this.handleNewSyncStartState(stateUpdate); }) ); @@ -78,6 +78,7 @@ export class SessionStartControlsComponent implements OnInit { private handleNewSyncStartState(state: SyncStartGameState) { this.isGameSyncStartReady = state.isReady; + this.ctx.player.isReady = state.teams.some(t => t.players.some(p => p.isReady && p.id === this.ctx.player.id)); if (!state || !state.teams.length) { this.playerReadyPct = 0; diff --git a/projects/gameboard-ui/src/app/game/pages/game-page/game-page.component.ts b/projects/gameboard-ui/src/app/game/pages/game-page/game-page.component.ts index 2b3f99d5..3e880a00 100644 --- a/projects/gameboard-ui/src/app/game/pages/game-page/game-page.component.ts +++ b/projects/gameboard-ui/src/app/game/pages/game-page/game-page.component.ts @@ -48,8 +48,6 @@ export class GamePageComponent implements OnDestroy { protected player$ = new BehaviorSubject(null); private needsReloadOnUnenroll = false; - private syncStartChangedSubscription?: Subscription; - private externalGameDeployStartSubscription?: Subscription; private hubEventsSubcription: Subscription; private localUserSubscription: Subscription; @@ -206,8 +204,6 @@ export class GamePageComponent implements OnDestroy { ngOnDestroy(): void { this.hubEventsSubcription?.unsubscribe(); this.localUserSubscription?.unsubscribe(); - this.syncStartChangedSubscription?.unsubscribe(); - this.externalGameDeployStartSubscription?.unsubscribe(); if (this.ctxIds.gameId) this.gameHubService.leaveGame(this.ctxIds.gameId); @@ -248,8 +244,6 @@ export class GamePageComponent implements OnDestroy { await this.hub.disconnect(); this.player$.next(null); - this.externalGameDeployStartSubscription?.unsubscribe(); - this.syncStartChangedSubscription?.unsubscribe(); if (this.needsReloadOnUnenroll) { this.windowService.get().location.reload(); From b7945b152d39bb7ad5e749ad620cb7d3e2c7badb Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Fri, 8 Mar 2024 15:41:34 -0500 Subject: [PATCH 13/18] Allow games with end dates in the future as advance targets. --- .../admin/player-registrar/player-registrar.component.html | 2 +- .../admin/player-registrar/player-registrar.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html index a4a94e2d..70019057 100644 --- a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html +++ b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html @@ -124,7 +124,7 @@

    Players — {{ctx.game.name}}

    With Scores -
    +
    Advance to {{g.name}} diff --git a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts index 0122b55d..09ecb6b3 100644 --- a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts +++ b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts @@ -32,7 +32,7 @@ import { ToastService } from '@/utility/services/toast.service'; export class PlayerRegistrarComponent { refresh$ = new BehaviorSubject(true); game!: Game; - ctx$: Observable<{ game: Game, futures: Game[], players: Player[] }>; + ctx$: Observable<{ game: Game, advanceTargetGames: Game[], players: Player[] }>; source: Player[] = []; selected: Player[] = []; viewed?: Player; @@ -112,9 +112,9 @@ export class PlayerRegistrarComponent { this.ctx$ = combineLatest([ game$, players$, - gameapi.list({ filter: ['future'] }) + gameapi.list({ filter: ['advanceable'] }) ]).pipe( - map(([game, players, futures]) => ({ game, players, futures })) + map(([game, players, advanceTargetGames]) => ({ game, players, advanceTargetGames })) ); this.unsub.add( From b3de1d4beb3b4b5274aa12346d7170ff4dca0b72 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Mon, 11 Mar 2024 14:00:26 -0400 Subject: [PATCH 14/18] Start admin enroll team --- .../src/app/admin/admin.module.ts | 2 + .../admin-enroll-team-modal.component.html | 25 +++++++++++ .../admin-enroll-team-modal.component.scss | 0 .../admin-enroll-team-modal.component.ts | 42 +++++++++++++++++++ .../player-registrar.component.html | 22 ++++++---- .../player-registrar.component.ts | 10 +++++ .../gameboard-ui/src/app/api/team.service.ts | 6 ++- .../gameboard-ui/src/app/api/teams.models.ts | 13 ++++++ 8 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html create mode 100644 projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.scss create mode 100644 projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.ts diff --git a/projects/gameboard-ui/src/app/admin/admin.module.ts b/projects/gameboard-ui/src/app/admin/admin.module.ts index 1e2f1ee7..a7733026 100644 --- a/projects/gameboard-ui/src/app/admin/admin.module.ts +++ b/projects/gameboard-ui/src/app/admin/admin.module.ts @@ -60,6 +60,7 @@ import { SupportSettingsComponent } from './components/support-settings/support- import { FeedbackEditorComponent } from './components/feedback-editor/feedback-editor.component'; import { ExtendTeamsModalComponent } from './components/extend-teams-modal/extend-teams-modal.component'; import { ActiveTeamsModalComponent } from './components/active-teams-modal/active-teams-modal.component'; +import { AdminEnrollTeamModalComponent } from './components/admin-enroll-team-modal/admin-enroll-team-modal.component'; @NgModule({ declarations: [ @@ -109,6 +110,7 @@ import { ActiveTeamsModalComponent } from './components/active-teams-modal/activ FeedbackEditorComponent, ExtendTeamsModalComponent, ActiveTeamsModalComponent, + AdminEnrollTeamModalComponent, ], imports: [ CommonModule, diff --git a/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html new file mode 100644 index 00000000..604a8774 --- /dev/null +++ b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html @@ -0,0 +1,25 @@ + + + + + diff --git a/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.scss b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.ts b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.ts new file mode 100644 index 00000000..5291b03e --- /dev/null +++ b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.ts @@ -0,0 +1,42 @@ +import { TeamService } from '@/api/team.service'; +import { ModalConfirmService } from '@/services/modal-confirm.service'; +import { Component, OnInit } from '@angular/core'; +import { firstValueFrom } from 'rxjs'; + +@Component({ + selector: 'app-admin-enroll-team-modal', + templateUrl: './admin-enroll-team-modal.component.html', + styleUrls: ['./admin-enroll-team-modal.component.scss'] +}) +export class AdminEnrollTeamModalComponent implements OnInit { + game?: { + id: string; + name: string; + isTeamGame: boolean; + }; + + protected addUserId = ""; + protected errors: any[] = []; + protected isWorking = false; + + constructor( + private modalConfirmService: ModalConfirmService, + private teamService: TeamService) { } + + ngOnInit(): void { + if (!this.game) + throw new Error("Can't resolve the game."); + } + + protected close() { + this.modalConfirmService.hide(); + } + + protected async handleAddClick(userId: string) { + if (!this.game?.id) + return; + + await firstValueFrom(this.teamService.adminEnroll({ userIds: [this.addUserId], gameId: this.game.id })); + this.modalConfirmService.hide(); + } +} diff --git a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html index 70019057..44f7b39a 100644 --- a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html +++ b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.html @@ -78,6 +78,14 @@

    Players — {{ctx.game.name}}


    + + | count: {{ctx.players.length}} selected: {{selected.length}} @@ -109,13 +117,11 @@

    Players — {{ctx.game.name}}

    advance - + +
    @@ -167,7 +173,7 @@

    Players — {{ctx.game.name}}

    Not Ready
    -
    Late Start
    +
    Late Start
    Advanced
    {{player.rank}} | diff --git a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts index 09ecb6b3..ec026d74 100644 --- a/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts +++ b/projects/gameboard-ui/src/app/admin/player-registrar/player-registrar.component.ts @@ -22,6 +22,7 @@ import { UnsubscriberService } from '@/services/unsubscriber.service'; import { ExtendTeamsModalComponent } from '../components/extend-teams-modal/extend-teams-modal.component'; import { unique } from 'projects/gameboard-ui/src/tools'; import { ToastService } from '@/utility/services/toast.service'; +import { AdminEnrollTeamModalComponent } from '../components/admin-enroll-team-modal/admin-enroll-team-modal.component'; @Component({ selector: 'app-player-registrar', @@ -287,6 +288,15 @@ export class PlayerRegistrarComponent { ); } + protected handlePlayerAddClick(game: Game) { + this.modalConfirmService.openComponent({ + content: AdminEnrollTeamModalComponent, + context: { + game: game, + }, + }); + } + protected handlePlayerChange(player: Player) { this.refresh$.next(true); } diff --git a/projects/gameboard-ui/src/app/api/team.service.ts b/projects/gameboard-ui/src/app/api/team.service.ts index da3f78c0..e557d5e6 100644 --- a/projects/gameboard-ui/src/app/api/team.service.ts +++ b/projects/gameboard-ui/src/app/api/team.service.ts @@ -2,7 +2,7 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; import { Observable, Subject, map, tap } from "rxjs"; import { SessionEndRequest, SessionExtendRequest, Team } from "./player-models"; -import { AdminExtendTeamSessionResponse, ResetTeamSessionRequest } from "./teams.models"; +import { AdminEnrollTeamRequest, AdminEnrollTeamResponse, AdminExtendTeamSessionResponse, ResetTeamSessionRequest } from "./teams.models"; import { ApiUrlService } from "@/services/api-url.service"; import { ApiDateTimeService } from "@/services/api-date-time.service"; @@ -22,6 +22,10 @@ export class TeamService { private apiUrl: ApiUrlService, private http: HttpClient) { } + adminEnroll(request: AdminEnrollTeamRequest): Observable { + return this.http.post(this.apiUrl.build("admin/team"), request); + } + adminExtendSession(request: { teamIds: string[], extensionDurationInMinutes: number }) { return this.http.put(this.apiUrl.build("admin/team/session"), request).pipe( tap(teamSessions => this._teamSessionsChanged$.next(teamSessions.teams.map(t => t.id))) diff --git a/projects/gameboard-ui/src/app/api/teams.models.ts b/projects/gameboard-ui/src/app/api/teams.models.ts index 6f48b4e5..1993c801 100644 --- a/projects/gameboard-ui/src/app/api/teams.models.ts +++ b/projects/gameboard-ui/src/app/api/teams.models.ts @@ -1,4 +1,17 @@ import { DateTime } from "luxon"; +import { PlayerWithSponsor } from "./models"; + +export interface AdminEnrollTeamRequest { + gameId: string; + userIds: string[]; +} + +export interface AdminEnrollTeamResponse { + id: string; + name: string; + gameId: string; + players: PlayerWithSponsor; +} export interface AdminExtendTeamSessionResponse { teams: [ From 8c22f006c0324c333e9b36c2a65f3b09ff035565 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Mon, 11 Mar 2024 14:14:12 -0400 Subject: [PATCH 15/18] Better hints for admin overview clicks --- .../core/components/big-stat/big-stat.component.html | 6 ++++-- .../core/components/big-stat/big-stat.component.scss | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.html b/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.html index fb216101..bec98356 100644 --- a/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.html +++ b/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.html @@ -1,5 +1,7 @@ -
    -
    {{ value }}
    +
    +
    + {{ value }} +
    {{ label }}
    {{subLabel}} diff --git a/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.scss b/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.scss index 223cc375..09ce11b5 100644 --- a/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.scss +++ b/projects/gameboard-ui/src/app/core/components/big-stat/big-stat.component.scss @@ -1,4 +1,14 @@ +@import "../../../../scss/variables"; + .value { font-size: 4rem; font-weight: bold; } + +.stat-button { + cursor: pointer; + + .value span { + border-bottom: dotted 3px $info; + } +} From 48a9e109c012a28c1cf8372baa6dbd87bba420c5 Mon Sep 17 00:00:00 2001 From: Ben Stein Date: Mon, 11 Mar 2024 15:00:09 -0400 Subject: [PATCH 16/18] Minor styling --- .../active-challenges-modal.component.scss | 2 +- .../admin-enroll-team-modal.component.html | 2 +- .../admin-enroll-team-modal.component.ts | 10 ++++++++-- .../deployment-admin-team-context-menu.component.html | 2 +- .../src/app/services/notification.service.ts | 2 +- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.scss b/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.scss index 5f3fcfc8..81efc15b 100644 --- a/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.scss +++ b/projects/gameboard-ui/src/app/admin/components/active-challenges-modal/active-challenges-modal.component.scss @@ -9,7 +9,7 @@ h5 { } li { - flex-basis: 32%; + flex-basis: 30%; min-width: 250px; } diff --git a/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html index 604a8774..ffa90e92 100644 --- a/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html +++ b/projects/gameboard-ui/src/app/admin/components/admin-enroll-team-modal/admin-enroll-team-modal.component.html @@ -4,10 +4,10 @@ -
    {{ scoreboardData.game.isTeamGame ? "Team" : "Player" }} Challenges - Cumulative - Time + + Cumulative Time + Score
    -
    +
    -
    - {{ sessionEnds }} + {{ sessionEnds }}