From afabd52e02f01b89b3c4bc1c513610037b13e835 Mon Sep 17 00:00:00 2001 From: Kirill Ivlev Date: Tue, 12 Nov 2024 02:22:00 +0400 Subject: [PATCH] versus implementation --- src/app/admin/admin-routing.module.ts | 10 ++- .../admin-testing.component.html | 12 +++ .../admin-testing.component.scss | 6 ++ .../admin-testing.component.spec.ts | 21 +++++ .../admin-testing/admin-testing.component.ts | 40 +++++++++ src/app/admin/admin.module.ts | 2 + .../admin-nav/admin-nav.component.html | 1 + .../featureflags/featureflags.component.ts | 9 ++ src/app/app.component.html | 4 +- src/app/app.component.ts | 23 ++++- src/app/app.module.ts | 2 + .../participant-item.component.html | 2 +- .../participant-item.component.scss | 4 + .../participant-item.component.ts | 2 + .../components/versus/versus.component.html | 14 +++ .../components/versus/versus.component.scss | 86 +++++++++++++++++++ .../versus/versus.component.spec.ts | 21 +++++ src/app/components/versus/versus.component.ts | 32 +++++++ src/app/services/event.service.ts | 8 +- src/app/services/testing-api.service.ts | 15 ++++ src/app/services/testingapi.service.spec.ts | 16 ++++ src/app/shared/featureflags.ts | 7 +- src/types/server-event.ts | 6 ++ 23 files changed, 332 insertions(+), 11 deletions(-) create mode 100644 src/app/admin/admin-testing/admin-testing.component.html create mode 100644 src/app/admin/admin-testing/admin-testing.component.scss create mode 100644 src/app/admin/admin-testing/admin-testing.component.spec.ts create mode 100644 src/app/admin/admin-testing/admin-testing.component.ts create mode 100644 src/app/components/versus/versus.component.html create mode 100644 src/app/components/versus/versus.component.scss create mode 100644 src/app/components/versus/versus.component.spec.ts create mode 100644 src/app/components/versus/versus.component.ts create mode 100644 src/app/services/testing-api.service.ts create mode 100644 src/app/services/testingapi.service.spec.ts diff --git a/src/app/admin/admin-routing.module.ts b/src/app/admin/admin-routing.module.ts index 39b798c..598a087 100644 --- a/src/app/admin/admin-routing.module.ts +++ b/src/app/admin/admin-routing.module.ts @@ -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], + } + ] }, ] diff --git a/src/app/admin/admin-testing/admin-testing.component.html b/src/app/admin/admin-testing/admin-testing.component.html new file mode 100644 index 0000000..8c78e96 --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.html @@ -0,0 +1,12 @@ +
+

Game testing menu

