Working on Admin page modal
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
namespace HopFrame.Database.Attributes;
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public sealed class ListingPropertyAttribute : Attribute;
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using HopFrame.Database.Attributes;
|
||||||
|
|
||||||
namespace HopFrame.Database.Models;
|
namespace HopFrame.Database.Models;
|
||||||
|
|
||||||
@@ -9,7 +10,7 @@ public class Permission {
|
|||||||
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[Key, Required, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
public long Id { get; init; }
|
public long Id { get; init; }
|
||||||
|
|
||||||
[Required, MaxLength(255)]
|
[Required, MaxLength(255), ListingProperty]
|
||||||
public string PermissionName { get; set; }
|
public string PermissionName { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using HopFrame.Database.Attributes;
|
||||||
|
|
||||||
namespace HopFrame.Database.Models;
|
namespace HopFrame.Database.Models;
|
||||||
|
|
||||||
public class PermissionGroup : IPermissionOwner {
|
public class PermissionGroup : IPermissionOwner {
|
||||||
|
|
||||||
[Key, Required, MaxLength(50)]
|
[Key, Required, MaxLength(50), ListingProperty]
|
||||||
public string Name { get; init; }
|
public string Name { get; init; }
|
||||||
|
|
||||||
[Required, DefaultValue(false)]
|
[Required, DefaultValue(false)]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using HopFrame.Database.Attributes;
|
||||||
|
|
||||||
namespace HopFrame.Database.Models;
|
namespace HopFrame.Database.Models;
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ public class User : IPermissionOwner {
|
|||||||
[Key, Required, MinLength(36), MaxLength(36)]
|
[Key, Required, MinLength(36), MaxLength(36)]
|
||||||
public Guid Id { get; init; }
|
public Guid Id { get; init; }
|
||||||
|
|
||||||
[MaxLength(50)]
|
[MaxLength(50), ListingProperty]
|
||||||
public string Username { get; set; }
|
public string Username { get; set; }
|
||||||
|
|
||||||
[Required, MaxLength(50), EmailAddress]
|
[Required, MaxLength(50), EmailAddress]
|
||||||
|
|||||||
@@ -23,5 +23,6 @@ public interface IAdminPageGenerator<TModel> {
|
|||||||
IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : ModelRepository<TModel>;
|
IAdminPageGenerator<TModel> ConfigureRepository<TRepository>() where TRepository : ModelRepository<TModel>;
|
||||||
|
|
||||||
IAdminPropertyGenerator Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression);
|
IAdminPropertyGenerator Property<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression);
|
||||||
|
IAdminPageGenerator<TModel> ListingProperty<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ public interface IAdminPropertyGenerator {
|
|||||||
IAdminPropertyGenerator Description(string description);
|
IAdminPropertyGenerator Description(string description);
|
||||||
IAdminPropertyGenerator Prefix(string prefix);
|
IAdminPropertyGenerator Prefix(string prefix);
|
||||||
IAdminPropertyGenerator Validator(Func<object, bool> validator);
|
IAdminPropertyGenerator Validator(Func<object, bool> validator);
|
||||||
|
IAdminPropertyGenerator IsSelector<TSelector>();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -113,6 +113,12 @@ internal sealed class AdminPageGenerator<TModel> : IAdminPageGenerator<TModel>,
|
|||||||
return generator;
|
return generator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IAdminPageGenerator<TModel> ListingProperty<TProperty>(Expression<Func<TModel, TProperty>> propertyExpression) {
|
||||||
|
var property = GetPropertyInfo(propertyExpression);
|
||||||
|
Page.ListingProperty = property.Name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public AdminPage<TModel> Compile() {
|
public AdminPage<TModel> Compile() {
|
||||||
var properties = new List<AdminPageProperty>();
|
var properties = new List<AdminPageProperty>();
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,11 @@ internal sealed class AdminPropertyGenerator(string name, Type type) : IAdminPro
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IAdminPropertyGenerator IsSelector<TSelector>() {
|
||||||
|
_property.SelectorType = typeof(TSelector);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public AdminPageProperty Compile() {
|
public AdminPageProperty Compile() {
|
||||||
_property.DisplayName ??= _property.Name;
|
_property.DisplayName ??= _property.Name;
|
||||||
return _property;
|
return _property;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ public class AdminPage {
|
|||||||
public string Url { get; set; }
|
public string Url { get; set; }
|
||||||
public AdminPagePermissions Permissions { get; set; }
|
public AdminPagePermissions Permissions { get; set; }
|
||||||
public IList<AdminPageProperty> Properties { get; set; }
|
public IList<AdminPageProperty> Properties { get; set; }
|
||||||
|
public string ListingProperty { get; set; }
|
||||||
|
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Type RepositoryProvider { get; set; }
|
public Type RepositoryProvider { get; set; }
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ public sealed class AdminPageProperty {
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public Type Type { get; set; }
|
public Type Type { get; set; }
|
||||||
|
|
||||||
|
public Type SelectorType { get; set; }
|
||||||
|
|
||||||
public Func<object, bool> Validator { get; set; }
|
public Func<object, bool> Validator { get; set; }
|
||||||
|
|
||||||
public object GetValue(object entry) {
|
public object GetValue(object entry) {
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ public interface IAdminPagesProvider {
|
|||||||
internal void RegisterAdminPage(string url, AdminPage page);
|
internal void RegisterAdminPage(string url, AdminPage page);
|
||||||
AdminPage LoadAdminPage(string url);
|
AdminPage LoadAdminPage(string url);
|
||||||
IList<AdminPage> LoadRegisteredAdminPages();
|
IList<AdminPage> LoadRegisteredAdminPages();
|
||||||
|
AdminPage HasPageFor(Type type);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -16,4 +16,11 @@ public class AdminPagesProvider : IAdminPagesProvider {
|
|||||||
public IList<AdminPage> LoadRegisteredAdminPages() {
|
public IList<AdminPage> LoadRegisteredAdminPages() {
|
||||||
return _pages.Values.ToList();
|
return _pages.Values.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AdminPage HasPageFor(Type type) {
|
||||||
|
return _pages
|
||||||
|
.Where(p => p.Value.ModelType == type)
|
||||||
|
.Select(p => p.Value)
|
||||||
|
.SingleOrDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,15 @@
|
|||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
|
@using System.Collections
|
||||||
|
@using System.ComponentModel.DataAnnotations
|
||||||
@using BlazorStrap
|
@using BlazorStrap
|
||||||
@using BlazorStrap.Shared.Components.Modal
|
@using BlazorStrap.Shared.Components.Modal
|
||||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||||
@using BlazorStrap.V5
|
@using BlazorStrap.V5
|
||||||
|
@using HopFrame.Database.Attributes
|
||||||
@using HopFrame.Web.Admin
|
@using HopFrame.Web.Admin
|
||||||
@using HopFrame.Web.Admin.Models
|
@using HopFrame.Web.Admin.Models
|
||||||
|
@using HopFrame.Web.Admin.Providers
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Web
|
@using Microsoft.AspNetCore.Components.Web
|
||||||
|
|
||||||
@@ -23,9 +27,54 @@
|
|||||||
@if (!_isEdit && !prop.Editable) continue;
|
@if (!_isEdit && !prop.Editable) continue;
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<BSLabel>@prop.DisplayName</BSLabel>
|
@if (IsListType(prop)) {
|
||||||
|
<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"/>
|
<BSListGroup>
|
||||||
|
<BSListGroupItem>
|
||||||
|
<BSListGroup IsFlush="true">
|
||||||
|
@foreach (var element in GetListPropertyValues(prop).Select((e, i) => new {e, i})) {
|
||||||
|
<BSListGroupItem>
|
||||||
|
<BSButton Color="BSColor.Danger" Size="Size.ExtraSmall" MarginEnd="Margins.Small" OnClick="() => DeleteListItem(prop, element.i)">
|
||||||
|
<HopIconDisplay Type="HopIconDisplay.HopIcon.Cross"/>
|
||||||
|
</BSButton>
|
||||||
|
|
||||||
|
<span>@element.e</span>
|
||||||
|
</BSListGroupItem>
|
||||||
|
}
|
||||||
|
</BSListGroup>
|
||||||
|
</BSListGroupItem>
|
||||||
|
<BSListGroupItem>
|
||||||
|
<div style="display: flex; gap: 20px">
|
||||||
|
@if (prop.SelectorType is null) {
|
||||||
|
<input type="text" class="form-control" @onchange="v => _inputValues[prop] = (string)v.Value"/>
|
||||||
|
<BSButton Color="BSColor.Secondary">Add</BSButton>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
@*<BSInput InputType="InputType.Select"> TODO: implement selector
|
||||||
|
<option selected>Select group</option>
|
||||||
|
|
||||||
|
@foreach (var group in _allGroups) {
|
||||||
|
@if (_group.Permissions.All(g => g.PermissionName != group.Name) && group.Name != _group.Name) {
|
||||||
|
<option value="@group.Name">@group.Name.Replace("group.", "")</option>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</BSInput>
|
||||||
|
<BSButton Color="BSColor.Secondary">Add</BSButton>*@
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</BSListGroupItem>
|
||||||
|
</BSListGroup>
|
||||||
|
}
|
||||||
|
else if (IsSwitch(prop)) {
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<BSLabel>@prop.DisplayName</BSLabel>
|
||||||
|
<input class="form-check-input" type="checkbox" checked="@_values[prop]" @onchange="e => _values[prop] = Convert.ToBoolean(e.Value)">
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
<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>
|
</div>
|
||||||
}
|
}
|
||||||
</BSModalContent>
|
</BSModalContent>
|
||||||
@@ -38,6 +87,7 @@
|
|||||||
</BSModal>
|
</BSModal>
|
||||||
|
|
||||||
@inject IServiceProvider Provider
|
@inject IServiceProvider Provider
|
||||||
|
@inject IAdminPagesProvider PageProvider
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[Parameter]
|
[Parameter]
|
||||||
@@ -51,9 +101,11 @@
|
|||||||
private AdminPage _currentPage;
|
private AdminPage _currentPage;
|
||||||
private object _entry;
|
private object _entry;
|
||||||
private bool _isEdit;
|
private bool _isEdit;
|
||||||
|
private IDictionary<AdminPageProperty, string> _inputValues;
|
||||||
|
|
||||||
public async Task Show(AdminPage page, object entryToEdit = null) {
|
public async Task Show(AdminPage page, object entryToEdit = null) {
|
||||||
_entry = null;
|
_entry = null;
|
||||||
|
_inputValues = new Dictionary<AdminPageProperty, string>();
|
||||||
|
|
||||||
_currentPage = page;
|
_currentPage = page;
|
||||||
_entry = entryToEdit;
|
_entry = entryToEdit;
|
||||||
@@ -81,11 +133,67 @@
|
|||||||
|
|
||||||
private bool IsDisabled(AdminPageProperty prop) => !prop.Editable;
|
private bool IsDisabled(AdminPageProperty prop) => !prop.Editable;
|
||||||
private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue;
|
private bool IsRequired(AdminPageProperty prop) => !_isEdit ? prop.Required : prop.Required && prop.EditDisplayValue;
|
||||||
|
private bool IsSwitch(AdminPageProperty prop) => prop.Type == typeof(bool);
|
||||||
|
|
||||||
|
private bool IsListType(AdminPageProperty prop) {
|
||||||
|
if (!prop.Type.IsGenericType) return false;
|
||||||
|
var generic = prop.Type.GenericTypeArguments[0];
|
||||||
|
var listType = typeof(IList<>).MakeGenericType(generic);
|
||||||
|
return prop.Type.IsAssignableFrom(listType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IList<string> GetListPropertyValues(AdminPageProperty prop) {
|
||||||
|
if (!IsListType(prop)) return new List<string>();
|
||||||
|
var list = new List<string>();
|
||||||
|
|
||||||
|
var values = prop.GetValue(_entry);
|
||||||
|
|
||||||
|
if (values is null) {
|
||||||
|
prop.SetValue(_entry, Activator.CreateInstance(prop.Type));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var value in (IEnumerable)values) {
|
||||||
|
list.Add(MapPropertyValue(value, prop));
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
private string GetPropertyValue(AdminPageProperty property) {
|
private string GetPropertyValue(AdminPageProperty property) {
|
||||||
if (!_isEdit) return "";
|
if (!_isEdit) return "";
|
||||||
if (!property.EditDisplayValue) return "";
|
if (!property.EditDisplayValue) return "";
|
||||||
return property.GetValue(_entry)?.ToString();
|
return MapPropertyValue(property.GetValue(_entry), property);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string MapPropertyValue(object value, AdminPageProperty property) {
|
||||||
|
if (value is null) return string.Empty;
|
||||||
|
var type = value.GetType();
|
||||||
|
|
||||||
|
var page = PageProvider.HasPageFor(type);
|
||||||
|
if (page is not null && !string.IsNullOrWhiteSpace(page.ListingProperty)) {
|
||||||
|
var prop = page.Properties
|
||||||
|
.SingleOrDefault(p => p.Name == page.ListingProperty);
|
||||||
|
|
||||||
|
if (prop is not null) {
|
||||||
|
return MapPropertyValue(prop.GetValue(value), prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type.GetProperties().Any(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute))) {
|
||||||
|
var prop = type.GetProperties()
|
||||||
|
.SingleOrDefault(p => p.GetCustomAttributes(false).Any(a => a is ListingPropertyAttribute));
|
||||||
|
|
||||||
|
return MapPropertyValue(prop?.GetValue(value), property);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringValue = value.ToString();
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(property.Prefix)) {
|
||||||
|
return stringValue?.Replace(property.Prefix, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetInputType(AdminPageProperty property) {
|
private string GetInputType(AdminPageProperty property) {
|
||||||
@@ -97,11 +205,18 @@
|
|||||||
|
|
||||||
private void Validate(object sender, ValidationRequestedEventArgs e) {
|
private void Validate(object sender, ValidationRequestedEventArgs e) {
|
||||||
foreach (var value in _values) {
|
foreach (var value in _values) {
|
||||||
|
if (value.Key.Validator is null) continue;
|
||||||
if (value.Key.Validator?.Invoke(value.Value) == true) continue;
|
if (value.Key.Validator?.Invoke(value.Value) == true) continue;
|
||||||
Console.WriteLine("INVALID");
|
Console.WriteLine("INVALID");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DeleteListItem(AdminPageProperty prop, int index) {
|
||||||
|
Console.WriteLine(index);
|
||||||
|
var list = prop.GetValue<IList>(_entry);
|
||||||
|
list.RemoveAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
private async void Save() {
|
private async void Save() {
|
||||||
foreach (var value in _values) {
|
foreach (var value in _values) {
|
||||||
value.Key.SetValue(_entry, value.Value);
|
value.Key.SetValue(_entry, value.Value);
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ public class HopAdminContext : AdminPagesContext {
|
|||||||
.Editable(false);
|
.Editable(false);
|
||||||
|
|
||||||
generator.Page<User>().Property(u => u.Permissions)
|
generator.Page<User>().Property(u => u.Permissions)
|
||||||
.DisplayInListing(false);
|
.DisplayInListing(false)
|
||||||
|
.IsSelector<PermissionGroup>();
|
||||||
|
|
||||||
generator.Page<User>().Property(u => u.Tokens)
|
generator.Page<User>().Property(u => u.Tokens)
|
||||||
.Ignore();
|
.Ignore();
|
||||||
|
|||||||
@@ -214,12 +214,8 @@
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetPrintableValue(object element, AdminPageProperty prop) {
|
private string GetPrintableValue(object element, AdminPageProperty prop) {
|
||||||
var entry = prop.GetValue(element);
|
return _modal?.MapPropertyValue(prop.GetValue(element), prop);
|
||||||
|
|
||||||
//TODO: convert relational types
|
|
||||||
|
|
||||||
return entry?.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void Create() {
|
private async void Create() {
|
||||||
|
|||||||
Reference in New Issue
Block a user