diff --git a/debug/TestApplication/Program.cs b/debug/TestApplication/Program.cs index 9e0b3a1..49c140f 100644 --- a/debug/TestApplication/Program.cs +++ b/debug/TestApplication/Program.cs @@ -23,7 +23,8 @@ builder.Services.AddHopFrame(config => { table.SetDescription("The user dataset. It contains all information for the users of the application."); table.Property(u => u.Password) - .Listable(false); + .Listable(false) + .SetType(PropertyType.Password); table.SetPreferredProperty(u => u.Username); }); diff --git a/src/HopFrame.Core/Helpers/ConfigurationHelper.cs b/src/HopFrame.Core/Helpers/ConfigurationHelper.cs index f9e4d78..26b9521 100644 --- a/src/HopFrame.Core/Helpers/ConfigurationHelper.cs +++ b/src/HopFrame.Core/Helpers/ConfigurationHelper.cs @@ -46,8 +46,10 @@ internal static class ConfigurationHelper { Table = table }; - if (property.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute))) + if (property.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute))) { table.PreferredProperty = config.Identifier; + config.Editable = false; + } return config; } diff --git a/src/HopFrame.Core/Services/IEntityAccessor.cs b/src/HopFrame.Core/Services/IEntityAccessor.cs index 1fba880..613361f 100644 --- a/src/HopFrame.Core/Services/IEntityAccessor.cs +++ b/src/HopFrame.Core/Services/IEntityAccessor.cs @@ -6,11 +6,18 @@ namespace HopFrame.Core.Services; public interface IEntityAccessor { /// - /// Returns the formatted content of the property, ready to be displayed + /// Returns the formatted value of the property, ready to be displayed /// /// The model to pull the property from /// The property that shall be extracted public string? GetValue(object model, PropertyConfig property); + + /// + /// Returns the real value of the property, ready to be displayed + /// + /// The model to pull the property from + /// The property that shall be extracted + public object? GetValueRaw(object model, PropertyConfig property); /// /// Formats the property to be displayed properly @@ -25,7 +32,7 @@ public interface IEntityAccessor { /// The model to save the property to /// The property that shall be modified /// The new value of the property - public void SetValue(object model, PropertyConfig property, object value); + public void SetValue(object model, PropertyConfig property, object? value); /// /// Sorts the provided dataset by the specified property diff --git a/src/HopFrame.Core/Services/Implementation/EntityAccessor.cs b/src/HopFrame.Core/Services/Implementation/EntityAccessor.cs index 406bbd7..383052d 100644 --- a/src/HopFrame.Core/Services/Implementation/EntityAccessor.cs +++ b/src/HopFrame.Core/Services/Implementation/EntityAccessor.cs @@ -7,12 +7,19 @@ namespace HopFrame.Core.Services.Implementation; internal class EntityAccessor(IConfigAccessor accessor) : IEntityAccessor { public string? GetValue(object model, PropertyConfig property) { + var value = GetValueRaw(model, property); + if (value is null) + return null; + + return FormatValue(value, property); + } + + public object? GetValueRaw(object model, PropertyConfig property) { var prop = model.GetType().GetProperty(property.Identifier); if (prop is null) return null; - var value = prop.GetValue(model); - return FormatValue(value, property); + return prop.GetValue(model); } public string? FormatValue(object? value, PropertyConfig property) { @@ -34,12 +41,12 @@ internal class EntityAccessor(IConfigAccessor accessor) : IEntityAccessor { return value.ToString(); } - public void SetValue(object model, PropertyConfig property, object value) { + public void SetValue(object model, PropertyConfig property, object? value) { var prop = model.GetType().GetProperty(property.Identifier); if (prop is null) return; - if (value.GetType() != property.Type) + if (value?.GetType() != property.Type) value = Convert.ChangeType(value, property.Type); prop.SetValue(model, value); diff --git a/src/HopFrame.Web/Components/Components/Editor.razor b/src/HopFrame.Web/Components/Components/Editor.razor index f61e3c6..7215385 100644 --- a/src/HopFrame.Web/Components/Components/Editor.razor +++ b/src/HopFrame.Web/Components/Components/Editor.razor @@ -18,13 +18,19 @@ } - - - @foreach (var property in GetProperties()) { - - } - - + @if (Entry is not null) { + + + @foreach (var property in GetProperties()) { + + } + + + } Save diff --git a/src/HopFrame.Web/Components/Components/Editor.razor.cs b/src/HopFrame.Web/Components/Components/Editor.razor.cs index a0213eb..4ff0138 100644 --- a/src/HopFrame.Web/Components/Components/Editor.razor.cs +++ b/src/HopFrame.Web/Components/Components/Editor.razor.cs @@ -1,10 +1,11 @@ using HopFrame.Core.Configuration; +using HopFrame.Core.Services; using Microsoft.AspNetCore.Components; using MudBlazor; namespace HopFrame.Web.Components.Components; -public partial class Editor(IDialogService dialogs) : ComponentBase { +public partial class Editor(IDialogService dialogs, IEntityAccessor accessor) : ComponentBase { private enum EditorMode { Editor, @@ -15,17 +16,20 @@ public partial class Editor(IDialogService dialogs) : ComponentBase { public required TableConfig Config { get; set; } private bool IsVisible { get; set; } - + private object? Entry { get; set; } private EditorMode Mode { get; set; } private TaskCompletionSource Completion { get; set; } = null!; + private Dictionary UpdatedValues { get; set; } = null!; + public Task Present(object? entry) { Completion = new (); Mode = entry is null ? EditorMode.Creator : EditorMode.Editor; - Entry = entry ?? Activator.CreateInstance(Config.TableType); + Entry = entry ?? Activator.CreateInstance(Config.TableType)!; + UpdatedValues = new(); StateHasChanged(); IsVisible = true; return Completion.Task; @@ -56,8 +60,19 @@ public partial class Editor(IDialogService dialogs) : ComponentBase { return query.OrderBy(p => p.OrderIndex); } + private object? GetPropertyValue(PropertyConfig property) { + return accessor.GetValueRaw(Entry!, property); + } + + private void OnPropertyUpdated(PropertyConfig property, object? value) { + UpdatedValues[property.Identifier] = value; + } + private void ApplyChanges() { - + foreach (var propUpdate in UpdatedValues) { + var property = Config.Properties.First(p => p.Identifier == propUpdate.Key); + accessor.SetValue(Entry!, property, propUpdate.Value); + } } } \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Components/PropertyInput.razor b/src/HopFrame.Web/Components/Components/PropertyInput.razor index e9eab81..99011a4 100644 --- a/src/HopFrame.Web/Components/Components/PropertyInput.razor +++ b/src/HopFrame.Web/Components/Components/PropertyInput.razor @@ -1,11 +1,14 @@ -@using HopFrame.Core.Configuration +@using System.Collections +@using HopFrame.Core.Configuration @switch ((PropertyType)((byte)Config.PropertyType & 0x0F)) { case PropertyType.Numeric: break; @@ -13,8 +16,9 @@ case PropertyType.Boolean: break; @@ -22,14 +26,18 @@ @@ -38,16 +46,20 @@ case PropertyType.DateOnly: break; case PropertyType.TimeOnly: break; @@ -55,9 +67,11 @@ case PropertyType.Email: break; @@ -65,28 +79,61 @@ case PropertyType.Password: + Variant="Variant" + Adornment="Adornment.End" + AdornmentIcon="@_passwordIcon" + OnAdornmentClick="@(OnPasswordIconClick)"/> break; case PropertyType.PhoneNumber: break; + case PropertyType.Enum: + + @if ((Config.PropertyType & PropertyType.List) != 0) { + @foreach (var value in Enum.GetValues(Config.Type.GenericTypeArguments[0])) { + @Enum.GetName(Config.Type.GenericTypeArguments[0], value) + } + } + else { + @foreach (var value in Enum.GetValues(Config.Type)) { + @Enum.GetName(Config.Type, value) + } + } + + break; + default: break; @@ -95,12 +142,90 @@ @code { [Parameter] - public object? Value { get; set; } + public object? Value { + get; + set { + field = value; + + if (value is null) + return; + + switch ((PropertyType)((byte)Config.PropertyType & 0x0F)) { + case PropertyType.DateTime: + _date = (DateTime)value; + _time = TimeOnly.FromDateTime((DateTime)value).ToTimeSpan(); + break; + + case PropertyType.DateOnly: + _date = ((DateOnly)value).ToDateTime(TimeOnly.MinValue); + break; + + case PropertyType.TimeOnly: + _time = ((TimeOnly)value).ToTimeSpan(); + break; + } + } + } [Parameter] public required PropertyConfig Config { get; set; } [Parameter] public Variant Variant { get; set; } - + + [Parameter] + public EventCallback ValueChanged { get; set; } + + private DateTime _date; + private TimeSpan _time; + + private InputType _passwordInputType = InputType.Password; + private string _passwordIcon = Icons.Material.Filled.VisibilityOff; + + private async Task OnValueChanged(object? value) { + if (value is DateTime dt) { + _date = dt; + switch ((PropertyType)((byte)Config.PropertyType & 0x0F)) { + case PropertyType.DateOnly: + value = DateOnly.FromDateTime(dt); + break; + + case PropertyType.DateTime: + value = DateOnly.FromDateTime(dt).ToDateTime(TimeOnly.FromTimeSpan(_time)); + break; + } + } + + if (value is TimeSpan ts) { + _time = ts; + switch ((PropertyType)((byte)Config.PropertyType & 0x0F)) { + case PropertyType.DateOnly: + value = TimeOnly.FromTimeSpan(ts); + break; + + case PropertyType.DateTime: + value = DateOnly.FromDateTime(_date).ToDateTime(TimeOnly.FromTimeSpan(ts)); + break; + } + } + + if ((PropertyType)((byte)Config.PropertyType & 0x0F) == PropertyType.Enum && (Config.PropertyType & PropertyType.List) != 0) { + var list = value as IReadOnlyCollection; + var newValue = Activator.CreateInstance(Config.Type) as IList; + foreach (var entry in list!) { + newValue!.Add(entry); + } + + value = newValue; + } + + if (ValueChanged.HasDelegate) + await ValueChanged.InvokeAsync(value); + } + + private void OnPasswordIconClick() { + _passwordIcon = _passwordInputType == InputType.Password ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility; + _passwordInputType = _passwordInputType == InputType.Password ? InputType.Text : InputType.Password; + } + } \ No newline at end of file