From d4018cceec2dace6ee0c503970e233e3e11730b5 Mon Sep 17 00:00:00 2001 From: Leon Hoppe Date: Wed, 15 Jan 2025 14:58:15 +0100 Subject: [PATCH] Added edit modal --- src/HopFrame.Core/Config/PropertyConfig.cs | 18 ++++++ src/HopFrame.Core/Config/TableConfig.cs | 17 +++++- src/HopFrame.Core/Services/ITableManager.cs | 2 +- .../Services/Implementations/TableManager.cs | 22 +++++-- .../Components/Dialogs/HopFrameEditor.razor | 59 +++++++++++++++++++ .../Components/Pages/HopFrameListView.razor | 33 ++++++++--- src/HopFrame.Web/Helpers/TypeExtensions.cs | 22 +++++++ src/HopFrame.Web/Models/EditorDialogData.cs | 8 +++ src/HopFrame.Web/_Imports.razor | 1 + testing/HopFrame.Testing/Models/Post.cs | 2 + testing/HopFrame.Testing/Models/User.cs | 4 -- testing/HopFrame.Testing/Program.cs | 10 +++- 12 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor create mode 100644 src/HopFrame.Web/Helpers/TypeExtensions.cs create mode 100644 src/HopFrame.Web/Models/EditorDialogData.cs diff --git a/src/HopFrame.Core/Config/PropertyConfig.cs b/src/HopFrame.Core/Config/PropertyConfig.cs index 00d8e0f..cca2090 100644 --- a/src/HopFrame.Core/Config/PropertyConfig.cs +++ b/src/HopFrame.Core/Config/PropertyConfig.cs @@ -10,6 +10,9 @@ public class PropertyConfig(PropertyInfo info) { public bool Sortable { get; set; } = true; public bool Searchable { get; set; } = true; public PropertyInfo? DisplayedProperty { get; set; } + public Func? Formatter { get; set; } + public bool Editable { get; set; } = true; + public bool Creatable { get; set; } = true; } public class PropertyConfig(PropertyConfig config) { @@ -39,5 +42,20 @@ public class PropertyConfig(PropertyConfig config) { config.DisplayedProperty = TableConfig.GetPropertyInfo(propertyExpression); return this; } + + public PropertyConfig Format(Func formatter) { + config.Formatter = obj => formatter.Invoke((TProp)obj); + return this; + } + + public PropertyConfig Editable(bool editable) { + config.Editable = editable; + return this; + } + + public PropertyConfig Creatable(bool creatable) { + config.Creatable = creatable; + return this; + } } diff --git a/src/HopFrame.Core/Config/TableConfig.cs b/src/HopFrame.Core/Config/TableConfig.cs index 0ef1bcc..22c7c19 100644 --- a/src/HopFrame.Core/Config/TableConfig.cs +++ b/src/HopFrame.Core/Config/TableConfig.cs @@ -1,4 +1,6 @@ -using System.Linq.Expressions; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq.Expressions; using System.Reflection; namespace HopFrame.Core.Config; @@ -17,7 +19,18 @@ public class TableConfig { ContextConfig = config; foreach (var info in tableType.GetProperties()) { - Properties.Add(new PropertyConfig(info)); + var propConfig = new PropertyConfig(info); + + if (info.GetCustomAttributes(true).Any(a => a is DatabaseGeneratedAttribute)) { + propConfig.Creatable = false; + propConfig.Editable = false; + } + + if (info.GetCustomAttributes(true).Any(a => a is KeyAttribute)) { + propConfig.Editable = false; + } + + Properties.Add(propConfig); } } } diff --git a/src/HopFrame.Core/Services/ITableManager.cs b/src/HopFrame.Core/Services/ITableManager.cs index 6b49d9e..9683121 100644 --- a/src/HopFrame.Core/Services/ITableManager.cs +++ b/src/HopFrame.Core/Services/ITableManager.cs @@ -9,5 +9,5 @@ public interface ITableManager { public int TotalPages(int perPage = 20); public Task DeleteItem(object item); - public string DisplayProperty(object item, PropertyInfo info, TableConfig? tableConfig); + public string DisplayProperty(object? item, PropertyInfo info, TableConfig? tableConfig); } \ No newline at end of file diff --git a/src/HopFrame.Core/Services/Implementations/TableManager.cs b/src/HopFrame.Core/Services/Implementations/TableManager.cs index 0f497c2..a25c8da 100644 --- a/src/HopFrame.Core/Services/Implementations/TableManager.cs +++ b/src/HopFrame.Core/Services/Implementations/TableManager.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; using System.Reflection; using HopFrame.Core.Config; using Microsoft.EntityFrameworkCore; @@ -50,14 +51,27 @@ internal sealed class TableManager(DbContext context, TableConfig config return false; } - public string DisplayProperty(object item, PropertyInfo info, TableConfig? tableConfig) { + public string DisplayProperty(object? item, PropertyInfo info, TableConfig? tableConfig) { if (item is null) return string.Empty; var prop = tableConfig?.Properties.Find(prop => prop.Info.Name == info.Name); if (prop is null) return item.ToString() ?? string.Empty; var propValue = prop.Info.GetValue(item); - if (propValue is null || prop.DisplayedProperty is null) - return propValue?.ToString() ?? string.Empty; + if (propValue is null) + return string.Empty; + + if (prop.Formatter is not null) { + return prop.Formatter.Invoke(propValue); + } + + if (prop.DisplayedProperty is null) { + var key = prop.Info.PropertyType + .GetProperties() + .Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)) + .FirstOrDefault(); + + return key?.GetValue(propValue)?.ToString() ?? propValue.ToString() ?? string.Empty; + } var innerConfig = explorer.GetTable(propValue.GetType()); return DisplayProperty(propValue, prop.DisplayedProperty, innerConfig); diff --git a/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor b/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor new file mode 100644 index 0000000..b803c91 --- /dev/null +++ b/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.razor @@ -0,0 +1,59 @@ +@implements IDialogContentComponent + +@using HopFrame.Web.Models +@using HopFrame.Core.Services +@using HopFrame.Web.Helpers + + + @foreach (var property in Content.Config.Properties) { + if (!_currentlyEditing && !property.Creatable) continue; + +
+ @if (property.Info.PropertyType.IsNumeric()) { + + } else if (property.Info.PropertyType == typeof(bool)) { + + } + else { + + } +
+ } +
+ +@inject IContextExplorer Explorer + +@code { + [Parameter] + public required EditorDialogData Content { get; set; } + + [CascadingParameter] + public required FluentDialog Dialog { get; set; } + + private ITableManager? _manager; + private bool _currentlyEditing; + + protected override void OnInitialized() { + if (Dialog.Instance is null) return; + _currentlyEditing = Content.CurrentObject is not null; + Dialog.Instance.Parameters.Title = Content.CurrentObject is null ? "Add entry" : "Edit entry"; + Dialog.Instance.Parameters.PreventScroll = true; + Dialog.Instance.Parameters.Width = "500px"; + Dialog.Instance.Parameters.PrimaryAction = "Save"; + _manager = Explorer.GetTableManager(Content.Config.PropertyName); + } +} diff --git a/src/HopFrame.Web/Components/Pages/HopFrameListView.razor b/src/HopFrame.Web/Components/Pages/HopFrameListView.razor index 2a221d2..b3c80b7 100644 --- a/src/HopFrame.Web/Components/Pages/HopFrameListView.razor +++ b/src/HopFrame.Web/Components/Pages/HopFrameListView.razor @@ -4,30 +4,38 @@ @using HopFrame.Core.Config @using HopFrame.Core.Services +@using HopFrame.Web.Models @using Microsoft.JSInterop +@using System.Text.Json + +

