Compare commits
No commits in common. "e75ea84e665ac48609165bb7a608e8209d0cef86" and "7cbdfe28e60f6c165405282f010719c08f82932d" have entirely different histories.
e75ea84e66
...
7cbdfe28e6
|
|
@ -2,7 +2,7 @@
|
||||||
@attribute [Authorize(Policy = "ClubViewer")]
|
@attribute [Authorize(Policy = "ClubViewer")]
|
||||||
|
|
||||||
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
@inherits Fluxor.Blazor.Web.Components.FluxorComponent
|
||||||
@implements IAsyncDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
@using Fluxor
|
@using Fluxor
|
||||||
@using Koogle.Application.DTOs
|
@using Koogle.Application.DTOs
|
||||||
|
|
@ -12,8 +12,6 @@
|
||||||
@using Koogle.Web.Store.PersonState
|
@using Koogle.Web.Store.PersonState
|
||||||
@using Koogle.Web.Store.TimerState
|
@using Koogle.Web.Store.TimerState
|
||||||
@using Koogle.Web.Components.Game
|
@using Koogle.Web.Components.Game
|
||||||
@using Koogle.Web.Hubs
|
|
||||||
@using Koogle.Web.Services
|
|
||||||
@using Microsoft.AspNetCore.Authorization
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|
||||||
@inject IState<DayState> DayState
|
@inject IState<DayState> DayState
|
||||||
|
|
@ -24,7 +22,6 @@
|
||||||
@inject ISnackbar Snackbar
|
@inject ISnackbar Snackbar
|
||||||
@inject IDialogService DialogService
|
@inject IDialogService DialogService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject GameHubService HubService
|
|
||||||
|
|
||||||
<PageTitle>Spieltag Details</PageTitle>
|
<PageTitle>Spieltag Details</PageTitle>
|
||||||
|
|
||||||
|
|
@ -878,30 +875,12 @@ else
|
||||||
_activeTabIndex = 1;
|
_activeTabIndex = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
await base.OnAfterRenderAsync(firstRender);
|
base.OnAfterRender(firstRender);
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
TimerState.StateChanged += OnTimerStateChanged;
|
TimerState.StateChanged += OnTimerStateChanged;
|
||||||
GameState.StateChanged += OnGameStateChanged;
|
|
||||||
|
|
||||||
// Subscribe to SignalR events
|
|
||||||
HubService.OnGameStateUpdated += HandleHubGameStateUpdated;
|
|
||||||
HubService.OnThrowRecorded += HandleHubThrowRecorded;
|
|
||||||
HubService.OnGameStarted += HandleHubGameStarted;
|
|
||||||
HubService.OnGameEnded += HandleHubGameEnded;
|
|
||||||
|
|
||||||
// Start SignalR connection and join day group
|
|
||||||
await HubService.StartAsync();
|
|
||||||
await HubService.JoinDayAsync(DayId);
|
|
||||||
|
|
||||||
// If there's an active game, join its group too
|
|
||||||
if (GameState.Value.ActiveGameId.HasValue)
|
|
||||||
{
|
|
||||||
await HubService.JoinGameAsync(GameState.Value.ActiveGameId.Value);
|
|
||||||
_previousGameId = GameState.Value.ActiveGameId.Value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -915,82 +894,8 @@ else
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guid? _previousGameId;
|
public void Dispose()
|
||||||
|
|
||||||
private async void OnGameStateChanged(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var currentGameId = GameState.Value.ActiveGameId;
|
|
||||||
|
|
||||||
// Game started - join game group
|
|
||||||
if (currentGameId.HasValue && _previousGameId != currentGameId)
|
|
||||||
{
|
|
||||||
if (_previousGameId.HasValue)
|
|
||||||
{
|
|
||||||
await HubService.LeaveGameAsync(_previousGameId.Value);
|
|
||||||
}
|
|
||||||
await HubService.JoinGameAsync(currentGameId.Value);
|
|
||||||
_previousGameId = currentGameId;
|
|
||||||
}
|
|
||||||
// Game ended - leave game group
|
|
||||||
else if (!currentGameId.HasValue && _previousGameId.HasValue)
|
|
||||||
{
|
|
||||||
await HubService.LeaveGameAsync(_previousGameId.Value);
|
|
||||||
_previousGameId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignalR event handlers
|
|
||||||
private void HandleHubGameStateUpdated(GameStateSerializationDto state)
|
|
||||||
{
|
|
||||||
// Dispatch action to update local state from remote
|
|
||||||
Dispatcher.Dispatch(new RemoteGameStateUpdatedAction(state));
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleHubThrowRecorded(Guid gameId, Guid playerId, int pinsKnocked)
|
|
||||||
{
|
|
||||||
// Throw was recorded by another client - refresh state
|
|
||||||
Snackbar.Add($"Wurf empfangen: {pinsKnocked} Pins", Severity.Info);
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleHubGameStarted(Guid gameId, string gameTypeName)
|
|
||||||
{
|
|
||||||
// New game started - reload active game
|
|
||||||
Dispatcher.Dispatch(new LoadActiveGameAction(DayId));
|
|
||||||
Snackbar.Add($"Neues Spiel gestartet: {gameTypeName}", Severity.Success);
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleHubGameEnded(GameResultDto result)
|
|
||||||
{
|
|
||||||
// Game ended - reload completed games
|
|
||||||
Dispatcher.Dispatch(new LoadCompletedGamesAction(DayId));
|
|
||||||
var message = result.WinnerName != null
|
|
||||||
? $"Spiel beendet! Gewinner: {result.WinnerName}"
|
|
||||||
: "Spiel beendet!";
|
|
||||||
Snackbar.Add(message, Severity.Success);
|
|
||||||
_activeTabIndex = 0;
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
{
|
||||||
TimerState.StateChanged -= OnTimerStateChanged;
|
TimerState.StateChanged -= OnTimerStateChanged;
|
||||||
GameState.StateChanged -= OnGameStateChanged;
|
|
||||||
|
|
||||||
// Unsubscribe from SignalR events
|
|
||||||
HubService.OnGameStateUpdated -= HandleHubGameStateUpdated;
|
|
||||||
HubService.OnThrowRecorded -= HandleHubThrowRecorded;
|
|
||||||
HubService.OnGameStarted -= HandleHubGameStarted;
|
|
||||||
HubService.OnGameEnded -= HandleHubGameEnded;
|
|
||||||
|
|
||||||
// Leave groups and stop hub connection
|
|
||||||
if (_previousGameId.HasValue)
|
|
||||||
{
|
|
||||||
await HubService.LeaveGameAsync(_previousGameId.Value);
|
|
||||||
}
|
|
||||||
await HubService.LeaveDayAsync(DayId);
|
|
||||||
await HubService.DisposeAsync();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using Koogle.Application.DTOs;
|
using Koogle.Application.DTOs;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
|
|
||||||
namespace Koogle.Web.Hubs;
|
namespace Koogle.Web.Hubs;
|
||||||
|
|
@ -6,8 +7,8 @@ namespace Koogle.Web.Hubs;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SignalR hub for real-time game state synchronization.
|
/// SignalR hub for real-time game state synchronization.
|
||||||
/// Clients join game-specific groups to receive updates.
|
/// Clients join game-specific groups to receive updates.
|
||||||
/// Note: Authorization handled at page level (DayDetails requires ClubViewer policy).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Authorize]
|
||||||
public class GameHub : Hub<IGameHubClient>
|
public class GameHub : Hub<IGameHubClient>
|
||||||
{
|
{
|
||||||
private readonly ILogger<GameHub> _logger;
|
private readonly ILogger<GameHub> _logger;
|
||||||
|
|
|
||||||
|
|
@ -258,11 +258,6 @@ public record GameStateUpdatedFromHubAction(
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public record GameEndedFromHubAction(GameSummaryDto Summary);
|
public record GameEndedFromHubAction(GameSummaryDto Summary);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Action dispatched when game state DTO is received from remote SignalR hub.
|
|
||||||
/// </summary>
|
|
||||||
public record RemoteGameStateUpdatedAction(Koogle.Application.DTOs.GameStateSerializationDto State);
|
|
||||||
|
|
||||||
// Error Actions
|
// Error Actions
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -448,15 +448,6 @@ public class GameEffects
|
||||||
dispatcher);
|
dispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast throw immediately to other clients (before debounced save)
|
|
||||||
if (state.ActiveGameId.HasValue)
|
|
||||||
{
|
|
||||||
await _hubService.BroadcastThrowAsync(
|
|
||||||
state.ActiveGameId.Value,
|
|
||||||
currentPlayerId.Value,
|
|
||||||
afterThrowState.PinsKnocked);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If game is over, skip save (user must confirm end via UI)
|
// If game is over, skip save (user must confirm end via UI)
|
||||||
if (isGameOver)
|
if (isGameOver)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Fluxor;
|
using Fluxor;
|
||||||
using Koogle.Application.DTOs;
|
|
||||||
using Koogle.Application.Games;
|
using Koogle.Application.Games;
|
||||||
using Koogle.Domain.Enums;
|
using Koogle.Domain.Enums;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
@ -510,52 +509,6 @@ public static class GameReducers
|
||||||
GameModel = action.GameModel
|
GameModel = action.GameModel
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles RemoteGameStateUpdatedAction - updates state from remote SignalR DTO.
|
|
||||||
/// </summary>
|
|
||||||
[ReducerMethod]
|
|
||||||
public static GameState OnRemoteGameStateUpdated(GameState state, RemoteGameStateUpdatedAction action)
|
|
||||||
{
|
|
||||||
var dto = action.State;
|
|
||||||
|
|
||||||
var throwPanelAfter = new ThrowPanelState
|
|
||||||
{
|
|
||||||
IsStarted = dto.ThrowPanelAfter.IsStarted,
|
|
||||||
Pin1 = (PinStatus)dto.ThrowPanelAfter.Pins[0],
|
|
||||||
Pin2 = (PinStatus)dto.ThrowPanelAfter.Pins[1],
|
|
||||||
Pin3 = (PinStatus)dto.ThrowPanelAfter.Pins[2],
|
|
||||||
Pin4 = (PinStatus)dto.ThrowPanelAfter.Pins[3],
|
|
||||||
Pin5 = (PinStatus)dto.ThrowPanelAfter.Pins[4],
|
|
||||||
Pin6 = (PinStatus)dto.ThrowPanelAfter.Pins[5],
|
|
||||||
Pin7 = (PinStatus)dto.ThrowPanelAfter.Pins[6],
|
|
||||||
Pin8 = (PinStatus)dto.ThrowPanelAfter.Pins[7],
|
|
||||||
Pin9 = (PinStatus)dto.ThrowPanelAfter.Pins[8],
|
|
||||||
ThrowsPerRound = dto.ThrowPanelAfter.ThrowsPerRound,
|
|
||||||
ThrowCounterPerRound = dto.ThrowPanelAfter.ThrowCounterPerRound,
|
|
||||||
ThrowMode = (ThrowMode)dto.ThrowPanelAfter.ThrowMode,
|
|
||||||
TotalThrowCounter = dto.ThrowPanelAfter.TotalThrowCounter,
|
|
||||||
BellValue = dto.ThrowPanelAfter.BellValue
|
|
||||||
};
|
|
||||||
|
|
||||||
var participants = new ParticipantsState
|
|
||||||
{
|
|
||||||
PlayerIds = dto.Participants.PlayerIds,
|
|
||||||
CurrentPlayerIndex = dto.Participants.CurrentPlayerIndex,
|
|
||||||
Mode = (ParticipantsMode)dto.Participants.Mode
|
|
||||||
};
|
|
||||||
|
|
||||||
object? gameModel = dto.GameModel.HasValue
|
|
||||||
? (object?)dto.GameModel.Value
|
|
||||||
: null;
|
|
||||||
|
|
||||||
return state with
|
|
||||||
{
|
|
||||||
ThrowPanelAfter = throwPanelAfter,
|
|
||||||
Participants = participants,
|
|
||||||
GameModel = gameModel
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles GameEndedFromHubAction - handles game ended from another client.
|
/// Handles GameEndedFromHubAction - handles game ended from another client.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue