Feature/setup #54

Merged
leon.hoppe merged 17 commits from feature/setup into dev 2025-01-18 13:30:40 +01:00
12 changed files with 396 additions and 57 deletions
Showing only changes of commit c4c0424559 - Show all commits

View File

@@ -9,19 +9,18 @@
<option name="autoReloadType" value="SELECTIVE" />
</component>
<component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="Added admin page navigation">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/IModelManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/ModelManager.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/Post.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Services/AuthService.cs" afterDir="false" />
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.cs" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameListView.razor" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameListView.razor.css" 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.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.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.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/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/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" />
</list>
<option name="SHOW_DIALOG" value="false" />
@@ -64,7 +63,10 @@
}
}</component>
<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/ece8533187fe96ce67b3ef1c9cc3502ef8da5510aadb132a9b21c5605d7c2119/PropertyColumn.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/ff37d54b3bf4d2756237fb789635831532603376e940f63d634b869d26d74c/Regular16.cs" root0="FORCE_HIGHLIGHTING" />
</component>
<component name="MetaFilesCheckinStateConfiguration" checkMetaFiles="true" />
<component name="ProjectColorInfo">{
@@ -78,24 +80,24 @@
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
".NET Launch Settings Profile.HopFrame.Testing.executor": "Run",
".NET Launch Settings Profile.HopFrame.Testing: https.executor": "Run",
".NET Project.HopFrame.Testing.executor": "Run",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.git.unshallow": "true",
"git-widget-placeholder": "feature/setup",
"list.type.of.created.stylesheet": "CSS",
"node.js.detected.package.eslint": "true",
"node.js.detected.package.tslint": "true",
"node.js.selected.package.eslint": "(autodetect)",
"node.js.selected.package.tslint": "(autodetect)",
"nodejs_package_manager_path": "npm",
"settings.editor.selected.configurable": "preferences.environmentSetup",
"vue.rearranger.settings.migration": "true"
<component name="PropertiesComponent">{
&quot;keyToString&quot;: {
&quot;.NET Launch Settings Profile.HopFrame.Testing.executor&quot;: &quot;Run&quot;,
&quot;.NET Launch Settings Profile.HopFrame.Testing: https.executor&quot;: &quot;Run&quot;,
&quot;.NET Project.HopFrame.Testing.executor&quot;: &quot;Run&quot;,
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;,
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;,
&quot;git-widget-placeholder&quot;: &quot;feature/setup&quot;,
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;,
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;,
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;,
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;,
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;,
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;,
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.environmentSetup&quot;,
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot;
}
}]]></component>
}</component>
<component name="RunManager" selected=".NET Launch Settings Profile.HopFrame.Testing: https">
<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" />
@@ -144,7 +146,9 @@
<workItem from="1736788853462" duration="780000" />
<workItem from="1736845367516" duration="283000" />
<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 id="LOCAL-00001" summary="Added basic configuration">
<option name="closed" value="true" />
@@ -162,7 +166,15 @@
<option name="project" value="LOCAL" />
<updated>1736855209077</updated>
</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 />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -174,6 +186,7 @@
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
<MESSAGE value="Added basic configuration" />
<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>
</project>

View 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;
}
}

View File

@@ -1,10 +1,25 @@
namespace HopFrame.Core.Config;
using System.Linq.Expressions;
using System.Reflection;
public class TableConfig(DbContextConfig config, Type tableType, string propertyName) {
public Type TableType { get; } = tableType;
public string PropertyName { get; } = propertyName;
public DbContextConfig ContextConfig { get; } = config;
namespace HopFrame.Core.Config;
public class TableConfig {
public Type TableType { get; }
public string PropertyName { get; }
public DbContextConfig ContextConfig { get; }
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) {
@@ -14,4 +29,39 @@ public class TableConfig<TModel>(TableConfig innerConfig) {
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;
}
}

View File

@@ -1,5 +1,8 @@
namespace HopFrame.Core.Services;
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);
}

View File

@@ -15,7 +15,7 @@ internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider pr
public TableConfig? GetTable(string tableName) {
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)
return table;
}
@@ -32,7 +32,7 @@ internal sealed class ContextExplorer(HopFrameConfig config, IServiceProvider pr
if (dbContext is null) return null;
var type = typeof(TableManager<>).MakeGenericType(table.TableType);
return Activator.CreateInstance(type, dbContext) as ITableManager;
return Activator.CreateInstance(type, dbContext, table) as ITableManager;
}
return null;

View File

@@ -1,15 +1,49 @@
using Microsoft.EntityFrameworkCore;
using HopFrame.Core.Config;
using Microsoft.EntityFrameworkCore;
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>();
return await table
return table
.Skip(page * perPage)
.Take(perPage)
.ToArrayAsync();
.Take(perPage);
}
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;
}
}

View File

@@ -10,7 +10,7 @@
<br>
@foreach (var table in Explorer.GetTableNames()) {
<FluentAppBarItem Href="@("/admin/" + table)"
<FluentAppBarItem Href="@("/admin/" + table.ToLower())"
Match="NavLinkMatch.All"
IconActive="new Icons.Filled.Size24.Database()"
IconRest="new Icons.Regular.Size24.Database()"

View 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();
}
}
}

View 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;
}

View File

@@ -5,7 +5,6 @@
}
.hopframe-content {
padding: 0.5rem 1.5rem;
align-self: stretch !important;
width: 100%;
}
@@ -14,4 +13,18 @@
min-height: calc(100dvh - 86px);
color: var(--neutral-foreground-rest);
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;
}

View File

@@ -1,5 +1,4 @@
@page "/"
@using HopFrame.Core.Services
<PageTitle>Home</PageTitle>
@@ -7,23 +6,43 @@
Welcome to your new Fluent Blazor app.
@inject IContextExplorer Explorer
@inject DatabaseContext Context
@code {
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() {
Email = "leon@ladenbau-hoppe.de",
Id = Guid.CreateVersion7()
Email = $"{username}-{Random.Shared.Next(0, 20)}@gmail.com",
Id = Guid.CreateVersion7(),
FirstName = first,
LastName = last,
Username = username
});
}
await Context.SaveChangesAsync();
}
var manager = Explorer.GetTableManager("Users");
var page = await manager!.LoadPage(0);
Console.WriteLine(string.Join(", ", page));
public static string GenerateName(int len) {
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;
}
}

View File

@@ -2,6 +2,7 @@ using HopFrame.Core.Services;
using HopFrame.Testing;
using Microsoft.FluentUI.AspNetCore.Components;
using HopFrame.Testing.Components;
using HopFrame.Testing.Models;
using HopFrame.Testing.Services;
using HopFrame.Web;
using HopFrame.Web.Components.Pages;
@@ -20,7 +21,21 @@ builder.Services.AddDbContext<DatabaseContext>(options => {
builder.Services.AddHopFrame(options => {
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>();