Extract workspace feedback service

This commit is contained in:
2026-04-04 23:57:59 +02:00
parent 4f77d4a702
commit 0124325c20
3 changed files with 61 additions and 29 deletions

View File

@@ -35,7 +35,7 @@ Frontend:
- `RpgRoller/Components/Pages/Home.razor`: minimal gateway page (loading/auth/workspace switch)
- `RpgRoller/Components/Pages/Home.razor.cs`: single `Home` code-behind with only gateway/session-view orchestration
- `RpgRoller/Components/Pages/Workspace.razor`: authenticated workspace UI and workspace-specific state/logic
- `RpgRoller/Components/Pages/WorkspaceState.cs` and `WorkspaceToast.cs`: extracted workspace UI state, toast records, and pure computed projections while `Workspace` remains the behavior owner
- `RpgRoller/Components/Pages/WorkspaceState.cs`, `WorkspaceToast.cs`, and `WorkspaceFeedbackService.cs`: extracted workspace UI state, toast records, and feedback/status handling while `Workspace` remains the behavior owner
- `RpgRoller/Components/**/*.razor.cs`: component code-behind classes (state, handlers, parameters, injected dependencies); `.razor` files remain markup-focused
- `RpgRoller/Components/Pages/Home.Models.cs`: reusable `FormState<TModel>` + page form models
- `RpgRoller/Components/Pages/HomeControls/`: auth, campaign management, play-screen, and modal controls extracted from `Home.razor`

View File

@@ -1077,41 +1077,17 @@ public partial class Workspace : IAsyncDisposable
AdminUsers = [];
HasLoadedAdminUsers = false;
IsAdminDataLoading = false;
Toasts.Clear();
Feedback.ClearToasts();
}
private void SetStatus(string message, bool isError)
{
AddToast(message, isError);
Announce(message);
Feedback.SetStatus(message, isError);
}
private void Announce(string message)
{
LiveAnnouncement = message;
}
private void AddToast(string message, bool isError)
{
var toastId = Guid.NewGuid();
Toasts.Add(new WorkspaceToast(toastId, message, isError));
_ = DismissToastLaterAsync(toastId);
}
private async Task DismissToastLaterAsync(Guid toastId)
{
await Task.Delay(ToastDurationMs);
if (Toasts.RemoveAll(toast => toast.Id == toastId) == 0)
return;
try
{
await InvokeAsync(StateHasChanged);
}
catch (ObjectDisposedException)
{
}
Feedback.Announce(message);
}
private void ToggleScreenMenu()
@@ -1220,6 +1196,7 @@ public partial class Workspace : IAsyncDisposable
private bool IsPlayScreen => State.IsPlayScreen;
private bool IsManagementScreen => State.IsManagementScreen;
private bool IsAdminScreen => State.IsAdminScreen;
private WorkspaceFeedbackService Feedback => m_Feedback ??= new(State, () => InvokeAsync(StateHasChanged));
private IReadOnlyList<AppHeaderMenuItem> HeaderMenuItems
{
get
@@ -1250,5 +1227,6 @@ public partial class Workspace : IAsyncDisposable
private const string MobilePanelSessionKey = "play-panel";
private const string RollVisibilitySessionKey = "roll-visibility";
private const int CampaignLogWindowSize = 25;
private const int ToastDurationMs = 3200;
private WorkspaceFeedbackService? m_Feedback;
}

View File

@@ -0,0 +1,54 @@
namespace RpgRoller.Components.Pages;
public sealed class WorkspaceFeedbackService
{
public WorkspaceFeedbackService(WorkspaceState state, Func<Task> requestRefreshAsync)
{
m_State = state;
m_RequestRefreshAsync = requestRefreshAsync;
}
public void SetStatus(string message, bool isError)
{
Announce(message);
AddToast(message, isError);
}
public void Announce(string message)
{
m_State.LiveAnnouncement = message;
}
public void ClearToasts()
{
m_State.Toasts.Clear();
}
private void AddToast(string message, bool isError)
{
var toastId = Guid.NewGuid();
m_State.Toasts.Add(new WorkspaceToast(toastId, message, isError));
_ = DismissToastLaterAsync(toastId);
}
private async Task DismissToastLaterAsync(Guid toastId)
{
await Task.Delay(ToastDurationMs);
if (m_State.Toasts.RemoveAll(toast => toast.Id == toastId) == 0)
return;
try
{
await m_RequestRefreshAsync();
}
catch (ObjectDisposedException)
{
}
}
private readonly Func<Task> m_RequestRefreshAsync;
private readonly WorkspaceState m_State;
private const int ToastDurationMs = 3200;
}