Extract workspace session coordinator
This commit is contained in:
@@ -35,7 +35,7 @@ Frontend:
|
|||||||
- `RpgRoller/Components/Pages/Home.razor`: minimal gateway page (loading/auth/workspace switch)
|
- `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/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/Workspace.razor`: authenticated workspace UI and workspace-specific state/logic
|
||||||
- `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/Pages/WorkspaceState.cs`, `WorkspaceToast.cs`, `WorkspaceFeedbackService.cs`, and `WorkspaceSessionCoordinator.cs`: extracted workspace UI state, toast records, feedback/status handling, and session/bootstrap orchestration 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/**/*.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/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`
|
- `RpgRoller/Components/Pages/HomeControls/`: auth, campaign management, play-screen, and modal controls extracted from `Home.razor`
|
||||||
|
|||||||
@@ -16,114 +16,18 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
if (!firstRender)
|
if (!firstRender)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await InitializeAsync();
|
await Session.InitializeAsync();
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InitializeAsync()
|
private Task RetryAfterHealthIssueAsync()
|
||||||
{
|
{
|
||||||
var storedScreen = await JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", ScreenSessionKey);
|
return Session.RetryAfterHealthIssueAsync();
|
||||||
CurrentScreen = NormalizeRequestedScreen(storedScreen) ?? ScreenPlay;
|
|
||||||
|
|
||||||
var storedPanel = await JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", MobilePanelSessionKey);
|
|
||||||
if (string.Equals(storedPanel, "log", StringComparison.OrdinalIgnoreCase))
|
|
||||||
MobilePanel = "log";
|
|
||||||
|
|
||||||
var storedRollVisibility = await JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", RollVisibilitySessionKey);
|
|
||||||
RollVisibility = NormalizeRollVisibility(storedRollVisibility);
|
|
||||||
|
|
||||||
Guid? preferredCampaignId = null;
|
|
||||||
var storedCampaignId = await JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", CampaignSessionKey);
|
|
||||||
if (Guid.TryParse(storedCampaignId, out var parsedCampaignId))
|
|
||||||
preferredCampaignId = parsedCampaignId;
|
|
||||||
|
|
||||||
await CheckHealthAsync();
|
|
||||||
await LoadRulesetsAsync();
|
|
||||||
|
|
||||||
var reloaded = await ReloadAuthenticatedSessionAsync(preferredCampaignId);
|
|
||||||
if (!reloaded)
|
|
||||||
await LoggedOut.InvokeAsync("Session expired. Please log in again.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RetryAfterHealthIssueAsync()
|
private Task LoadKnownUsernamesAsync()
|
||||||
{
|
{
|
||||||
await CheckHealthAsync();
|
return Session.LoadKnownUsernamesAsync();
|
||||||
if (!HasHealthIssue && User is not null)
|
|
||||||
{
|
|
||||||
var reloaded = await ReloadAuthenticatedSessionAsync(SelectedCampaignId);
|
|
||||||
if (!reloaded)
|
|
||||||
await LoggedOut.InvokeAsync("Session expired. Please log in again.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckHealthAsync()
|
|
||||||
{
|
|
||||||
HasHealthIssue = false;
|
|
||||||
HealthIssueMessage = string.Empty;
|
|
||||||
await Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadRulesetsAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Rulesets = (await WorkspaceQuery.GetRulesetsAsync()).ToList();
|
|
||||||
}
|
|
||||||
catch (ApiRequestException ex)
|
|
||||||
{
|
|
||||||
SetStatus(ex.Message, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task LoadKnownUsernamesAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var usernames = await WorkspaceQuery.GetUsernamesAsync();
|
|
||||||
KnownUsernames = usernames.OrderBy(username => username, StringComparer.OrdinalIgnoreCase).ToList();
|
|
||||||
}
|
|
||||||
catch (ApiRequestException ex)
|
|
||||||
{
|
|
||||||
KnownUsernames = [];
|
|
||||||
SetStatus(ex.Message, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> ReloadAuthenticatedSessionAsync(Guid? preferredCampaignId)
|
|
||||||
{
|
|
||||||
var me = await TryGetMeAsync();
|
|
||||||
if (me is null)
|
|
||||||
{
|
|
||||||
ClearAuthenticatedState();
|
|
||||||
await StopStateEventsAsync();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
User = me.User;
|
|
||||||
ActiveCharacterId = me.ActiveCharacterId;
|
|
||||||
await EnsureScreenAccessAsync();
|
|
||||||
|
|
||||||
await ReloadCampaignsAsync(preferredCampaignId ?? me.CurrentCampaignId);
|
|
||||||
await ReloadCharacterCampaignOptionsAsync();
|
|
||||||
await RefreshCampaignScopeAsync();
|
|
||||||
await SyncStateEventsAsync();
|
|
||||||
|
|
||||||
if (IsAdminScreen)
|
|
||||||
await EnsureAdminUsersLoadedAsync();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<MeResponse?> TryGetMeAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await WorkspaceQuery.GetMeAsync();
|
|
||||||
}
|
|
||||||
catch (ApiRequestException ex) when (ex.StatusCode == 401)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ReloadCampaignsAsync(Guid? preferredCampaignId)
|
private async Task ReloadCampaignsAsync(Guid? preferredCampaignId)
|
||||||
@@ -263,52 +167,9 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LogoutAsync()
|
private Task LogoutAsync() => Session.LogoutAsync();
|
||||||
{
|
|
||||||
if (IsMutating)
|
|
||||||
return;
|
|
||||||
|
|
||||||
IsMutating = true;
|
private Task SwitchScreenAsync(string screen) => Session.SwitchScreenAsync(screen);
|
||||||
try
|
|
||||||
{
|
|
||||||
await ApiClient.RequestWithoutPayloadAsync("POST", "/api/auth/logout");
|
|
||||||
}
|
|
||||||
catch (ApiRequestException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IsMutating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClearAuthenticatedState();
|
|
||||||
await StopStateEventsAsync();
|
|
||||||
await LoggedOut.InvokeAsync("Logged out.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task SwitchScreenAsync(string screen)
|
|
||||||
{
|
|
||||||
var targetScreen = NormalizeRequestedScreen(screen) ?? ScreenPlay;
|
|
||||||
if (string.Equals(targetScreen, ScreenAdmin, StringComparison.OrdinalIgnoreCase) && !IsCurrentUserAdmin)
|
|
||||||
targetScreen = ScreenPlay;
|
|
||||||
|
|
||||||
CurrentScreen = targetScreen;
|
|
||||||
IsScreenMenuOpen = false;
|
|
||||||
await PersistScreenPreferenceAsync(CurrentScreen);
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
|
|
||||||
if (User is not null)
|
|
||||||
{
|
|
||||||
await RefreshCampaignScopeAsync();
|
|
||||||
await SyncStateEventsAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsAdminScreen)
|
|
||||||
{
|
|
||||||
await EnsureAdminUsersLoadedAsync();
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Task SwitchToPlayAsync()
|
private Task SwitchToPlayAsync()
|
||||||
{
|
{
|
||||||
@@ -325,21 +186,6 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
return SwitchScreenAsync(ScreenAdmin);
|
return SwitchScreenAsync(ScreenAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task EnsureScreenAccessAsync()
|
|
||||||
{
|
|
||||||
if (IsCurrentUserAdmin)
|
|
||||||
return;
|
|
||||||
|
|
||||||
AdminUsers = [];
|
|
||||||
HasLoadedAdminUsers = false;
|
|
||||||
|
|
||||||
if (!IsAdminScreen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
CurrentScreen = ScreenPlay;
|
|
||||||
await PersistScreenPreferenceAsync(CurrentScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task EnsureAdminUsersLoadedAsync()
|
private async Task EnsureAdminUsersLoadedAsync()
|
||||||
{
|
{
|
||||||
if (!IsCurrentUserAdmin || HasLoadedAdminUsers || IsAdminDataLoading)
|
if (!IsCurrentUserAdmin || HasLoadedAdminUsers || IsAdminDataLoading)
|
||||||
@@ -762,44 +608,7 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
Announce("Roll result updated.");
|
Announce("Roll result updated.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnRollVisibilityChanged(string visibility)
|
private Task OnRollVisibilityChanged(string visibility) => Session.OnRollVisibilityChangedAsync(visibility);
|
||||||
{
|
|
||||||
RollVisibility = NormalizeRollVisibility(visibility);
|
|
||||||
await JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", RollVisibilitySessionKey, RollVisibility);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string NormalizeRollVisibility(string? visibility)
|
|
||||||
{
|
|
||||||
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, "management", StringComparison.OrdinalIgnoreCase))
|
|
||||||
return "management";
|
|
||||||
|
|
||||||
if (string.Equals(screen, ScreenPlay, StringComparison.OrdinalIgnoreCase))
|
|
||||||
return ScreenPlay;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task PersistScreenPreferenceAsync(string screen)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", ScreenSessionKey, screen);
|
|
||||||
}
|
|
||||||
catch (JSDisconnectedException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex) when (IsStaticRenderInteropException(ex))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CanEditSkill(CharacterSheetSkill skill)
|
private bool CanEditSkill(CharacterSheetSkill skill)
|
||||||
{
|
{
|
||||||
@@ -1051,34 +860,7 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
FreshCampaignLogRollId = rollId;
|
FreshCampaignLogRollId = rollId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearAuthenticatedState()
|
private void ClearAuthenticatedState() => Session.ClearAuthenticatedState();
|
||||||
{
|
|
||||||
User = null;
|
|
||||||
ActiveCharacterId = null;
|
|
||||||
SelectedCampaignId = null;
|
|
||||||
SelectedCampaign = null;
|
|
||||||
Campaigns = [];
|
|
||||||
CharacterCampaignOptions = [];
|
|
||||||
SelectedCharacterSkills = [];
|
|
||||||
SelectedCharacterSkillGroups = [];
|
|
||||||
CampaignLog = [];
|
|
||||||
CampaignLogCursor = null;
|
|
||||||
ResetCampaignLogDetailState();
|
|
||||||
SelectedCharacterId = null;
|
|
||||||
LastRoll = null;
|
|
||||||
KnownUsernames = [];
|
|
||||||
ShowCreateCharacterModal = false;
|
|
||||||
ShowEditCharacterModal = false;
|
|
||||||
CanEditCharacterOwner = false;
|
|
||||||
CreateCharacterInitialModel = new();
|
|
||||||
EditCharacterInitialModel = new();
|
|
||||||
CreateCharacterFormVersion = 0;
|
|
||||||
EditCharacterFormVersion = 0;
|
|
||||||
AdminUsers = [];
|
|
||||||
HasLoadedAdminUsers = false;
|
|
||||||
IsAdminDataLoading = false;
|
|
||||||
Feedback.ClearToasts();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetStatus(string message, bool isError)
|
private void SetStatus(string message, bool isError)
|
||||||
{
|
{
|
||||||
@@ -1197,6 +979,21 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
private bool IsManagementScreen => State.IsManagementScreen;
|
private bool IsManagementScreen => State.IsManagementScreen;
|
||||||
private bool IsAdminScreen => State.IsAdminScreen;
|
private bool IsAdminScreen => State.IsAdminScreen;
|
||||||
private WorkspaceFeedbackService Feedback => m_Feedback ??= new(State, () => InvokeAsync(StateHasChanged));
|
private WorkspaceFeedbackService Feedback => m_Feedback ??= new(State, () => InvokeAsync(StateHasChanged));
|
||||||
|
private WorkspaceSessionCoordinator Session => m_Session ??= new(
|
||||||
|
State,
|
||||||
|
Feedback,
|
||||||
|
JS,
|
||||||
|
ApiClient,
|
||||||
|
WorkspaceQuery,
|
||||||
|
ReloadCampaignsAsync,
|
||||||
|
ReloadCharacterCampaignOptionsAsync,
|
||||||
|
RefreshCampaignScopeAsync,
|
||||||
|
SyncStateEventsAsync,
|
||||||
|
StopStateEventsAsync,
|
||||||
|
EnsureAdminUsersLoadedAsync,
|
||||||
|
ResetCampaignLogDetailState,
|
||||||
|
() => InvokeAsync(StateHasChanged),
|
||||||
|
message => LoggedOut.InvokeAsync(message));
|
||||||
private IReadOnlyList<AppHeaderMenuItem> HeaderMenuItems
|
private IReadOnlyList<AppHeaderMenuItem> HeaderMenuItems
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
@@ -1219,14 +1016,11 @@ public partial class Workspace : IAsyncDisposable
|
|||||||
private string AppCssClass => State.AppCssClass;
|
private string AppCssClass => State.AppCssClass;
|
||||||
private string AdminDatabaseDownloadUrl => Navigation.ToAbsoluteUri("api/admin/database").ToString();
|
private string AdminDatabaseDownloadUrl => Navigation.ToAbsoluteUri("api/admin/database").ToString();
|
||||||
|
|
||||||
private const string ScreenPlay = "play";
|
|
||||||
private const string ScreenManagement = "management";
|
|
||||||
private const string ScreenAdmin = "admin";
|
private const string ScreenAdmin = "admin";
|
||||||
private const string ScreenSessionKey = "screen";
|
|
||||||
private const string CampaignSessionKey = "campaign";
|
private const string CampaignSessionKey = "campaign";
|
||||||
private const string MobilePanelSessionKey = "play-panel";
|
private const string MobilePanelSessionKey = "play-panel";
|
||||||
private const string RollVisibilitySessionKey = "roll-visibility";
|
|
||||||
private const int CampaignLogWindowSize = 25;
|
private const int CampaignLogWindowSize = 25;
|
||||||
|
|
||||||
private WorkspaceFeedbackService? m_Feedback;
|
private WorkspaceFeedbackService? m_Feedback;
|
||||||
|
private WorkspaceSessionCoordinator? m_Session;
|
||||||
}
|
}
|
||||||
|
|||||||
303
RpgRoller/Components/Pages/WorkspaceSessionCoordinator.cs
Normal file
303
RpgRoller/Components/Pages/WorkspaceSessionCoordinator.cs
Normal file
@@ -0,0 +1,303 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
using RpgRoller.Contracts;
|
||||||
|
|
||||||
|
namespace RpgRoller.Components.Pages;
|
||||||
|
|
||||||
|
public sealed class WorkspaceSessionCoordinator
|
||||||
|
{
|
||||||
|
public 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)
|
||||||
|
{
|
||||||
|
m_State = state;
|
||||||
|
m_Feedback = feedback;
|
||||||
|
m_JS = js;
|
||||||
|
m_ApiClient = apiClient;
|
||||||
|
m_WorkspaceQuery = workspaceQuery;
|
||||||
|
m_ReloadCampaignsAsync = reloadCampaignsAsync;
|
||||||
|
m_ReloadCharacterCampaignOptionsAsync = reloadCharacterCampaignOptionsAsync;
|
||||||
|
m_RefreshCampaignScopeAsync = refreshCampaignScopeAsync;
|
||||||
|
m_SyncStateEventsAsync = syncStateEventsAsync;
|
||||||
|
m_StopStateEventsAsync = stopStateEventsAsync;
|
||||||
|
m_EnsureAdminUsersLoadedAsync = ensureAdminUsersLoadedAsync;
|
||||||
|
m_ResetCampaignLogDetailState = resetCampaignLogDetailState;
|
||||||
|
m_RequestRefreshAsync = requestRefreshAsync;
|
||||||
|
m_OnLoggedOutAsync = onLoggedOutAsync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitializeAsync()
|
||||||
|
{
|
||||||
|
var storedScreen = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", ScreenSessionKey);
|
||||||
|
m_State.CurrentScreen = NormalizeRequestedScreen(storedScreen) ?? ScreenPlay;
|
||||||
|
|
||||||
|
var storedPanel = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", MobilePanelSessionKey);
|
||||||
|
if (string.Equals(storedPanel, "log", StringComparison.OrdinalIgnoreCase))
|
||||||
|
m_State.MobilePanel = "log";
|
||||||
|
|
||||||
|
var storedRollVisibility = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", RollVisibilitySessionKey);
|
||||||
|
m_State.RollVisibility = NormalizeRollVisibility(storedRollVisibility);
|
||||||
|
|
||||||
|
Guid? preferredCampaignId = null;
|
||||||
|
var storedCampaignId = await m_JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", CampaignSessionKey);
|
||||||
|
if (Guid.TryParse(storedCampaignId, out var parsedCampaignId))
|
||||||
|
preferredCampaignId = parsedCampaignId;
|
||||||
|
|
||||||
|
await CheckHealthAsync();
|
||||||
|
await LoadRulesetsAsync();
|
||||||
|
|
||||||
|
var reloaded = await ReloadAuthenticatedSessionAsync(preferredCampaignId);
|
||||||
|
if (!reloaded)
|
||||||
|
await m_OnLoggedOutAsync("Session expired. Please log in again.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RetryAfterHealthIssueAsync()
|
||||||
|
{
|
||||||
|
await CheckHealthAsync();
|
||||||
|
if (!m_State.HasHealthIssue && m_State.User is not null)
|
||||||
|
{
|
||||||
|
var reloaded = await ReloadAuthenticatedSessionAsync(m_State.SelectedCampaignId);
|
||||||
|
if (!reloaded)
|
||||||
|
await m_OnLoggedOutAsync("Session expired. Please log in again.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LoadKnownUsernamesAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var usernames = await m_WorkspaceQuery.GetUsernamesAsync();
|
||||||
|
m_State.KnownUsernames = usernames.OrderBy(username => username, StringComparer.OrdinalIgnoreCase).ToList();
|
||||||
|
}
|
||||||
|
catch (ApiRequestException ex)
|
||||||
|
{
|
||||||
|
m_State.KnownUsernames = [];
|
||||||
|
m_Feedback.SetStatus(ex.Message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task LogoutAsync()
|
||||||
|
{
|
||||||
|
if (m_State.IsMutating)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_State.IsMutating = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await m_ApiClient.RequestWithoutPayloadAsync("POST", "/api/auth/logout");
|
||||||
|
}
|
||||||
|
catch (ApiRequestException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
m_State.IsMutating = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClearAuthenticatedState();
|
||||||
|
await m_StopStateEventsAsync();
|
||||||
|
await m_OnLoggedOutAsync("Logged out.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SwitchScreenAsync(string screen)
|
||||||
|
{
|
||||||
|
var targetScreen = NormalizeRequestedScreen(screen) ?? ScreenPlay;
|
||||||
|
if (string.Equals(targetScreen, ScreenAdmin, StringComparison.OrdinalIgnoreCase) && !m_State.IsCurrentUserAdmin)
|
||||||
|
targetScreen = ScreenPlay;
|
||||||
|
|
||||||
|
m_State.CurrentScreen = targetScreen;
|
||||||
|
m_State.IsScreenMenuOpen = false;
|
||||||
|
await PersistScreenPreferenceAsync(m_State.CurrentScreen);
|
||||||
|
await m_RequestRefreshAsync();
|
||||||
|
|
||||||
|
if (m_State.User is not null)
|
||||||
|
{
|
||||||
|
await m_RefreshCampaignScopeAsync();
|
||||||
|
await m_SyncStateEventsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_State.IsAdminScreen)
|
||||||
|
{
|
||||||
|
await m_EnsureAdminUsersLoadedAsync();
|
||||||
|
await m_RequestRefreshAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnRollVisibilityChangedAsync(string visibility)
|
||||||
|
{
|
||||||
|
m_State.RollVisibility = NormalizeRollVisibility(visibility);
|
||||||
|
await m_JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", RollVisibilitySessionKey, m_State.RollVisibility);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearAuthenticatedState()
|
||||||
|
{
|
||||||
|
m_State.User = null;
|
||||||
|
m_State.ActiveCharacterId = null;
|
||||||
|
m_State.SelectedCampaignId = null;
|
||||||
|
m_State.SelectedCampaign = null;
|
||||||
|
m_State.Campaigns = [];
|
||||||
|
m_State.CharacterCampaignOptions = [];
|
||||||
|
m_State.SelectedCharacterSkills = [];
|
||||||
|
m_State.SelectedCharacterSkillGroups = [];
|
||||||
|
m_State.CampaignLog = [];
|
||||||
|
m_State.CampaignLogCursor = null;
|
||||||
|
m_ResetCampaignLogDetailState();
|
||||||
|
m_State.SelectedCharacterId = null;
|
||||||
|
m_State.LastRoll = null;
|
||||||
|
m_State.KnownUsernames = [];
|
||||||
|
m_State.ShowCreateCharacterModal = false;
|
||||||
|
m_State.ShowEditCharacterModal = false;
|
||||||
|
m_State.CanEditCharacterOwner = false;
|
||||||
|
m_State.CreateCharacterInitialModel = new();
|
||||||
|
m_State.EditCharacterInitialModel = new();
|
||||||
|
m_State.CreateCharacterFormVersion = 0;
|
||||||
|
m_State.EditCharacterFormVersion = 0;
|
||||||
|
m_State.AdminUsers = [];
|
||||||
|
m_State.HasLoadedAdminUsers = false;
|
||||||
|
m_State.IsAdminDataLoading = false;
|
||||||
|
m_Feedback.ClearToasts();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CheckHealthAsync()
|
||||||
|
{
|
||||||
|
m_State.HasHealthIssue = false;
|
||||||
|
m_State.HealthIssueMessage = string.Empty;
|
||||||
|
await Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadRulesetsAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_State.Rulesets = (await m_WorkspaceQuery.GetRulesetsAsync()).ToList();
|
||||||
|
}
|
||||||
|
catch (ApiRequestException ex)
|
||||||
|
{
|
||||||
|
m_Feedback.SetStatus(ex.Message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> ReloadAuthenticatedSessionAsync(Guid? preferredCampaignId)
|
||||||
|
{
|
||||||
|
var me = await TryGetMeAsync();
|
||||||
|
if (me is null)
|
||||||
|
{
|
||||||
|
ClearAuthenticatedState();
|
||||||
|
await m_StopStateEventsAsync();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_State.User = me.User;
|
||||||
|
m_State.ActiveCharacterId = me.ActiveCharacterId;
|
||||||
|
await EnsureScreenAccessAsync();
|
||||||
|
|
||||||
|
await m_ReloadCampaignsAsync(preferredCampaignId ?? me.CurrentCampaignId);
|
||||||
|
await m_ReloadCharacterCampaignOptionsAsync();
|
||||||
|
await m_RefreshCampaignScopeAsync();
|
||||||
|
await m_SyncStateEventsAsync();
|
||||||
|
|
||||||
|
if (m_State.IsAdminScreen)
|
||||||
|
await m_EnsureAdminUsersLoadedAsync();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<MeResponse?> TryGetMeAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await m_WorkspaceQuery.GetMeAsync();
|
||||||
|
}
|
||||||
|
catch (ApiRequestException ex) when (ex.StatusCode == 401)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureScreenAccessAsync()
|
||||||
|
{
|
||||||
|
if (m_State.IsCurrentUserAdmin)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_State.AdminUsers = [];
|
||||||
|
m_State.HasLoadedAdminUsers = false;
|
||||||
|
|
||||||
|
if (!m_State.IsAdminScreen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_State.CurrentScreen = ScreenPlay;
|
||||||
|
await PersistScreenPreferenceAsync(m_State.CurrentScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PersistScreenPreferenceAsync(string screen)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await m_JS.InvokeVoidAsync("rpgRollerApi.setSessionValue", ScreenSessionKey, screen);
|
||||||
|
}
|
||||||
|
catch (JSDisconnectedException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException ex) when (IsStaticRenderInteropException(ex))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string NormalizeRollVisibility(string? visibility)
|
||||||
|
{
|
||||||
|
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 readonly RpgRollerApiClient m_ApiClient;
|
||||||
|
private readonly WorkspaceFeedbackService m_Feedback;
|
||||||
|
private readonly Func<Task> m_EnsureAdminUsersLoadedAsync;
|
||||||
|
private readonly IJSRuntime m_JS;
|
||||||
|
private readonly Func<string?, Task> m_OnLoggedOutAsync;
|
||||||
|
private readonly Func<Task> m_ReloadCharacterCampaignOptionsAsync;
|
||||||
|
private readonly Func<Guid?, Task> m_ReloadCampaignsAsync;
|
||||||
|
private readonly Action m_ResetCampaignLogDetailState;
|
||||||
|
private readonly Func<Task> m_RefreshCampaignScopeAsync;
|
||||||
|
private readonly Func<Task> m_RequestRefreshAsync;
|
||||||
|
private readonly WorkspaceState m_State;
|
||||||
|
private readonly Func<Task> m_StopStateEventsAsync;
|
||||||
|
private readonly Func<Task> m_SyncStateEventsAsync;
|
||||||
|
private readonly WorkspaceQueryService m_WorkspaceQuery;
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user