Started working on admin page modal

This commit is contained in:
2024-10-08 21:34:00 +02:00
parent bc0651cb75
commit d2729870e3
7 changed files with 180 additions and 29 deletions

View File

@@ -13,5 +13,6 @@ public interface IAdminPropertyGenerator {
IAdminPropertyGenerator DisplayName(string displayName); IAdminPropertyGenerator DisplayName(string displayName);
IAdminPropertyGenerator Description(string description); IAdminPropertyGenerator Description(string description);
IAdminPropertyGenerator Prefix(string prefix); IAdminPropertyGenerator Prefix(string prefix);
IAdminPropertyGenerator Validator(Func<object, bool> validator);
} }

View File

@@ -14,7 +14,8 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
public AdminPageGenerator() { public AdminPageGenerator() {
Page = new AdminPage<TModel> { Page = new AdminPage<TModel> {
Permissions = new AdminPagePermissions() Permissions = new AdminPagePermissions(),
ModelType = typeof(TModel)
}; };
_propertyGenerators = new Dictionary<string, AdminPropertyGenerator>(); _propertyGenerators = new Dictionary<string, AdminPropertyGenerator>();

View File

@@ -65,6 +65,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro
return this; return this;
} }
public IAdminPropertyGenerator Validator(Func<object, bool> validator) {
_property.Validator = validator;
return this;
}
public AdminPageProperty Compile() { public AdminPageProperty Compile() {
_property.DisplayName ??= _property.Name; _property.DisplayName ??= _property.Name;
return _property; return _property;
@@ -111,6 +116,10 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro
Bold(attribute?.Bold == true); Bold(attribute?.Bold == true);
} }
if (attributes.Any(a => a is RequiredAttribute)) {
_property.Required = true;
}
if (attributes.Any(a => a is AdminPrefixAttribute)) { if (attributes.Any(a => a is AdminPrefixAttribute)) {
var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute; var attribute = attributes.Single(a => a is AdminPrefixAttribute) as AdminPrefixAttribute;
Prefix(attribute?.Prefix); Prefix(attribute?.Prefix);

View File

@@ -15,6 +15,8 @@ public class AdminPage {
[JsonIgnore] [JsonIgnore]
public Type RepositoryProvider { get; set; } public Type RepositoryProvider { get; set; }
public Type ModelType { get; set; }
public string DefaultSortPropertyName { get; set; } public string DefaultSortPropertyName { get; set; }
public ListSortDirection DefaultSortDirection { get; set; } public ListSortDirection DefaultSortDirection { get; set; }

View File

@@ -13,8 +13,23 @@ public sealed class AdminPageProperty {
public bool Editable { get; set; } = true; public bool Editable { get; set; } = true;
public bool EditDisplayValue { get; set; } = true; public bool EditDisplayValue { get; set; } = true;
public bool Generated { get; set; } 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; } public bool Ignore { get; set; }
[JsonIgnore] [JsonIgnore]
public Type Type { get; set; } public Type Type { get; set; }
public Func<object, bool> Validator { get; set; }
public object GetValue(object entry) {
return entry.GetType().GetProperty(Name)?.GetValue(entry);
}
public T GetValue<T>(object entry) {
return (T)entry.GetType().GetProperty(Name)?.GetValue(entry);
}
public void SetValue(object entry, object value) {
entry.GetType().GetProperty(Name)?.SetValue(entry, value);
}
} }

View File

@@ -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
<BSModal DataId="admin-page-modal" HideOnValidSubmit="true" IsStaticBackdrop="true" @ref="_modal">
<BSForm TValue="object" EditContext="_context" OnValidSubmit="Save">
@if (!_isEdit) {
<BSModalHeader>Create entry</BSModalHeader>
}
else {
<BSModalHeader>Edit entry</BSModalHeader>
}
<BSModalContent>
@foreach (var prop in GetEditableProperties()) {
@if (!_isEdit && !prop.Editable) continue;
<div class="mb-3">
<BSLabel>@prop.DisplayName</BSLabel>
<input type="@GetInputType(prop)" class="form-control" disabled="@IsDisabled(prop)" required="@IsRequired(prop)" value="@GetPropertyValue(prop)" @onchange="e => _values[prop] = e.Value"/>
</div>
}
</BSModalContent>
<BSModalFooter>
<BSButton Target="admin-page-modal">Cancel</BSButton>
<BSButton IsSubmit="true" Color="BSColor.Primary">Save</BSButton>
</BSModalFooter>
</BSForm>
</BSModal>
@inject IServiceProvider Provider
@code {
[Parameter]
public Func<Task> ReloadDelegate { get; set; }
private BSModalBase _modal;
private EditContext _context;
private IDictionary<AdminPageProperty, object> _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<AdminPageProperty, object>();
foreach (var property in _currentPage.Properties) {
_values.Add(property, property.GetValue(_entry));
}
await _modal.ShowAsync();
}
private IList<AdminPageProperty> 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();
}
}

View File

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