Extract workspace session coordinator

This commit is contained in:
2026-04-05 00:03:21 +02:00
parent 0124325c20
commit 4af1c87639
3 changed files with 329 additions and 232 deletions

View File

@@ -16,114 +16,18 @@ public partial class Workspace : IAsyncDisposable
if (!firstRender)
return;
await InitializeAsync();
await Session.InitializeAsync();
await InvokeAsync(StateHasChanged);
}
private async Task InitializeAsync()
private Task RetryAfterHealthIssueAsync()
{
var storedScreen = await JS.InvokeAsync<string?>("rpgRollerApi.getSessionValue", ScreenSessionKey);
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.");
return Session.RetryAfterHealthIssueAsync();
}
private async Task RetryAfterHealthIssueAsync()
private Task LoadKnownUsernamesAsync()
{
await CheckHealthAsync();
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;
}
return Session.LoadKnownUsernamesAsync();
}
private async Task ReloadCampaignsAsync(Guid? preferredCampaignId)
@@ -263,52 +167,9 @@ public partial class Workspace : IAsyncDisposable
}
}
private async Task LogoutAsync()
{
if (IsMutating)
return;
private Task LogoutAsync() => Session.LogoutAsync();
IsMutating = true;
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 SwitchScreenAsync(string screen) => Session.SwitchScreenAsync(screen);
private Task SwitchToPlayAsync()
{
@@ -325,21 +186,6 @@ public partial class Workspace : IAsyncDisposable
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()
{
if (!IsCurrentUserAdmin || HasLoadedAdminUsers || IsAdminDataLoading)
@@ -762,44 +608,7 @@ public partial class Workspace : IAsyncDisposable
Announce("Roll result updated.");
}
private async Task OnRollVisibilityChanged(string 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 Task OnRollVisibilityChanged(string visibility) => Session.OnRollVisibilityChangedAsync(visibility);
private bool CanEditSkill(CharacterSheetSkill skill)
{
@@ -1051,34 +860,7 @@ public partial class Workspace : IAsyncDisposable
FreshCampaignLogRollId = rollId;
}
private void 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 ClearAuthenticatedState() => Session.ClearAuthenticatedState();
private void SetStatus(string message, bool isError)
{
@@ -1197,6 +979,21 @@ public partial class Workspace : IAsyncDisposable
private bool IsManagementScreen => State.IsManagementScreen;
private bool IsAdminScreen => State.IsAdminScreen;
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
{
get
@@ -1219,14 +1016,11 @@ public partial class Workspace : IAsyncDisposable
private string AppCssClass => State.AppCssClass;
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 ScreenSessionKey = "screen";
private const string CampaignSessionKey = "campaign";
private const string MobilePanelSessionKey = "play-panel";
private const string RollVisibilitySessionKey = "roll-visibility";
private const int CampaignLogWindowSize = 25;
private WorkspaceFeedbackService? m_Feedback;
private WorkspaceSessionCoordinator? m_Session;
}