From 4b70a4ac4a84e3f9ceab8ffffb2fbbd59014038b Mon Sep 17 00:00:00 2001 From: Jan-Marlon Leibl Date: Thu, 3 Apr 2025 10:02:15 +0200 Subject: [PATCH] feat(blackjack): add animated number component and usage --- frontend/bun.lock | 8 ++ frontend/package.json | 1 + .../game/blackjack/blackjack.component.html | 13 --- .../game/blackjack/blackjack.component.ts | 5 -- .../animated-number.component.ts | 80 +++++++++++++++++++ .../game-controls/game-controls.component.ts | 43 +++------- .../game-info/game-info.component.ts | 5 +- .../game-result/game-result.component.ts | 17 ++-- .../debt-dialog/debt-dialog.component.ts | 9 ++- .../components/navbar/navbar.component.html | 6 +- .../components/navbar/navbar.component.ts | 3 +- 11 files changed, 127 insertions(+), 63 deletions(-) create mode 100644 frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts diff --git a/frontend/bun.lock b/frontend/bun.lock index 7112f3e..e51c957 100644 --- a/frontend/bun.lock +++ b/frontend/bun.lock @@ -21,6 +21,7 @@ "@tailwindcss/postcss": "^4.0.3", "ajv": "8.17.1", "ajv-formats": "3.0.1", + "countup.js": "^2.8.0", "gsap": "^3.12.7", "keycloak-angular": "^19.0.0", "keycloak-js": "^26.0.0", @@ -897,6 +898,13 @@ "cosmiconfig": ["cosmiconfig@9.0.0", "", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg=="], +<<<<<<< HEAD +======= + "countup.js": ["countup.js@2.8.0", "", {}, "sha512-f7xEhX0awl4NOElHulrl4XRfKoNH3rB+qfNSZZyjSZhaAoUk6elvhH+MNxMmlmuUJ2/QNTWPSA7U4mNtIAKljQ=="], + + "critters": ["critters@0.0.24", "", { "dependencies": { "chalk": "^4.1.0", "css-select": "^5.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.2", "htmlparser2": "^8.0.2", "postcss": "^8.4.23", "postcss-media-query-parser": "^0.2.3" } }, "sha512-Oyqew0FGM0wYUSNqR0L6AteO5MpMoUU0rhKRieXeiKs+PmRTxiJMyaunYB2KF6fQ3dzChXKCpbFOEJx3OQ1v/Q=="], + +>>>>>>> f2d447a (feat(blackjack): add animated number component and usage) "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "css-loader": ["css-loader@7.1.2", "", { "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.33", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.5.4" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "webpack": "^5.27.0" }, "optionalPeers": ["@rspack/core", "webpack"] }, "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA=="], diff --git a/frontend/package.json b/frontend/package.json index 4775e66..2ee7ed0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "@tailwindcss/postcss": "^4.0.3", "ajv": "8.17.1", "ajv-formats": "3.0.1", + "countup.js": "^2.8.0", "gsap": "^3.12.7", "keycloak-angular": "^19.0.0", "keycloak-js": "^26.0.0", diff --git a/frontend/src/app/feature/game/blackjack/blackjack.component.html b/frontend/src/app/feature/game/blackjack/blackjack.component.html index 93328a3..aa7426a 100644 --- a/frontend/src/app/feature/game/blackjack/blackjack.component.html +++ b/frontend/src/app/feature/game/blackjack/blackjack.component.html @@ -6,19 +6,6 @@ - @if (isActionInProgress()) { -
-
-
- {{ currentAction() }} -
-
- } - @if (gameInProgress()) { (''); showDebtDialog = signal(false); debtAmount = signal(0); @@ -96,7 +95,6 @@ export default class BlackjackComponent implements OnInit { onNewGame(bet: number): void { this.isActionInProgress.set(true); - this.currentAction.set('Spiel wird gestartet...'); this.blackjackService.startGame(bet).subscribe({ next: (game) => { @@ -115,7 +113,6 @@ export default class BlackjackComponent implements OnInit { if (!this.currentGameId() || this.isActionInProgress()) return; this.isActionInProgress.set(true); - this.currentAction.set('Karte wird gezogen...'); this.blackjackService.hit(this.currentGameId()!).subscribe({ next: (game) => { @@ -142,7 +139,6 @@ export default class BlackjackComponent implements OnInit { } this.isActionInProgress.set(true); - this.currentAction.set('Dealer zieht Karten...'); this.blackjackService.stand(this.currentGameId()!).subscribe({ next: (game) => { @@ -167,7 +163,6 @@ export default class BlackjackComponent implements OnInit { } this.isActionInProgress.set(true); - this.currentAction.set('Einsatz wird verdoppelt...'); this.blackjackService.doubleDown(this.currentGameId()!).subscribe({ next: (game) => { diff --git a/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts b/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts new file mode 100644 index 0000000..aa4c1b6 --- /dev/null +++ b/frontend/src/app/feature/game/blackjack/components/animated-number/animated-number.component.ts @@ -0,0 +1,80 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ElementRef, ViewChild, AfterViewInit } from '@angular/core'; +import { CommonModule, CurrencyPipe } from '@angular/common'; +import { CountUp } from 'countup.js'; + +@Component({ + selector: 'app-animated-number', + standalone: true, + imports: [CommonModule, CurrencyPipe], + template: ` + {{ formattedValue }} + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AnimatedNumberComponent implements OnChanges, AfterViewInit { + @Input() value = 0; + @Input() duration = 1; + @Input() ease = 'power1.out'; + + @ViewChild('numberElement') numberElement!: ElementRef; + + private countUp: CountUp | null = null; + private previousValue = 0; + formattedValue = '0,00 €'; + + ngAfterViewInit(): void { + this.initializeCountUp(); + if (this.countUp && this.value !== 0) { + this.countUp.start(() => { + this.previousValue = this.value; + }); + } + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes['value']) { + if (this.countUp) { + const startVal = this.previousValue; + const endVal = this.value; + + // Update the CountUp instance with new start and end values + this.countUp.update(endVal); + this.previousValue = endVal; + } else { + // Format the initial value if CountUp is not yet initialized + this.formattedValue = new Intl.NumberFormat('de-DE', { + style: 'currency', + currency: 'EUR', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(this.value); + } + } + } + + private initializeCountUp(): void { + if (this.numberElement) { + this.countUp = new CountUp(this.numberElement.nativeElement, this.value, { + startVal: this.previousValue, + duration: this.duration, + easingFn: (t, b, c, d) => { + // Custom easing function based on the input ease type + if (this.ease === 'power1.out') { + return c * (1 - Math.pow(1 - t / d, 1)) + b; + } + return c * (t / d) + b; // linear fallback + }, + formattingFn: (value) => { + const formatted = new Intl.NumberFormat('de-DE', { + style: 'currency', + currency: 'EUR', + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(value); + this.formattedValue = formatted; + return formatted; + }, + }); + } + } +} \ No newline at end of file diff --git a/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts b/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts index d01adc6..9799b08 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-controls/game-controls.component.ts @@ -28,54 +28,27 @@ import { GameControlsService } from '@blackjack/services/game-controls.service'; (click)="hit.emit()" class="button-primary px-8 py-4 text-lg font-medium min-w-[120px] relative" [disabled]="gameState !== GameState.IN_PROGRESS || isActionInProgress" - [class.opacity-50]="isActionInProgress" > - Ziehen - @if (isActionInProgress) { -
-
-
- } + Ziehen @@ -97,4 +70,12 @@ export class GameControlsComponent { protected readonly GameState = GameState; constructor(protected gameControlsService: GameControlsService) {} + + get canDoubleDown(): boolean { + return ( + this.gameState === GameState.IN_PROGRESS && + this.playerCards.length === 2 && + !this.isActionInProgress + ); + } } diff --git a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts index df29e81..ecd1fad 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-info/game-info.component.ts @@ -11,11 +11,12 @@ import { import { CommonModule, CurrencyPipe } from '@angular/common'; import { FormGroup, ReactiveFormsModule } from '@angular/forms'; import { BettingService } from '@blackjack/services/betting.service'; +import { AnimatedNumberComponent } from '../animated-number/animated-number.component'; @Component({ selector: 'app-game-info', standalone: true, - imports: [CommonModule, CurrencyPipe, ReactiveFormsModule], + imports: [CommonModule, CurrencyPipe, ReactiveFormsModule, AnimatedNumberComponent], template: `

Spiel Informationen

@@ -23,7 +24,7 @@ import { BettingService } from '@blackjack/services/betting.service';
Aktuelle Wette: - {{ currentBet | currency: 'EUR' }} +
diff --git a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts index 63ca955..5547e27 100644 --- a/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts +++ b/frontend/src/app/feature/game/blackjack/components/game-result/game-result.component.ts @@ -2,11 +2,12 @@ import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter } from import { CommonModule, CurrencyPipe } from '@angular/common'; import { animate, style, transition, trigger } from '@angular/animations'; import { GameState } from '../../enum/gameState'; +import { AnimatedNumberComponent } from '../animated-number/animated-number.component'; @Component({ selector: 'app-game-result', standalone: true, - imports: [CommonModule, CurrencyPipe], + imports: [CommonModule, CurrencyPipe, AnimatedNumberComponent], template: ` diff --git a/frontend/src/app/shared/components/debt-dialog/debt-dialog.component.ts b/frontend/src/app/shared/components/debt-dialog/debt-dialog.component.ts index dd60fdc..a97f431 100644 --- a/frontend/src/app/shared/components/debt-dialog/debt-dialog.component.ts +++ b/frontend/src/app/shared/components/debt-dialog/debt-dialog.component.ts @@ -11,18 +11,19 @@ import { import { CommonModule } from '@angular/common'; import { animate, style, transition, trigger } from '@angular/animations'; import { interval, Subscription, takeWhile } from 'rxjs'; +import { AnimatedNumberComponent } from '@blackjack/components/animated-number/animated-number.component'; @Component({ selector: 'app-debt-dialog', standalone: true, - imports: [CommonModule], + imports: [CommonModule, AnimatedNumberComponent], template: `