Simplify workspace composition root

This commit is contained in:
2026-04-05 01:19:12 +02:00
parent 6cdd29ed93
commit b291d0531f
6 changed files with 319 additions and 394 deletions

View File

@@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using RpgRoller.Components.Pages.HomeControls;
using RpgRoller.Contracts;
using RpgRoller.Domain;
namespace RpgRoller.Components.Pages;
@@ -12,7 +11,7 @@ public partial class Workspace : IAsyncDisposable
{
protected override async Task OnAfterRenderAsync(bool firstRender)
{
HasInteractiveRenderStarted = true;
State.HasInteractiveRenderStarted = true;
if (!firstRender)
return;
@@ -20,133 +19,26 @@ public partial class Workspace : IAsyncDisposable
await InvokeAsync(StateHasChanged);
}
private Task RetryAfterHealthIssueAsync()
{
return Session.RetryAfterHealthIssueAsync();
}
private Task LoadKnownUsernamesAsync()
{
return Session.LoadKnownUsernamesAsync();
}
private Task ReloadCampaignsAsync(Guid? preferredCampaignId) => Scope.ReloadCampaignsAsync(preferredCampaignId);
private Task ReloadCharacterCampaignOptionsAsync() => Scope.ReloadCharacterCampaignOptionsAsync();
private Task RefreshCampaignRosterAsync() => Scope.RefreshCampaignRosterAsync();
private Task RefreshCampaignLogAsync(Guid? afterRollId = null) => Play.RefreshCampaignLogAsync(afterRollId);
private Task RefreshCampaignScopeAsync() => Scope.RefreshCampaignScopeAsync();
private Task LogoutAsync() => Session.LogoutAsync();
private Task SwitchScreenAsync(string screen) => Session.SwitchScreenAsync(screen);
private Task SwitchToPlayAsync()
{
return SwitchScreenAsync("play");
}
private Task SwitchToManagementAsync()
{
return SwitchScreenAsync("management");
}
private Task SwitchToAdminAsync()
{
return SwitchScreenAsync(ScreenAdmin);
}
private Task EnsureAdminUsersLoadedAsync() => Admin.EnsureAdminUsersLoadedAsync();
private Task ToggleAdminRoleAsync(AdminUserSummary user) => Admin.ToggleAdminRoleAsync(user);
private Task DeleteUserAsync(AdminUserSummary user) => Admin.DeleteUserAsync(user);
private Task SetMobilePanelAsync(string panel) => Scope.SetMobilePanelAsync(panel);
private Task SetMobilePanelCharacterAsync()
{
return SetMobilePanelAsync("character");
}
private Task SetMobilePanelLogAsync()
{
return SetMobilePanelAsync("log");
}
private Task OnCampaignSelectionChangedAsync(ChangeEventArgs args) => CampaignsFlow.OnCampaignSelectionChangedAsync(args);
private Task OnCampaignCreatedAsync(Guid campaignId) => CampaignsFlow.OnCampaignCreatedAsync(campaignId);
private void OpenCreateCharacterModal() => CampaignsFlow.OpenCreateCharacterModal();
private Task OpenEditCharacterModal(CharacterSummary character) => CampaignsFlow.OpenEditCharacterModal(character);
private void CloseCharacterModals() => CampaignsFlow.CloseCharacterModals();
private Task OnCharacterCreatedAsync(Guid? campaignId) => CampaignsFlow.OnCharacterCreatedAsync(campaignId);
private Task OnCharacterUpdatedAsync(Guid? campaignId) => CampaignsFlow.OnCharacterUpdatedAsync(campaignId);
private Task DeleteSelectedCampaignAsync() => CampaignsFlow.DeleteSelectedCampaignAsync();
private Task DeleteCharacterAsync(CharacterSummary character) => CampaignsFlow.DeleteCharacterAsync(character);
private Task SelectCharacterAsync(Guid characterId) => Play.SelectCharacterAsync(characterId);
private bool CanEditCharacter(CharacterSummary character) => CampaignsFlow.CanEditCharacter(character);
private bool CanDeleteCharacter(CharacterSummary character) => CampaignsFlow.CanDeleteCharacter(character);
private Task EnsureSelectedCharacterActiveAsync() => Play.EnsureSelectedCharacterActiveAsync();
private Task RefreshSelectedCharacterSheetAsync() => Play.RefreshSelectedCharacterSheetAsync();
private Task ToggleRollDetailAsync(Guid rollId) => Play.ToggleRollDetailAsync(rollId);
private Task OnSkillCreatedAsync(Guid id) => Play.OnSkillCreatedAsync(id);
private Task OnSkillUpdatedAsync(Guid id) => Play.OnSkillUpdatedAsync(id);
private Task OnSkillGroupCreatedAsync(Guid id) => Play.OnSkillGroupCreatedAsync(id);
private Task OnSkillGroupUpdatedAsync(Guid id) => Play.OnSkillGroupUpdatedAsync(id);
private Task OnSkillDeletedAsync(Guid id) => Play.OnSkillDeletedAsync(id);
private Task OnSkillGroupDeletedAsync(Guid id) => Play.OnSkillGroupDeletedAsync(id);
private Task OnCharacterPanelErrorAsync(string message) => Play.OnCharacterPanelErrorAsync(message);
private Task OnCampaignLogPanelErrorAsync(string message) => Play.OnCampaignLogPanelErrorAsync(message);
private Task RollSkillAsync(Guid skillId) => Play.RollSkillAsync(skillId);
private Task OnCustomRollCreatedAsync(RollResult roll) => Play.OnCustomRollCreatedAsync(roll);
private Task OnRollVisibilityChanged(string visibility) => Session.OnRollVisibilityChangedAsync(visibility);
private bool CanEditSkill(CharacterSheetSkill skill) => Play.CanEditSkill(skill);
[JSInvokable]
public Task OnStateEventReceived(CampaignStateSnapshot state) => Live.OnStateEventReceivedAsync(state);
[JSInvokable]
public Task OnConnectionStateChanged(string state) => Live.OnConnectionStateChangedAsync(state);
private Task SyncStateEventsAsync() => Live.SyncStateEventsAsync();
private Task StopStateEventsAsync() => Live.StopStateEventsAsync();
public async ValueTask DisposeAsync()
{
await StopStateEventsAsync();
DotNetRef?.Dispose();
}
private bool CanEditCharacter(CharacterSummary character) => Campaigns.CanEditCharacter(character);
private void ClearAuthenticatedState() => Session.ClearAuthenticatedState();
private Task EnsureAdminUsersLoadedAsync() => Admin.EnsureAdminUsersLoadedAsync();
private Task StopStateEventsAsync() => Live.StopStateEventsAsync();
private async Task StartStateEventsCoreAsync(Guid campaignId)
{
DotNetRef ??= DotNetObjectReference.Create(this);
@@ -167,71 +59,16 @@ public partial class Workspace : IAsyncDisposable
}
}
private void ToggleScreenMenu()
{
State.IsScreenMenuOpen = !State.IsScreenMenuOpen;
}
private static bool IsStaticRenderInteropException(InvalidOperationException exception)
{
return exception.Message.Contains("statically rendered", StringComparison.OrdinalIgnoreCase);
}
private string OwnerLabel(Guid ownerUserId)
{
if (User is not null && ownerUserId == User.Id)
return "You";
if (SelectedCampaign is null)
return "Unknown owner";
if (ownerUserId == SelectedCampaign.Gm.Id)
return $"{SelectedCampaign.Gm.DisplayName} (GM)";
var ownerDisplayName = SelectedCampaign.Characters
.Where(character => character.OwnerUserId == ownerUserId)
.Select(character => character.OwnerDisplayName)
.FirstOrDefault(displayName => !string.IsNullOrWhiteSpace(displayName));
return string.IsNullOrWhiteSpace(ownerDisplayName) ? "Unknown owner" : ownerDisplayName;
}
private string SkillDefinitionLabel(CharacterSheetSkill skill)
{
if (!string.Equals(SelectedCampaign?.RulesetId, "d6", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(SelectedCampaign?.RulesetId, RulesetFormHelpers.RulesetIds.Rolemaster, StringComparison.OrdinalIgnoreCase))
return RulesetFormHelpers.DescribeRolemasterExpression(skill.DiceRollDefinition, skill.FumbleRange);
return skill.DiceRollDefinition;
}
var fumbleLabel = skill.AllowFumble ? "fumble on" : "fumble off";
return $"{skill.DiceRollDefinition}, wild {skill.WildDice}, {fumbleLabel}";
}
private CampaignRollDetail? ResolveRollDetail(Guid rollId) => Play.ResolveRollDetail(rollId);
private bool IsRollDetailLoading(Guid rollId) => Play.IsRollDetailLoading(rollId);
private string? GetRollDetailError(Guid rollId) => Play.GetRollDetailError(rollId);
private void ResetCampaignLogDetailState() => Play.ResetCampaignLogDetailState();
private void ClearAuthenticatedState() => Session.ClearAuthenticatedState();
private void SetStatus(string message, bool isError)
{
Feedback.SetStatus(message, isError);
}
private void Announce(string message)
{
Feedback.Announce(message);
}
private void ToggleScreenMenu()
{
IsScreenMenuOpen = !IsScreenMenuOpen;
}
private void ResetCampaignStateTracking() => Play.ResetCampaignStateTracking();
[Inject]
private IJSRuntime JS { get; set; } = null!;
@@ -244,97 +81,35 @@ public partial class Workspace : IAsyncDisposable
[Inject]
private NavigationManager Navigation { get; set; } = null!;
private WorkspaceState State { get; } = new();
private UserSummary? User { get => State.User; set => State.User = value; }
private Guid? ActiveCharacterId { get => State.ActiveCharacterId; set => State.ActiveCharacterId = value; }
private Guid? SelectedCampaignId { get => State.SelectedCampaignId; set => State.SelectedCampaignId = value; }
private CampaignRoster? SelectedCampaign { get => State.SelectedCampaign; set => State.SelectedCampaign = value; }
private List<CampaignSummary> Campaigns { get => State.Campaigns; set => State.Campaigns = value; }
private List<CampaignOption> CharacterCampaignOptions { get => State.CharacterCampaignOptions; set => State.CharacterCampaignOptions = value; }
private List<CharacterSheetSkill> SelectedCharacterSkills { get => State.SelectedCharacterSkills; set => State.SelectedCharacterSkills = value; }
private List<CharacterSheetSkillGroup> SelectedCharacterSkillGroups { get => State.SelectedCharacterSkillGroups; set => State.SelectedCharacterSkillGroups = value; }
private List<CampaignLogListEntry> CampaignLog { get => State.CampaignLog; set => State.CampaignLog = value; }
private List<RulesetDefinition> Rulesets { get => State.Rulesets; set => State.Rulesets = value; }
private List<AdminUserSummary> AdminUsers { get => State.AdminUsers; set => State.AdminUsers = value; }
private Guid? SelectedCharacterId { get => State.SelectedCharacterId; set => State.SelectedCharacterId = value; }
private RollResult? LastRoll { get => State.LastRoll; set => State.LastRoll = value; }
private List<string> KnownUsernames { get => State.KnownUsernames; set => State.KnownUsernames = value; }
private string RollVisibility { get => State.RollVisibility; set => State.RollVisibility = value; }
private bool IsMutating { get => State.IsMutating; set => State.IsMutating = value; }
private bool IsCampaignDataLoading { get => State.IsCampaignDataLoading; set => State.IsCampaignDataLoading = value; }
private bool IsAdminDataLoading { get => State.IsAdminDataLoading; set => State.IsAdminDataLoading = value; }
private bool HasLoadedAdminUsers { get => State.HasLoadedAdminUsers; set => State.HasLoadedAdminUsers = value; }
private bool HasHealthIssue { get => State.HasHealthIssue; set => State.HasHealthIssue = value; }
private string HealthIssueMessage { get => State.HealthIssueMessage; set => State.HealthIssueMessage = value; }
private List<WorkspaceToast> Toasts => State.Toasts;
private string CurrentScreen { get => State.CurrentScreen; set => State.CurrentScreen = value; }
private string MobilePanel { get => State.MobilePanel; set => State.MobilePanel = value; }
private string ConnectionState { get => State.ConnectionState; set => State.ConnectionState = value; }
private string LiveAnnouncement { get => State.LiveAnnouncement; set => State.LiveAnnouncement = value; }
private bool IsScreenMenuOpen { get => State.IsScreenMenuOpen; set => State.IsScreenMenuOpen = value; }
private bool ShowCreateCharacterModal { get => State.ShowCreateCharacterModal; set => State.ShowCreateCharacterModal = value; }
private bool ShowEditCharacterModal { get => State.ShowEditCharacterModal; set => State.ShowEditCharacterModal = value; }
private bool CanEditCharacterOwner { get => State.CanEditCharacterOwner; set => State.CanEditCharacterOwner = value; }
private Guid? EditingCharacterId { get => State.EditingCharacterId; set => State.EditingCharacterId = value; }
private CharacterFormModel CreateCharacterInitialModel { get => State.CreateCharacterInitialModel; set => State.CreateCharacterInitialModel = value; }
private CharacterFormModel EditCharacterInitialModel { get => State.EditCharacterInitialModel; set => State.EditCharacterInitialModel = value; }
private int CreateCharacterFormVersion { get => State.CreateCharacterFormVersion; set => State.CreateCharacterFormVersion = value; }
private int EditCharacterFormVersion { get => State.EditCharacterFormVersion; set => State.EditCharacterFormVersion = value; }
private bool StateRefreshInProgress { get => State.StateRefreshInProgress; set => State.StateRefreshInProgress = value; }
private bool HasInteractiveRenderStarted { get => State.HasInteractiveRenderStarted; set => State.HasInteractiveRenderStarted = value; }
private DotNetObjectReference<Workspace>? DotNetRef { get; set; }
private CampaignStateSnapshot? CurrentCampaignState { get => State.CurrentCampaignState; set => State.CurrentCampaignState = value; }
private Guid? CampaignLogCursor { get => State.CampaignLogCursor; set => State.CampaignLogCursor = value; }
private Guid? ExpandedCampaignLogRollId { get => State.ExpandedCampaignLogRollId; set => State.ExpandedCampaignLogRollId = value; }
private Guid? FreshCampaignLogRollId { get => State.FreshCampaignLogRollId; set => State.FreshCampaignLogRollId = value; }
private Dictionary<Guid, CampaignRollDetail> CampaignLogDetails => State.CampaignLogDetails;
private HashSet<Guid> CampaignLogDetailsLoading => State.CampaignLogDetailsLoading;
private Dictionary<Guid, string> CampaignLogDetailErrors => State.CampaignLogDetailErrors;
[Parameter]
public EventCallback<string?> LoggedOut { get; set; }
private string? SelectedCampaignName => State.SelectedCampaignName;
private CharacterSummary? SelectedCharacter => State.SelectedCharacter;
private CampaignRoster? PlaySelectedCampaign => State.PlaySelectedCampaign;
private CharacterSummary? PlaySelectedCharacter => State.PlaySelectedCharacter;
private Guid? PlaySelectedCharacterId => State.PlaySelectedCharacterId;
private List<CharacterSheetSkill> PlaySelectedCharacterSkills => State.PlaySelectedCharacterSkills;
private List<CharacterSheetSkillGroup> PlaySelectedCharacterSkillGroups => State.PlaySelectedCharacterSkillGroups;
private List<CampaignLogListEntry> PlayVisibleCampaignLog => State.PlayVisibleCampaignLog;
private bool IsCurrentUserGm => State.IsCurrentUserGm;
private bool IsCurrentUserAdmin => State.IsCurrentUserAdmin;
private bool CanDeleteSelectedCampaign => State.CanDeleteSelectedCampaign;
private bool IsSelectedCampaignD6 => State.IsSelectedCampaignD6;
private WorkspaceState State { get; } = new();
private bool IsPlayScreen => State.IsPlayScreen;
private bool IsManagementScreen => State.IsManagementScreen;
private bool IsAdminScreen => State.IsAdminScreen;
private WorkspaceCampaignScopeCoordinator Scope => m_Scope ??= new(
State,
Feedback,
JS,
WorkspaceQuery,
EnsureSelectedCharacterActiveAsync,
RefreshSelectedCharacterSheetAsync,
RefreshCampaignLogAsync,
ResetCampaignLogDetailState,
ResetCampaignStateTracking,
Play.EnsureSelectedCharacterActiveAsync,
Play.RefreshSelectedCharacterSheetAsync,
Play.RefreshCampaignLogAsync,
Play.ResetCampaignLogDetailState,
Play.ResetCampaignStateTracking,
ClearAuthenticatedState,
StopStateEventsAsync,
message => LoggedOut.InvokeAsync(message));
private WorkspaceLiveStateController Live => m_Live ??= new(
State,
Feedback,
StartStateEventsCoreAsync,
StopStateEventsCoreAsync,
RefreshCampaignRosterAsync,
RefreshSelectedCharacterSheetAsync,
RefreshCampaignLogAsync,
Scope.RefreshCampaignRosterAsync,
Play.RefreshSelectedCharacterSheetAsync,
Play.RefreshCampaignLogAsync,
() => InvokeAsync(StateHasChanged));
private WorkspacePlayCoordinator Play => m_Play ??= new(
State,
Feedback,
@@ -342,16 +117,18 @@ public partial class Workspace : IAsyncDisposable
WorkspaceQuery,
CanEditCharacter,
() => InvokeAsync(StateHasChanged));
private WorkspaceCampaignCoordinator CampaignsFlow => m_CampaignsFlow ??= new(
private WorkspaceCampaignCoordinator Campaigns => m_Campaigns ??= new(
State,
Feedback,
JS,
ApiClient,
LoadKnownUsernamesAsync,
ReloadCampaignsAsync,
ReloadCharacterCampaignOptionsAsync,
RefreshCampaignScopeAsync,
SyncStateEventsAsync);
Session.LoadKnownUsernamesAsync,
Scope.ReloadCampaignsAsync,
Scope.ReloadCharacterCampaignOptionsAsync,
Scope.RefreshCampaignScopeAsync,
Live.SyncStateEventsAsync);
private WorkspaceAdminCoordinator Admin => m_Admin ??= new(
State,
Feedback,
@@ -361,50 +138,51 @@ public partial class Workspace : IAsyncDisposable
ClearAuthenticatedState,
StopStateEventsAsync,
message => LoggedOut.InvokeAsync(message));
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,
Scope.ReloadCampaignsAsync,
Scope.ReloadCharacterCampaignOptionsAsync,
Scope.RefreshCampaignScopeAsync,
Live.SyncStateEventsAsync,
Live.StopStateEventsAsync,
EnsureAdminUsersLoadedAsync,
ResetCampaignLogDetailState,
Play.ResetCampaignLogDetailState,
() => InvokeAsync(StateHasChanged),
message => LoggedOut.InvokeAsync(message));
private IReadOnlyList<AppHeaderMenuItem> HeaderMenuItems
{
get
{
var items = new List<AppHeaderMenuItem>
{
new() { Label = "Play", IsActive = IsPlayScreen, OnSelected = SwitchToPlayAsync },
new() { Label = "Campaign Management", IsActive = IsManagementScreen, OnSelected = SwitchToManagementAsync }
new() { Label = "Play", IsActive = State.IsPlayScreen, OnSelected = () => Session.SwitchScreenAsync("play") },
new() { Label = "Campaign Management", IsActive = State.IsManagementScreen, OnSelected = () => Session.SwitchScreenAsync("management") }
};
if (IsCurrentUserAdmin)
items.Add(new AppHeaderMenuItem { Label = "Admin", IsActive = IsAdminScreen, OnSelected = SwitchToAdminAsync });
if (State.IsCurrentUserAdmin)
items.Add(new AppHeaderMenuItem { Label = "Admin", IsActive = State.IsAdminScreen, OnSelected = () => Session.SwitchScreenAsync(ScreenAdmin) });
return items;
}
}
private string ConnectionStateLabel => State.ConnectionStateLabel;
private string ConnectionStateCssClass => State.ConnectionStateCssClass;
private string AppCssClass => State.AppCssClass;
private string AdminDatabaseDownloadUrl => Navigation.ToAbsoluteUri("api/admin/database").ToString();
private DotNetObjectReference<Workspace>? DotNetRef { get; set; }
private const string ScreenAdmin = "admin";
private WorkspaceCampaignScopeCoordinator? m_Scope;
private WorkspaceLiveStateController? m_Live;
private WorkspacePlayCoordinator? m_Play;
private WorkspaceCampaignCoordinator? m_CampaignsFlow;
private WorkspaceCampaignCoordinator? m_Campaigns;
private WorkspaceAdminCoordinator? m_Admin;
private WorkspaceFeedbackService? m_Feedback;
private WorkspaceSessionCoordinator? m_Session;