feat(blackjack): add action indicators and loading states

This commit is contained in:
Jan-Marlon Leibl 2025-03-27 15:44:38 +01:00
commit acdbea5a99
Signed by: jleibl
GPG key ID: 300B2F906DC6F1D5
7 changed files with 208 additions and 27 deletions

View file

@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { ChangeDetectionStrategy, Component, Input, AfterViewInit, ElementRef, OnChanges, SimpleChanges } from '@angular/core';
import { CommonModule } from '@angular/common';
import { suitSymbols, Suit } from '../../models/blackjack.model';
import { gsap } from 'gsap';
@Component({
selector: 'app-playing-card',
@ -8,31 +9,90 @@ import { suitSymbols, Suit } from '../../models/blackjack.model';
imports: [CommonModule],
template: `
<div
class="w-24 h-36 rounded-lg p-2 relative flex flex-col justify-between shadow-lg"
#cardElement
class="w-24 h-36 rounded-lg p-2 relative flex flex-col justify-between shadow-lg card-element"
[class]="hidden ? 'bg-red-800' : 'bg-white'"
>
@if (!hidden) {
<span class="text-xl font-bold text-accent-red">{{ getDisplayRank(rank) }}</span>
<span class="text-xl font-bold" [class]="isRedSuit ? 'text-accent-red' : 'text-black'">{{ getDisplayRank(rank) }}</span>
}
@if (!hidden) {
<span
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl text-accent-red"
class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-3xl"
[class]="isRedSuit ? 'text-accent-red' : 'text-black'"
>{{ getSuitSymbol(suit) }}</span
>
}
@if (!hidden) {
<span class="text-xl font-bold text-accent-red self-end rotate-180">{{
<span class="text-xl font-bold self-end rotate-180" [class]="isRedSuit ? 'text-accent-red' : 'text-black'">{{
getDisplayRank(rank)
}}</span>
}
</div>
`,
styles: [`
.card-element {
transform-style: preserve-3d;
backface-visibility: hidden;
}
`],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PlayingCardComponent {
export class PlayingCardComponent implements AfterViewInit, OnChanges {
@Input({ required: true }) rank!: string;
@Input({ required: true }) suit!: Suit;
@Input({ required: true }) hidden!: boolean;
@Input() isNew: boolean = false;
constructor(private elementRef: ElementRef) {}
get isRedSuit(): boolean {
return this.suit === 'HEARTS' || this.suit === 'DIAMONDS';
}
ngAfterViewInit(): void {
if (this.isNew) {
this.animateNewCard();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['hidden'] && !changes['hidden'].firstChange) {
this.animateCardFlip();
}
}
private animateNewCard(): void {
const cardElement = this.elementRef.nativeElement.querySelector('.card-element');
gsap.fromTo(
cardElement,
{
y: -100,
opacity: 0,
rotation: -10,
scale: 0.7
},
{
y: 0,
opacity: 1,
rotation: 0,
scale: 1,
duration: 0.5,
ease: 'power2.out'
}
);
}
private animateCardFlip(): void {
const cardElement = this.elementRef.nativeElement.querySelector('.card-element');
gsap.to(cardElement, {
rotationY: 180,
duration: 0.3,
onComplete: () => {
gsap.set(cardElement, { rotationY: 0 });
}
});
}
protected getSuitSymbol(suit: Suit): string {
return suitSymbols[suit];