Implement milestone 2 route navigation

This commit is contained in:
2026-05-04 21:23:45 +02:00
parent c13a2ce7c7
commit def2a3f680
21 changed files with 334 additions and 193 deletions

View File

@@ -3,18 +3,31 @@ using RpgRoller.Contracts;
namespace RpgRoller.Components.Pages;
public sealed class WorkspaceSessionCoordinator(WorkspaceState state, WorkspaceFeedbackService feedback, IJSRuntime js, RpgRollerApiClient apiClient, WorkspaceQueryService workspaceQuery, Func<Guid?, Task> reloadCampaignsAsync, Func<Task> reloadCharacterCampaignOptionsAsync, Func<Task> refreshCampaignScopeAsync, Func<Task> syncStateEventsAsync, Func<Task> stopStateEventsAsync, Func<Task> ensureAdminUsersLoadedAsync, Action resetCampaignLogDetailState, Func<Task> requestRefreshAsync, Func<string?, Task> onLoggedOutAsync)
public sealed class WorkspaceSessionCoordinator(
WorkspaceState state,
WorkspaceFeedbackService feedback,
IJSRuntime js,
RpgRollerApiClient apiClient,
WorkspaceQueryService workspaceQuery,
Func<bool> isAdminRoute,
Func<Task> redirectToPlayAsync,
Func<Guid?, Task> reloadCampaignsAsync,
Func<Task> reloadCharacterCampaignOptionsAsync,
Func<Task> refreshCampaignScopeAsync,
Func<Task> syncStateEventsAsync,
Func<Task> stopStateEventsAsync,
Func<Task> ensureAdminUsersLoadedAsync,
Action resetCampaignLogDetailState,
Func<string?, Task> onLoggedOutAsync)
{
public async Task InitializeAsync()
{
var storedScreen = await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", ScreenSessionKey);
state.CurrentScreen = NormalizeRequestedScreen(storedScreen) ?? ScreenPlay;
var storedPanel = await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", MobilePanelSessionKey);
if (string.Equals(storedPanel, "log", StringComparison.OrdinalIgnoreCase))
state.MobilePanel = "log";
var storedRollVisibility = await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", RollVisibilitySessionKey);
var storedRollVisibility =
await js.InvokeAsync<string?>("rpgRollerApi.getSessionValue", RollVisibilitySessionKey);
state.RollVisibility = NormalizeRollVisibility(storedRollVisibility);
Guid? preferredCampaignId = null;
@@ -78,30 +91,6 @@ public sealed class WorkspaceSessionCoordinator(WorkspaceState state, WorkspaceF
await onLoggedOutAsync("Logged out.");
}
public async Task SwitchScreenAsync(string screen)
{
var targetScreen = NormalizeRequestedScreen(screen) ?? ScreenPlay;
if (string.Equals(targetScreen, ScreenAdmin, StringComparison.OrdinalIgnoreCase) && !state.IsCurrentUserAdmin)
targetScreen = ScreenPlay;
state.CurrentScreen = targetScreen;
state.IsScreenMenuOpen = false;
await PersistScreenPreferenceAsync(state.CurrentScreen);
await requestRefreshAsync();
if (state.User is not null)
{
await refreshCampaignScopeAsync();
await syncStateEventsAsync();
}
if (state.IsAdminScreen)
{
await ensureAdminUsersLoadedAsync();
await requestRefreshAsync();
}
}
public async Task OnRollVisibilityChangedAsync(string visibility)
{
state.RollVisibility = NormalizeRollVisibility(visibility);
@@ -168,14 +157,15 @@ public sealed class WorkspaceSessionCoordinator(WorkspaceState state, WorkspaceF
state.User = me.User;
state.ActiveCharacterId = me.ActiveCharacterId;
await EnsureScreenAccessAsync();
if (!await EnsureRouteAccessAsync())
return true;
await reloadCampaignsAsync(preferredCampaignId ?? me.CurrentCampaignId);
await reloadCharacterCampaignOptionsAsync();
await refreshCampaignScopeAsync();
await syncStateEventsAsync();
if (state.IsAdminScreen)
if (isAdminRoute())
await ensureAdminUsersLoadedAsync();
return true;
@@ -193,33 +183,17 @@ public sealed class WorkspaceSessionCoordinator(WorkspaceState state, WorkspaceF
}
}
private async Task EnsureScreenAccessAsync()
private async Task<bool> EnsureRouteAccessAsync()
{
if (state.IsCurrentUserAdmin)
return;
if (state.IsCurrentUserAdmin || !isAdminRoute())
{
return true;
}
state.AdminUsers = [];
state.HasLoadedAdminUsers = false;
if (!state.IsAdminScreen)
return;
state.CurrentScreen = ScreenPlay;
await PersistScreenPreferenceAsync(state.CurrentScreen);
}
private async Task PersistScreenPreferenceAsync(string screen)
{
try
{
await js.InvokeVoidAsync("rpgRollerApi.setSessionValue", ScreenSessionKey, screen);
}
catch (JSDisconnectedException)
{
}
catch (InvalidOperationException ex) when (IsStaticRenderInteropException(ex))
{
}
await redirectToPlayAsync();
return false;
}
private static string NormalizeRollVisibility(string? visibility)
@@ -227,29 +201,6 @@ public sealed class WorkspaceSessionCoordinator(WorkspaceState state, WorkspaceF
return string.Equals(visibility, "private", StringComparison.OrdinalIgnoreCase) ? "private" : "public";
}
private static string? NormalizeRequestedScreen(string? screen)
{
if (string.Equals(screen, ScreenAdmin, StringComparison.OrdinalIgnoreCase))
return ScreenAdmin;
if (string.Equals(screen, ScreenManagement, StringComparison.OrdinalIgnoreCase))
return ScreenManagement;
if (string.Equals(screen, ScreenPlay, StringComparison.OrdinalIgnoreCase))
return ScreenPlay;
return null;
}
private static bool IsStaticRenderInteropException(InvalidOperationException exception)
{
return exception.Message.Contains("JavaScript interop calls cannot be issued", StringComparison.OrdinalIgnoreCase);
}
private const string ScreenPlay = "play";
private const string ScreenManagement = "management";
private const string ScreenAdmin = "admin";
private const string ScreenSessionKey = "screen";
private const string CampaignSessionKey = "campaign";
private const string MobilePanelSessionKey = "play-panel";
private const string RollVisibilitySessionKey = "roll-visibility";