Started working on listing page
This commit is contained in:
73
.idea/.idea.HopFrame/.idea/workspace.xml
generated
73
.idea/.idea.HopFrame/.idea/workspace.xml
generated
@@ -9,19 +9,18 @@
|
|||||||
<option name="autoReloadType" value="SELECTIVE" />
|
<option name="autoReloadType" value="SELECTIVE" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added admin page navigation">
|
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="">
|
||||||
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IModelManager.cs" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.cs" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ModelManager.cs" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameListView.razor" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/Post.cs" afterDir="false" />
|
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameListView.razor.css" afterDir="false" />
|
||||||
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Services/AuthService.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/.idea.HopFrame/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/IContextExplorer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IContextExplorer.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/ITableManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/ITableManager.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ContextExplorer.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameLayout.razor" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/TableManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/TableManager.cs" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameNavigation.razor" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Layout/HopFrameSideMenu.razor" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/wwwroot/hopframe.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/wwwroot/hopframe.css" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Components/Pages/Home.razor" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Components/Pages/Home.razor" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Components/Pages/Home.razor" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Components/Pages/Home.razor" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/DatabaseContext.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/DatabaseContext.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/User.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/User.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Program.cs" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
@@ -64,7 +63,10 @@
|
|||||||
}
|
}
|
||||||
}</component>
|
}</component>
|
||||||
<component name="HighlightingSettingsPerFile">
|
<component name="HighlightingSettingsPerFile">
|
||||||
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/b3ccb66df3646cb51df73ad51716136ebd2eefb4edb1308dd52a7e999582d59e/IBindableColumn.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ece8533187fe96ce67b3ef1c9cc3502ef8da5510aadb132a9b21c5605d7c2119/PropertyColumn.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ff37d54b3bf4d2756237fb789635831532603376e940f63d634b869d26d74c/Regular16.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
</component>
|
</component>
|
||||||
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
|
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
|
||||||
<component name="ProjectColorInfo">{
|
<component name="ProjectColorInfo">{
|
||||||
@@ -78,24 +80,24 @@
|
|||||||
<option name="hideEmptyMiddlePackages" value="true" />
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
<option name="showLibraryContents" value="true" />
|
<option name="showLibraryContents" value="true" />
|
||||||
</component>
|
</component>
|
||||||
<component name="PropertiesComponent"><![CDATA[{
|
<component name="PropertiesComponent">{
|
||||||
"keyToString": {
|
"keyToString": {
|
||||||
".NET Launch Settings Profile.HopFrame.Testing.executor": "Run",
|
".NET Launch Settings Profile.HopFrame.Testing.executor": "Run",
|
||||||
".NET Launch Settings Profile.HopFrame.Testing: https.executor": "Run",
|
".NET Launch Settings Profile.HopFrame.Testing: https.executor": "Run",
|
||||||
".NET Project.HopFrame.Testing.executor": "Run",
|
".NET Project.HopFrame.Testing.executor": "Run",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"git-widget-placeholder": "feature/setup",
|
"git-widget-placeholder": "feature/setup",
|
||||||
"list.type.of.created.stylesheet": "CSS",
|
"list.type.of.created.stylesheet": "CSS",
|
||||||
"node.js.detected.package.eslint": "true",
|
"node.js.detected.package.eslint": "true",
|
||||||
"node.js.detected.package.tslint": "true",
|
"node.js.detected.package.tslint": "true",
|
||||||
"node.js.selected.package.eslint": "(autodetect)",
|
"node.js.selected.package.eslint": "(autodetect)",
|
||||||
"node.js.selected.package.tslint": "(autodetect)",
|
"node.js.selected.package.tslint": "(autodetect)",
|
||||||
"nodejs_package_manager_path": "npm",
|
"nodejs_package_manager_path": "npm",
|
||||||
"settings.editor.selected.configurable": "preferences.environmentSetup",
|
"settings.editor.selected.configurable": "preferences.environmentSetup",
|
||||||
"vue.rearranger.settings.migration": "true"
|
"vue.rearranger.settings.migration": "true"
|
||||||
}
|
}
|
||||||
}]]></component>
|
}</component>
|
||||||
<component name="RunManager" selected=".NET Launch Settings Profile.HopFrame.Testing: https">
|
<component name="RunManager" selected=".NET Launch Settings Profile.HopFrame.Testing: https">
|
||||||
<configuration name="HopFrame.Testing: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
|
<configuration name="HopFrame.Testing: http" type="LaunchSettings" factoryName=".NET Launch Settings Profile">
|
||||||
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/testing/HopFrame.Testing/HopFrame.Testing.csproj" />
|
<option name="LAUNCH_PROFILE_PROJECT_FILE_PATH" value="$PROJECT_DIR$/testing/HopFrame.Testing/HopFrame.Testing.csproj" />
|
||||||
@@ -144,7 +146,9 @@
|
|||||||
<workItem from="1736788853462" duration="780000" />
|
<workItem from="1736788853462" duration="780000" />
|
||||||
<workItem from="1736845367516" duration="283000" />
|
<workItem from="1736845367516" duration="283000" />
|
||||||
<workItem from="1736845655122" duration="165000" />
|
<workItem from="1736845655122" duration="165000" />
|
||||||
<workItem from="1736845825812" duration="13786000" />
|
<workItem from="1736845825812" duration="14084000" />
|
||||||
|
<workItem from="1736863626597" duration="7027000" />
|
||||||
|
<workItem from="1736875984621" duration="8464000" />
|
||||||
</task>
|
</task>
|
||||||
<task id="LOCAL-00001" summary="Added basic configuration">
|
<task id="LOCAL-00001" summary="Added basic configuration">
|
||||||
<option name="closed" value="true" />
|
<option name="closed" value="true" />
|
||||||
@@ -162,7 +166,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1736855209077</updated>
|
<updated>1736855209077</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="3" />
|
<task id="LOCAL-00003" summary="Added database loading logic">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1736859917232</created>
|
||||||
|
<option name="number" value="00003" />
|
||||||
|
<option name="presentableId" value="LOCAL-00003" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1736859917232</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="4" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -174,6 +186,7 @@
|
|||||||
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||||
<MESSAGE value="Added basic configuration" />
|
<MESSAGE value="Added basic configuration" />
|
||||||
<MESSAGE value="Added admin page navigation" />
|
<MESSAGE value="Added admin page navigation" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Added admin page navigation" />
|
<MESSAGE value="Added database loading logic" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="Added database loading logic" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
36
src/HopFrame.Core/Config/PropertyConfig.cs
Normal file
36
src/HopFrame.Core/Config/PropertyConfig.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace HopFrame.Core.Config;
|
||||||
|
|
||||||
|
public class PropertyConfig(PropertyInfo info) {
|
||||||
|
public PropertyInfo Info { get; init; } = info;
|
||||||
|
public string Name { get; set; } = info.Name;
|
||||||
|
public bool List { get; set; } = true;
|
||||||
|
public bool Sortable { get; set; } = true;
|
||||||
|
public bool Searchable { get; set; } = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropertyConfig<TProp>(PropertyConfig config) {
|
||||||
|
|
||||||
|
public PropertyConfig<TProp> SetDisplayName(string displayName) {
|
||||||
|
config.Name = displayName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyConfig<TProp> List(bool display) {
|
||||||
|
config.List = display;
|
||||||
|
config.Searchable = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyConfig<TProp> Sortable(bool sortable) {
|
||||||
|
config.Sortable = sortable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertyConfig<TProp> Searchable(bool searchable) {
|
||||||
|
config.Searchable = searchable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,25 @@
|
|||||||
namespace HopFrame.Core.Config;
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
public class TableConfig(DbContextConfig config, Type tableType, string propertyName) {
|
namespace HopFrame.Core.Config;
|
||||||
public Type TableType { get; } = tableType;
|
|
||||||
public string PropertyName { get; } = propertyName;
|
public class TableConfig {
|
||||||
public DbContextConfig ContextConfig { get; } = config;
|
public Type TableType { get; }
|
||||||
|
public string PropertyName { get; }
|
||||||
|
public DbContextConfig ContextConfig { get; }
|
||||||
public bool Ignored { get; set; }
|
public bool Ignored { get; set; }
|
||||||
|
|
||||||
|
public List<PropertyConfig> Properties { get; } = new();
|
||||||
|
|
||||||
|
public TableConfig(DbContextConfig config, Type tableType, string propertyName) {
|
||||||
|
TableType = tableType;
|
||||||
|
PropertyName = propertyName;
|
||||||
|
ContextConfig = config;
|
||||||
|
|
||||||
|
foreach (var info in tableType.GetProperties()) {
|
||||||
|
Properties.Add(new PropertyConfig(info));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TableConfig<TModel>(TableConfig innerConfig) {
|
public class TableConfig<TModel>(TableConfig innerConfig) {
|
||||||
@@ -13,5 +28,40 @@ public class TableConfig<TModel>(TableConfig innerConfig) {
|
|||||||
innerConfig.Ignored = true;
|
innerConfig.Ignored = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PropertyConfig<TProp> Property<TProp>(Expression<Func<TModel, TProp>> propertyExpression) {
|
||||||
|
var info = GetPropertyInfo(propertyExpression);
|
||||||
|
var prop = innerConfig.Properties
|
||||||
|
.Single(prop => prop.Info.Name == info.Name);
|
||||||
|
return new PropertyConfig<TProp>(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TableConfig<TModel> Property<TProp>(Expression<Func<TModel, TProp>> propertyExpression, Action<PropertyConfig<TProp>> configurator) {
|
||||||
|
var prop = Property(propertyExpression);
|
||||||
|
configurator.Invoke(prop);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PropertyInfo GetPropertyInfo<TSource, TProperty>(Expression<Func<TSource, TProperty>> propertyLambda) {
|
||||||
|
if (propertyLambda.Body is not MemberExpression member) {
|
||||||
|
throw new ArgumentException($"Expression '{propertyLambda}' refers to a method, not a property.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member.Member is not PropertyInfo propInfo) {
|
||||||
|
throw new ArgumentException($"Expression '{propertyLambda}' refers to a field, not a property.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Type type = typeof(TSource);
|
||||||
|
if (propInfo.ReflectedType != null && type != propInfo.ReflectedType &&
|
||||||
|
!type.IsSubclassOf(propInfo.ReflectedType)) {
|
||||||
|
throw new ArgumentException($"Expression '{propertyLambda}' refers to a property that is not from type {type}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (propInfo.Name is null)
|
||||||
|
throw new ArgumentException($"Expression '{propertyLambda}' refers a not existing property.");
|
||||||
|
|
||||||
|
return propInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
namespace HopFrame.Core.Services;
|
namespace HopFrame.Core.Services;
|
||||||
|
|
||||||
public interface ITableManager {
|
public interface ITableManager {
|
||||||
public Task<IEnumerable<object>> LoadPage(int page, int perPage = 25);
|
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 DeleteItem(object item);
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider pr
|
|||||||
|
|
||||||
public TableConfig? GetTable(string tableName) {
|
public TableConfig? GetTable(string tableName) {
|
||||||
foreach (var context in config.Contexts) {
|
foreach (var context in config.Contexts) {
|
||||||
var table = context.Tables.FirstOrDefault(table => table.PropertyName == tableName);
|
var table = context.Tables.FirstOrDefault(table => table.PropertyName.Equals(tableName, StringComparison.CurrentCultureIgnoreCase));
|
||||||
if (table is not null)
|
if (table is not null)
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider pr
|
|||||||
if (dbContext is null) return null;
|
if (dbContext is null) return null;
|
||||||
|
|
||||||
var type = typeof(TableManager<>).MakeGenericType(table.TableType);
|
var type = typeof(TableManager<>).MakeGenericType(table.TableType);
|
||||||
return Activator.CreateInstance(type, dbContext) as ITableManager;
|
return Activator.CreateInstance(type, dbContext, table) as ITableManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,15 +1,49 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using HopFrame.Core.Config;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace HopFrame.Core.Services.Implementations;
|
namespace HopFrame.Core.Services.Implementations;
|
||||||
|
|
||||||
internal sealed class TableManager<TModel>(DbContext context) : ITableManager where TModel : class {
|
internal sealed class TableManager<TModel>(DbContext context, TableConfig config) : ITableManager where TModel : class {
|
||||||
|
|
||||||
public async Task<IEnumerable<object>> LoadPage(int page, int perPage = 25) {
|
public IQueryable<object> LoadPage(int page, int perPage = 20) {
|
||||||
var table = context.Set<TModel>();
|
var table = context.Set<TModel>();
|
||||||
return await table
|
return table
|
||||||
.Skip(page * perPage)
|
.Skip(page * perPage)
|
||||||
.Take(perPage)
|
.Take(perPage);
|
||||||
.ToArrayAsync();
|
}
|
||||||
|
|
||||||
|
public (IEnumerable<object>, int) Search(string searchTerm, int page = 0, int perPage = 20) {
|
||||||
|
var table = context.Set<TModel>();
|
||||||
|
var all = table
|
||||||
|
.AsEnumerable()
|
||||||
|
.Where(item => ItemSearched(item, searchTerm))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return (all.Skip(page * perPage).Take(perPage), (int)Math.Ceiling(all.Count / (double)perPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public int TotalPages(int perPage = 20) {
|
||||||
|
var table = context.Set<TModel>();
|
||||||
|
return (int)Math.Ceiling(table.Count() / (double)perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteItem(object item) {
|
||||||
|
var table = context.Set<TModel>();
|
||||||
|
table.Remove((item as TModel)!);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ItemSearched(TModel item, string searchTerm) {
|
||||||
|
foreach (var property in config.Properties) {
|
||||||
|
if (!property.Searchable) continue;
|
||||||
|
var value = property.Info.GetValue(item);
|
||||||
|
if (value is null) continue;
|
||||||
|
|
||||||
|
var strValue = value.ToString();
|
||||||
|
if (strValue?.Contains(searchTerm) == true)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
@foreach (var table in Explorer.GetTableNames()) {
|
@foreach (var table in Explorer.GetTableNames()) {
|
||||||
<FluentAppBarItem Href="@("/admin/" + table)"
|
<FluentAppBarItem Href="@("/admin/" + table.ToLower())"
|
||||||
Match="NavLinkMatch.All"
|
Match="NavLinkMatch.All"
|
||||||
IconActive="new Icons.Filled.Size24.Database()"
|
IconActive="new Icons.Filled.Size24.Database()"
|
||||||
IconRest="new Icons.Regular.Size24.Database()"
|
IconRest="new Icons.Regular.Size24.Database()"
|
||||||
|
|||||||
144
src/HopFrame.Web/Components/Pages/HopFrameListView.razor
Normal file
144
src/HopFrame.Web/Components/Pages/HopFrameListView.razor
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
@page "/admin/{TableName}"
|
||||||
|
@layout HopFrameLayout
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
|
||||||
|
@using HopFrame.Core.Config
|
||||||
|
@using HopFrame.Core.Services
|
||||||
|
@using Microsoft.JSInterop
|
||||||
|
|
||||||
|
<div style="display: flex; flex-direction: column; height: 100%">
|
||||||
|
<FluentToolbar Class="hopframe-toolbar">
|
||||||
|
<h3>@_config?.PropertyName</h3>
|
||||||
|
<FluentSpacer />
|
||||||
|
<FluentSearch @oninput="OnSearch" @onchange="OnSearch" />
|
||||||
|
<FluentButton>Add Entry</FluentButton>
|
||||||
|
</FluentToolbar>
|
||||||
|
|
||||||
|
<div style="overflow-y: auto; flex-grow: 1">
|
||||||
|
<FluentDataGrid Items="_currentlyDisplayedModels?.AsQueryable()">
|
||||||
|
@{ var dataIndex = 0; }
|
||||||
|
@foreach (var property in _config!.Properties.Where(prop => prop.List)) {
|
||||||
|
<PropertyColumn Title="@property.Name" Property="o => property.Info.GetValue(o)" Style="min-width: max-content; min-height: 43px" Sortable="@property.Sortable"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
<TemplateColumn Title="Actions" Align="@Align.End" Style="min-height: 43px; min-width: max-content">
|
||||||
|
@{ var currentElement = _currentlyDisplayedModels!.ElementAt(dataIndex); }
|
||||||
|
<FluentButton aria-label="Edit entry">
|
||||||
|
<FluentIcon Value="@(new Icons.Regular.Size16.Edit())"/>
|
||||||
|
</FluentButton>
|
||||||
|
|
||||||
|
<FluentButton aria-label="Delete entry" OnClick="() => { DeleteEntry(currentElement); }">
|
||||||
|
<FluentIcon Value="@(new Icons.Regular.Size16.Delete())" Color="Color.Warning"/>
|
||||||
|
</FluentButton>
|
||||||
|
|
||||||
|
@{
|
||||||
|
dataIndex++;
|
||||||
|
dataIndex %= 20;
|
||||||
|
}
|
||||||
|
</TemplateColumn>
|
||||||
|
</FluentDataGrid>
|
||||||
|
|
||||||
|
@if (_currentlyDisplayedModels?.Any() == true) {
|
||||||
|
<div class="hopframe-paginator">
|
||||||
|
<FluentButton BackgroundColor="transparent" OnClick="() => ChangePage(_currentPage - 1)">
|
||||||
|
<FluentIcon Value="@(new Icons.Regular.Size20.ArrowPrevious())" Color="Color.Neutral" />
|
||||||
|
</FluentButton>
|
||||||
|
|
||||||
|
<span>Page</span>
|
||||||
|
|
||||||
|
<FluentSelect TOption="int"
|
||||||
|
Items="Enumerable.Range(0, _totalPages)"
|
||||||
|
OptionValue="@(p => p.ToString())"
|
||||||
|
OptionText="@(p => (p + 1).ToString())"
|
||||||
|
ValueChanged="s => ChangePage(Convert.ToInt32(s))"
|
||||||
|
Width="max-content" SelectedOption="@_currentPage"/>
|
||||||
|
|
||||||
|
<span>of @_totalPages</span>
|
||||||
|
|
||||||
|
<FluentButton BackgroundColor="transparent" OnClick="() => ChangePage(_currentPage + 1)">
|
||||||
|
<FluentIcon Value="@(new Icons.Regular.Size20.ArrowNext())" Color="Color.Neutral" />
|
||||||
|
</FluentButton>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function removeBg() {
|
||||||
|
const elements = document.querySelectorAll(".col-sort-button");
|
||||||
|
const style = new CSSStyleSheet();
|
||||||
|
style.replaceSync(".control { background: none !important; }");
|
||||||
|
elements.forEach(e => e.shadowRoot.adoptedStyleSheets.push(style));
|
||||||
|
console.log(elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBg();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@inject IContextExplorer Explorer
|
||||||
|
@inject NavigationManager Navigator
|
||||||
|
@inject IJSRuntime Js
|
||||||
|
|
||||||
|
@code {
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public required string TableName { get; set; }
|
||||||
|
|
||||||
|
private TableConfig? _config;
|
||||||
|
private ITableManager? _manager;
|
||||||
|
|
||||||
|
private IEnumerable<object>? _currentlyDisplayedModels;
|
||||||
|
private int _currentPage = 0;
|
||||||
|
private int _totalPages;
|
||||||
|
private string? _searchTerm;
|
||||||
|
|
||||||
|
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 OnAfterRenderAsync(bool firstRender) {
|
||||||
|
await Js.InvokeVoidAsync("removeBg");
|
||||||
|
}
|
||||||
|
|
||||||
|
private CancellationTokenSource _searchCancel = new();
|
||||||
|
private async Task OnSearch(ChangeEventArgs eventArgs) {
|
||||||
|
await _searchCancel.CancelAsync();
|
||||||
|
_searchTerm = eventArgs.Value?.ToString();
|
||||||
|
if (_searchTerm is null) return;
|
||||||
|
_searchCancel = new();
|
||||||
|
|
||||||
|
await Task.Delay(500, _searchCancel.Token);
|
||||||
|
(_currentlyDisplayedModels, _totalPages) = _manager!.Search(_searchTerm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ChangePage(int page) {
|
||||||
|
if (page < 0 || page > _totalPages - 1) return;
|
||||||
|
_currentPage = page;
|
||||||
|
if (!string.IsNullOrEmpty(_searchTerm)) {
|
||||||
|
(_currentlyDisplayedModels, _totalPages) = _manager!.Search(_searchTerm, page);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_currentlyDisplayedModels = _manager!.LoadPage(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteEntry(object element) { //TODO: display confirmation
|
||||||
|
await _manager!.DeleteItem(element);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(_searchTerm)) {
|
||||||
|
(_currentlyDisplayedModels, _totalPages) = _manager!.Search(_searchTerm);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
OnInitialized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/HopFrame.Web/Components/Pages/HopFrameListView.razor.css
Normal file
12
src/HopFrame.Web/Components/Pages/HopFrameListView.razor.css
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hopframe-paginator {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-block: 20px;
|
||||||
|
}
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hopframe-content {
|
.hopframe-content {
|
||||||
padding: 0.5rem 1.5rem;
|
|
||||||
align-self: stretch !important;
|
align-self: stretch !important;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -14,4 +13,18 @@
|
|||||||
min-height: calc(100dvh - 86px);
|
min-height: calc(100dvh - 86px);
|
||||||
color: var(--neutral-foreground-rest);
|
color: var(--neutral-foreground-rest);
|
||||||
align-items: stretch !important;
|
align-items: stretch !important;
|
||||||
|
column-gap: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hopframe-toolbar {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hopframe-content .empty-content-row.empty-content-cell {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
fluent-option {
|
||||||
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
@using HopFrame.Core.Services
|
|
||||||
|
|
||||||
<PageTitle>Home</PageTitle>
|
<PageTitle>Home</PageTitle>
|
||||||
|
|
||||||
@@ -7,23 +6,43 @@
|
|||||||
|
|
||||||
Welcome to your new Fluent Blazor app.
|
Welcome to your new Fluent Blazor app.
|
||||||
|
|
||||||
@inject IContextExplorer Explorer
|
|
||||||
@inject DatabaseContext Context
|
@inject DatabaseContext Context
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync() {
|
protected override async Task OnInitializedAsync() {
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 100; i++) {
|
||||||
|
var first = GenerateName(Random.Shared.Next(4, 6));
|
||||||
|
var last = GenerateName(Random.Shared.Next(4, 6));
|
||||||
|
var username = $"{first}.{last}";
|
||||||
Context.Users.Add(new() {
|
Context.Users.Add(new() {
|
||||||
Email = "leon@ladenbau-hoppe.de",
|
Email = $"{username}-{Random.Shared.Next(0, 20)}@gmail.com",
|
||||||
Id = Guid.CreateVersion7()
|
Id = Guid.CreateVersion7(),
|
||||||
|
FirstName = first,
|
||||||
|
LastName = last,
|
||||||
|
Username = username
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
|
}
|
||||||
var manager = Explorer.GetTableManager("Users");
|
|
||||||
var page = await manager!.LoadPage(0);
|
public static string GenerateName(int len) {
|
||||||
Console.WriteLine(string.Join(", ", page));
|
Random r = new Random();
|
||||||
|
string[] consonants = { "b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "l", "n", "p", "q", "r", "s", "sh", "zh", "t", "v", "w", "x" };
|
||||||
|
string[] vowels = { "a", "e", "i", "o", "u", "ae", "y" };
|
||||||
|
string Name = "";
|
||||||
|
Name += consonants[r.Next(consonants.Length)].ToUpper();
|
||||||
|
Name += vowels[r.Next(vowels.Length)];
|
||||||
|
int b = 2; //b tells how many times a new letter has been added. It's 2 right now because the first two letters are already in the name.
|
||||||
|
while (b < len) {
|
||||||
|
Name += consonants[r.Next(consonants.Length)];
|
||||||
|
b++;
|
||||||
|
Name += vowels[r.Next(vowels.Length)];
|
||||||
|
b++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using HopFrame.Core.Services;
|
|||||||
using HopFrame.Testing;
|
using HopFrame.Testing;
|
||||||
using Microsoft.FluentUI.AspNetCore.Components;
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
using HopFrame.Testing.Components;
|
using HopFrame.Testing.Components;
|
||||||
|
using HopFrame.Testing.Models;
|
||||||
using HopFrame.Testing.Services;
|
using HopFrame.Testing.Services;
|
||||||
using HopFrame.Web;
|
using HopFrame.Web;
|
||||||
using HopFrame.Web.Components.Pages;
|
using HopFrame.Web.Components.Pages;
|
||||||
@@ -20,7 +21,21 @@ builder.Services.AddDbContext<DatabaseContext>(options => {
|
|||||||
|
|
||||||
builder.Services.AddHopFrame(options => {
|
builder.Services.AddHopFrame(options => {
|
||||||
options.SetAuthHandler<AuthService>();
|
options.SetAuthHandler<AuthService>();
|
||||||
options.AddDbContext<DatabaseContext>();
|
options.AddDbContext<DatabaseContext>(context => {
|
||||||
|
context.Table<User>(table => {
|
||||||
|
table.Property(u => u.Password)
|
||||||
|
.List(false);
|
||||||
|
|
||||||
|
table.Property(u => u.FirstName)
|
||||||
|
.SetDisplayName("First Name");
|
||||||
|
|
||||||
|
table.Property(u => u.LastName)
|
||||||
|
.SetDisplayName("Last Name");
|
||||||
|
|
||||||
|
table.Property(u => u.Id)
|
||||||
|
.Sortable(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddTransient<IHopFrameAuthHandler, AuthService>();
|
builder.Services.AddTransient<IHopFrameAuthHandler, AuthService>();
|
||||||
|
|||||||
Reference in New Issue
Block a user