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

@@ -277,6 +277,29 @@
/>
<div class="flex-1 min-w-0">
<p class="text-sm text-foreground truncate">{{ currentUser()?.displayName }}</p>
@if (currentUser()?.gameActivity; as activity) {
<p class="mt-0.5 flex items-center gap-1 truncate text-[10px] text-muted-foreground">
<ng-icon
name="lucideGamepad2"
class="h-2.5 w-2.5 shrink-0"
/>
@if (activity.store?.url) {
<button
type="button"
class="truncate text-left hover:text-foreground hover:underline"
(click)="openGameStore($event, activity)"
(dblclick)="$event.stopPropagation()"
(keydown.enter)="$event.stopPropagation()"
(keydown.space)="$event.stopPropagation()"
>
Playing {{ activity.name }}
</button>
} @else {
<span class="truncate">Playing {{ activity.name }}</span>
}
<span class="shrink-0">{{ gameActivityElapsed(currentUser()) }}</span>
</p>
}
<div class="flex items-center gap-2">
@if (currentUser()?.voiceState?.isConnected) {
<p class="text-[10px] text-muted-foreground flex items-center gap-1">
@@ -340,6 +363,29 @@
<span class="text-[10px] bg-green-500/20 text-green-400 px-1 py-0.5 rounded font-medium">Mod</span>
}
</div>
@if (user.gameActivity; as activity) {
<p class="mt-0.5 flex items-center gap-1 truncate text-[10px] text-muted-foreground">
<ng-icon
name="lucideGamepad2"
class="h-2.5 w-2.5 shrink-0"
/>
@if (activity.store?.url) {
<button
type="button"
class="truncate text-left hover:text-foreground hover:underline"
(click)="openGameStore($event, activity)"
(dblclick)="$event.stopPropagation()"
(keydown.enter)="$event.stopPropagation()"
(keydown.space)="$event.stopPropagation()"
>
Playing {{ activity.name }}
</button>
} @else {
<span class="truncate">Playing {{ activity.name }}</span>
}
<span class="shrink-0">{{ gameActivityElapsed(user) }}</span>
</p>
}
<div class="flex items-center gap-2">
@if (user.voiceState?.isConnected) {
<p class="text-[10px] text-muted-foreground flex items-center gap-1">

View File

@@ -4,6 +4,7 @@ import {
inject,
computed,
input,
OnDestroy,
signal
} from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -22,7 +23,8 @@ import {
lucideHash,
lucideUsers,
lucidePlus,
lucideVolumeX
lucideVolumeX,
lucideGamepad2
} from '@ng-icons/lucide';
import { selectOnlineUsers, selectCurrentUser } from '../../../store/users/users.selectors';
import {
@@ -46,6 +48,8 @@ import {
import { VoiceSessionFacade, VoiceWorkspaceService } from '../../../domains/voice-session';
import { DirectMessageService } from '../../../domains/direct-message';
import { VoicePlaybackService } from '../../../domains/voice-connection';
import { formatGameActivityElapsed } from '../../../domains/game-activity';
import { ExternalLinkService } from '../../../core/platform/external-link.service';
import { VoiceControlsComponent } from '../../../domains/voice-session/feature/voice-controls/voice-controls.component';
import { isChannelNameTaken, normalizeChannelName } from '../../../store/rooms/room-channels.rules';
import {
@@ -64,6 +68,7 @@ import {
import {
Channel,
ChatEvent,
GameActivity,
RoomMember,
Room,
User
@@ -98,12 +103,13 @@ type PanelMode = 'channels' | 'users';
lucideHash,
lucideUsers,
lucidePlus,
lucideVolumeX
lucideVolumeX,
lucideGamepad2
})
],
templateUrl: './rooms-side-panel.component.html'
})
export class RoomsSidePanelComponent {
export class RoomsSidePanelComponent implements OnDestroy {
private store = inject(Store);
private router = inject(Router);
private realtime = inject(RealtimeSessionFacade);
@@ -115,9 +121,11 @@ export class RoomsSidePanelComponent {
private voicePlayback = inject(VoicePlaybackService);
private profileCard = inject(ProfileCardService);
private directMessages = inject(DirectMessageService);
private readonly externalLinks = inject(ExternalLinkService);
private readonly voiceActivity = inject(VoiceActivityService);
private readonly voiceConnectivity = inject(VoiceConnectivityHealthService);
private profileCardOpenTimer: ReturnType<typeof setTimeout> | null = null;
private readonly activityTimer = setInterval(() => this.activityNow.set(Date.now()), 1_000);
readonly panelMode = input<PanelMode>('channels');
readonly showVoiceControls = input(true);
@@ -198,6 +206,26 @@ export class RoomsSidePanelComponent {
volumeMenuDisplayName = signal('');
draggedVoiceUserId = signal<string | null>(null);
dragTargetVoiceChannelId = signal<string | null>(null);
activityNow = signal(Date.now());
ngOnDestroy(): void {
clearInterval(this.activityTimer);
this.cancelQueuedProfileCardOpen();
}
gameActivityElapsed(user: User | null | undefined): string {
const activity = user?.gameActivity;
return activity ? formatGameActivityElapsed(activity.startedAt, this.activityNow()) : '';
}
openGameStore(event: Event, activity: GameActivity): void {
event.stopPropagation();
if (activity.store?.url) {
this.externalLinks.open(activity.store.url);
}
}
openProfileCard(event: Event, user: User, editable: boolean): void {
event.stopPropagation();

View File

@@ -28,6 +28,7 @@
(keydown.space)="$event.stopPropagation()"
role="dialog"
aria-modal="true"
aria-labelledby="settings-modal-title"
tabindex="-1"
>
<!-- Side Navigation -->
@@ -36,7 +37,12 @@
class="flex w-56 flex-shrink-0 flex-col border-r border-border bg-card"
>
<div class="border-b border-border px-3 py-3">
<h2 class="text-lg font-semibold text-foreground">Settings</h2>
<h2
id="settings-modal-title"
class="text-lg font-semibold text-foreground"
>
Settings
</h2>
</div>
<div class="flex-1 overflow-y-auto py-2">