Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/Web/Shared/Components/Form/ItemTable.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace MUnique.OpenMU.Web.Shared.Components.Form;
using Microsoft.AspNetCore.Components;
using MUnique.OpenMU.DataModel.Composition;
using MUnique.OpenMU.Persistence;
using MUnique.OpenMU.Web.Shared;

/// <summary>
/// A component which shows a collection of <typeparamref name="TItem"/> in a table.
Expand Down Expand Up @@ -99,6 +100,12 @@ private async Task OnCreateClickAsync()
var parameters = new ModalParameters();
parameters.Add(nameof(ModalCreateNew<TItem>.Item), item);
parameters.Add(nameof(ModalCreateNew<TItem>.PersistenceContext), this.PersistenceContext);
var owner = this.GetOwnerFromValueExpression();
if (owner is not null)
{
parameters.Add(nameof(ModalCreateNew<TItem>.Owner), owner);
}

var options = new ModalOptions
{
DisableBackgroundCancel = true,
Expand Down Expand Up @@ -131,4 +138,17 @@ private async Task OnRemoveClickAsync(TItem item)
await this.PersistenceContext.SaveChangesAsync().ConfigureAwait(false);
}
}

private object? GetOwnerFromValueExpression()
{
// This handles the common case where the expression is a simple member access on a captured constant,
// e.g. () => character.LearnedSkills, as generated by CreatePropertyExpression.
// More complex expression trees (e.g. nested properties) are not supported and will return null.
if (this.ValueExpression?.Body is MemberExpression { Expression: ConstantExpression { Value: { } owner } })
{
return owner;
}

return null;
}
}
24 changes: 23 additions & 1 deletion src/Web/Shared/Components/Form/ModalCreateNew.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
@using MUnique.OpenMU.Persistence
@* <copyright file="ModalCreateNew.razor" company="MUnique">
Licensed under the MIT License. See LICENSE file in the project root for full license information.
</copyright> *@

@using MUnique.OpenMU.Persistence
@using MUnique.OpenMU.DataModel.Entities
@using MUnique.OpenMU.Web.Shared.Components.ItemEdit

Expand All @@ -13,6 +17,17 @@
<ItemEdit Item="@item" OnCancel="@this.CancelAsync" OnValidSubmit="@this.SubmitAsync" />
</CascadingValue>
}
else if (typeof(TItem) == typeof(SkillEntry) && this.Owner is Character character)
{
@* The intermediate cast through object is required because C# does not allow a direct generic-to-concrete cast at compile time. *@
var skillEntry = (SkillEntry)(object)this.Item!;

<SkillEntryCreationForm SkillEntry="@skillEntry"
Character="@character"
PersistenceContext="@this.PersistenceContext"
OnSubmit="@this.SubmitAsync"
OnCancel="@this.CancelAsync" />
}
else
{
<EditForm Model="@Item">
Expand Down Expand Up @@ -46,6 +61,13 @@ else
[Parameter]
public IContext PersistenceContext { get; set; } = null!;

/// <summary>
/// Gets or sets the owner object of the collection containing this item.
/// Used to provide context for creation (e.g., filtering skills by character class).
/// </summary>
[Parameter]
public object? Owner { get; set; }

private Task SubmitAsync()
{
return this.BlazoredModal.CloseAsync(ModalResult.Ok(Item));
Expand Down
81 changes: 81 additions & 0 deletions src/Web/Shared/Components/Form/SkillEntryCreationForm.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
@* <copyright file="SkillEntryCreationForm.razor" company="MUnique">
Licensed under the MIT License. See LICENSE file in the project root for full license information.
</copyright> *@

@using MUnique.OpenMU.DataModel.Configuration
@using MUnique.OpenMU.DataModel.Entities
@using MUnique.OpenMU.Persistence
@using MUnique.OpenMU.Web.Shared.Services

@inject IDataSource<GameConfiguration> GameConfigurationSource

<EditForm Model="@SkillEntry">
<CascadingValue Value="@PersistenceContext">
<DataAnnotationsValidator />
<LookupField TObject="Skill"
@bind-Value="SkillEntry.Skill"
ExplicitLookupController="@this._skillLookupController" />
<NumberField @bind-Value="SkillEntry.Level" />
<ValidationSummary />
</CascadingValue>
<button type="submit" @onclick="@OnSubmit" class="primary-button">Submit</button>
<button type="button" @onclick="@OnCancel" class="cancel-button">Cancel</button>
</EditForm>

@code {

private ILookupController? _skillLookupController;

private CharacterClass? _lastCharacterClass;

/// <summary>
/// Gets or sets the skill entry which is being created.
/// </summary>
[Parameter]
public SkillEntry SkillEntry { get; set; } = null!;

/// <summary>
/// Gets or sets the character for which the skill entry is being created.
/// The character's class is used to filter the available skills.
/// </summary>
[Parameter]
public Character Character { get; set; } = null!;

/// <summary>
/// Gets or sets the persistence context.
/// </summary>
[Parameter]
public IContext PersistenceContext { get; set; } = null!;

/// <summary>
/// Gets or sets the callback invoked when the form is submitted.
/// </summary>
[Parameter]
public EventCallback OnSubmit { get; set; }

/// <summary>
/// Gets or sets the callback invoked when the form is cancelled.
/// </summary>
[Parameter]
public EventCallback OnCancel { get; set; }

/// <inheritdoc />
protected override void OnParametersSet()
{
base.OnParametersSet();
var characterClass = this.Character?.CharacterClass;
if (characterClass == this._lastCharacterClass && this._skillLookupController is not null)
{
return;
}

this._lastCharacterClass = characterClass;
var skills = this.GameConfigurationSource.GetAll<Skill>();
if (characterClass is not null)
{
skills = skills.Where(s => s.QualifiedCharacters.Contains(characterClass));
}

this._skillLookupController = new EnumerableLookupController(skills.OrderBy(s => s.GetName()).ToList());
}
}