+ + + +
+ +
+
+ You are in prod mode, testing disabled +
+
\ No newline at end of file diff --git a/src/app/admin/admin-testing/admin-testing.component.scss b/src/app/admin/admin-testing/admin-testing.component.scss new file mode 100644 index 0000000..57fb940 --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.scss @@ -0,0 +1,6 @@ +div { + + button { + margin: 5px; + } +} \ No newline at end of file diff --git a/src/app/admin/admin-testing/admin-testing.component.spec.ts b/src/app/admin/admin-testing/admin-testing.component.spec.ts new file mode 100644 index 0000000..d3e3612 --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.spec.ts @@ -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; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AdminTestingComponent] + }); + fixture = TestBed.createComponent(AdminTestingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/admin/admin-testing/admin-testing.component.ts b/src/app/admin/admin-testing/admin-testing.component.ts new file mode 100644 index 0000000..4d2522b --- /dev/null +++ b/src/app/admin/admin-testing/admin-testing.component.ts @@ -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(); + 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)); + } +} diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts index f1c9840..8798690 100644 --- a/src/app/admin/admin.module.ts +++ b/src/app/admin/admin.module.ts @@ -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, diff --git a/src/app/admin/components/admin-nav/admin-nav.component.html b/src/app/admin/components/admin-nav/admin-nav.component.html index d882c6e..1b34e8a 100644 --- a/src/app/admin/components/admin-nav/admin-nav.component.html +++ b/src/app/admin/components/admin-nav/admin-nav.component.html @@ -1,3 +1,4 @@ Main +Testing Config diff --git a/src/app/admin/components/featureflags/featureflags.component.ts b/src/app/admin/components/featureflags/featureflags.component.ts index ff30782..d72e250 100644 --- a/src/app/admin/components/featureflags/featureflags.component.ts +++ b/src/app/admin/components/featureflags/featureflags.component.ts @@ -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; diff --git a/src/app/app.component.html b/src/app/app.component.html index bbd7a4b..ed3f1f6 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,5 +1,7 @@ - + + + diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 7f8d511..3a8d2c0 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -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(); + 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; + } + }) + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f2a7954..f6ef8ff 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -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, diff --git a/src/app/components/participant-item/participant-item.component.html b/src/app/components/participant-item/participant-item.component.html index 85bf2df..105c9a3 100644 --- a/src/app/components/participant-item/participant-item.component.html +++ b/src/app/components/participant-item/participant-item.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/app/components/participant-item/participant-item.component.scss b/src/app/components/participant-item/participant-item.component.scss index 2b4119d..346c927 100644 --- a/src/app/components/participant-item/participant-item.component.scss +++ b/src/app/components/participant-item/participant-item.component.scss @@ -11,6 +11,10 @@ padding: 0px; } +.transparent { + background: inherit; +} + figure { border-radius:100%; display:inline-block; diff --git a/src/app/components/participant-item/participant-item.component.ts b/src/app/components/participant-item/participant-item.component.ts index 10c3b8a..1b671bc 100644 --- a/src/app/components/participant-item/participant-item.component.ts +++ b/src/app/components/participant-item/participant-item.component.ts @@ -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) { } diff --git a/src/app/components/versus/versus.component.html b/src/app/components/versus/versus.component.html new file mode 100644 index 0000000..4106edb --- /dev/null +++ b/src/app/components/versus/versus.component.html @@ -0,0 +1,14 @@ +
+ +
+
+ + +
+
+ + + +
+
+
\ No newline at end of file diff --git a/src/app/components/versus/versus.component.scss b/src/app/components/versus/versus.component.scss new file mode 100644 index 0000000..0511793 --- /dev/null +++ b/src/app/components/versus/versus.component.scss @@ -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; +} diff --git a/src/app/components/versus/versus.component.spec.ts b/src/app/components/versus/versus.component.spec.ts new file mode 100644 index 0000000..2cad49d --- /dev/null +++ b/src/app/components/versus/versus.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VersusComponent } from './versus.component'; + +describe('VersusComponent', () => { + let component: VersusComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [VersusComponent] + }); + fixture = TestBed.createComponent(VersusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/versus/versus.component.ts b/src/app/components/versus/versus.component.ts new file mode 100644 index 0000000..63805f1 --- /dev/null +++ b/src/app/components/versus/versus.component.ts @@ -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(); + + 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); + } +} diff --git a/src/app/services/event.service.ts b/src/app/services/event.service.ts index 9e3ebd3..0b9ee15 100644 --- a/src/app/services/event.service.ts +++ b/src/app/services/event.service.ts @@ -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>(); public notificationEvent = new EventEmitter>(); public userPropertyChanged = new EventEmitter>(); - public featureFlagChanged = new EventEmitter>(); + public featureFlagChanged = new EventEmitter>() + public versusBegin = new EventEmitter>(); constructor() { } public emit(event: ServerEvent) { @@ -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); + break; } } } diff --git a/src/app/services/testing-api.service.ts b/src/app/services/testing-api.service.ts new file mode 100644 index 0000000..26321e2 --- /dev/null +++ b/src/app/services/testing-api.service.ts @@ -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`, {}); + } +} diff --git a/src/app/services/testingapi.service.spec.ts b/src/app/services/testingapi.service.spec.ts new file mode 100644 index 0000000..65c79e7 --- /dev/null +++ b/src/app/services/testingapi.service.spec.ts @@ -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(); + }); +}); diff --git a/src/app/shared/featureflags.ts b/src/app/shared/featureflags.ts index 5c86195..c78fa83 100644 --- a/src/app/shared/featureflags.ts +++ b/src/app/shared/featureflags.ts @@ -1,3 +1,8 @@ export class FeatureFlagList { - static readonly FeatureFlags: string[] = ["EnableEndgamePoints"]; + static readonly FeatureFlags: string[] = [ + "EnableEndgamePoints", + "DontMarkQuestionsAsCompleted", + "DisableVoice", + "ProdMode", + ]; } \ No newline at end of file diff --git a/src/types/server-event.ts b/src/types/server-event.ts index ca4c376..8321e71 100644 --- a/src/types/server-event.ts +++ b/src/types/server-event.ts @@ -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 { | 'notification' | 'user_property_changed' | 'feature_flag_changed' + | 'begin_versus' data: T }