versus implementation
This commit is contained in:
parent
3bb63d1d5a
commit
afabd52e02
23 changed files with 332 additions and 11 deletions
|
|
@ -4,6 +4,7 @@ import { HomeComponent } from "./home/home.component";
|
|||
import { Observable, of } from "rxjs";
|
||||
import {ConfigurationComponent} from "./configuration/configuration.component";
|
||||
import {AdminMainComponent} from "./admin-main/admin-main.component";
|
||||
import {AdminTestingComponent} from "./admin-testing/admin-testing.component";
|
||||
|
||||
export class AdminGuard {
|
||||
|
||||
|
|
@ -20,7 +21,6 @@ export class AdminGuard {
|
|||
|
||||
}
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
|
|
@ -36,7 +36,13 @@ const routes: Routes = [
|
|||
path: 'configuration',
|
||||
component: ConfigurationComponent,
|
||||
canDeactivate: [AdminGuard],
|
||||
}]
|
||||
},
|
||||
{
|
||||
path:'testing',
|
||||
component: AdminTestingComponent,
|
||||
canDeactivate: [AdminGuard],
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
]
|
||||
|
|
|
|||
12
src/app/admin/admin-testing/admin-testing.component.html
Normal file
12
src/app/admin/admin-testing/admin-testing.component.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div class="game-testing m-2" *ngIf="!prodMode">
|
||||
<h3>Game testing menu</h3>
|
||||
<button class="btn btn-danger" (click)="simulateVersus()">Begin versus</button>
|
||||
<button class="btn btn-danger" disabled>Stop versus</button>
|
||||
<button class="btn btn-danger" disabled>Simulate endgame points</button>
|
||||
</div>
|
||||
|
||||
<div class="game-testing m-2" *ngIf="prodMode">
|
||||
<div class="alert alert-danger">
|
||||
You are in prod mode, testing disabled
|
||||
</div>
|
||||
</div>
|
||||
6
src/app/admin/admin-testing/admin-testing.component.scss
Normal file
6
src/app/admin/admin-testing/admin-testing.component.scss
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
div {
|
||||
|
||||
button {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
21
src/app/admin/admin-testing/admin-testing.component.spec.ts
Normal file
21
src/app/admin/admin-testing/admin-testing.component.spec.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AdminTestingComponent } from './admin-testing.component';
|
||||
|
||||
describe('AdminTestingComponent', () => {
|
||||
let component: AdminTestingComponent;
|
||||
let fixture: ComponentFixture<AdminTestingComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AdminTestingComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(AdminTestingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
40
src/app/admin/admin-testing/admin-testing.component.ts
Normal file
40
src/app/admin/admin-testing/admin-testing.component.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import {Component, OnDestroy, OnInit} from '@angular/core';
|
||||
import {ApiService} from "../../services/api.service";
|
||||
import {Subject} from "rxjs";
|
||||
import {takeUntil} from "rxjs/operators";
|
||||
import {EventService} from "../../services/event.service";
|
||||
import {TestingApiService} from "../../services/testing-api.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin-testing',
|
||||
templateUrl: './admin-testing.component.html',
|
||||
styleUrls: ['./admin-testing.component.scss']
|
||||
})
|
||||
export class AdminTestingComponent implements OnInit, OnDestroy {
|
||||
prodMode = false;
|
||||
destroyed$ = new Subject<void>();
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private eventService: EventService,
|
||||
private testingApiService: TestingApiService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.getFFState();
|
||||
this.eventService.featureFlagChanged.pipe(takeUntil(this.destroyed$)).subscribe((r) => this.getFFState());
|
||||
}
|
||||
|
||||
private getFFState() {
|
||||
this.apiService.getFeatureFlagState("ProdMode").pipe(takeUntil(this.destroyed$)).subscribe((res) =>
|
||||
{
|
||||
this.prodMode = res.state;
|
||||
});
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
|
||||
simulateVersus() {
|
||||
this.testingApiService.simulateVersus().pipe(takeUntil(this.destroyed$)).subscribe((r) => console.log(r));
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import { ConfigurationComponent } from './configuration/configuration.component'
|
|||
import { AdminNavComponent } from './components/admin-nav/admin-nav.component';
|
||||
import { AdminMainComponent } from './admin-main/admin-main.component';
|
||||
import { FeatureflagsComponent } from './components/featureflags/featureflags.component';
|
||||
import { AdminTestingComponent } from './admin-testing/admin-testing.component';
|
||||
|
||||
|
||||
|
||||
|
|
@ -21,6 +22,7 @@ import { FeatureflagsComponent } from './components/featureflags/featureflags.co
|
|||
AdminNavComponent,
|
||||
AdminMainComponent,
|
||||
FeatureflagsComponent,
|
||||
AdminTestingComponent,
|
||||
],
|
||||
imports: [
|
||||
CommonModule, AdminRoutingModule, SharedModule,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
<a routerLink="/admin/">Main</a>
|
||||
<a routerLink="/admin/testing">Testing</a>
|
||||
<a routerLink="/admin/configuration">Config</a>
|
||||
|
|
|
|||
|
|
@ -29,6 +29,15 @@ export class FeatureflagsComponent implements OnInit, OnDestroy {
|
|||
this.apiService.getFeatureFlagState(featureFlag).pipe(takeUntil(this.destroyed$)).subscribe((result) => {
|
||||
if(!this.features.find((x) => x.name === result.name)) {
|
||||
this.features.push(result);
|
||||
this.features.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} else {
|
||||
const index = this.features.findIndex((x) => x.name === result.name);
|
||||
this.features[index] = result;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<app-toast>
|
||||
<app-versus *ngIf="versusInProgress" [player1]="versusData.player1" [player2]="versusData.player2">
|
||||
|
||||
</app-versus>
|
||||
<app-toast>
|
||||
</app-toast>
|
||||
<audio *ngIf="audioSrc" [src]="audioSrc" autoplay (ended)="onAudioEnded()"></audio>
|
||||
<router-outlet></router-outlet>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
|
|||
import { io, Socket } from "socket.io-client";
|
||||
import { API_URL, WEBSOCK_URL } from '../app.constants';
|
||||
import { EventService } from "./services/event.service";
|
||||
import { EventStateChanged, ServerEvent } from "../types/server-event";
|
||||
import {EventStateChanged, ServerEvent, VersusBeginEvent} from "../types/server-event";
|
||||
import { ApiService } from "./services/api.service";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { filter, map, takeUntil } from "rxjs/operators";
|
||||
|
|
@ -20,6 +20,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
title = 'thanksgiving';
|
||||
connection = io(WEBSOCK_URL, { transports: ['websocket']});
|
||||
destroyed = new Subject<void>();
|
||||
versusInProgress = false;
|
||||
versusData: VersusBeginEvent;
|
||||
audioSrc: string;
|
||||
|
||||
constructor(
|
||||
|
|
@ -39,9 +41,11 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
this.eventService.emit(data);
|
||||
});
|
||||
this.apiService.getAppState('main').subscribe((result) => {
|
||||
this.router.navigate([`/${result.value}`]).then(() => {
|
||||
console.log(`navigated to ${result.value}`);
|
||||
})
|
||||
if(this.router.url.indexOf('admin') === -1) {
|
||||
this.router.navigate([`/${result.value}`]).then(() => {
|
||||
console.log(`navigated to ${result.value}`);
|
||||
})
|
||||
}
|
||||
});
|
||||
this.eventService.stateChangedEvent.pipe(
|
||||
map(e => e.data),
|
||||
|
|
@ -55,6 +59,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
console.log(text);
|
||||
this.audioSrc = text;
|
||||
})
|
||||
this.setupVersusHandler();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.destroyed.complete();
|
||||
|
|
@ -63,4 +68,14 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
onAudioEnded() {
|
||||
this.voiceService.audioEnded();
|
||||
}
|
||||
|
||||
private setupVersusHandler() {
|
||||
console.log(this.routeSnapshot.snapshot.url);
|
||||
this.eventService.versusBegin.pipe(takeUntil(this.destroyed)).subscribe(r => {
|
||||
if(this.router.url.indexOf('admin') === -1) {
|
||||
this.versusInProgress = true;
|
||||
this.versusData = r.data;
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { AvatarComponent } from './components/avatar/avatar.component';
|
|||
import { FinishComponent } from './views/finish/finish.component';
|
||||
import { InitialComponent } from './views/initial/initial.component';
|
||||
import { SkrepaComponent } from './components/skrepa/skrepa.component';
|
||||
import { VersusComponent } from './components/versus/versus.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
|
@ -50,6 +51,7 @@ import { SkrepaComponent } from './components/skrepa/skrepa.component';
|
|||
FinishComponent,
|
||||
InitialComponent,
|
||||
SkrepaComponent,
|
||||
VersusComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div class="card shadow rounded m-3 animate__animated" [ngClass]="{ 'small': small, 'banned': banned, 'animate__flipInY': small }">
|
||||
<div class="card rounded m-3 animate__animated" [ngClass]="{ 'small': small, 'shadow': shadow, 'transparent': transparent, 'banned': banned, 'animate__flipInY': small }">
|
||||
<figure class="p-1">
|
||||
<img [src]="getImageUrl()" class="participant-photo img-fluid">
|
||||
</figure>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@
|
|||
padding: 0px;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
figure {
|
||||
border-radius:100%;
|
||||
display:inline-block;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ export class ParticipantItemComponent implements OnInit, OnDestroy, OnChanges {
|
|||
imgTimestamp = (new Date()).getTime();
|
||||
addAnimatedClass = false;
|
||||
@Input() bannedRemaining: number|undefined = 0;
|
||||
@Input() transparent = false;
|
||||
@Input() shadow = true;
|
||||
|
||||
constructor(private eventService: EventService, private apiService: ApiService) {
|
||||
}
|
||||
|
|
|
|||
14
src/app/components/versus/versus.component.html
Normal file
14
src/app/components/versus/versus.component.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<div class="versus">
|
||||
|
||||
<div class="d-flex players">
|
||||
<div class="player-one">
|
||||
<app-participant-item [participant]="player1data" [small]="true" [shadow]="false" [transparent]="true">
|
||||
</app-participant-item>
|
||||
</div>
|
||||
<div class="player-two">
|
||||
<app-participant-item [participant]="player2data" [small]="true" [shadow]="false" [transparent]="true">
|
||||
|
||||
</app-participant-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
86
src/app/components/versus/versus.component.scss
Normal file
86
src/app/components/versus/versus.component.scss
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
@import '../../../styles';
|
||||
@keyframes slideDown {
|
||||
0% {
|
||||
top: -100vh;
|
||||
}
|
||||
100% {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
@keyframes slideRight {
|
||||
0% {
|
||||
left: -100vh;
|
||||
}
|
||||
100% {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideLeft {
|
||||
0% {
|
||||
right: -100vh;
|
||||
}
|
||||
100% {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.versus {
|
||||
background-color: $thg_brown;
|
||||
z-index: 20000;
|
||||
position: fixed;
|
||||
top: -100vh;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
animation: slideDown 1s ease forwards;
|
||||
}
|
||||
.versus:before {
|
||||
content: "VS";
|
||||
position: absolute;
|
||||
font-size: 20vw; /* Large size for the background */
|
||||
color: rgba(255, 255, 255, 0.2); /* Light opacity */
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: bold;
|
||||
z-index: 22000; /* Puts it behind other content */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.players {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
/* Left player area */
|
||||
.player-one {
|
||||
position: relative;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background-color: #4a90e2;
|
||||
clip-path: polygon(0 0, 100% 0, 50% 100%, 0 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
animation: slideRight 1s ease forwards;
|
||||
}
|
||||
|
||||
/* Right player area */
|
||||
.player-two {
|
||||
position: relative;
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
background-color: #d9534f;
|
||||
clip-path: polygon(50% 0, 100% 0, 100% 100%, 0 100%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: white;
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
animation: slideLeft 1s ease forwards;
|
||||
}
|
||||
21
src/app/components/versus/versus.component.spec.ts
Normal file
21
src/app/components/versus/versus.component.spec.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VersusComponent } from './versus.component';
|
||||
|
||||
describe('VersusComponent', () => {
|
||||
let component: VersusComponent;
|
||||
let fixture: ComponentFixture<VersusComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [VersusComponent]
|
||||
});
|
||||
fixture = TestBed.createComponent(VersusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
32
src/app/components/versus/versus.component.ts
Normal file
32
src/app/components/versus/versus.component.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
import {ApiService} from "../../services/api.service";
|
||||
import {Subject} from "rxjs";
|
||||
import {takeUntil} from "rxjs/operators";
|
||||
import {Participant} from "../../../types/participant";
|
||||
|
||||
@Component({
|
||||
selector: 'app-versus',
|
||||
templateUrl: './versus.component.html',
|
||||
styleUrls: ['./versus.component.scss']
|
||||
})
|
||||
export class VersusComponent implements OnInit, OnDestroy{
|
||||
@Input() player1: number;
|
||||
@Input() player2: number;
|
||||
player1data: Participant;
|
||||
player2data: Participant;
|
||||
destroyed$ = new Subject<void>();
|
||||
|
||||
constructor(private apiService: ApiService) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.loadPlayersData();
|
||||
}
|
||||
ngOnDestroy() {
|
||||
this.destroyed$.complete();
|
||||
}
|
||||
|
||||
loadPlayersData() {
|
||||
this.apiService.getParticipant(this.player1).pipe(takeUntil(this.destroyed$)).subscribe((r) => this.player1data = r);
|
||||
this.apiService.getParticipant(this.player2).pipe(takeUntil(this.destroyed$)).subscribe((r) => this.player2data = r);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import {
|
|||
EventUserAdded,
|
||||
EventWrongAnswerReceived,
|
||||
QuestionChangedEvent,
|
||||
ServerEvent, UserPropertyChanged
|
||||
ServerEvent, UserPropertyChanged, VersusBeginEvent
|
||||
} from "../../types/server-event";
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -31,7 +31,8 @@ export class EventService {
|
|||
public gameResumed = new EventEmitter<ServerEvent<void>>();
|
||||
public notificationEvent = new EventEmitter<ServerEvent<EventNotification>>();
|
||||
public userPropertyChanged = new EventEmitter<ServerEvent<UserPropertyChanged>>();
|
||||
public featureFlagChanged = new EventEmitter<ServerEvent<void>>();
|
||||
public featureFlagChanged = new EventEmitter<ServerEvent<void>>()
|
||||
public versusBegin = new EventEmitter<ServerEvent<VersusBeginEvent>>();
|
||||
constructor() { }
|
||||
|
||||
public emit(event: ServerEvent<any>) {
|
||||
|
|
@ -85,6 +86,9 @@ export class EventService {
|
|||
case "feature_flag_changed":
|
||||
this.featureFlagChanged.emit(event);
|
||||
break;
|
||||
case "begin_versus":
|
||||
this.versusBegin.emit(event as ServerEvent<VersusBeginEvent>);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
15
src/app/services/testing-api.service.ts
Normal file
15
src/app/services/testing-api.service.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {API_URL} from "../../app.constants";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TestingApiService {
|
||||
|
||||
constructor(private httpClient: HttpClient) { }
|
||||
|
||||
public simulateVersus() {
|
||||
return this.httpClient.post(`${API_URL}/game/simulate-versus`, {});
|
||||
}
|
||||
}
|
||||
16
src/app/services/testingapi.service.spec.ts
Normal file
16
src/app/services/testingapi.service.spec.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TestingApiService } from './testing-api.service';
|
||||
|
||||
describe('TestingapiService', () => {
|
||||
let service: TestingApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(TestingApiService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
export class FeatureFlagList {
|
||||
static readonly FeatureFlags: string[] = ["EnableEndgamePoints"];
|
||||
static readonly FeatureFlags: string[] = [
|
||||
"EnableEndgamePoints",
|
||||
"DontMarkQuestionsAsCompleted",
|
||||
"DisableVoice",
|
||||
"ProdMode",
|
||||
];
|
||||
}
|
||||
|
|
@ -51,6 +51,11 @@ export interface EventScoreChanged {
|
|||
newScore: number;
|
||||
}
|
||||
|
||||
export interface VersusBeginEvent {
|
||||
player1: number;
|
||||
player2: number;
|
||||
}
|
||||
|
||||
export interface EventGameQueue {
|
||||
text?: string;
|
||||
target: number;
|
||||
|
|
@ -88,5 +93,6 @@ export interface ServerEvent<T> {
|
|||
| 'notification'
|
||||
| 'user_property_changed'
|
||||
| 'feature_flag_changed'
|
||||
| 'begin_versus'
|
||||
data: T
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue