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.9.3 #123

Merged
merged 1 commit into from
May 18, 2023
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
47 changes: 31 additions & 16 deletions projects/gameboard-ui/src/app/api/toc.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, of, BehaviorSubject, iif, Subject } from 'rxjs';
import { switchMap, map, tap, delay } from 'rxjs/operators';
import { Observable, of, BehaviorSubject } from 'rxjs';
import { switchMap, map, tap, delay, catchError } from 'rxjs/operators';
import { ConfigService } from '../utility/config.service';
import { LogService } from '../services/log.service';

@Injectable({
providedIn: 'root'
})
@Injectable({ providedIn: 'root' })
export class TocService {
toc$: Observable<TocFile[]>;
tocfile$: (id: string) => Observable<string>;
loaded$ = new BehaviorSubject<boolean>(false);
private cache: TocFile[] = [];

constructor(
config: ConfigService,
private http: HttpClient,
private config: ConfigService
private log: LogService
) {
const tag = `?t=${new Date().valueOf()}`;
const tocUrl = `${config.tochost}/${config.settings.tocfile + ''}${tag}`;
Expand All @@ -30,32 +30,47 @@ export class TocService {
url += `/${config.settings.tocfile?.substring(0, i)}`;
}

this.toc$ = iif(
() => !!config.settings.tocfile,
http.get<string[]>(tocUrl).pipe(
if (!!config.settings.tocfile) {
this.toc$ = http.get<string[]>(tocUrl).pipe(
catchError(err => {
// don't report error here - ops will know what the 404 means
this.cache = [];
return [];
}),
switchMap((list) => this.mapTocFromList(list)),
tap(list => this.cache = list),
),
of([])
).pipe(
tap(() => this.loaded$.next(true))
);
);
}
else {
this.log.logInfo("No toc file configured. Skipped loading.");
this.toc$ = of([]);
}

this.toc$ = this.toc$.pipe(tap(toc => this.loaded$.next(true)));

this.tocfile$ = (id: string) => {
const tocfile = this.cache.find(f =>
f.filename === id ||
f.link === id
);

if (!tocfile) {
return of('not found');
}

if (!!tocfile.text) {
return of(tocfile.text);
}

const tocFileUrl = `${url}/${tocfile?.filename}${tag}`;
return this.http.get(
`${url}/${tocfile?.filename}${tag}`,
{ responseType: 'text'}
tocFileUrl,
{ responseType: 'text' }
).pipe(
catchError(err => {
// don't report - ops will know 404
return '';
}),
tap(t => tocfile.text = t)
);
};
Expand Down
7 changes: 3 additions & 4 deletions projects/gameboard-ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
<nav [class]="custom_bg" [class.nonsticky]="!(layoutService.stickyMenu$ | async)">
<div class="text-right">
<a class="btn btn-link text-success mx-1" routerLinkActive="text-info" [routerLink]="['/home']">Home</a>
<ng-container *ngFor="let t of toc$ | async">
<a class="btn btn-link text-success mx-1" routerLinkActive="text-info"
<ng-container *ngIf="(toc$ | async)?.length">
<a *ngFor="let t of toc$ | async" class="btn btn-link text-success mx-1" routerLinkActive="text-info"
[routerLink]="['doc', t.link]">{{t.display}}</a>
</ng-container>
<a *ngIf="isPracticeModeEnabled" class="btn btn-link text-success mx-1" routerLinkActive="text-info"
[routerLink]="['prac']">Practice</a>
<a class="btn btn-link text-success mx-1" routerLinkActive="text-info" [routerLink]="['profile']">Profile</a>
<ng-container *ngIf="user$ | async as user">
<a class="btn btn-link text-success mx-1" routerLinkActive="text-info"
[routerLink]="['support']">
<a class="btn btn-link text-success mx-1" routerLinkActive="text-info" [routerLink]="['support']">
<span>Support</span>
<app-support-pill></app-support-pill>
</a>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export class GamespaceQuizComponent implements OnInit {
}

ngOnInit(): void {
console.log("spec", this.spec);
}

submit(): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<ng-container *ngIf="ctx$ | async as ctx">
<div *ngIf="ctx.player" class="team-leader d-flex align-items-center align-content-center">
<app-player-avatar-list *ngIf="ctx.hasTeammates" [avatarUris]="avatarUris" [captainSession]="ctx.player.session"
<app-player-avatar-list *ngIf="ctx.hasTeammates" [avatarUris]="ctx.avatarUris" [captainSession]="ctx.player.session"
class="d-inline-block mr-4 my-2"></app-player-avatar-list>
<app-player-avatar *ngIf="!ctx.hasTeammates" [avatarUri]="ctx.player.sponsorLogo" [enableSessionStatus]="true"
[session]="ctx.player.session" class="d-inline-block mr-4 my-2"></app-player-avatar>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
// Copyright 2021 Carnegie Mellon University. All Rights Reserved.
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core';
import { BehaviorSubject, combineLatest, Observable, Subscription } from 'rxjs';
import { first, map, tap } from 'rxjs/operators';
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { combineLatest, Observable, of } from 'rxjs';
import { first, map, startWith, tap } from 'rxjs/operators';
import { faChevronCircleUp } from '@fortawesome/free-solid-svg-icons';
import { HubPlayer, Player } from '../../api/player-models';
import { PlayerService } from '../../api/player.service';
import { NotificationService } from '../../services/notification.service';
import { GameHubService } from '../../services/signalR/game-hub.service';
import { SyncStartState } from '../game.models';
import { SyncStartService } from '../../services/sync-start.service';
import { SimpleEntity } from '../../api/models';
import { HubConnectionState } from '@microsoft/signalr';
import { LogService } from '../../services/log.service';

interface PlayerPresenceContext {
avatarUris: string[];
hasTeammates: boolean;
manager: HubPlayer | undefined;
allPlayers: HubPlayer[];
player: HubPlayer;
playerIsManager: boolean;
readyPlayers: SimpleEntity[];
notReadyPlayers: SimpleEntity[];
teamAvatar: string[],
teamName: string;
}
Expand All @@ -31,62 +31,67 @@ interface PlayerPresenceContext {
templateUrl: './player-presence.component.html',
})
export class PlayerPresenceComponent implements OnInit {
@Input() player$?: Observable<Player | undefined>;
@Input() player$: Observable<Player | undefined> = of(undefined);
@Input() isSyncStartGame: boolean = false;
@Output() onManagerPromoted = new EventEmitter<string>();

private syncStartState$ = new BehaviorSubject<SyncStartState | null>(null);
private syncStartStateSubscription?: Subscription;

protected promoteIcon = faChevronCircleUp;
protected ctx$?: Observable<PlayerPresenceContext | null>;
protected avatarUris: string[] = [];

constructor(
private gameHub: GameHubService,
private hub: NotificationService,
private log: LogService,
private playerApi: PlayerService,
private syncStartService: SyncStartService,
) { }

ngOnInit(): void {
this.ctx$ = combineLatest([
this.hub.state$,
this.hub.actors$,
this.player$,
this.gameHub.syncStartChanged$
this.gameHub.syncStartChanged$.pipe(startWith(null))
]).pipe(
map(combo => combo as unknown as { 0: HubPlayer[], 1: HubPlayer, 2: SyncStartState }),
map(combo => ({ actors: combo[0], player: combo[1], syncStartState: combo[2] })),
// map(combo => combo as unknown as { 0: HubPlayer[], 1: HubPlayer, 2: SyncStartState }),
map(combo => ({ hubState: combo[0], actors: combo[1], player: combo[2], syncStartState: combo[3] })),
map(context => {
if (!context.player) {
if (!context.hubState || context.hubState.connectionState == HubConnectionState.Disconnected) {
this.log.logWarning("Can't render player presence component: SignalR hub is disconnected.");
return null;
}

const actorInfo = this.findPlayerAndTeammates(context.player, context.actors);
this.avatarUris = actorInfo.allPlayers.map(p => p.sponsorLogo);

// grab team members on this screen and show their ready/not ready flag
// (read it into the player objects coming from the hub - should think about streamlining this later)
const playerReadyStates = this.syncStartService.getAllPlayers(context.syncStartState);
for (let player of actorInfo.allPlayers) {
const playerReadyState = playerReadyStates.find(p => p.id === player.id);

if (playerReadyState) {
player.isReady = playerReadyState.isReady;
}
if (!context.player) {
this.log.logWarning("Can't render player presence component: the context has no Player object.");
return null;
}

return {
const actorInfo = this.findPlayerAndTeammates(context.player, context.actors);
const ctx: PlayerPresenceContext = {
avatarUris: actorInfo.allPlayers.map(p => p.sponsorLogo),
hasTeammates: !!actorInfo.teammates.length,
manager: actorInfo.manager,
allPlayers: actorInfo.allPlayers,
player: actorInfo.player,
playerIsManager: !!actorInfo.manager && actorInfo.manager.id === actorInfo.player?.id,
readyPlayers: this.syncStartService.getAllPlayers(context.syncStartState),
notReadyPlayers: this.syncStartService.getNotReadyPlayers(context.syncStartState),
teamAvatar: this.computeTeamAvatarList(actorInfo.allPlayers),
teamName: actorInfo.manager?.approvedName || actorInfo.player?.approvedName || "",
};

if (this.isSyncStartGame && context.syncStartState) {
// grab team members on this screen and show their ready/not ready flag
// (read it into the player objects coming from the hub - should think about streamlining this later)
const playerReadyStates = this.syncStartService.getAllPlayers(context.syncStartState);
for (let player of actorInfo.allPlayers) {
const playerReadyState = playerReadyStates.find(p => p.id === player.id);

if (playerReadyState) {
player.isReady = playerReadyState.isReady;
}
}
}

return ctx;
})
);
}
Expand Down Expand Up @@ -116,6 +121,7 @@ export class PlayerPresenceComponent implements OnInit {
if (!s.id) {
throw new Error("Can't promote a manager while the hub is disconnected.");
}

if (!localPlayer) {
throw new Error("Can't resolve the current player to promote manager.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@
</ng-container>
</div>

<app-player-presence *ngIf="player$ | async" [player$]=" player$"
[isSyncStartGame]="ctx.game.requireSynchronizedStart"></app-player-presence>
<ng-container *ngIf="playerObservable$ && (playerObservable$ | async)">
<app-player-presence [player$]="playerObservable$"
[isSyncStartGame]="ctx.game.requireSynchronizedStart"></app-player-presence>
</ng-container>

<app-gameboard-performance-summary *ngIf="performanceSummaryViewModel$ | async"
[ctx$]="performanceSummaryViewModel$"></app-gameboard-performance-summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal';
import { BehaviorSubject, combineLatest, interval, Observable, of, Subject, Subscription } from 'rxjs';
import { first, map, tap } from 'rxjs/operators';
import { BehaviorSubject, firstValueFrom, Observable, Subscription } from 'rxjs';
import { first, tap } from 'rxjs/operators';
import { GameContext } from '../../api/models';
import { Player, TimeWindow } from '../../api/player-models';
import { PlayerService } from '../../api/player.service';
Expand All @@ -25,6 +25,8 @@ export class PlayerSessionComponent implements OnDestroy {

errors: any[] = [];
myCtx$!: Observable<GameContext | undefined>;
player$ = new BehaviorSubject<Player | undefined>(undefined);
playerObservable$ = this.player$.asObservable();

private ctxSub?: Subscription;

Expand All @@ -33,7 +35,6 @@ export class PlayerSessionComponent implements OnDestroy {
protected modalConfig?: ModalConfirmConfig;
protected isDoubleChecking = false;
protected performanceSummaryViewModel$ = new BehaviorSubject<GameboardPerformanceSummaryViewModel | undefined>(undefined);
protected player$ = new BehaviorSubject<Player | undefined>(undefined);

constructor(
private api: PlayerService,
Expand Down Expand Up @@ -91,13 +92,9 @@ export class PlayerSessionComponent implements OnDestroy {
this.isDoubleChecking = isDoubleChecking;
}

handleStart(player: Player): void {
this.api.start(player).pipe(
first()
).subscribe(
p => this.onSessionStart.emit(p),
err => this.errors.push(err),
);
async handleStart(player: Player): Promise<void> {
const startedPlayer = await firstValueFrom(this.api.start(player));
this.onSessionStart.emit(startedPlayer);
}

handleReset(p: Player): void {
Expand Down
4 changes: 0 additions & 4 deletions projects/gameboard-ui/src/app/guards/sync-start.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export class SyncStartGuard implements CanActivate {
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const playerId = route.paramMap.get("playerId");
const gameId = route.paramMap.get("gameId");
console.log("sync start guard", playerId, gameId);

// can't make a decision without a playerId or a gameId, sorry/not sorry
if (!playerId && !gameId) {
Expand Down Expand Up @@ -51,17 +50,14 @@ export class SyncStartGuard implements CanActivate {
}),
map(syncStartStateOrTrue => {
if (syncStartStateOrTrue === true) {
console.log("can proceed because no sync required");
return true;
}

const typedState = syncStartStateOrTrue as SyncStartState;
if (typedState.isReady) {
console.log("can proceed because sync is ready");
return true;
}

console.log("can't, have to go back to game");
return this.router.parseUrl(`/game/{gameId}`);
})
);
Expand Down
6 changes: 3 additions & 3 deletions projects/gameboard-ui/src/app/services/log.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ export class LogService {

logInfo(...params: any[]) {
if (this._logLevel == LogLevel.Info)
console.info(params);
console.info(...params);
}

logWarning(...params: any[]) {
if (this._logLevel <= LogLevel.Warning)
console.warn(params);
console.warn(...params);
}

logError(...params: any[]) {
if (this._logLevel <= LogLevel.Error) {
console.error(params);
console.error(...params);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,10 @@ export class ModalConfirmService implements OnDestroy {
}

// open<TComponent extends IModalReady<TComponent>>(componentType: TComponent, config?: Partial<TComponent>): void {
// console.log("config", config);
// this.bsModalRef = this.bsModalService.show(componentType, { initialState: { config }, class: "modal-dialog-centered" });
// }

// open<TConfig>(config?: { teamId: string}): void {
// console.log("config", config);
// this.bsModalRef = this.bsModalService.show(ManageManualChallengeBonusesComponent, { initialState: { config }, class: "modal-dialog-centered" });
// }

Expand Down
Loading