diff --git a/FRONTEND_PROGRESS.md b/FRONTEND_PROGRESS.md index a0fdf67..33f8405 100644 --- a/FRONTEND_PROGRESS.md +++ b/FRONTEND_PROGRESS.md @@ -9,6 +9,7 @@ Tracking against `UX.md` tasks and decisions. - Legacy TypeScript frontend/runtime artifacts: removed - Home page orchestration split by concern (`Home.*.cs` partials + `HomeControls/*`) to reduce merge churn and keep auth/campaign/character/skill flows isolated. - Concern controls now own their local form state and mutation workflows; `Home` handles shared cross-control state refresh. +- Skill create/edit flow is now owned by `CharacterPanel` (where characters and their skills are presented together). ## UX Checklist diff --git a/README.md b/README.md index 5f605b5..aaf5b90 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Frontend: - `RpgRoller/Components/Pages/Home.Models.cs`: reusable `FormState` + page form models - `RpgRoller/Components/Pages/HomeControls/`: auth, campaign management, play-screen, and modal controls extracted from `Home.razor` - Form ownership model: controls own transient form/error state and execute their concern-specific API mutations directly +- Skill create/edit workflow ownership: `CharacterPanel` (characters own skills in UI and behavior) - `RpgRoller/Components/RpgRollerApiClient.cs`: shared browser API client used by `Home` and leaf controls - `RpgRoller/wwwroot/js/rpgroller-api.js`: browser-side API + SSE + session storage interop for Blazor - `RpgRoller/wwwroot/styles.css`: responsive UX styling and theme tokens diff --git a/RpgRoller/Components/Pages/Home.Presentation.cs b/RpgRoller/Components/Pages/Home.Presentation.cs index 354f375..6045895 100644 --- a/RpgRoller/Components/Pages/Home.Presentation.cs +++ b/RpgRoller/Components/Pages/Home.Presentation.cs @@ -153,16 +153,10 @@ public partial class Home LastRoll = null; ShowCreateCharacterModal = false; ShowEditCharacterModal = false; - ShowCreateSkillModal = false; - ShowEditSkillModal = false; CreateCharacterInitialModel = new(); EditCharacterInitialModel = new(); - CreateSkillInitialModel = new(); - EditSkillInitialModel = new(); CreateCharacterFormVersion = 0; EditCharacterFormVersion = 0; - CreateSkillFormVersion = 0; - EditSkillFormVersion = 0; } private void SetStatus(string message, bool isError) diff --git a/RpgRoller/Components/Pages/Home.Skill.cs b/RpgRoller/Components/Pages/Home.Skill.cs index f084160..ad27c6e 100644 --- a/RpgRoller/Components/Pages/Home.Skill.cs +++ b/RpgRoller/Components/Pages/Home.Skill.cs @@ -4,49 +4,8 @@ namespace RpgRoller.Components.Pages; public partial class Home { - private void OpenCreateSkillModal() - { - CreateSkillInitialModel = new SkillFormModel - { - Name = string.Empty, - DiceRollDefinition = string.Empty, - WildDice = IsSelectedCampaignD6 ? 1 : 0, - AllowFumble = IsSelectedCampaignD6 - }; - CreateSkillFormVersion++; - ShowCreateSkillModal = true; - } - - private void OpenEditSkillModal() - { - if (SelectedSkill is null) - { - return; - } - - EditingSkillId = SelectedSkill.Id; - - EditSkillInitialModel = new SkillFormModel - { - Name = SelectedSkill.Name, - DiceRollDefinition = SelectedSkill.DiceRollDefinition, - WildDice = SelectedSkill.WildDice, - AllowFumble = SelectedSkill.AllowFumble - }; - EditSkillFormVersion++; - ShowEditSkillModal = true; - } - - private void CloseSkillModals() - { - ShowCreateSkillModal = false; - ShowEditSkillModal = false; - EditingSkillId = null; - } - private async Task OnSkillCreatedAsync(Guid _) { - CloseSkillModals(); await RefreshCampaignScopeAsync(); SetStatus("Skill created.", false); } @@ -54,7 +13,6 @@ public partial class Home private async Task OnSkillUpdatedAsync(Guid skillId) { SelectedSkillId = skillId; - CloseSkillModals(); await RefreshCampaignScopeAsync(); SetStatus("Skill updated.", false); } diff --git a/RpgRoller/Components/Pages/Home.State.cs b/RpgRoller/Components/Pages/Home.State.cs index 8165f96..e38b580 100644 --- a/RpgRoller/Components/Pages/Home.State.cs +++ b/RpgRoller/Components/Pages/Home.State.cs @@ -41,18 +41,11 @@ public partial class Home private bool ShowCreateCharacterModal { get; set; } private bool ShowEditCharacterModal { get; set; } - private bool ShowCreateSkillModal { get; set; } - private bool ShowEditSkillModal { get; set; } private Guid? EditingCharacterId { get; set; } - private Guid? EditingSkillId { get; set; } private CharacterFormModel CreateCharacterInitialModel { get; set; } = new(); private CharacterFormModel EditCharacterInitialModel { get; set; } = new(); - private SkillFormModel CreateSkillInitialModel { get; set; } = new(); - private SkillFormModel EditSkillInitialModel { get; set; } = new(); private int CreateCharacterFormVersion { get; set; } private int EditCharacterFormVersion { get; set; } - private int CreateSkillFormVersion { get; set; } - private int EditSkillFormVersion { get; set; } private bool StateRefreshInProgress { get; set; } private bool HasInteractiveRenderStarted { get; set; } private DotNetObjectReference? DotNetRef { get; set; } diff --git a/RpgRoller/Components/Pages/Home.razor b/RpgRoller/Components/Pages/Home.razor index 08e0540..9bfbf34 100644 --- a/RpgRoller/Components/Pages/Home.razor +++ b/RpgRoller/Components/Pages/Home.razor @@ -74,6 +74,7 @@ SelectedCharacterSkills="SelectedCharacterSkills" SelectedSkillId="SelectedSkillId" SelectedSkill="SelectedSkill" + IsD6="IsSelectedCampaignD6" RollVisibility="RollVisibility" RollVisibilityChanged="OnRollVisibilityChanged" LastRoll="LastRoll" @@ -85,8 +86,8 @@ CharacterSelected="SelectCharacterAsync" SkillSelected="SelectSkill" EditCharacterRequested="OpenEditCharacterModal" - CreateSkillRequested="OpenCreateSkillModal" - EditSkillRequested="OpenEditSkillModal" + SkillCreated="OnSkillCreatedAsync" + SkillUpdated="OnSkillUpdatedAsync" RollRequested="RollSelectedSkillAsync" /> - - - - diff --git a/RpgRoller/Components/Pages/HomeControls/CharacterPanel.razor b/RpgRoller/Components/Pages/HomeControls/CharacterPanel.razor index 5c328a4..f7e0fa5 100644 --- a/RpgRoller/Components/Pages/HomeControls/CharacterPanel.razor +++ b/RpgRoller/Components/Pages/HomeControls/CharacterPanel.razor @@ -1,4 +1,5 @@ @using System.Diagnostics.CodeAnalysis +@using RpgRoller.Components.Pages @using RpgRoller.Contracts @attribute [ExcludeFromCodeCoverage] @@ -43,8 +44,8 @@

