Add skill-group prototypes, delete flows, and per-group skill creation UX

This commit is contained in:
2026-02-26 14:12:15 +01:00
parent 04bc8095e6
commit 3b1a314a75
17 changed files with 740 additions and 111 deletions

View File

@@ -7,15 +7,19 @@ namespace RpgRoller.Components.Pages.HomeControls;
[ExcludeFromCodeCoverage]
public partial class CharacterPanel
{
private void OpenCreateSkillModal()
private void OpenCreateSkillModal(Guid? skillGroupId = null)
{
var selectedGroup = skillGroupId.HasValue
? SelectedCharacterSkillGroups.FirstOrDefault(group => group.Id == skillGroupId.Value)
: null;
CreateSkillInitialModel = new()
{
Name = string.Empty,
DiceRollDefinition = string.Empty,
SkillGroupId = string.Empty,
WildDice = IsD6 ? 1 : 0,
AllowFumble = IsD6
DiceRollDefinition = selectedGroup?.DiceRollDefinition ?? string.Empty,
SkillGroupId = selectedGroup?.Id.ToString() ?? string.Empty,
WildDice = selectedGroup?.WildDice ?? (IsD6 ? 1 : 0),
AllowFumble = selectedGroup?.AllowFumble ?? IsD6
};
CreateSkillFormVersion++;
@@ -71,6 +75,9 @@ public partial class CharacterPanel
private void OpenCreateSkillGroupModal()
{
SkillGroupState.Model.Name = string.Empty;
SkillGroupState.Model.DiceRollDefinition = string.Empty;
SkillGroupState.Model.WildDice = IsD6 ? 1 : 0;
SkillGroupState.Model.AllowFumble = IsD6;
SkillGroupState.ResetValidation();
ShowCreateSkillGroupModal = true;
}
@@ -79,6 +86,9 @@ public partial class CharacterPanel
{
EditingSkillGroupId = skillGroup.Id;
SkillGroupState.Model.Name = skillGroup.Name;
SkillGroupState.Model.DiceRollDefinition = skillGroup.DiceRollDefinition;
SkillGroupState.Model.WildDice = skillGroup.WildDice;
SkillGroupState.Model.AllowFumble = skillGroup.AllowFumble;
SkillGroupState.ResetValidation();
ShowEditSkillGroupModal = true;
}
@@ -98,6 +108,12 @@ public partial class CharacterPanel
if (string.IsNullOrWhiteSpace(SkillGroupState.Model.Name))
SkillGroupState.Errors["name"] = "Skill group name is required.";
if (string.IsNullOrWhiteSpace(SkillGroupState.Model.DiceRollDefinition))
SkillGroupState.Errors["diceRollDefinition"] = "Expression is required.";
if (IsD6 && SkillGroupState.Model.WildDice < 1)
SkillGroupState.Errors["wildDice"] = "D6 groups require at least one wild die.";
if (!SelectedCharacterId.HasValue)
SkillGroupState.Errors["character"] = "Select a character first.";
@@ -111,7 +127,14 @@ public partial class CharacterPanel
try
{
var selectedCharacterId = SelectedCharacterId!.Value;
var createdGroup = await ApiClient.RequestAsync<SkillGroupSummary>("POST", $"/api/characters/{selectedCharacterId}/skill-groups", new CreateSkillGroupRequest(SkillGroupState.Model.Name.Trim()));
var createdGroup = await ApiClient.RequestAsync<SkillGroupSummary>(
"POST",
$"/api/characters/{selectedCharacterId}/skill-groups",
new CreateSkillGroupRequest(
SkillGroupState.Model.Name.Trim(),
SkillGroupState.Model.DiceRollDefinition.Trim(),
SkillGroupState.Model.WildDice,
SkillGroupState.Model.AllowFumble));
CloseSkillGroupModals();
await SkillGroupCreated.InvokeAsync(createdGroup.Id);
}
@@ -132,6 +155,12 @@ public partial class CharacterPanel
if (string.IsNullOrWhiteSpace(SkillGroupState.Model.Name))
SkillGroupState.Errors["name"] = "Skill group name is required.";
if (string.IsNullOrWhiteSpace(SkillGroupState.Model.DiceRollDefinition))
SkillGroupState.Errors["diceRollDefinition"] = "Expression is required.";
if (IsD6 && SkillGroupState.Model.WildDice < 1)
SkillGroupState.Errors["wildDice"] = "D6 groups require at least one wild die.";
if (!EditingSkillGroupId.HasValue)
SkillGroupState.Errors["group"] = "Select a skill group first.";
@@ -145,7 +174,14 @@ public partial class CharacterPanel
try
{
var editingSkillGroupId = EditingSkillGroupId!.Value;
var updatedGroup = await ApiClient.RequestAsync<SkillGroupSummary>("PUT", $"/api/skill-groups/{editingSkillGroupId}", new UpdateSkillGroupRequest(SkillGroupState.Model.Name.Trim()));
var updatedGroup = await ApiClient.RequestAsync<SkillGroupSummary>(
"PUT",
$"/api/skill-groups/{editingSkillGroupId}",
new UpdateSkillGroupRequest(
SkillGroupState.Model.Name.Trim(),
SkillGroupState.Model.DiceRollDefinition.Trim(),
SkillGroupState.Model.WildDice,
SkillGroupState.Model.AllowFumble));
CloseSkillGroupModals();
await SkillGroupUpdated.InvokeAsync(updatedGroup.Id);
}
@@ -159,6 +195,32 @@ public partial class CharacterPanel
}
}
private async Task DeleteSkillAsync(Guid skillId)
{
try
{
await ApiClient.RequestAsync<bool>("DELETE", $"/api/skills/{skillId}");
await SkillDeleted.InvokeAsync(skillId);
}
catch (ApiRequestException ex)
{
await ErrorOccurred.InvokeAsync(ex.Message);
}
}
private async Task DeleteSkillGroupAsync(Guid skillGroupId)
{
try
{
await ApiClient.RequestAsync<bool>("DELETE", $"/api/skill-groups/{skillGroupId}");
await SkillGroupDeleted.InvokeAsync(skillGroupId);
}
catch (ApiRequestException ex)
{
await ErrorOccurred.InvokeAsync(ex.Message);
}
}
private static string InitialsFor(string value)
{
var words = value.Split(' ', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
@@ -250,6 +312,15 @@ public partial class CharacterPanel
[Parameter]
public EventCallback<Guid> SkillGroupUpdated { get; set; }
[Parameter]
public EventCallback<Guid> SkillDeleted { get; set; }
[Parameter]
public EventCallback<Guid> SkillGroupDeleted { get; set; }
[Parameter]
public EventCallback<string> ErrorOccurred { get; set; }
[Parameter]
public EventCallback<Guid> RollRequested { get; set; }
}