-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Fix broken practicea area url builder (base href) * Add team challenge management modal * Move context menu item down and mark it danger
- Loading branch information
1 parent
34b5b69
commit 7e26017
Showing
23 changed files
with
299 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
...rd-ui/src/app/admin/components/team-challenges-modal/team-challenges-modal.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
<app-modal-content *ngIf="ctx; else loading" title="Challenges" [subtitle]="ctx.team.name" [subSubtitle]="ctx.game.name" | ||
[hideCancel]=true> | ||
<app-error-div [errors]="errors"></app-error-div> | ||
|
||
<ng-container *ngIf="ctx.challengeSpecStatuses.length; else noChallenges"> | ||
<alert *ngIf="('Teams_CreateEditDeleteChallenges' | can) || ('Teams_DeployGameResources' | can)" type="danger"> | ||
<div class="d-flex align-items-center"> | ||
<fa-icon class="d-block mr-2" size="2xl" [icon]="fa.triangleExclamation"></fa-icon> | ||
<div class="flex-grow-1 pl-2"> | ||
<p> | ||
<strong>Be <em>very</em> careful with these tools.</strong> Starting a challenge on a player or | ||
team's behalf will affect their cumulative time, and undeploying or purging it will erase all | ||
progress they've made on it. Use with caution! | ||
</p> | ||
|
||
<p class="mt-2" *ngIf="!isUnlocked"> | ||
Enter the team's admin code <strong>({{ unlockAdminCode }})</strong> below to unlock challenge | ||
management tools. | ||
</p> | ||
</div> | ||
</div> | ||
|
||
<div class="form-group p-0 mt-4" *ngIf="!isUnlocked"> | ||
<input type="text" class="form-control" [placeholder]="unlockAdminCode" | ||
(input)="handleUnlockAdminCodeInput(unlockAdminCodeInput.value)" #unlockAdminCodeInput> | ||
</div> | ||
</alert> | ||
<table class="table gameboard-table"> | ||
<thead> | ||
<tr> | ||
<th>Challenge</th> | ||
<th>Available</th> | ||
<th>Manage</th> | ||
</tr> | ||
</thead> | ||
|
||
<tbody> | ||
<tr *ngFor="let challenge of ctx.challengeSpecStatuses"> | ||
<td> | ||
{{ challenge.spec.name }} | ||
<div *ngIf="challenge.challengeId" class="d-flex align-items-center text-muted"> | ||
<div>{{ challenge.score || 0 }} / {{ challenge.scoreMax }} points ·</div> | ||
<button class="btn btn-link p-0" [appCopyOnClick]="challenge.challengeId">{{ {id: | ||
challenge.challengeId} | toSupportCode }}</button> | ||
</div> | ||
</td> | ||
<td> | ||
<div *ngIf="challenge.availabilityRange; else noAvailability" | ||
class="challenge-availability-window"> | ||
{{ challenge.availabilityRange.start | friendlyDateAndTime }} | ||
‐ | ||
{{ challenge.availabilityRange.end | friendlyDateAndTime }} | ||
</div> | ||
</td> | ||
<td> | ||
<div *ngIf="!isWorking; else working"> | ||
<div class="gb-button-group btn-group"> | ||
<button | ||
*ngIf="challenge.state == 'notStarted' && ('Teams_CreateEditDeleteChallenges' | can)" | ||
class="btn btn-danger" tooltip="Start the challenge" | ||
(click)="handleStartClick(challenge.spec.id)" [disabled]="!isUnlocked"> | ||
<fa-icon [icon]="fa.circlePlay"></fa-icon> | ||
</button> | ||
<button *ngIf="challenge.state == 'notDeployed' && ('Teams_DeployGameResources' | can)" | ||
class="btn btn-danger" (click)="handleDeployClick(challenge.challengeId)" | ||
tooltip="Deploy challenge resources" [disabled]="!isUnlocked"> | ||
<fa-icon [icon]="fa.play"></fa-icon> | ||
</button> | ||
<button *ngIf="challenge.state == 'deployed' && ('Teams_DeployGameResources' | can)" | ||
class="btn btn-danger" (click)="handleUneployClick(challenge.challengeId)" | ||
tooltip="Undeploy challenge resources" [disabled]="!isUnlocked"> | ||
<fa-icon [icon]="fa.stop"></fa-icon> | ||
</button> | ||
<button | ||
*ngIf="challenge.state != 'notStarted' && ('Teams_CreateEditDeleteChallenges' | can)" | ||
class="btn btn-danger" (click)="handlePurgeClick(challenge.challengeId)" | ||
tooltip="Purge this challenge" [disabled]="!isUnlocked"> | ||
<fa-icon [icon]="fa.trash"></fa-icon> | ||
</button> | ||
</div> | ||
</div> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
</ng-container> | ||
</app-modal-content> | ||
|
||
<ng-template #loading> | ||
<app-spinner>Loading challenges...</app-spinner> | ||
</ng-template> | ||
|
||
<ng-template #working> | ||
<div class="d-flex align-items-center justify-content-center"> | ||
<app-spinner size="small" color="#fff"></app-spinner> | ||
</div> | ||
</ng-template> | ||
|
||
<ng-template #noAvailability> | ||
<div class="text-muted text-center">--</div> | ||
</ng-template> | ||
|
||
<ng-template #noChallenges> | ||
<em class="d-block my-2 text-muted text-center">No challenges have been configured for this game.</em> | ||
</ng-template> |
Empty file.
99 changes: 99 additions & 0 deletions
99
...oard-ui/src/app/admin/components/team-challenges-modal/team-challenges-modal.component.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Component, inject, OnInit } from '@angular/core'; | ||
import { CommonModule } from '@angular/common'; | ||
import { CoreModule } from "@/core/core.module"; | ||
import { TeamService } from '@/api/team.service'; | ||
import { GetTeamChallengeSpecsStatusesResponse } from '@/api/teams.models'; | ||
import { SpinnerComponent } from '@/standalone/core/components/spinner/spinner.component'; | ||
import { ErrorDivComponent } from "../../../standalone/core/components/error-div/error-div.component"; | ||
import { ToSupportCodePipe } from '@/standalone/core/pipes/to-support-code.pipe'; | ||
import { ChallengesService } from '@/api/challenges.service'; | ||
import { firstValueFrom, Subject } from 'rxjs'; | ||
import { fa } from '@/services/font-awesome.service'; | ||
|
||
@Component({ | ||
selector: 'app-team-challenges-modal', | ||
standalone: true, | ||
imports: [ | ||
CommonModule, | ||
CoreModule, | ||
ErrorDivComponent, | ||
SpinnerComponent, | ||
ToSupportCodePipe | ||
], | ||
templateUrl: './team-challenges-modal.component.html', | ||
styleUrls: ['./team-challenges-modal.component.scss'] | ||
}) | ||
export class TeamChallengesModalComponent implements OnInit { | ||
private challengeService = inject(ChallengesService); | ||
private teamService = inject(TeamService); | ||
teamId!: string; | ||
|
||
protected ctx?: GetTeamChallengeSpecsStatusesResponse; | ||
protected errors: any[] = []; | ||
protected fa = fa; | ||
protected isWorking = false; | ||
protected isUnlocked = false; | ||
protected unlockAdminCode = ""; | ||
protected unlockAdminCodeInput$ = new Subject<string>(); | ||
|
||
async ngOnInit(): Promise<void> { | ||
await this.load(); | ||
} | ||
|
||
async handleDeployClick(challengeId?: string): Promise<void> { | ||
if (!challengeId) { | ||
this.errors.push("No challenge instance."); | ||
} | ||
|
||
await this.doChallengeStuff(() => firstValueFrom(this.challengeService.deploy({ id: challengeId! }))); | ||
} | ||
|
||
async handleUneployClick(challengeId?: string): Promise<void> { | ||
if (!challengeId) { | ||
this.errors.push("No challenge instance."); | ||
} | ||
|
||
await this.doChallengeStuff(() => firstValueFrom(this.challengeService.undeploy({ id: challengeId! }))); | ||
} | ||
|
||
async handlePurgeClick(challengeId?: string) { | ||
if (!challengeId) { | ||
this.errors.push("No challenge instance."); | ||
} | ||
|
||
await this.doChallengeStuff(() => this.challengeService.delete(challengeId!)); | ||
} | ||
|
||
async handleStartClick(challengeSpecId: string): Promise<void> { | ||
await this.doChallengeStuff(() => this.challengeService.start({ teamId: this.teamId, challengeSpecId })); | ||
} | ||
|
||
handleUnlockAdminCodeInput(value: string) { | ||
this.isUnlocked = value.toLowerCase() === this.unlockAdminCode.toLowerCase(); | ||
} | ||
|
||
private async doChallengeStuff<T>(challengeTask: () => Promise<T>) { | ||
try { | ||
this.isWorking = true; | ||
await challengeTask(); | ||
} | ||
catch (err) { | ||
this.errors.push(err); | ||
} | ||
finally { | ||
this.isWorking = false; | ||
} | ||
|
||
await this.load(); | ||
} | ||
|
||
private async load(): Promise<void> { | ||
this.errors = []; | ||
if (!this.teamId) { | ||
throw new Error("TeamId is required."); | ||
} | ||
|
||
this.ctx = await this.teamService.getChallengeSpecStatuses(this.teamId); | ||
this.unlockAdminCode = this.teamId.substring(0, 6); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.