Skills

- - + +
@if (SelectedCharacterSkills.Count == 0) @@ -90,7 +91,49 @@ + + + + @code { + private bool ShowCreateSkillModal { get; set; } + private bool ShowEditSkillModal { get; set; } + private Guid? EditingSkillId { get; set; } + private SkillFormModel CreateSkillInitialModel { get; set; } = new(); + private SkillFormModel EditSkillInitialModel { get; set; } = new(); + private int CreateSkillFormVersion { get; set; } + private int EditSkillFormVersion { get; set; } + [Parameter] public bool IsCampaignDataLoading { get; set; } @@ -115,6 +158,9 @@ [Parameter] public SkillSummary? SelectedSkill { get; set; } + [Parameter] + public bool IsD6 { get; set; } + [Parameter] public string RollVisibility { get; set; } = "public"; @@ -149,14 +195,67 @@ public EventCallback EditCharacterRequested { get; set; } [Parameter] - public EventCallback CreateSkillRequested { get; set; } + public EventCallback SkillCreated { get; set; } [Parameter] - public EventCallback EditSkillRequested { get; set; } + public EventCallback SkillUpdated { get; set; } [Parameter] public EventCallback RollRequested { get; set; } + private void OpenCreateSkillModal() + { + CreateSkillInitialModel = new SkillFormModel + { + Name = string.Empty, + DiceRollDefinition = string.Empty, + WildDice = IsD6 ? 1 : 0, + AllowFumble = IsD6 + }; + + CreateSkillFormVersion++; + ShowCreateSkillModal = true; + } + + private void OpenEditSkillModal() + { + if (SelectedSkill is null) + { + return; + } + + EditingSkillId = SelectedSkill.Id; + EditSkillInitialModel = new SkillFormModel + { + Name = SelectedSkill.Name, + DiceRollDefinition = SelectedSkill.DiceRollDefinition, + WildDice = SelectedSkill.WildDice, + AllowFumble = SelectedSkill.AllowFumble + }; + + EditSkillFormVersion++; + ShowEditSkillModal = true; + } + + private void CloseSkillModals() + { + ShowCreateSkillModal = false; + ShowEditSkillModal = false; + EditingSkillId = null; + } + + private async Task OnSkillCreatedAsync(Guid skillId) + { + CloseSkillModals(); + await SkillCreated.InvokeAsync(skillId); + } + + private async Task OnSkillUpdatedAsync(Guid skillId) + { + CloseSkillModals(); + await SkillUpdated.InvokeAsync(skillId); + } + private async Task OnRollVisibilityChangedAsync(ChangeEventArgs args) { var selectedVisibility = args.Value?.ToString() ?? "public"; diff --git a/TECH.md b/TECH.md index 743e4fe..74eaa8b 100644 --- a/TECH.md +++ b/TECH.md @@ -95,6 +95,7 @@ This pattern is a strong baseline for low to medium scale and should be the defa - Home page logic split by concern with partials (`Home.State/Auth/Campaign/Character/Skill/Lifecycle/Realtime/Api/Presentation/Validation.cs`) to keep churn localized. - Form UX state uses reusable `FormState` containers in leaf controls (`HomeControls/*`) rather than parallel form/error/message property sets in `Home`. - Concern controls execute their own auth/campaign/character/skill mutation workflows and notify `Home` only for shared-state refresh/orchestration. +- Skill management workflows are owned by `CharacterPanel` to keep character-skill behavior cohesive. - Shared browser API interop is centralized in `RpgRollerApiClient` and reused by `Home` plus concern controls. - Browser API calls and SSE are handled via `wwwroot/js/rpgroller-api.js` interop. - UI state is maintained server-side per circuit with session/tab persistence for campaign + screen selection.