diff --git a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs index 1e0091a..b62e7af 100644 --- a/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/IAdminPropertyGenerator.cs @@ -13,5 +13,6 @@ public interface IAdminPropertyGenerator { IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Prefix(string prefix); + IAdminPropertyGenerator Validator(Func validator); } \ No newline at end of file diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs index 33db4d7..fab851a 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPageGenerator.cs @@ -14,7 +14,8 @@ internal sealed class AdminPageGenerator : IAdminPageGenerator, public AdminPageGenerator() { Page = new AdminPage { - Permissions = new AdminPagePermissions() + Permissions = new AdminPagePermissions(), + ModelType = typeof(TModel) }; _propertyGenerators = new Dictionary(); diff --git a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs index 92a755a..e14ff75 100644 --- a/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs +++ b/src/HopFrame.Web.Admin/Generators/Implementation/AdminPropertyGenerator.cs @@ -65,6 +65,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro return this; } + public IAdminPropertyGenerator Validator(Func validator) { + _property.Validator = validator; + return this; + } + public AdminPageProperty Compile() { _property.DisplayName ??= _property.Name; return _property; @@ -111,6 +116,10 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro Bold(attribute?.Bold == true); } + if (attributes.Any(a => a is RequiredAttribute)) { + _property.Required = true; + } + if (attributes.Any(a => a is AdminPrefixAttribute)) { var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; Prefix(attribute?.Prefix); diff --git a/src/HopFrame.Web.Admin/Models/AdminPage.cs b/src/HopFrame.Web.Admin/Models/AdminPage.cs index 241f69c..509fed8 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPage.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPage.cs @@ -14,6 +14,8 @@ public class AdminPage { [JsonIgnore] public Type RepositoryProvider { get; set; } + + public Type ModelType { get; set; } public string DefaultSortPropertyName { get; set; } public ListSortDirection DefaultSortDirection { get; set; } diff --git a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs index cf5bbc6..db2f743 100644 --- a/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs +++ b/src/HopFrame.Web.Admin/Models/AdminPageProperty.cs @@ -13,8 +13,23 @@ public sealed class AdminPageProperty { public bool Editable { get; set; } = true; public bool EditDisplayValue { get; set; } = true; public bool Generated { get; set; } - public bool Bold { get; set; } = false; + public bool Bold { get; set; } + public bool Required { get; set; } public bool Ignore { get; set; } [JsonIgnore] public Type Type { get; set; } + + public Func Validator { get; set; } + + public object GetValue(object entry) { + return entry.GetType().GetProperty(Name)?.GetValue(entry); + } + + public T GetValue(object entry) { + return (T)entry.GetType().GetProperty(Name)?.GetValue(entry); + } + + public void SetValue(object entry, object value) { + entry.GetType().GetProperty(Name)?.SetValue(entry, value); + } } \ No newline at end of file diff --git a/src/HopFrame.Web/Components/Administration/AdminPageModal.razor b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor new file mode 100644 index 0000000..04241f2 --- /dev/null +++ b/src/HopFrame.Web/Components/Administration/AdminPageModal.razor @@ -0,0 +1,119 @@ +@rendermode InteractiveServer + +@using BlazorStrap +@using BlazorStrap.Shared.Components.Modal +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using BlazorStrap.V5 +@using HopFrame.Web.Admin +@using HopFrame.Web.Admin.Models +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Web + + + + @if (!_isEdit) { + Create entry + } + else { + Edit entry + } + + + @foreach (var prop in GetEditableProperties()) { + @if (!_isEdit && !prop.Editable) continue; + +
+ @prop.DisplayName + + +
+ } +
+ + + Cancel + Save + +
+
+ +@inject IServiceProvider Provider + +@code { + [Parameter] + public Func ReloadDelegate { get; set; } + + private BSModalBase _modal; + private EditContext _context; + private IDictionary _values; + private IModelRepository _repository; + + private AdminPage _currentPage; + private object _entry; + private bool _isEdit; + + public async Task Show(AdminPage page, object entryToEdit = null) { + _entry = null; + + _currentPage = page; + _entry = entryToEdit; + _isEdit = entryToEdit is not null; + _repository = Provider.GetService(_currentPage.RepositoryProvider) as IModelRepository; + + _entry ??= Activator.CreateInstance(_currentPage.ModelType); + _context = new EditContext(_entry); + _context.OnValidationRequested += Validate; + + _values = new Dictionary(); + foreach (var property in _currentPage.Properties) { + _values.Add(property, property.GetValue(_entry)); + } + + await _modal.ShowAsync(); + } + + private IList GetEditableProperties() { + return _currentPage.Properties + .Where(p => !p.Ignore) + .OrderBy(p => p.Editable) + .ToList(); + } + + private bool IsDisabled(AdminPageProperty prop) => !prop.Editable; + private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue; + + private string GetPropertyValue(AdminPageProperty property) { + if (!_isEdit) return ""; + if (!property.EditDisplayValue) return ""; + return property.GetValue(_entry)?.ToString(); + } + + private string GetInputType(AdminPageProperty property) { + if (!property.EditDisplayValue) + return "password"; + + return "text"; + } + + private void Validate(object sender, ValidationRequestedEventArgs e) { + foreach (var value in _values) { + if (value.Key.Validator?.Invoke(value.Value) == true) continue; + Console.WriteLine("INVALID"); + } + } + + private async void Save() { + foreach (var value in _values) { + value.Key.SetValue(_entry, value.Value); + } + + if (!_isEdit) { + await _repository.CreateO(_entry); + } + else { + await _repository.UpdateO(_entry); + } + + await ReloadDelegate.Invoke(); + } +} \ No newline at end of file diff --git a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor index 4798210..a04a374 100644 --- a/src/HopFrame.Web/Pages/Administration/AdminPageList.razor +++ b/src/HopFrame.Web/Pages/Administration/AdminPageList.razor @@ -20,6 +20,8 @@ @_pageData.Title + +

@_pageData.Title administration @@ -63,9 +65,16 @@ @foreach (var entry in _displayedModels) { @foreach (var prop in GetListingProperties()) { - - @GetValue(entry, prop) - + @if (prop.Bold) { + + @GetPrintableValue(entry, prop) + + } + else { + + @GetPrintableValue(entry, prop) + + } } @if (_hasEditPermission || _hasDeletePermission) { @@ -105,6 +114,7 @@ private AdminPage _pageData; private IModelRepository _modelRepository; private IEnumerable _modelBuffer; + private AdminPageModal _modal; private bool _hasEditPermission; private bool _hasDeletePermission; @@ -114,14 +124,12 @@ private DateTime _lastSearch; private IList _displayedModels; - protected override void OnInitialized() { + protected override async Task OnInitializedAsync() { _pageData = Pages.LoadAdminPage(Url); _currentSortProperty = _pageData.DefaultSortPropertyName; _currentSortDirection = _pageData.DefaultSortDirection; - } - - protected override async Task OnInitializedAsync() { + _modelRepository = Provider.GetService(_pageData.RepositoryProvider) as IModelRepository; if (_modelRepository is null) throw new ArgumentException($"AdminPage '{_pageData.Title}' does not specify a model repository!'"); @@ -129,7 +137,7 @@ _hasEditPermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Update); _hasDeletePermission = await Permissions.HasPermission(Auth.User, _pageData.Permissions.Delete); - Reload(); + await Reload(); } private IList GetListingProperties() { @@ -139,7 +147,7 @@ .ToList(); } - private async void Reload() { + private async Task Reload() { _modelBuffer = await _modelRepository.ReadAllO(); _displayedModels = _modelBuffer.ToList(); @@ -157,15 +165,15 @@ var prop = GetListingProperties() .SingleOrDefault(p => p.Name == property); var comparer = Comparer.Create((x, y) => { - if (prop.Type == typeof(DateTime)) { + if (prop?.Type == typeof(DateTime)) { DateTime dateX = (DateTime) x.GetType().GetProperty(prop.Name)?.GetValue(x)!; DateTime dateY = (DateTime) y.GetType().GetProperty(prop.Name)?.GetValue(y)!; return DateTime.Compare(dateX, dateY); } - - var propX = GetValue(x, prop); - var propY = GetValue(y, prop); + + var propX = GetPrintableValue(x, prop); + var propY = GetPrintableValue(y, prop); return String.CompareOrdinal(propX, propY); }); @@ -198,7 +206,7 @@ var props = GetListingProperties(); _displayedModels = _modelBuffer - .Where(model => props.Any(prop => GetValue(model, prop).Contains(search))) + .Where(model => props.Any(prop => GetPrintableValue(model, prop).Contains(search))) .ToList(); } @@ -206,29 +214,25 @@ StateHasChanged(); } - private string GetValue(object entry, AdminPageProperty property) { - object propValue = entry.GetType().GetProperty(property.Name)?.GetValue(entry); + private static string GetPrintableValue(object element, AdminPageProperty prop) { + var entry = prop.GetValue(element); + + //TODO: convert relational types - return propValue?.ToString(); - } - - private string GetClass(AdminPageProperty property) { - if (property.Bold) - return "bold"; - return ""; + return entry?.ToString(); } private async void Create() { - //TODO: Open create modal + await _modal.Show(_pageData); } private async void Edit(object entry) { - //TODO: Open edit modal + await _modal.Show(_pageData, entry); } private async void Delete(object entry) { var result = await Alerts.FireAsync(new SweetAlertOptions { - Title = "Do you really want to delete this entry?", + Title = "Are you sure?", Text = "You won't be able to revert this!", Icon = SweetAlertIcon.Warning, ConfirmButtonText = "Yes", @@ -238,7 +242,7 @@ if (result.IsConfirmed) { await _modelRepository.DeleteO(entry); - Reload(); + await Reload(); await Alerts.FireAsync(new SweetAlertOptions { Title = "Deleted!",