Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v3.27.1 #216

Merged
merged 6 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading