feat: Add game activity status (Experimental)
All checks were successful
Queue Release Build / prepare (push) Successful in 21s
Deploy Web Apps / deploy (push) Successful in 5m14s
Queue Release Build / build-windows (push) Successful in 16m18s
Queue Release Build / build-linux (push) Successful in 29m20s
Queue Release Build / finalize (push) Successful in 36s

This commit is contained in:
2026-04-27 05:46:33 +02:00
parent 3858beb28e
commit 66c6f34cd3
52 changed files with 2120 additions and 113 deletions

View File

@@ -3,15 +3,24 @@ import {
computed,
effect,
inject,
OnDestroy,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { Store } from '@ngrx/store';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideCheck, lucideChevronDown } from '@ng-icons/lucide';
import {
lucideCheck,
lucideChevronDown,
lucideGamepad2
} from '@ng-icons/lucide';
import { UserAvatarComponent } from '../user-avatar/user-avatar.component';
import { UserStatusService } from '../../../core/services/user-status.service';
import { User, UserStatus } from '../../../shared-kernel';
import {
GameActivity,
User,
UserStatus
} from '../../../shared-kernel';
import {
EditableProfileAvatarSource,
ProfileAvatarFacade,
@@ -22,6 +31,8 @@ import {
import { UsersActions } from '../../../store/users/users.actions';
import { selectUsersEntities } from '../../../store/users/users.selectors';
import { ThemeNodeDirective } from '../../../domains/theme';
import { formatGameActivityElapsed } from '../../../domains/game-activity';
import { ExternalLinkService } from '../../../core/platform/external-link.service';
@Component({
selector: 'app-profile-card',
@@ -32,10 +43,10 @@ import { ThemeNodeDirective } from '../../../domains/theme';
UserAvatarComponent,
ThemeNodeDirective
],
viewProviders: [provideIcons({ lucideCheck, lucideChevronDown })],
viewProviders: [provideIcons({ lucideCheck, lucideChevronDown, lucideGamepad2 })],
templateUrl: './profile-card.component.html'
})
export class ProfileCardComponent {
export class ProfileCardComponent implements OnDestroy {
readonly user = signal<User>({ id: '', oderId: '', username: '', displayName: '', status: 'offline', role: 'member', joinedAt: 0 });
readonly displayedUser = computed(() => {
const snapshot = this.user();
@@ -52,6 +63,7 @@ export class ProfileCardComponent {
readonly editingField = signal<'displayName' | 'description' | null>(null);
readonly displayNameDraft = signal('');
readonly descriptionDraft = signal('');
readonly activityNow = signal(Date.now());
readonly statusOptions: { value: UserStatus | null; label: string; color: string }[] = [
{ value: null, label: 'Online', color: 'bg-green-500' },
@@ -65,6 +77,8 @@ export class ProfileCardComponent {
private readonly userStatus = inject(UserStatusService);
private readonly profileAvatar = inject(ProfileAvatarFacade);
private readonly profileAvatarEditor = inject(ProfileAvatarEditorService);
private readonly externalLinks = inject(ExternalLinkService);
private readonly activityTimer = setInterval(() => this.activityNow.set(Date.now()), 1_000);
private readonly syncProfileDrafts = effect(
() => {
const user = this.displayedUser();
@@ -115,6 +129,24 @@ export class ProfileCardComponent {
}
}
gameActivityElapsed(): string {
const activity = this.displayedUser().gameActivity;
return activity ? formatGameActivityElapsed(activity.startedAt, this.activityNow()) : '';
}
openGameStore(activity: GameActivity, event: Event): void {
event.stopPropagation();
if (activity.store?.url) {
this.externalLinks.open(activity.store.url);
}
}
ngOnDestroy(): void {
clearInterval(this.activityTimer);
}
toggleStatusMenu(): void {
this.showStatusMenu.update((isOpen) => !isOpen);
}