@_config?.PropertyName

- Add Entry + Add Entry
@{ var dataIndex = 0; } @foreach (var property in _config!.Properties.Where(prop => prop.List)) { - + } - @{ var currentElement = _currentlyDisplayedModels!.ElementAt(dataIndex); } - + @{ var currentElement = _currentlyDisplayedModels!.ElementAtOrDefault(dataIndex); } + - + @@ -38,7 +46,7 @@ - @if (_currentlyDisplayedModels?.Any() == true) { + @if (_totalPages > 1) {
@@ -78,6 +86,7 @@ @inject IContextExplorer Explorer @inject NavigationManager Navigator @inject IJSRuntime Js +@inject IDialogService Dialogs @code { @@ -106,7 +115,9 @@ } protected override async Task OnAfterRenderAsync(bool firstRender) { - await Js.InvokeVoidAsync("removeBg"); + try { + await Js.InvokeVoidAsync("removeBg"); + }catch (Exception) {} } private CancellationTokenSource _searchCancel = new(); @@ -132,6 +143,10 @@ } private async Task DeleteEntry(object element) { //TODO: display confirmation + var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?"); + var result = await dialog.Result; + if (result.Cancelled) return; + await _manager!.DeleteItem(element); if (!string.IsNullOrEmpty(_searchTerm)) { @@ -141,4 +156,8 @@ OnInitialized(); } } + + private async Task CreateOrEdit(object? element) { + var panel = await Dialogs.ShowPanelAsync(new EditorDialogData(_config!, element), new()); + } } \ No newline at end of file diff --git a/src/HopFrame.Web/Helpers/TypeExtensions.cs b/src/HopFrame.Web/Helpers/TypeExtensions.cs new file mode 100644 index 0000000..9745208 --- /dev/null +++ b/src/HopFrame.Web/Helpers/TypeExtensions.cs @@ -0,0 +1,22 @@ +namespace HopFrame.Web.Helpers; + +internal static class TypeExtensions { + public static bool IsNumeric(this Type o) { + switch (Type.GetTypeCode(o)) { + case TypeCode.Byte: + case TypeCode.SByte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Decimal: + case TypeCode.Double: + case TypeCode.Single: + return true; + default: + return false; + } + } +} diff --git a/src/HopFrame.Web/Models/EditorDialogData.cs b/src/HopFrame.Web/Models/EditorDialogData.cs new file mode 100644 index 0000000..a055980 --- /dev/null +++ b/src/HopFrame.Web/Models/EditorDialogData.cs @@ -0,0 +1,8 @@ +using HopFrame.Core.Config; + +namespace HopFrame.Web.Models; + +public sealed class EditorDialogData(TableConfig config, object? current = null) { + public object? CurrentObject { get; } = current; + public TableConfig Config { get; } = config; +} diff --git a/src/HopFrame.Web/_Imports.razor b/src/HopFrame.Web/_Imports.razor index 4a8dc26..1c2fd1f 100644 --- a/src/HopFrame.Web/_Imports.razor +++ b/src/HopFrame.Web/_Imports.razor @@ -9,3 +9,4 @@ @using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons @using Microsoft.FluentUI.AspNetCore.Components.Extensions @using HopFrame.Web.Components.Layout +@using HopFrame.Web.Components.Dialogs diff --git a/testing/HopFrame.Testing/Models/Post.cs b/testing/HopFrame.Testing/Models/Post.cs index 4481134..c2c233f 100644 --- a/testing/HopFrame.Testing/Models/Post.cs +++ b/testing/HopFrame.Testing/Models/Post.cs @@ -14,4 +14,6 @@ public class Post { [ForeignKey("author")] public User? Author { get; set; } + + public bool Published { get; set; } } \ No newline at end of file diff --git a/testing/HopFrame.Testing/Models/User.cs b/testing/HopFrame.Testing/Models/User.cs index b6448ae..1551e46 100644 --- a/testing/HopFrame.Testing/Models/User.cs +++ b/testing/HopFrame.Testing/Models/User.cs @@ -10,8 +10,4 @@ public class User { public string? Password { get; set; } public string? FirstName { get; set; } public string? LastName { get; set; } - - public override string ToString() { - return Id.ToString(); - } } \ No newline at end of file diff --git a/testing/HopFrame.Testing/Program.cs b/testing/HopFrame.Testing/Program.cs index 5d5286d..06890fb 100644 --- a/testing/HopFrame.Testing/Program.cs +++ b/testing/HopFrame.Testing/Program.cs @@ -36,9 +36,17 @@ builder.Services.AddHopFrame(options => { .Sortable(false); }); + /* context.Table() + .Property(p => p.Author) + .DisplayedProperty(u => u!.Username); */ context.Table() .Property(p => p.Author) - .DisplayedProperty(u => u!.Username); + .Format(user => $"{user?.FirstName} {user?.LastName}"); + + context.Table() + .Property(p => p.Id) + .SetDisplayName("ID") + .Editable(true); }); });