Skip to content

Commit

Permalink
v3.27.1 (#216)
Browse files Browse the repository at this point in the history
* Fixed an issue that caused active system notifications to make certificates print weirdly.

* Make default sys notification modal bigger. Relabel certificate dropdown default option.

* Fix cert display on game page

* Fix Game Center -> Challenges -> Map not rendering the map correctly.

* Restored orphaned team advance feature
  • Loading branch information
sei-bstein authored Jan 3, 2025
1 parent 308c300 commit c83c3bc
Show file tree
Hide file tree
Showing 15 changed files with 149 additions and 29 deletions.
6 changes: 2 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
# multi-stage target: dev

FROM node:18-alpine as dev
ARG commit
FROM node:18-alpine AS dev
WORKDIR /app
COPY package.json package-lock.json tools/ ./
RUN npm install && \
sh fixup-wmks.sh
COPY . .
RUN if [ -e "wmks.tar" ]; then tar xf wmks.tar -C node_modules/vmware-wmks; fi
RUN $(npm root)/.bin/ng build gameboard-ui --output-path /app/dist && \
$(npm root)/.bin/ng build gameboard-mks --base-href=/mks/ --output-path /app/dist/mks
$(npm root)/.bin/ng build gameboard-mks --base-href=/mks/ --output-path /app/dist/mks
CMD ["npm", "start"]

# multi-stage target: prod
FROM nginx:alpine
WORKDIR /var/www
ENV COMMIT=$commit
COPY --from=dev /app/dist .
COPY --from=dev /app/dist/assets/oidc-silent.html .
COPY --from=dev /app/LICENSE.md ./LICENSE.md
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<app-modal-content *ngIf="game" title="Advance Teams" [subtitle]="game.name" (confirm)="handleConfirm()"
[confirmDisabled]="!selectedGame" confirmButtonText="Advance">
<p>
You've selected <strong>{{ teams.length }}</strong> teams for advancement. Choose a <strong>game</strong>
to which they'll advance, and specify whether you want their starting score to be migrated from the current
game.
</p>

<div class="my-2">
<label class="form-label">Select a game</label>
<select class="form-control" [(ngModel)]="selectedGame">
<option [ngValue]="undefined">[Choose a game]</option>
<option *ngFor="let game of targetGames" [ngValue]="game">{{ game.name }}</option>
</select>
</div>

<div class="my-2">
<label class="form-label">
<input type="checkbox" [(ngModel)]="includeScores">
Include scores during advancement?
</label>
</div>
</app-modal-content>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Component, inject, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CoreModule } from "../../../core/core.module";
import { SimpleEntity } from '@/api/models';
import { Game } from '@/api/game-models';
import { GameService } from '@/api/game.service';
import { firstValueFrom } from 'rxjs';
import { TeamService } from '@/api/team.service';

