Skip to content

Commit

Permalink
v3.17.0-beta0 (#171)
Browse files Browse the repository at this point in the history
* Add tooltip to support code copy button

* Dependabot alert

* Merge from next

* Correct support tools links

* More support url bugs and make player registrar view searched player

* Remove logging

* Initial work on feedback yaml changes.

* More feedback YAML work

* Clean up use of support code in Support views.

* Finish yaml feedback helper

* Add submissions to challenge browser

* Finish including submissions in admin -> challenges.

* Comments

* Add summary card for support report.

* Resolve GBAPI#195

* Add new date filters and clarify existing time filters for support report.

* Enable session extension by duration.

* Finish batch extension. Make team event horizon not accidentally closeable.

* Correct event horizon modal dismiss change.

* Light restyling of event horizon component.

* Make event horizon 'start' events clickable.

* Improve bonus yaml editing experience

* Fixed width style for bonus config

* Fix potential spamming external game admin endpoint

* Fix RXJS endpoint-spamming bug.

* Add success notification/error handling to the sync spects button in the game editor.

* Minor cleanup

* Loading/error handling for extend session.

* Fix tooltip for new scoreboard. Also misc. cleanup

* Fix bugs with batch extend. Reenable game engine sync from challenge browser.

* Improve usability of batch session extend.

* Funnel extensions by iso date into the same functionality as duration-based extensions.

* Style challenge browser

* Initial work on 383

* Fix error display bug with extend.

* Iteration on 383

* Progress on 383.

* Minor cleanup

* Progress on 383

* Finish 383
  • Loading branch information
sei-bstein authored Mar 4, 2024
1 parent 005930b commit e0f5f88
Show file tree
Hide file tree
Showing 24 changed files with 532 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ import { Player, Team, TimeWindow } from '../../api/player-models';
import { PlayerService } from '../../api/player.service';
import { GameSessionService } from '../../services/game-session.service';
import { TeamAdminContextMenuSessionResetRequest } from '../components/team-admin-context-menu/team-admin-context-menu.component';
import { TeamService } from '@/api/team.service';
import { ToastService } from '@/utility/services/toast.service';
import { DateTime } from 'luxon';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { FriendlyDatesService } from '@/services/friendly-dates.service';
import { ExtendTeamsModalComponent } from '../components/extend-teams-modal/extend-teams-modal.component';
import { GameService } from '@/api/game.service';

Expand Down Expand Up @@ -43,12 +40,9 @@ export class PlayerSessionComponent implements OnInit {

constructor(
private api: PlayerService,
private friendlyDatesAndTimes: FriendlyDatesService,
private gameService: GameService,
private sessionService: GameSessionService,
private modalService: ModalConfirmService,
private teamService: TeamService,
private toastService: ToastService,
) { }

ngOnInit(): void {
Expand Down
5 changes: 4 additions & 1 deletion projects/gameboard-ui/src/app/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { CoreModule } from '../core/core.module';
import { SponsorsModule } from '@/sponsors/sponsors.module';
import { UtilityModule } from '../utility/utility.module';

import { ActiveChallengesModalComponent } from './components/active-challenges-modal/active-challenges-modal.component';
import { AdminOverviewComponent } from './components/admin-overview/admin-overview.component';
import { AdminPageComponent } from './admin-page/admin-page.component';
import { AnnounceComponent } from './announce/announce.component';
Expand Down Expand Up @@ -58,10 +59,11 @@ import { EventHorizonModule } from '@/event-horizon/event-horizon.module';
import { SupportSettingsComponent } from './components/support-settings/support-settings.component';
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';

@NgModule({
declarations: [
ActiveChallengesModalComponent,
AdminPageComponent,
AnnounceComponent,
ChallengeBrowserComponent,
Expand Down Expand Up @@ -106,6 +108,7 @@ import { ExtendTeamsModalComponent } from './components/extend-teams-modal/exten
SupportSettingsComponent,
FeedbackEditorComponent,
ExtendTeamsModalComponent,
ActiveTeamsModalComponent,
],
imports: [
CommonModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ <h2>Observe Challenges</h2>
<span id="search-label" class="input-group-text">Search</span>
</div>
<input id="search-input" type="search" class="form-control" #search (input)="typing$.next(search.value)"
aria-label="search term" aria-describedby="search-label" placeholder="term">
aria-label="search term" aria-describedby="search-label" placeholder="term" [ngModel]="searchText">
</div>
<div class="input-group mt-2 mb-0 ml-auto col-5">
<div id="feedbackType-input" #selectType class="btn-group mb-2 ml-auto" btnRadioGroup name="sortType" tabindex="0"
Expand Down Expand Up @@ -72,13 +72,13 @@ <h4 class="col-2 text-right">Consoles</h4>
<div class="col-2 align-self-center text-right">{{row.value.challengeScore | number}}</div>
<div class="col-2 align-self-center text-right">
<button *ngIf="row.value.consoles?.length" class="btn btn-sm"
[class]="row.value.expanded ? 'btn-success' : 'btn-outline-success' "
[class]="row.value.expanded || table.size === 1 ? 'btn-success' : 'btn-outline-success' "
(click)="toggleShowConsoles(row.value)">
<fa-icon [icon]="faGrid"></fa-icon>
</button>
</div>
</div> <!-- end main challenge row -->
<ng-container *ngIf="row.value.expanded">
<ng-container *ngIf="row.value.expanded || table.size === 1">
<div class="row mx-0 mt-1">
<ng-container *ngFor="let vm of row.value.consoles;">
<div *ngIf="!vm.minimized" class="p-2 vm-console" [class]="vm.fullWidth ? 'col-12' : 'col-4'">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Released under a MIT (SEI)-style license. See LICENSE.md in the project root for license information.

import { KeyValue } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Component, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { faArrowLeft, faSyncAlt, faTv, faExternalLinkAlt, faExpandAlt, faUser, faThLarge, faMinusSquare, faPlusSquare, faCompressAlt, faSortAlphaDown, faSortAmountDownAlt, faAngleDoubleUp, faWindowRestore } from '@fortawesome/free-solid-svg-icons';
import { combineLatest, timer, BehaviorSubject, Observable, Subscription } from 'rxjs';
Expand All @@ -12,13 +12,15 @@ import { BoardService } from '../../api/board.service';
import { Game } from '../../api/game-models';
import { GameService } from '../../api/game.service';
import { ConfigService } from '../../utility/config.service';
import { MatchesTermPipe } from '@/utility/pipes/matches-term.pipe';

@Component({
selector: 'app-challenge-observer',
templateUrl: './challenge-observer.component.html',
styleUrls: ['./challenge-observer.component.scss'],
providers: [MatchesTermPipe]
})
export class ChallengeObserverComponent implements OnInit, OnDestroy {
export class ChallengeObserverComponent implements OnDestroy {
refresh$ = new BehaviorSubject<boolean>(true);
game?: Game;
table: Map<string, ObserveChallenge> = new Map<string, ObserveChallenge>(); // table of player challenges to display
Expand Down Expand Up @@ -47,16 +49,20 @@ export class ChallengeObserverComponent implements OnInit, OnDestroy {
faAngleDoubleUp = faAngleDoubleUp;
faWindowRestore = faWindowRestore;

protected searchText = "";

constructor(
route: ActivatedRoute,
private api: BoardService,
private gameApi: GameService,
private conf: ConfigService
private conf: ConfigService,
private matchesTerm: MatchesTermPipe
) {
this.mksHost = conf.mkshost;
this.gameData = route.params.pipe(
switchMap(a => this.gameApi.retrieve(a.id))
).subscribe(game => this.game = game);

this.tableData = combineLatest([
route.params,
this.refresh$,
Expand All @@ -67,9 +73,14 @@ export class ChallengeObserverComponent implements OnInit, OnDestroy {
tap(() => this.isLoading = true),
switchMap(() => this.api.consoles(this.gid)),
).subscribe(data => {
const queryTerm = route.snapshot.queryParams?.search?.toLowerCase();
this.searchText = queryTerm;
this.typing$.next(queryTerm);

this.updateTable(data);
this.isLoading = false;
});

this.fetchActors$ = combineLatest([
route.params,
this.refresh$,
Expand All @@ -88,6 +99,7 @@ export class ChallengeObserverComponent implements OnInit, OnDestroy {
return map;
}, new Map<string, ConsoleActor[]>()))
);

this.term$ = this.typing$.pipe(
debounceTime(500)
);
Expand All @@ -105,14 +117,12 @@ export class ChallengeObserverComponent implements OnInit, OnDestroy {
} else {
this.table.set(updatedChallenge.id, updatedChallenge);
}

if (updatedChallenge.gameRank > this.maxRank)
this.maxRank = updatedChallenge.gameRank;
}
}

ngOnInit(): void {
}

ngOnDestroy(): void {
this.tableData.unsubscribe();
this.gameData.unsubscribe();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title">Active Challenges: {{ playerMode | titlecase }}</h2>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="close()">
<span aria-hidden="true">&times;</span>
</button>
<app-error-div [errors]="errors"></app-error-div>
</div>

<div class="modal-body" *ngIf="!isWorking; else loading">
<ng-container *ngIf="specs?.length; else noChallenges">
<div *ngFor="let spec of specs" class="spec-container">
<h3>{{ spec.name }}</h3>
<h4 class="game-link">
<a target="_blank" [routerLink]="['game/' + spec.game.id]">
{{ spec.game.name }}
</a>
</h4>

<ul class="d-flex">
<li *ngFor="let challenge of spec.challenges" class="card challenge-card my-3 mr-3">
<div class="card-body">
<h5 class="overflow-ellipsis">
{{ challenge.team.name }}
</h5>
<button type="button" class="btn btn-link text-info" appCopyOnClick
tooltip="Copy this support code" placement="bottom">
{{ {id: challenge.id, tag: spec.tag } | toSupportCode }}
</button>
<div class="challenge-times mt-2">
<div class="start-time">
<span class="font-bold">Launched: </span>
<span>{{ challenge.startedAt | datetimeToDate | friendlyDateAndTime }}</span>
</div>
<div class="end-time">
<span class="font-bold">Session End: </span>
<span>{{ challenge.team.session.end | datetimeToDate | friendlyDateAndTime }}</span>
</div>
</div>
</div>
<div class="card-footer d-flex align-items-center px-3">
<div class="flex-grow-1">
<a [href]="'/support/tickets?search=' + challenge.id" target="_blank"
*ngIf="challenge.hasTickets" class="btn btn-warning"
tooltip="View open tickets for this challenge" placement="bottom">
<fa-icon [icon]="fa.ticket" size="lg"></fa-icon>
</a>
</div>
<a [href]="'/admin/support?search=' + challenge.id | relativeToAbsoluteHref" target="_blank"
class="btn btn-info mr-2" tooltip="View this challenge's state" placement="bottom">
<fa-icon [icon]="fa.barsStaggered" size="lg"></fa-icon>
</a>

<a [href]="'/admin/registrar/' + spec.game.id + '?term=' + challenge.team.id"
target="_blank" class="btn btn-info mr-2"
[tooltip]="'View this ' + (spec.game.isTeamGame | thisOrThat:'team':'player') + '\'s session'"
placement="bottom">
<fa-icon [icon]="spec.game.isTeamGame | thisOrThat:fa.peopleGroup:fa.person"
size="lg"></fa-icon>
</a>

<a [href]="'/admin/observer/challenges/' + spec.game.id + '?search=' + challenge.id"
target="_blank" class="btn btn-info mr-2" tooltip="Observe this challenge"
placement="bottom">
<fa-icon [icon]="fa.eye" size="lg"></fa-icon>
</a>

<a [href]="'/game/' + spec.game.id" target="_blank" class="btn btn-info"
tooltip="View this game" placement="bottom">
<fa-icon [icon]="fa.chessBoard" size="lg"></fa-icon>
</a>
</div>
</li>
</ul>
</div>
</ng-container>
</div>

<div class="modal-footer">
<button type="button" class="btn btn-info" (click)="close()">OK</button>
</div>
</div>

<ng-template #noChallenges>
<div class="text-center gray-text fs-11">There aren't any active challenges of this type right now.</div>
</ng-template>

<ng-template #loading>
<app-spinner></app-spinner>
</ng-template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
h3 {
margin-bottom: 0;
}

h5 {
margin-bottom: 0;
padding-bottom: 0;
text-overflow: ellipsis;
}

li {
flex-basis: 32%;
min-width: 250px;
}

// bootstrap overrides (yuck)
.card-footer {
background-color: unset !important;
}

.spec-container:not(:nth-of-type(1)) {
margin-top: 2rem;
}

.btn-link {
padding: 0;
}

.game-link {
font-size: 0.9rem;
font-weight: bold;
margin: 0;
text-transform: uppercase;
}

.card-footer {
border-top: dashed 1px gray;

button {
padding-left: 8px;
padding-right: 8px;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Component, OnInit } from '@angular/core';
import { fa } from "@/services/font-awesome.service";
import { firstValueFrom } from 'rxjs';
import { PlayerMode } from '@/api/player-models';
import { AppActiveChallengeSpec } from '@/api/admin.models';
import { AdminService } from '@/api/admin.service';
import { ModalConfirmService } from '@/services/modal-confirm.service';
import { RouterService } from '@/services/router.service';

@Component({
selector: 'app-active-challenges-modal',
templateUrl: './active-challenges-modal.component.html',
styleUrls: ['./active-challenges-modal.component.scss']
})
export class ActiveChallengesModalComponent implements OnInit {
playerMode?: PlayerMode;

protected errors: any[] = [];
protected fa = fa;
protected isPracticeMode = false;
protected isWorking = false;
protected specs: AppActiveChallengeSpec[] = [];

constructor(
private adminService: AdminService,
private modalService: ModalConfirmService,
private routerService: RouterService) { }

async ngOnInit(): Promise<void> {
if (!this.playerMode)
throw new Error("Player mode not passed to active challenges modal.");

await this.load(this.playerMode);
}

protected close() {
this.modalService.hide();
}

private async load(playerMode: PlayerMode): Promise<void> {
this.errors = [];
this.isPracticeMode = playerMode === PlayerMode.practice;

try {
this.isWorking = true;
const response = await firstValueFrom(this.adminService.getActiveChallenges(playerMode));
this.specs = response.specs;
this.isWorking = false;
}
catch (err: any) {
this.errors.push(err);
}
}
}
Loading

0 comments on commit e0f5f88

Please sign in to comment.