Complete phase H8: Undo functionality (unbegrenzt)
- Add UndoButton.razor as reusable component - Refactor GameInputPanel to use UndoButton component - Enhance GameEffects with proper UndoThrowAction handler - IState injection for state access in effects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
aefa676d62
commit
e436ee2754
|
|
@ -955,7 +955,7 @@ Web: Store/GameState/, Components/Game/, Hubs/GameHub
|
|||
| ✓ | H5 | Games | Scheiss-Spiel + Trigger-Integration | 6 |
|
||||
| ✓ | H6 | UI | Game Setup Dialog | 4 |
|
||||
| ✓ | H7 | Integration | DayDetails Tabs | 3 |
|
||||
| ☐ | H8 | Features | Undo (unbegrenzt, ohne Redo) | 2 |
|
||||
| ✓ | H8 | Features | Undo (unbegrenzt, ohne Redo) | 2 |
|
||||
| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 |
|
||||
| ☐ | H9b | Sync | SignalR komplett + Auto-Reconnect | 5 |
|
||||
| ☐ | H10 | Testing | Kern-Logik Tests | 2 |
|
||||
|
|
|
|||
|
|
@ -51,18 +51,9 @@
|
|||
</div>
|
||||
|
||||
@* Undo button *@
|
||||
@if (GameState.Value.UndoStack.Count > 0)
|
||||
{
|
||||
<div class="undo-section">
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Secondary"
|
||||
FullWidth="true"
|
||||
OnClick="HandleUndo"
|
||||
StartIcon="@Icons.Material.Filled.Undo">
|
||||
Letzten Wurf rückgängig (@GameState.Value.UndoStack.Count)
|
||||
</MudButton>
|
||||
<UndoButton />
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Error display *@
|
||||
@if (!string.IsNullOrEmpty(GameState.Value.Error))
|
||||
|
|
@ -271,18 +262,6 @@
|
|||
await OnThrowCompleted.InvokeAsync(result);
|
||||
}
|
||||
|
||||
private void HandleUndo()
|
||||
{
|
||||
if (GameState.Value.UndoStack.Count > 0)
|
||||
{
|
||||
var lastSnapshot = GameState.Value.UndoStack[^1];
|
||||
Dispatcher.Dispatch(new UndoThrowSuccessAction(
|
||||
lastSnapshot.ThrowPanel,
|
||||
lastSnapshot.Participants,
|
||||
lastSnapshot.GameModel));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ShowPlayerSelector()
|
||||
{
|
||||
await OnShowPlayerSelector.InvokeAsync();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
@using Koogle.Web.Store.GameState
|
||||
@inherits FluxorComponent
|
||||
|
||||
@inject IState<GameState> GameState
|
||||
@inject IDispatcher Dispatcher
|
||||
|
||||
@* Only show when undo is available *@
|
||||
@if (CanUndo)
|
||||
{
|
||||
<MudButton Variant="@Variant"
|
||||
Color="Color.Secondary"
|
||||
FullWidth="@FullWidth"
|
||||
Size="@Size"
|
||||
Disabled="@IsDisabled"
|
||||
OnClick="HandleUndo"
|
||||
StartIcon="@Icons.Material.Filled.Undo"
|
||||
Class="@Class">
|
||||
@ButtonText
|
||||
</MudButton>
|
||||
}
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Whether to show the button even when no undo is available.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool AlwaysShow { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Button variant (default: Outlined).
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Variant Variant { get; set; } = Variant.Outlined;
|
||||
|
||||
/// <summary>
|
||||
/// Whether button should be full width.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool FullWidth { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Button size.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Size Size { get; set; } = Size.Medium;
|
||||
|
||||
/// <summary>
|
||||
/// Additional CSS classes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? Class { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional custom button text. If not set, shows count.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string? CustomText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show the undo count in the button text.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowCount { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Callback when undo is triggered.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback OnUndoTriggered { get; set; }
|
||||
|
||||
private int UndoCount => GameState.Value.UndoStack.Count;
|
||||
private bool CanUndo => UndoCount > 0 || AlwaysShow;
|
||||
private bool IsDisabled => UndoCount == 0 || GameState.Value.IsSaving;
|
||||
|
||||
private string ButtonText
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(CustomText))
|
||||
return CustomText;
|
||||
|
||||
var baseText = "Letzten Wurf rückgängig";
|
||||
return ShowCount && UndoCount > 0
|
||||
? $"{baseText} ({UndoCount})"
|
||||
: baseText;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleUndo()
|
||||
{
|
||||
if (UndoCount == 0)
|
||||
return;
|
||||
|
||||
var lastSnapshot = GameState.Value.UndoStack[^1];
|
||||
|
||||
Dispatcher.Dispatch(new UndoThrowSuccessAction(
|
||||
lastSnapshot.ThrowPanel,
|
||||
lastSnapshot.Participants,
|
||||
lastSnapshot.GameModel));
|
||||
|
||||
await OnUndoTriggered.InvokeAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,24 +10,44 @@ namespace Koogle.Web.Store.GameState;
|
|||
public class GameEffects
|
||||
{
|
||||
private readonly ILogger<GameEffects> _logger;
|
||||
private readonly IState<GameState> _gameState;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the GameEffects class.
|
||||
/// </summary>
|
||||
public GameEffects(ILogger<GameEffects> logger)
|
||||
public GameEffects(ILogger<GameEffects> logger, IState<GameState> gameState)
|
||||
{
|
||||
_logger = logger;
|
||||
_gameState = gameState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles UndoThrowAction - pops last snapshot from stack.
|
||||
/// Handles UndoThrowAction - pops last snapshot from stack and restores state.
|
||||
/// </summary>
|
||||
[EffectMethod]
|
||||
public Task HandleUndoThrow(UndoThrowAction action, IDispatcher dispatcher)
|
||||
{
|
||||
// Note: Actual state access will be handled via IState injection in later phases
|
||||
// For now, this is a placeholder that the reducer handles directly
|
||||
_logger.LogDebug("Undo throw action received");
|
||||
var state = _gameState.Value;
|
||||
|
||||
if (state.UndoStack.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("Undo requested but undo stack is empty");
|
||||
dispatcher.Dispatch(new UndoThrowFailureAction("Keine Würfe zum Rückgängigmachen vorhanden"));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var lastSnapshot = state.UndoStack[^1];
|
||||
|
||||
_logger.LogDebug(
|
||||
"Undoing throw. Stack size before: {StackSize}, Restoring to throw counter: {ThrowCounter}",
|
||||
state.UndoStack.Count,
|
||||
lastSnapshot.ThrowPanel.TotalThrowCounter);
|
||||
|
||||
dispatcher.Dispatch(new UndoThrowSuccessAction(
|
||||
lastSnapshot.ThrowPanel,
|
||||
lastSnapshot.Participants,
|
||||
lastSnapshot.GameModel));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue