-
-
Advanced from
+
+
+
Advanced from
{{team.advancement.fromGame.name}}
-
-
Score
+
+
Score
{{team.advancement.score || 0}}
diff --git a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.html b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.html
index 017f912c..29593529 100644
--- a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.html
+++ b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.html
@@ -26,6 +26,12 @@
{{ game.isTeamGame ? "Team" : "Player" }}{{ selectedTeamIds.length === 1 ? "" : "s" }}
+
+
+
+
diff --git a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.ts b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.ts
index 5ac5caea..bc5686ba 100644
--- a/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.ts
+++ b/projects/gameboard-ui/src/app/admin/components/game-center/game-center-teams/game-center-teams.component.ts
@@ -21,6 +21,7 @@ import { NowService } from '@/services/now.service';
import { GameCenterTeamDetailComponent } from '../game-center-team-detail/game-center-team-detail.component';
import { TeamService } from '@/api/team.service';
import { eventTargetValueToString } from 'projects/gameboard-ui/src/tools/functions';
+import { AdvanceTeamsModalComponent } from '../../advance-teams-modal/advance-teams-modal.component';
interface GameCenterTeamsFilterSettings {
advancement?: GameCenterTeamsAdvancementFilter;
@@ -100,6 +101,31 @@ export class GameCenterTeamsComponent implements OnInit {
await this.load(this.game?.id);
}
+ protected async handleConfirmAdvanceTeams() {
+ if (!this.game) {
+ return;
+ }
+
+ const teams = this.resolveSelectedTeams();
+ if (!teams.length) {
+ return;
+ }
+
+ this.modalService.openComponent({
+ content: AdvanceTeamsModalComponent,
+ context: {
+ game: this.game,
+ teams: teams,
+ onConfirm: async (targetGame: SimpleEntity) => {
+ await this.load(this.gameId);
+ this.toastService.showMessage(`**${teams.length}** teams were advanced to **${targetGame.name}**.`);
+ this.selectedTeamIds = [];
+ }
+ },
+ modalClasses: ["modal-xl"],
+ });
+ }
+
protected async handleConfirmDeployGameResources() {
const teams = this.resolveSelectedTeams();
const nowish = this.nowService.nowToMsEpoch();
diff --git a/projects/gameboard-ui/src/app/admin/components/game-map-editor/game-map-editor.component.ts b/projects/gameboard-ui/src/app/admin/components/game-map-editor/game-map-editor.component.ts
index c3a262d5..34e0e46e 100644
--- a/projects/gameboard-ui/src/app/admin/components/game-map-editor/game-map-editor.component.ts
+++ b/projects/gameboard-ui/src/app/admin/components/game-map-editor/game-map-editor.component.ts
@@ -45,6 +45,8 @@ export class GameMapEditorComponent implements OnInit {
throw new Error("GameId is required");
this.specs = await firstValueFrom(this.gameService.retrieveSpecs(this.gameId));
+ const game = await firstValueFrom(this.gameService.retrieve(this.gameId));
+ this.mapImageUrl = game.mapUrl;
}
protected async mousemove(e: MouseEvent) {
diff --git a/projects/gameboard-ui/src/app/api/team.service.ts b/projects/gameboard-ui/src/app/api/team.service.ts
index 4e88e36e..9080e019 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, firstValueFrom, from, map, tap } from "rxjs";
import { SessionEndRequest, SessionExtendRequest, Team, TeamSummary } from "./player-models";
-import { AddToTeamResponse, AdminEnrollTeamRequest, AdminEnrollTeamResponse, AdminExtendTeamSessionResponse, RemoveFromTeamResponse, TeamSessionResetType, TeamSessionUpdate } from "./teams.models";
+import { AddToTeamResponse, AdminEnrollTeamRequest, AdminEnrollTeamResponse, AdminExtendTeamSessionResponse, AdvanceTeamsRequest, RemoveFromTeamResponse, TeamSessionResetType, TeamSessionUpdate } from "./teams.models";
import { ApiUrlService } from "@/services/api-url.service";
import { unique } from "../../tools/tools";
import { GamePlayState } from "./game-models";
@@ -61,6 +61,10 @@ export class TeamService {
return result;
}
+ public async advance(request: AdvanceTeamsRequest) {
+ return firstValueFrom(this.http.post(this.apiUrl.build("team/advance"), request));
+ }
+
unenroll(request: { teamId: string, resetType?: TeamSessionResetType }) {
return this.http.post(this.apiUrl.build(`team/${request.teamId}/session`), {
resetType: request.resetType || "unenrollAndArchiveChallenges"
diff --git a/projects/gameboard-ui/src/app/api/teams.models.ts b/projects/gameboard-ui/src/app/api/teams.models.ts
index 8e767a37..7dc0ac34 100644
--- a/projects/gameboard-ui/src/app/api/teams.models.ts
+++ b/projects/gameboard-ui/src/app/api/teams.models.ts
@@ -26,6 +26,12 @@ export interface AddToTeamResponse {
user: SimpleEntity;
}
+export interface AdvanceTeamsRequest {
+ gameId: string;
+ includeScores: boolean;
+ teamIds: string[];
+}
+
export interface RemoveFromTeamResponse {
game: SimpleEntity;
player: SimpleEntity;
diff --git a/projects/gameboard-ui/src/app/core/components/dropzone/dropzone.component.ts b/projects/gameboard-ui/src/app/core/components/dropzone/dropzone.component.ts
index e29609d5..c77be698 100644
--- a/projects/gameboard-ui/src/app/core/components/dropzone/dropzone.component.ts
+++ b/projects/gameboard-ui/src/app/core/components/dropzone/dropzone.component.ts
@@ -22,6 +22,7 @@ export class DropzoneComponent {
// Handle component events
filesSelected(ev: any): void {
this.dropped.emit(Array.from(ev.target.files));
+ ev.target.value = null;
}
// Handle drag/drop events
diff --git a/projects/gameboard-ui/src/app/core/pipes/game-map-image-url.pipe.ts b/projects/gameboard-ui/src/app/core/pipes/game-map-image-url.pipe.ts
index bfa53cb6..c61d4b65 100644
--- a/projects/gameboard-ui/src/app/core/pipes/game-map-image-url.pipe.ts
+++ b/projects/gameboard-ui/src/app/core/pipes/game-map-image-url.pipe.ts
@@ -1,13 +1,17 @@
import { ConfigService } from '@/utility/config.service';
-import { Pipe, PipeTransform } from '@angular/core';
+import { DOCUMENT } from '@angular/common';
+import { inject, Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'gameMapImageUrl' })
export class GameMapImageUrlPipe implements PipeTransform {
+ private document = inject(DOCUMENT);
constructor(private config: ConfigService) { }
transform(value?: string): string {
- return value ?
- `${this.config.imagehost}/${value}`
- : `${this.config.basehref}assets/map.png`;
+ if (!value) {
+ return `${this.config.basehref}assets/map.png`;
+ }
+ const isAbsolute = new URL(this.document.baseURI).origin !== new URL(value, this.document.baseURI).origin;
+ return isAbsolute ? value : `${this.config.imagehost}/${value}`;
}
}
diff --git a/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.html b/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.html
index 0bf96cbd..42d2ba92 100644
--- a/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.html
+++ b/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.html
@@ -56,13 +56,13 @@
-
+
Enroll
-
+
Admin Enroll
@@ -148,7 +148,7 @@
Team Up
placeholder="Enter your invitation code here to join a team">
-
+
Join
@@ -165,7 +165,7 @@
Team Up
diff --git a/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.ts b/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.ts
index 98aafdac..3662c7fb 100644
--- a/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.ts
+++ b/projects/gameboard-ui/src/app/game/player-enroll/player-enroll.component.ts
@@ -2,8 +2,7 @@
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
-import { faCopy, faEdit, faPaste, faTrash, faUser } from '@fortawesome/free-solid-svg-icons';
-import { firstValueFrom, Observable, of, Subject, Subscription, timer } from 'rxjs';
+import { BehaviorSubject, firstValueFrom, Observable, of, Subscription, timer } from 'rxjs';
import { map, tap, delay, first } from 'rxjs/operators';
import { GameContext } from '../../api/models';
import { HubPlayer, NewPlayer, Player, PlayerEnlistment, PlayerRole, TimeWindow } from '../../api/player-models';
@@ -45,18 +44,13 @@ export class PlayerEnrollComponent implements OnInit, OnDestroy {
protected hasSelectedSponsor = false;
protected managerRole = PlayerRole.manager;
protected isEnrolled$: Observable
;
- protected isManager$ = new Subject();
+ protected isManager$ = new BehaviorSubject(null);
protected isRegistrationOpen = false;
protected hasTeammates$: Observable = of(false);
protected unenrollTooltip?: string;
private hubSub?: Subscription;
fa = fa;
- faUser = faUser;
- faEdit = faEdit;
- faCopy = faCopy;
- faPaste = faPaste;
- faTrash = faTrash;
constructor(
private api: PlayerService,
@@ -75,6 +69,10 @@ export class PlayerEnrollComponent implements OnInit, OnDestroy {
ctx.game.registration = new TimeWindow(ctx.game?.registrationOpen, ctx.game?.registrationClose);
this.isRegistrationOpen = ctx.game.registrationType !== GameRegistrationType.none;
this.enrollTooltip = this.isRegistrationOpen ? "" : "Registration is currently closed for this game.";
+
+ if (this.isManager$.value === null) {
+ this.isManager$.next(ctx.player?.isManager || true);
+ }
}),
tap((gc) => {
if (gc.player.nameStatus && gc.player.nameStatus != 'pending') {
@@ -112,13 +110,13 @@ export class PlayerEnrollComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.manageUnenrollAvailability(this.hubService.actors$.getValue());
- this.isManager$.next(this.ctx.player.isManager);
this.hubSub = this.hubService.actors$.subscribe(a => {
this.manageUnenrollAvailability(a);
const manager = a.find(a => a.isManager);
- if (manager)
- this.isManager$.next(manager?.id == this.ctx.player.id);
+ if (this.ctx.player?.id && manager) {
+ this.isManager$.next(manager.id == this.ctx.player.id);
+ }
});
}
diff --git a/projects/gameboard-ui/src/app/services/font-awesome.service.ts b/projects/gameboard-ui/src/app/services/font-awesome.service.ts
index 51fc3c93..36e34960 100644
--- a/projects/gameboard-ui/src/app/services/font-awesome.service.ts
+++ b/projects/gameboard-ui/src/app/services/font-awesome.service.ts
@@ -18,6 +18,7 @@ import {
faChessBoard,
faChevronDown,
faChevronUp,
+ faCircleArrowUp,
faCircleUser,
faClipboard,
faClock,
@@ -47,6 +48,7 @@ import {
faLongArrowAltDown,
faMapMarker,
faPaperclip,
+ faPaste,
faPeopleGroup,
faPerson,
faPlus,
@@ -92,6 +94,7 @@ export const fa = {
chessBoard: faChessBoard,
chevronDown: faChevronDown,
chevronUp: faChevronUp,
+ circleArrowUp: faCircleArrowUp,
circleUser: faCircleUser,
clipboard: faClipboard,
clock: faClock,
@@ -121,6 +124,7 @@ export const fa = {
mapMarker: faMapMarker,
openId: faOpenid,
paperclip: faPaperclip,
+ paste: faPaste,
peopleGroup: faPeopleGroup,
person: faPerson,
plus: faPlus,