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 |
|
| ✓ | H5 | Games | Scheiss-Spiel + Trigger-Integration | 6 |
|
||||||
| ✓ | H6 | UI | Game Setup Dialog | 4 |
|
| ✓ | H6 | UI | Game Setup Dialog | 4 |
|
||||||
| ✓ | H7 | Integration | DayDetails Tabs | 3 |
|
| ✓ | 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 |
|
| ☐ | H9 | Persistenz | DB Persistence & Recovery | 4 |
|
||||||
| ☐ | H9b | Sync | SignalR komplett + Auto-Reconnect | 5 |
|
| ☐ | H9b | Sync | SignalR komplett + Auto-Reconnect | 5 |
|
||||||
| ☐ | H10 | Testing | Kern-Logik Tests | 2 |
|
| ☐ | H10 | Testing | Kern-Logik Tests | 2 |
|
||||||
|
|
|
||||||
|
|
@ -51,18 +51,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* Undo button *@
|
@* Undo button *@
|
||||||
@if (GameState.Value.UndoStack.Count > 0)
|
<div class="undo-section">
|
||||||
{
|
<UndoButton />
|
||||||
<div class="undo-section">
|
</div>
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* Error display *@
|
@* Error display *@
|
||||||
@if (!string.IsNullOrEmpty(GameState.Value.Error))
|
@if (!string.IsNullOrEmpty(GameState.Value.Error))
|
||||||
|
|
@ -271,18 +262,6 @@
|
||||||
await OnThrowCompleted.InvokeAsync(result);
|
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()
|
private async Task ShowPlayerSelector()
|
||||||
{
|
{
|
||||||
await OnShowPlayerSelector.InvokeAsync();
|
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
|
public class GameEffects
|
||||||
{
|
{
|
||||||
private readonly ILogger<GameEffects> _logger;
|
private readonly ILogger<GameEffects> _logger;
|
||||||
|
private readonly IState<GameState> _gameState;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the GameEffects class.
|
/// Initializes a new instance of the GameEffects class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GameEffects(ILogger<GameEffects> logger)
|
public GameEffects(ILogger<GameEffects> logger, IState<GameState> gameState)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
_gameState = gameState;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handles UndoThrowAction - pops last snapshot from stack.
|
/// Handles UndoThrowAction - pops last snapshot from stack and restores state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[EffectMethod]
|
[EffectMethod]
|
||||||
public Task HandleUndoThrow(UndoThrowAction action, IDispatcher dispatcher)
|
public Task HandleUndoThrow(UndoThrowAction action, IDispatcher dispatcher)
|
||||||
{
|
{
|
||||||
// Note: Actual state access will be handled via IState injection in later phases
|
var state = _gameState.Value;
|
||||||
// For now, this is a placeholder that the reducer handles directly
|
|
||||||
_logger.LogDebug("Undo throw action received");
|
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;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue