Move form state ownership from Home to leaf controls

This commit is contained in:
2026-02-26 09:54:04 +01:00
parent b17490e5ac
commit 4d728f91cf
16 changed files with 315 additions and 143 deletions

View File

@@ -4,23 +4,25 @@ namespace RpgRoller.Components.Pages;
public partial class Home
{
private async Task RegisterAsync()
private async Task<FormSubmissionResult> RegisterAsync(RegisterFormModel model)
{
RegisterState.ResetValidation();
var model = RegisterState.Model;
var validationErrors = new Dictionary<string, string>();
AddRequiredError(RegisterState.Errors, "username", model.Username, "Username is required.");
AddRequiredError(RegisterState.Errors, "displayName", model.DisplayName, "Display name is required.");
AddRequiredError(validationErrors, "username", model.Username, "Username is required.");
AddRequiredError(validationErrors, "displayName", model.DisplayName, "Display name is required.");
if (string.IsNullOrWhiteSpace(model.Password) || model.Password.Length < 8)
{
RegisterState.Errors["password"] = "Password must be at least 8 characters.";
validationErrors["password"] = "Password must be at least 8 characters.";
}
if (RegisterState.Errors.Count > 0)
if (validationErrors.Count > 0)
{
RegisterState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -31,18 +33,23 @@ public partial class Home
"/api/auth/register",
new RegisterRequest(model.Username.Trim(), model.Password, model.DisplayName.Trim()));
model.Password = string.Empty;
SetStatus("Registration successful. You can log in now.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
if (ex.Message.Contains("already taken", StringComparison.OrdinalIgnoreCase))
{
RegisterState.Errors["username"] = "Username is already taken. Choose another one.";
return;
return new FormSubmissionResult
{
Errors = new Dictionary<string, string>
{
["username"] = "Username is already taken. Choose another one."
}
};
}
RegisterState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{
@@ -50,18 +57,20 @@ public partial class Home
}
}
private async Task LoginAsync()
private async Task<FormSubmissionResult> LoginAsync(LoginFormModel model)
{
LoginState.ResetValidation();
var model = LoginState.Model;
var validationErrors = new Dictionary<string, string>();
AddRequiredError(LoginState.Errors, "username", model.Username, "Username is required.");
AddRequiredError(LoginState.Errors, "password", model.Password, "Password is required.");
AddRequiredError(validationErrors, "username", model.Username, "Username is required.");
AddRequiredError(validationErrors, "password", model.Password, "Password is required.");
if (LoginState.Errors.Count > 0)
if (validationErrors.Count > 0)
{
LoginState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -72,13 +81,13 @@ public partial class Home
"/api/auth/login",
new LoginRequest(model.Username.Trim(), model.Password));
model.Password = string.Empty;
await ReloadAuthenticatedSessionAsync(null);
SetStatus("Logged in.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
LoginState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{

View File

@@ -51,18 +51,20 @@ public partial class Home
await SyncStateEventsAsync();
}
private async Task CreateCampaignAsync()
private async Task<FormSubmissionResult> CreateCampaignAsync(CampaignFormModel model)
{
CampaignState.ResetValidation();
var model = CampaignState.Model;
var validationErrors = new Dictionary<string, string>();
AddRequiredError(CampaignState.Errors, "name", model.Name, "Campaign name is required.");
AddRequiredError(CampaignState.Errors, "rulesetId", model.RulesetId, "Ruleset is required.");
AddRequiredError(validationErrors, "name", model.Name, "Campaign name is required.");
AddRequiredError(validationErrors, "rulesetId", model.RulesetId, "Ruleset is required.");
if (CampaignState.Errors.Count > 0)
if (validationErrors.Count > 0)
{
CampaignState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -73,15 +75,15 @@ public partial class Home
"/api/campaigns",
new CreateCampaignRequest(model.Name.Trim(), model.RulesetId));
model.Name = string.Empty;
await ReloadCampaignsAsync(campaign.Id);
await RefreshCampaignScopeAsync();
await SyncStateEventsAsync();
SetStatus("Campaign created.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
CampaignState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{

View File

@@ -6,10 +6,12 @@ public partial class Home
{
private void OpenCreateCharacterModal()
{
var model = CreateCharacterState.Model;
model.Name = string.Empty;
model.CampaignId = SelectedCampaignId?.ToString() ?? string.Empty;
CreateCharacterState.ResetValidation();
CreateCharacterInitialModel = new CharacterFormModel
{
Name = string.Empty,
CampaignId = SelectedCampaignId?.ToString() ?? string.Empty
};
CreateCharacterFormVersion++;
ShowCreateCharacterModal = true;
}
@@ -17,11 +19,12 @@ public partial class Home
{
EditingCharacterId = character.Id;
var model = EditCharacterState.Model;
model.Name = character.Name;
model.CampaignId = character.CampaignId.ToString();
EditCharacterState.ResetValidation();
EditCharacterInitialModel = new CharacterFormModel
{
Name = character.Name,
CampaignId = character.CampaignId.ToString()
};
EditCharacterFormVersion++;
ShowEditCharacterModal = true;
}
@@ -32,23 +35,25 @@ public partial class Home
EditingCharacterId = null;
}
private async Task CreateCharacterAsync()
private async Task<FormSubmissionResult> CreateCharacterAsync(CharacterFormModel model)
{
CreateCharacterState.ResetValidation();
var model = CreateCharacterState.Model;
var validationErrors = new Dictionary<string, string>();
AddRequiredError(CreateCharacterState.Errors, "name", model.Name, "Character name is required.");
AddRequiredError(validationErrors, "name", model.Name, "Character name is required.");
var hasCampaignId = TryParseGuid(
model.CampaignId,
CreateCharacterState.Errors,
validationErrors,
"campaignId",
"Campaign is required.",
out var campaignId);
if (CreateCharacterState.Errors.Count > 0 || !hasCampaignId)
if (validationErrors.Count > 0 || !hasCampaignId)
{
CreateCharacterState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -64,10 +69,11 @@ public partial class Home
await RefreshCampaignScopeAsync();
await SyncStateEventsAsync();
SetStatus("Character created.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
CreateCharacterState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{
@@ -75,29 +81,29 @@ public partial class Home
}
}
private async Task UpdateCharacterAsync()
private async Task<FormSubmissionResult> UpdateCharacterAsync(CharacterFormModel model)
{
EditCharacterState.ResetValidation();
if (!EditingCharacterId.HasValue)
{
EditCharacterState.ErrorMessage = "No character selected.";
return;
return new FormSubmissionResult { ErrorMessage = "No character selected." };
}
var model = EditCharacterState.Model;
AddRequiredError(EditCharacterState.Errors, "name", model.Name, "Character name is required.");
var validationErrors = new Dictionary<string, string>();
AddRequiredError(validationErrors, "name", model.Name, "Character name is required.");
var hasCampaignId = TryParseGuid(
model.CampaignId,
EditCharacterState.Errors,
validationErrors,
"campaignId",
"Campaign is required.",
out var campaignId);
if (EditCharacterState.Errors.Count > 0 || !hasCampaignId)
if (validationErrors.Count > 0 || !hasCampaignId)
{
EditCharacterState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -113,10 +119,11 @@ public partial class Home
await RefreshCampaignScopeAsync();
await SyncStateEventsAsync();
SetStatus("Character updated.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
EditCharacterState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{

View File

@@ -80,10 +80,6 @@ public partial class Home
try
{
Rulesets = (await RequestAsync<IReadOnlyList<RulesetDefinition>>("GET", "/api/rulesets")).ToList();
if (Rulesets.Count > 0 && string.IsNullOrWhiteSpace(CampaignState.Model.RulesetId))
{
CampaignState.Model.RulesetId = Rulesets[0].Id;
}
}
catch (ApiRequestException ex)
{

View File

@@ -14,6 +14,13 @@ public sealed class FormState<TModel>
}
}
public sealed class FormSubmissionResult
{
public Dictionary<string, string> Errors { get; init; } = [];
public string? ErrorMessage { get; init; }
public bool IsSuccess => Errors.Count == 0 && string.IsNullOrWhiteSpace(ErrorMessage);
}
public sealed class RegisterFormModel
{
public string Username { get; set; } = string.Empty;

View File

@@ -155,6 +155,14 @@ public partial class Home
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)

View File

@@ -6,13 +6,14 @@ public partial class Home
{
private void OpenCreateSkillModal()
{
var model = CreateSkillState.Model;
model.Name = string.Empty;
model.DiceRollDefinition = string.Empty;
model.WildDice = IsSelectedCampaignD6 ? 1 : 0;
model.AllowFumble = IsSelectedCampaignD6;
CreateSkillState.ResetValidation();
CreateSkillInitialModel = new SkillFormModel
{
Name = string.Empty,
DiceRollDefinition = string.Empty,
WildDice = IsSelectedCampaignD6 ? 1 : 0,
AllowFumble = IsSelectedCampaignD6
};
CreateSkillFormVersion++;
ShowCreateSkillModal = true;
}
@@ -25,13 +26,14 @@ public partial class Home
EditingSkillId = SelectedSkill.Id;
var model = EditSkillState.Model;
model.Name = SelectedSkill.Name;
model.DiceRollDefinition = SelectedSkill.DiceRollDefinition;
model.WildDice = SelectedSkill.WildDice;
model.AllowFumble = SelectedSkill.AllowFumble;
EditSkillState.ResetValidation();
EditSkillInitialModel = new SkillFormModel
{
Name = SelectedSkill.Name,
DiceRollDefinition = SelectedSkill.DiceRollDefinition,
WildDice = SelectedSkill.WildDice,
AllowFumble = SelectedSkill.AllowFumble
};
EditSkillFormVersion++;
ShowEditSkillModal = true;
}
@@ -42,29 +44,29 @@ public partial class Home
EditingSkillId = null;
}
private async Task CreateSkillAsync()
private async Task<FormSubmissionResult> CreateSkillAsync(SkillFormModel model)
{
CreateSkillState.ResetValidation();
if (SelectedCharacter is null)
{
CreateSkillState.ErrorMessage = "Select a character first.";
return;
return new FormSubmissionResult { ErrorMessage = "Select a character first." };
}
var model = CreateSkillState.Model;
AddRequiredError(CreateSkillState.Errors, "name", model.Name, "Skill name is required.");
AddRequiredError(CreateSkillState.Errors, "diceRollDefinition", model.DiceRollDefinition, "Expression is required.");
var validationErrors = new Dictionary<string, string>();
AddRequiredError(validationErrors, "name", model.Name, "Skill name is required.");
AddRequiredError(validationErrors, "diceRollDefinition", model.DiceRollDefinition, "Expression is required.");
if (IsSelectedCampaignD6 && model.WildDice < 1)
{
CreateSkillState.Errors["wildDice"] = "D6 skills require at least one wild die.";
validationErrors["wildDice"] = "D6 skills require at least one wild die.";
}
if (CreateSkillState.Errors.Count > 0)
if (validationErrors.Count > 0)
{
CreateSkillState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -78,10 +80,11 @@ public partial class Home
CloseSkillModals();
await RefreshCampaignScopeAsync();
SetStatus("Skill created.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
CreateSkillState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{
@@ -89,29 +92,29 @@ public partial class Home
}
}
private async Task UpdateSkillAsync()
private async Task<FormSubmissionResult> UpdateSkillAsync(SkillFormModel model)
{
EditSkillState.ResetValidation();
if (!EditingSkillId.HasValue)
{
EditSkillState.ErrorMessage = "No skill selected.";
return;
return new FormSubmissionResult { ErrorMessage = "No skill selected." };
}
var model = EditSkillState.Model;
AddRequiredError(EditSkillState.Errors, "name", model.Name, "Skill name is required.");
AddRequiredError(EditSkillState.Errors, "diceRollDefinition", model.DiceRollDefinition, "Expression is required.");
var validationErrors = new Dictionary<string, string>();
AddRequiredError(validationErrors, "name", model.Name, "Skill name is required.");
AddRequiredError(validationErrors, "diceRollDefinition", model.DiceRollDefinition, "Expression is required.");
if (IsSelectedCampaignD6 && model.WildDice < 1)
{
EditSkillState.Errors["wildDice"] = "D6 skills require at least one wild die.";
validationErrors["wildDice"] = "D6 skills require at least one wild die.";
}
if (EditSkillState.Errors.Count > 0)
if (validationErrors.Count > 0)
{
EditSkillState.ErrorMessage = "Resolve validation issues before submitting.";
return;
return new FormSubmissionResult
{
ErrorMessage = "Resolve validation issues before submitting.",
Errors = validationErrors
};
}
IsMutating = true;
@@ -126,10 +129,11 @@ public partial class Home
CloseSkillModals();
await RefreshCampaignScopeAsync();
SetStatus("Skill updated.", false);
return new FormSubmissionResult();
}
catch (ApiRequestException ex)
{
EditSkillState.ErrorMessage = ex.Message;
return new FormSubmissionResult { ErrorMessage = ex.Message };
}
finally
{

View File

@@ -17,14 +17,6 @@ public partial class Home
private const string MobilePanelSessionKey = "play-panel";
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
private FormState<RegisterFormModel> RegisterState { get; } = new();
private FormState<LoginFormModel> LoginState { get; } = new();
private FormState<CampaignFormModel> CampaignState { get; } = new();
private FormState<CharacterFormModel> CreateCharacterState { get; } = new();
private FormState<CharacterFormModel> EditCharacterState { get; } = new();
private FormState<SkillFormModel> CreateSkillState { get; } = new();
private FormState<SkillFormModel> EditSkillState { get; } = new();
private UserSummary? User { get; set; }
private Guid? ActiveCharacterId { get; set; }
private Guid? SelectedCampaignId { get; set; }
@@ -55,6 +47,14 @@ public partial class Home
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<Home>? DotNetRef { get; set; }

View File

@@ -27,8 +27,6 @@
case HomeViewMode.Anonymous:
<AuthSection
RegisterState="RegisterState"
LoginState="LoginState"
IsMutating="IsMutating"
StatusMessage="StatusMessage"
StatusIsError="StatusIsError"
@@ -116,7 +114,6 @@
SelectedCampaignId="SelectedCampaignId"
SelectedCampaignName="SelectedCampaignName"
SelectedCampaign="SelectedCampaign"
CampaignState="CampaignState"
Rulesets="Rulesets"
IsMutating="IsMutating"
OwnerLabel="OwnerLabel"
@@ -137,7 +134,8 @@
SubmitLabel="Create Character"
NameInputId="character-create-name"
CampaignInputId="character-create-campaign"
FormState="CreateCharacterState"
InitialModel="CreateCharacterInitialModel"
FormVersion="CreateCharacterFormVersion"
Campaigns="Campaigns"
IsMutating="IsMutating"
SubmitRequested="CreateCharacterAsync"
@@ -149,7 +147,8 @@
SubmitLabel="Save Character"
NameInputId="character-edit-name"
CampaignInputId="character-edit-campaign"
FormState="EditCharacterState"
InitialModel="EditCharacterInitialModel"
FormVersion="EditCharacterFormVersion"
Campaigns="Campaigns"
IsMutating="IsMutating"
SubmitRequested="UpdateCharacterAsync"
@@ -164,7 +163,8 @@
ExpressionInputId="skill-create-expression"
WildDiceInputId="skill-create-wild-dice"
AllowFumbleInputId="skill-create-allow-fumble"
FormState="CreateSkillState"
InitialModel="CreateSkillInitialModel"
FormVersion="CreateSkillFormVersion"
IsMutating="IsMutating"
SubmitRequested="CreateSkillAsync"
CancelRequested="CloseSkillModals" />
@@ -178,7 +178,8 @@
ExpressionInputId="skill-edit-expression"
WildDiceInputId="skill-edit-wild-dice"
AllowFumbleInputId="skill-edit-allow-fumble"
FormState="EditSkillState"
InitialModel="EditSkillInitialModel"
FormVersion="EditSkillFormVersion"
IsMutating="IsMutating"
SubmitRequested="UpdateSkillAsync"
CancelRequested="CloseSkillModals" />

View File

@@ -65,11 +65,8 @@
</main>
@code {
[Parameter]
public FormState<RegisterFormModel> RegisterState { get; set; } = new();
[Parameter]
public FormState<LoginFormModel> LoginState { get; set; } = new();
private FormState<RegisterFormModel> RegisterState { get; } = new();
private FormState<LoginFormModel> LoginState { get; } = new();
[Parameter]
public bool IsMutating { get; set; }
@@ -81,18 +78,55 @@
public bool StatusIsError { get; set; }
[Parameter]
public EventCallback RegisterSubmitted { get; set; }
public Func<RegisterFormModel, Task<FormSubmissionResult>> RegisterSubmitted { get; set; } = _ => Task.FromResult(new FormSubmissionResult());
[Parameter]
public EventCallback LoginSubmitted { get; set; }
public Func<LoginFormModel, Task<FormSubmissionResult>> LoginSubmitted { get; set; } = _ => Task.FromResult(new FormSubmissionResult());
private async Task SubmitRegisterAsync()
{
await RegisterSubmitted.InvokeAsync();
RegisterState.ResetValidation();
var result = await RegisterSubmitted.Invoke(new RegisterFormModel
{
Username = RegisterState.Model.Username,
DisplayName = RegisterState.Model.DisplayName,
Password = RegisterState.Model.Password
});
ApplyResult(RegisterState, result);
if (result.IsSuccess)
{
RegisterState.Model.Password = string.Empty;
}
}
private async Task SubmitLoginAsync()
{
await LoginSubmitted.InvokeAsync();
LoginState.ResetValidation();
var result = await LoginSubmitted.Invoke(new LoginFormModel
{
Username = LoginState.Model.Username,
Password = LoginState.Model.Password
});
ApplyResult(LoginState, result);
if (result.IsSuccess)
{
LoginState.Model.Password = string.Empty;
}
}
private static void ApplyResult<TModel>(FormState<TModel> state, FormSubmissionResult result)
where TModel : new()
{
state.Errors.Clear();
foreach (var (key, value) in result.Errors)
{
state.Errors[key] = value;
}
state.ErrorMessage = result.ErrorMessage;
}
}

View File

@@ -29,7 +29,7 @@
{
<p class="form-error">@CampaignState.ErrorMessage</p>
}
<form class="form-grid" @onsubmit="CreateCampaignSubmitted" @onsubmit:preventDefault>
<form class="form-grid" @onsubmit="SubmitCreateCampaignAsync" @onsubmit:preventDefault>
<label for="campaign-name">Campaign name</label>
<input id="campaign-name" @bind="CampaignState.Model.Name" @bind:event="oninput" />
@if (CampaignState.Errors.TryGetValue("name", out var campaignNameError))
@@ -98,6 +98,8 @@
</main>
@code {
private FormState<CampaignFormModel> CampaignState { get; } = new();
[Parameter]
public IReadOnlyList<CampaignSummary> Campaigns { get; set; } = [];
@@ -110,9 +112,6 @@
[Parameter]
public CampaignDetails? SelectedCampaign { get; set; }
[Parameter]
public FormState<CampaignFormModel> CampaignState { get; set; } = new();
[Parameter]
public IReadOnlyList<RulesetDefinition> Rulesets { get; set; } = [];
@@ -129,11 +128,42 @@
public EventCallback<ChangeEventArgs> CampaignSelectionChanged { get; set; }
[Parameter]
public EventCallback CreateCampaignSubmitted { get; set; }
public Func<CampaignFormModel, Task<FormSubmissionResult>> CreateCampaignSubmitted { get; set; } = _ => Task.FromResult(new FormSubmissionResult());
[Parameter]
public EventCallback CreateCharacterRequested { get; set; }
[Parameter]
public EventCallback<CharacterSummary> EditCharacterRequested { get; set; }
protected override void OnParametersSet()
{
if (string.IsNullOrWhiteSpace(CampaignState.Model.RulesetId) && Rulesets.Count > 0)
{
CampaignState.Model.RulesetId = Rulesets[0].Id;
}
}
private async Task SubmitCreateCampaignAsync()
{
CampaignState.ResetValidation();
var result = await CreateCampaignSubmitted.Invoke(new CampaignFormModel
{
Name = CampaignState.Model.Name,
RulesetId = CampaignState.Model.RulesetId
});
CampaignState.Errors.Clear();
foreach (var (key, value) in result.Errors)
{
CampaignState.Errors[key] = value;
}
CampaignState.ErrorMessage = result.ErrorMessage;
if (result.IsSuccess)
{
CampaignState.Model.Name = string.Empty;
}
}
}

View File

@@ -41,6 +41,9 @@
}
@code {
private FormState<CharacterFormModel> FormState { get; } = new();
private int AppliedFormVersion { get; set; } = -1;
[Parameter]
public bool Visible { get; set; }
@@ -57,7 +60,10 @@
public string CampaignInputId { get; set; } = "character-campaign";
[Parameter]
public FormState<CharacterFormModel> FormState { get; set; } = new();
public CharacterFormModel InitialModel { get; set; } = new();
[Parameter]
public int FormVersion { get; set; }
[Parameter]
public IReadOnlyList<CampaignSummary> Campaigns { get; set; } = [];
@@ -66,13 +72,39 @@
public bool IsMutating { get; set; }
[Parameter]
public EventCallback SubmitRequested { get; set; }
public Func<CharacterFormModel, Task<FormSubmissionResult>> SubmitRequested { get; set; } = _ => Task.FromResult(new FormSubmissionResult());
[Parameter]
public EventCallback CancelRequested { get; set; }
protected override void OnParametersSet()
{
if (!Visible || FormVersion == AppliedFormVersion)
{
return;
}
FormState.Model.Name = InitialModel.Name;
FormState.Model.CampaignId = InitialModel.CampaignId;
FormState.ResetValidation();
AppliedFormVersion = FormVersion;
}
private async Task SubmitAsync()
{
await SubmitRequested.InvokeAsync();
FormState.ResetValidation();
var result = await SubmitRequested.Invoke(new CharacterFormModel
{
Name = FormState.Model.Name,
CampaignId = FormState.Model.CampaignId
});
foreach (var (key, value) in result.Errors)
{
FormState.Errors[key] = value;
}
FormState.ErrorMessage = result.ErrorMessage;
}
}

View File

@@ -45,6 +45,9 @@
}
@code {
private FormState<SkillFormModel> FormState { get; } = new();
private int AppliedFormVersion { get; set; } = -1;
[Parameter]
public bool Visible { get; set; }
@@ -70,19 +73,52 @@
public string AllowFumbleInputId { get; set; } = "skill-fumble";
[Parameter]
public FormState<SkillFormModel> FormState { get; set; } = new();
public SkillFormModel InitialModel { get; set; } = new();
[Parameter]
public int FormVersion { get; set; }
[Parameter]
public bool IsMutating { get; set; }
[Parameter]
public EventCallback SubmitRequested { get; set; }
public Func<SkillFormModel, Task<FormSubmissionResult>> SubmitRequested { get; set; } = _ => Task.FromResult(new FormSubmissionResult());
[Parameter]
public EventCallback CancelRequested { get; set; }
protected override void OnParametersSet()
{
if (!Visible || FormVersion == AppliedFormVersion)
{
return;
}
FormState.Model.Name = InitialModel.Name;
FormState.Model.DiceRollDefinition = InitialModel.DiceRollDefinition;
FormState.Model.WildDice = InitialModel.WildDice;
FormState.Model.AllowFumble = InitialModel.AllowFumble;
FormState.ResetValidation();
AppliedFormVersion = FormVersion;
}
private async Task SubmitAsync()
{
await SubmitRequested.InvokeAsync();
FormState.ResetValidation();
var result = await SubmitRequested.Invoke(new SkillFormModel
{
Name = FormState.Model.Name,
DiceRollDefinition = FormState.Model.DiceRollDefinition,
WildDice = FormState.Model.WildDice,
AllowFumble = FormState.Model.AllowFumble
});
foreach (var (key, value) in result.Errors)
{
FormState.Errors[key] = value;
}
FormState.ErrorMessage = result.ErrorMessage;
}
}