@Component({
selector: 'app-advance-teams-modal',
standalone: true,
imports: [CommonModule, CoreModule],
templateUrl: './advance-teams-modal.component.html',
styleUrls: ['./advance-teams-modal.component.scss']
})
export class AdvanceTeamsModalComponent implements OnInit {
private gameService = inject(GameService);
private teamService = inject(TeamService);

game?: SimpleEntity;
onConfirm?: (targetGame: SimpleEntity) => Promise<void>
teams: SimpleEntity[] = [];

protected includeScores = false;
protected selectedGame?: Game;
protected targetGames: Game[] = [];

async ngOnInit(): Promise<void> {
this.targetGames = await firstValueFrom(this.gameService.list({ filter: ['advanceable'] }));
}

protected async handleConfirm() {
if (!this.selectedGame) {
return;
}

await this.teamService.advance({
gameId: this.selectedGame.id,
includeScores: this.includeScores,
teamIds: this.teams.map(t => t.id)
});

if (this.onConfirm) {
await this.onConfirm(this.selectedGame);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<app-modal-content *ngIf="team" [title]="team.name" [subtitle]="game.name" [hideCancel]="true">
<div *ngIf="team.advancement" class="advancement-container px-3">
<h4 class="mt-4">Advancement</h4>
<div class="d-flex justify-content-between">
<div class="advancement-info">
<h6>Advanced from</h6>
<div class="d-flex">
<div class="advancement-info flex-grow-1">
<h6 class="text-muted">Advanced from</h6>
<div>{{team.advancement.fromGame.name}}</div>
</div>

<div class="advancement-info">
<h6>Score</h6>
<div class="advancement-info flex-grow-1">
<h6 class="text-muted">Score</h6>
<div>{{team.advancement.score || 0}}</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
{{ game.isTeamGame ? "Team" : "Player" }}{{ selectedTeamIds.length === 1 ? "" : "s" }}
</button>

<button type="button" class="btn btn-success"
tooltip="Advance selected {{ game.isTeamGame ? 'teams' : 'players' }}" [disabled]="!selectedTeamIds.length"
(click)="handleConfirmAdvanceTeams()">
<fa-icon [icon]="fa.circleArrowUp"></fa-icon>
</button>

<button type="button" class="btn btn-success"
tooltip="Deploy game resources for {{ selectedTeamIds.length ? 'selected' : 'all' }}"
[disabled]="!results?.teams?.items?.length" (click)="handleConfirmDeployGameResources()">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
6 changes: 5 additions & 1 deletion projects/gameboard-ui/src/app/api/team.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 6 additions & 0 deletions projects/gameboard-ui/src/app/api/teams.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,13 @@
<app-confirm-button btnClass="btn btn-success btn-lg mr-4"
[disabled]="!isRegistrationOpen && !canStandardEnroll && !canAdminEnroll" [tooltip]="enrollTooltip"
(confirm)="handleEnroll(ctx.user.id, ctx.game.id)">
<fa-icon [icon]="faEdit"></fa-icon>
<fa-icon [icon]="fa.edit"></fa-icon>
<span>Enroll</span>
</app-confirm-button>

<app-confirm-button *ngIf="canAdminEnroll" btnClass="btn btn-warning btn-lg"
(confirm)="handleEnroll(ctx.user.id, ctx.game.id)">
<fa-icon [icon]="faEdit"></fa-icon>
<fa-icon [icon]="fa.edit"></fa-icon>
<span>Admin Enroll</span>
</app-confirm-button>
</div>
Expand Down Expand Up @@ -148,7 +148,7 @@ <h3>Team Up</h3>
placeholder="Enter your invitation code here to join a team">
<div class="input-group-append">
<button class="btn btn-success" [disabled]="!token" (click)="redeem(ctx.player)">
<fa-icon [icon]="faPaste"></fa-icon>
<fa-icon [icon]="fa.paste"></fa-icon>
<span>Join</span>
</button>
</div>
Expand All @@ -165,7 +165,7 @@ <h3>Team Up</h3>
<div class="tooltip-holder" [tooltip]="unenrollTooltip" container="body" placement="top">
<app-confirm-button btnClass="btn btn-danger btn" (confirm)="handleUnenroll(ctx.player)"
[disabled]="!!unenrollTooltip">
<fa-icon [icon]="faTrash"></fa-icon>
<fa-icon [icon]="fa.trash"></fa-icon>
<span>Unenroll</span>
</app-confirm-button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -45,18 +44,13 @@ export class PlayerEnrollComponent implements OnInit, OnDestroy {
protected hasSelectedSponsor = false;
protected managerRole = PlayerRole.manager;
protected isEnrolled$: Observable<boolean>;
protected isManager$ = new Subject<boolean>();
protected isManager$ = new BehaviorSubject<boolean | null>(null);
protected isRegistrationOpen = false;
protected hasTeammates$: Observable<boolean> = of(false);
protected unenrollTooltip?: string;
private hubSub?: Subscription;

fa = fa;
faUser = faUser;
faEdit = faEdit;
faCopy = faCopy;
faPaste = faPaste;
faTrash = faTrash;

constructor(
private api: PlayerService,
Expand All @@ -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') {
Expand Down Expand Up @@ -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);
}
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
faChessBoard,
faChevronDown,
faChevronUp,
faCircleArrowUp,
faCircleUser,
faClipboard,
faClock,
Expand Down Expand Up @@ -47,6 +48,7 @@ import {
faLongArrowAltDown,
faMapMarker,
faPaperclip,
faPaste,
faPeopleGroup,
faPerson,
faPlus,
Expand Down Expand Up @@ -92,6 +94,7 @@ export const fa = {
chessBoard: faChessBoard,
chevronDown: faChevronDown,
chevronUp: faChevronUp,
circleArrowUp: faCircleArrowUp,
circleUser: faCircleUser,
clipboard: faClipboard,
clock: faClock,
Expand Down Expand Up @@ -121,6 +124,7 @@ export const fa = {
mapMarker: faMapMarker,
openId: faOpenid,
paperclip: faPaperclip,
paste: faPaste,
peopleGroup: faPeopleGroup,
person: faPerson,
plus: faPlus,
Expand Down

0 comments on commit c83c3bc

Please sign in to comment.