Added reload button and animation

This commit is contained in:
2025-01-16 11:24:16 +01:00
parent 1cf2b44503
commit fc85425189
9 changed files with 110 additions and 79 deletions

View File

@@ -21,7 +21,8 @@ public class DbContextConfig {
}
}
public class DbContextConfig<TDbContext>(Type context) : DbContextConfig(context) where TDbContext : DbContext {
public class DbContextConfig<TDbContext>(DbContextConfig config) {
public DbContextConfig InnerConfig { get; } = config;
public DbContextConfig<TDbContext> Table<TModel>(Action<TableConfig<TModel>> configurator) where TModel : class {
var table = Table<TModel>();
@@ -30,7 +31,7 @@ public class DbContextConfig<TDbContext>(Type context) : DbContextConfig(context
}
public TableConfig<TModel> Table<TModel>() where TModel : class {
var table = Tables.Single(table => table.TableType == typeof(TModel));
var table = InnerConfig.Tables.Single(table => table.TableType == typeof(TModel));
return new TableConfig<TModel>(table);
}

View File

@@ -4,14 +4,15 @@ using Microsoft.EntityFrameworkCore;
namespace HopFrame.Core.Config;
public class HopFrameConfig {
public List<DbContextConfig> Contexts { get; init; } = new();
public List<DbContextConfig> Contexts { get; } = new();
public bool DisplayUserInfo { get; set; } = true;
public Type? AuthHandler { get; set; }
public string? BasePolicy { get; set; }
public string? LoginPageRewrite { get; set; }
}
public class HopFrameConfigurator(HopFrameConfig config) {
public HopFrameConfig InnerConfig { get; } = config;
public HopFrameConfigurator AddDbContext<TDbContext>(Action<DbContextConfig<TDbContext>> configurator) where TDbContext : DbContext {
var context = AddDbContext<TDbContext>();
configurator.Invoke(context);
@@ -19,28 +20,23 @@ public class HopFrameConfigurator(HopFrameConfig config) {
}
public DbContextConfig<TDbContext> AddDbContext<TDbContext>() where TDbContext : DbContext {
var context = new DbContextConfig<TDbContext>(typeof(TDbContext));
config.Contexts.Add(context);
return context;
var context = new DbContextConfig(typeof(TDbContext));
InnerConfig.Contexts.Add(context);
return new DbContextConfig<TDbContext>(context);
}
public HopFrameConfigurator DisplayUserInfo(bool display) {
config.DisplayUserInfo = display;
return this;
}
public HopFrameConfigurator SetAuthHandler<TAuthHandler>() where TAuthHandler : IHopFrameAuthHandler {
config.AuthHandler = typeof(TAuthHandler);
InnerConfig.DisplayUserInfo = display;
return this;
}
public HopFrameConfigurator SetBasePolicy(string basePolicy) {
config.BasePolicy = basePolicy;
InnerConfig.BasePolicy = basePolicy;
return this;
}
public HopFrameConfigurator SetLoginPage(string url) {
config.LoginPageRewrite = url;
InnerConfig.LoginPageRewrite = url;
return this;
}
}

View File

@@ -16,63 +16,70 @@ public class PropertyConfig(PropertyInfo info) {
public bool Editable { get; set; } = true;
public bool Creatable { get; set; } = true;
public bool DisplayValue { get; set; } = true;
public bool IsRelation { get; set; }
}
public class PropertyConfig<TProp>(PropertyConfig config) {
public PropertyConfig InnerConfig { get; } = config;
public PropertyConfig<TProp> SetDisplayName(string displayName) {
config.Name = displayName;
InnerConfig.Name = displayName;
return this;
}
public PropertyConfig<TProp> List(bool list) {
config.List = list;
config.Searchable = false;
InnerConfig.List = list;
InnerConfig.Searchable = false;
return this;
}
public PropertyConfig<TProp> Sortable(bool sortable) {
config.Sortable = sortable;
InnerConfig.Sortable = sortable;
return this;
}
public PropertyConfig<TProp> Searchable(bool searchable) {
config.Searchable = searchable;
InnerConfig.Searchable = searchable;
return this;
}
public PropertyConfig<TProp> DisplayedProperty<TInnerProp>(Expression<Func<TProp, TInnerProp>> propertyExpression) {
config.DisplayedProperty = TableConfig<TProp>.GetPropertyInfo(propertyExpression);
InnerConfig.DisplayedProperty = TableConfig<TProp>.GetPropertyInfo(propertyExpression);
return this;
}
public PropertyConfig<TProp> Format(Func<TProp, string> formatter) {
config.Formatter = obj => formatter.Invoke((TProp)obj);
InnerConfig.Formatter = obj => formatter.Invoke((TProp)obj);
return this;
}
public PropertyConfig<TProp> ValueParser(Func<string, TProp> parser) {
config.Parser = str => parser.Invoke(str)!;
InnerConfig.Parser = str => parser.Invoke(str)!;
return this;
}
public PropertyConfig<TProp> ValueTemplate(Func<TProp> template) {
config.Template = () => template.Invoke()!;
InnerConfig.Template = () => template.Invoke()!;
return this;
}
public PropertyConfig<TProp> Editable(bool editable) {
config.Editable = editable;
InnerConfig.Editable = editable;
return this;
}
public PropertyConfig<TProp> Creatable(bool creatable) {
config.Creatable = creatable;
InnerConfig.Creatable = creatable;
return this;
}
public PropertyConfig<TProp> DisplayValue(bool display) {
config.DisplayValue = display;
InnerConfig.DisplayValue = display;
return this;
}
public PropertyConfig<TProp> IsRelation(bool isRelation) {
InnerConfig.IsRelation = isRelation;
return this;
}

View File

@@ -35,16 +35,17 @@ public class TableConfig {
}
}
public class TableConfig<TModel>(TableConfig innerConfig) {
public class TableConfig<TModel>(TableConfig config) {
public TableConfig InnerConfig { get; } = config;
public TableConfig<TModel> Ignore() {
innerConfig.Ignored = true;
InnerConfig.Ignored = true;
return this;
}
public PropertyConfig<TProp> Property<TProp>(Expression<Func<TModel, TProp>> propertyExpression) {
var info = GetPropertyInfo(propertyExpression);
var prop = innerConfig.Properties
var prop = InnerConfig.Properties
.Single(prop => prop.Info.Name == info.Name);
return new PropertyConfig<TProp>(prop);
}

View File

@@ -5,8 +5,8 @@ namespace HopFrame.Core.Services;
public interface ITableManager {
public IQueryable<object> LoadPage(int page, int perPage = 20);
public (IEnumerable<object>, int) Search(string searchTerm, int page = 0, int perPage = 20);
public int TotalPages(int perPage = 20);
public Task<(IEnumerable<object>, int)> Search(string searchTerm, int page = 0, int perPage = 20);
public Task<int> TotalPages(int perPage = 20);
public Task DeleteItem(object item);
public Task EditItem(object item);
public Task AddItem(object item);

View File

@@ -16,19 +16,21 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
.Take(perPage);
}
public (IEnumerable<object>, int) Search(string searchTerm, int page = 0, int perPage = 20) {
public Task<(IEnumerable<object>, int)> Search(string searchTerm, int page = 0, int perPage = 20) {
var table = context.Set<TModel>();
var all = IncludeForgeinKeys(table)
.AsEnumerable()
.Where(item => ItemSearched(item, searchTerm))
.ToList();
return (all.Skip(page * perPage).Take(perPage), (int)Math.Ceiling(all.Count / (double)perPage));
return Task.FromResult((
(IEnumerable<object>)all.Skip(page * perPage).Take(perPage),
(int)Math.Ceiling(all.Count / (double)perPage)));
}
public int TotalPages(int perPage = 20) {
public async Task<int> TotalPages(int perPage = 20) {
var table = context.Set<TModel>();
return (int)Math.Ceiling(table.Count() / (double)perPage);
return (int)Math.Ceiling(await table.CountAsync() / (double)perPage);
}
public async Task DeleteItem(object item) {

View File

@@ -1,22 +1,32 @@
@page "/admin/{TableName}"
@layout HopFrameLayout
@rendermode InteractiveServer
@implements IDisposable
@using HopFrame.Core.Config
@using HopFrame.Core.Services
@using HopFrame.Web.Models
@using Microsoft.JSInterop
@using System.Text.Json
@using Microsoft.EntityFrameworkCore
<FluentDialogProvider />
<div style="display: flex; flex-direction: column; height: 100%">
<FluentToolbar Class="hopframe-toolbar">
<h3>@_config?.PropertyName</h3>
<FluentButton
IconStart="@(new Icons.Regular.Size16.ArrowClockwise())"
OnClick="Reload"
Loading="_loading"
Style="margin-left: 10px">
Refresh
</FluentButton>
<FluentSpacer />
<FluentSearch @oninput="OnSearch" @onchange="OnSearch" />
<FluentButton OnClick="async () => { await CreateOrEdit(null); }">Add Entry</FluentButton>
</FluentToolbar>
<FluentProgress Visible="_loading" Width="100%" />
<div style="overflow-y: auto; flex-grow: 1">
<FluentDataGrid Items="_currentlyDisplayedModels?.AsQueryable()">
@@ -48,7 +58,7 @@
@if (_totalPages > 1) {
<div class="hopframe-paginator">
<FluentButton BackgroundColor="transparent" OnClick="() => ChangePage(_currentPage - 1)">
<FluentButton BackgroundColor="transparent" OnClick="async () => await ChangePage(_currentPage - 1)">
<FluentIcon Value="@(new Icons.Regular.Size20.ArrowPrevious())" Color="Color.Neutral" />
</FluentButton>
@@ -58,12 +68,12 @@
Items="Enumerable.Range(0, _totalPages)"
OptionValue="@(p => p.ToString())"
OptionText="@(p => (p + 1).ToString())"
ValueChanged="s => ChangePage(Convert.ToInt32(s))"
ValueChanged="async s => await ChangePage(Convert.ToInt32(s))"
Width="max-content" SelectedOption="@_currentPage"/>
<span>of @_totalPages</span>
<FluentButton BackgroundColor="transparent" OnClick="() => ChangePage(_currentPage + 1)">
<FluentButton BackgroundColor="transparent" OnClick="async () => await ChangePage(_currentPage + 1)">
<FluentIcon Value="@(new Icons.Regular.Size20.ArrowNext())" Color="Color.Neutral" />
</FluentButton>
</div>
@@ -99,18 +109,20 @@
private int _currentPage;
private int _totalPages;
private string? _searchTerm;
private bool _loading;
protected override void OnInitialized() {
_config ??= Explorer.GetTable(TableName);
if (_config is null) {
Navigator.NavigateTo("/admin", true);
return;
}
}
_manager ??= Explorer.GetTableManager(_config.PropertyName);
_currentlyDisplayedModels = _manager!.LoadPage(_currentPage).ToArray();
_totalPages = _manager.TotalPages();
protected override async Task OnInitializedAsync() {
_manager ??= Explorer.GetTableManager(_config!.PropertyName);
_currentlyDisplayedModels = await _manager!.LoadPage(_currentPage).ToArrayAsync();
_totalPages = await _manager.TotalPages();
}
protected override async Task OnAfterRenderAsync(bool firstRender) {
@@ -122,6 +134,10 @@
}
}
public void Dispose() {
_searchCancel.Dispose();
}
private CancellationTokenSource _searchCancel = new();
private async Task OnSearch(ChangeEventArgs eventArgs) {
await _searchCancel.CancelAsync();
@@ -130,24 +146,26 @@
_searchCancel = new();
await Task.Delay(500, _searchCancel.Token);
(var query, _totalPages) = _manager!.Search(_searchTerm);
(var query, _totalPages) = await _manager!.Search(_searchTerm);
_currentlyDisplayedModels = query.ToArray();
}
private void Reload() {
private async Task Reload() {
_loading = true;
if (!string.IsNullOrEmpty(_searchTerm)) {
(var query, _totalPages) = _manager!.Search(_searchTerm);
(var query, _totalPages) = await _manager!.Search(_searchTerm);
_currentlyDisplayedModels = query.ToArray();
}
else {
OnInitialized();
await OnInitializedAsync();
}
_loading = false;
}
private void ChangePage(int page) {
private async Task ChangePage(int page) {
if (page < 0 || page > _totalPages - 1) return;
_currentPage = page;
Reload();
await Reload();
}
private async Task DeleteEntry(object element) {
@@ -156,8 +174,7 @@
if (result.Cancelled) return;
await _manager!.DeleteItem(element);
Reload();
await Reload();
}
private async Task CreateOrEdit(object? element) {
@@ -176,6 +193,6 @@
else
await _manager!.EditItem(data!.CurrentObject!);
Reload();
await Reload();
}
}