Added entry saving support

This commit is contained in:
2025-01-15 20:43:57 +01:00
parent d4018cceec
commit 1cf2b44503
11 changed files with 285 additions and 76 deletions

View File

@@ -10,17 +10,16 @@
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="0648788e-7696-4e60-bf12-5d5601f33d8c" name="Changes" comment=""> <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$/.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/Config/TableConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.cs" afterDir="false" /> <change beforePath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/PropertyConfig.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/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/TableManager.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Services/Implementations/TableManager.cs" 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/Components/Dialogs/HopFrameEditor.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.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$/src/HopFrame.Web/Components/Pages/HopFrameListView.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameListView.razor" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Helpers/TypeExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Helpers/TypeExtensions.cs" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Models/EditorDialogData.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Models/EditorDialogData.cs" 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/Models/Post.cs" beforeDir="false" afterPath="$PROJECT_DIR$/testing/HopFrame.Testing/Models/Post.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" />
@@ -63,6 +62,8 @@
} }
}</component> }</component>
<component name="HighlightingSettingsPerFile"> <component name="HighlightingSettingsPerFile">
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/60e7b22380df80ef6fefe43138047f49ec6eff4b25c12b42ce3d6ed5aac/MethodInvokerCommon.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/87c584767b46b5fd42769be76547105558e6690f785614efddca134b2d682/Type.cs" root0="FORCE_HIGHLIGHTING" />
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/b3ccb66df3646cb51df73ad51716136ebd2eefb4edb1308dd52a7e999582d59e/IBindableColumn.cs" root0="SKIP_HIGHLIGHTING" /> <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/ece8533187fe96ce67b3ef1c9cc3502ef8da5510aadb132a9b21c5605d7c2119/PropertyColumn.cs" root0="FORCE_HIGHLIGHTING" />
@@ -80,24 +81,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">{ <component name="PropertiesComponent"><![CDATA[{
&quot;keyToString&quot;: { "keyToString": {
&quot;.NET Launch Settings Profile.HopFrame.Testing.executor&quot;: &quot;Run&quot;, ".NET Launch Settings Profile.HopFrame.Testing.executor": "Run",
&quot;.NET Launch Settings Profile.HopFrame.Testing: https.executor&quot;: &quot;Run&quot;, ".NET Launch Settings Profile.HopFrame.Testing: https.executor": "Run",
&quot;.NET Project.HopFrame.Testing.executor&quot;: &quot;Run&quot;, ".NET Project.HopFrame.Testing.executor": "Run",
&quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, "RunOnceActivity.ShowReadmeOnStart": "true",
&quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, "RunOnceActivity.git.unshallow": "true",
&quot;git-widget-placeholder&quot;: &quot;feature/setup&quot;, "git-widget-placeholder": "feature/setup",
&quot;list.type.of.created.stylesheet&quot;: &quot;CSS&quot;, "list.type.of.created.stylesheet": "CSS",
&quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, "node.js.detected.package.eslint": "true",
&quot;node.js.detected.package.tslint&quot;: &quot;true&quot;, "node.js.detected.package.tslint": "true",
&quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.eslint": "(autodetect)",
&quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, "node.js.selected.package.tslint": "(autodetect)",
&quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, "nodejs_package_manager_path": "npm",
&quot;settings.editor.selected.configurable&quot;: &quot;preferences.environmentSetup&quot;, "settings.editor.selected.configurable": "preferences.environmentSetup",
&quot;vue.rearranger.settings.migration&quot;: &quot;true&quot; "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" />
@@ -149,6 +150,8 @@
<workItem from="1736845825812" duration="14084000" /> <workItem from="1736845825812" duration="14084000" />
<workItem from="1736863626597" duration="7027000" /> <workItem from="1736863626597" duration="7027000" />
<workItem from="1736875984621" duration="8464000" /> <workItem from="1736875984621" duration="8464000" />
<workItem from="1736884461354" duration="1075000" />
<workItem from="1736962119221" duration="7774000" />
</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" />
@@ -174,7 +177,15 @@
<option name="project" value="LOCAL" /> <option name="project" value="LOCAL" />
<updated>1736859917232</updated> <updated>1736859917232</updated>
</task> </task>
<option name="localTasksCounter" value="4" /> <task id="LOCAL-00004" summary="Started working on listing page">
<option name="closed" value="true" />
<created>1736885531216</created>
<option name="number" value="00004" />
<option name="presentableId" value="LOCAL-00004" />
<option name="project" value="LOCAL" />
<updated>1736885531216</updated>
</task>
<option name="localTasksCounter" value="5" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
@@ -187,6 +198,7 @@
<MESSAGE value="Added basic configuration" /> <MESSAGE value="Added basic configuration" />
<MESSAGE value="Added admin page navigation" /> <MESSAGE value="Added admin page navigation" />
<MESSAGE value="Added database loading logic" /> <MESSAGE value="Added database loading logic" />
<option name="LAST_COMMIT_MESSAGE" value="Added database loading logic" /> <MESSAGE value="Started working on listing page" />
<option name="LAST_COMMIT_MESSAGE" value="Started working on listing page" />
</component> </component>
</project> </project>

View File

@@ -11,8 +11,11 @@ public class PropertyConfig(PropertyInfo info) {
public bool Searchable { get; set; } = true; public bool Searchable { get; set; } = true;
public PropertyInfo? DisplayedProperty { get; set; } public PropertyInfo? DisplayedProperty { get; set; }
public Func<object, string>? Formatter { get; set; } public Func<object, string>? Formatter { get; set; }
public Func<string, object>? Parser { get; set; }
public Func<object>? Template { get; set; }
public bool Editable { get; set; } = true; public bool Editable { get; set; } = true;
public bool Creatable { get; set; } = true; public bool Creatable { get; set; } = true;
public bool DisplayValue { get; set; } = true;
} }
public class PropertyConfig<TProp>(PropertyConfig config) { public class PropertyConfig<TProp>(PropertyConfig config) {
@@ -22,8 +25,8 @@ public class PropertyConfig<TProp>(PropertyConfig config) {
return this; return this;
} }
public PropertyConfig<TProp> List(bool display) { public PropertyConfig<TProp> List(bool list) {
config.List = display; config.List = list;
config.Searchable = false; config.Searchable = false;
return this; return this;
} }
@@ -48,6 +51,16 @@ public class PropertyConfig<TProp>(PropertyConfig config) {
return this; return this;
} }
public PropertyConfig<TProp> ValueParser(Func<string, TProp> parser) {
config.Parser = str => parser.Invoke(str)!;
return this;
}
public PropertyConfig<TProp> ValueTemplate(Func<TProp> template) {
config.Template = () => template.Invoke()!;
return this;
}
public PropertyConfig<TProp> Editable(bool editable) { public PropertyConfig<TProp> Editable(bool editable) {
config.Editable = editable; config.Editable = editable;
return this; return this;
@@ -58,4 +71,9 @@ public class PropertyConfig<TProp>(PropertyConfig config) {
return this; return this;
} }
public PropertyConfig<TProp> DisplayValue(bool display) {
config.DisplayValue = display;
return this;
}
} }

View File

@@ -8,6 +8,9 @@ public interface ITableManager {
public (IEnumerable<object>, int) Search(string searchTerm, int page = 0, int perPage = 20); public (IEnumerable<object>, int) Search(string searchTerm, int page = 0, int perPage = 20);
public int TotalPages(int perPage = 20); public int TotalPages(int perPage = 20);
public Task DeleteItem(object item); public Task DeleteItem(object item);
public Task EditItem(object item);
public Task AddItem(object item);
public Task RevertChanges(object item);
public string DisplayProperty(object? item, PropertyInfo info, TableConfig? tableConfig); public string DisplayProperty(object? item, PropertyInfo info, TableConfig? tableConfig);
} }

View File

@@ -37,6 +37,20 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
public async Task EditItem(object item) {
await context.SaveChangesAsync();
}
public async Task AddItem(object item) {
var table = context.Set<TModel>();
await table.AddAsync((TModel)item);
await context.SaveChangesAsync();
}
public async Task RevertChanges(object item) {
await context.Entry((TModel)item).ReloadAsync();
}
private bool ItemSearched(TModel item, string searchTerm) { private bool ItemSearched(TModel item, string searchTerm) {
foreach (var property in config.Properties) { foreach (var property in config.Properties) {
if (!property.Searchable) continue; if (!property.Searchable) continue;

View File

@@ -1,8 +1,10 @@
@implements IDialogContentComponent<EditorDialogData> @implements IDialogContentComponent<EditorDialogData>
@using HopFrame.Web.Models @using HopFrame.Core.Config
@using HopFrame.Core.Services @using HopFrame.Core.Services
@using HopFrame.Web.Models
@using HopFrame.Web.Helpers @using HopFrame.Web.Helpers
@using Microsoft.EntityFrameworkCore.Internal
<FluentDialogBody> <FluentDialogBody>
@foreach (var property in Content.Config.Properties) { @foreach (var property in Content.Config.Properties) {
@@ -13,23 +15,69 @@
<FluentNumberField <FluentNumberField
TValue="double" TValue="double"
Label="@property.Name" Label="@property.Name"
Value="@(Convert.ToDouble(property.Info.GetValue(Content.CurrentObject)))" Value="GetPropertyValue<double>(property)"
Style="width: 100%;" Style="width: 100%;"
Disabled="@(!property.Editable)" Disabled="@(!property.Editable)"
/> ValueChanged="@(v => SetPropertyValue(property, v, InputType.Number))" />
} else if (property.Info.PropertyType == typeof(bool)) { }
else if (Type.GetTypeCode(property.Info.PropertyType) == TypeCode.Boolean) {
<FluentSwitch <FluentSwitch
Label="@property.Name" Label="@property.Name"
Value="@(Convert.ToBoolean(property.Info.GetValue(Content.CurrentObject)))" Value="GetPropertyValue<bool>(property)"
/> Disabled="@(!property.Editable)"
ValueChanged="@(v => SetPropertyValue(property, v, InputType.Switch))" />
}
else if (Type.GetTypeCode(property.Info.PropertyType) == TypeCode.DateTime) {
<div style="display: flex; gap: 10px">
<div style="display: flex; flex-direction: column; width: 100%">
<FluentDatePicker
Label="@property.Name"
Value="GetPropertyValue<DateTime>(property)"
Disabled="@(!property.Editable)"
ValueChanged="@(v => SetPropertyValue(property, v, InputType.Date))" />
</div>
<div style="display: flex; flex-direction: column; justify-content: flex-end">
<FluentTimePicker
Value="GetPropertyValue<DateTime>(property)"
Disabled="@(!property.Editable)"
ValueChanged="@(v => SetPropertyValue(property, v, InputType.Time))" />
</div>
</div>
}
else if (property.Info.PropertyType == typeof(DateOnly)) {
<FluentDatePicker
Label="@property.Name"
Value="GetPropertyValue<DateOnly>(property).ToDateTime(TimeOnly.MinValue)"
Style="width: 100%"
Disabled="@(!property.Editable)"
ValueChanged="@(v => SetPropertyValue(property, v, InputType.Date))" />
}
else if (property.Info.PropertyType == typeof(TimeOnly)) {
<FluentTimePicker
Label="@property.Name"
Value="GetPropertyValue<TimeOnly>(property).ToDateTime()"
Style="width: 100%"
Disabled="@(!property.Editable)"
ValueChanged="@(v => SetPropertyValue(property, v, InputType.Time))" />
}
else if (property.Info.PropertyType.IsEnum) {
<FluentSelect
TOption="string"
Label="@property.Name"
Items="Enum.GetNames(property.Info.PropertyType)"
Value="@(GetPropertyValue<string>(property))"
Style="width: 100%"
Height="250px"
Disabled="@(!property.Editable)"
ValueChanged="@(v => SetPropertyValue(property, v, InputType.Enum))" />
} }
else { else {
<FluentTextField <FluentTextField
Label="@property.Name" Label="@property.Name"
Value="@_manager?.DisplayProperty(Content.CurrentObject, property.Info, Content.Config)" Value="@(GetPropertyValue<string>(property))"
Style="width: 100%;" Style="width: 100%;"
Disabled="@(!property.Editable)" Disabled="@(!property.Editable)"
/> ValueChanged="@(v => SetPropertyValue(property, v, InputType.Text))" />
} }
</div> </div>
} }
@@ -44,16 +92,103 @@
[CascadingParameter] [CascadingParameter]
public required FluentDialog Dialog { get; set; } public required FluentDialog Dialog { get; set; }
private ITableManager? _manager;
private bool _currentlyEditing; private bool _currentlyEditing;
private ITableManager? _manager;
protected override void OnInitialized() { protected override void OnInitialized() {
if (Dialog.Instance is null) return;
_currentlyEditing = Content.CurrentObject is not null; _currentlyEditing = Content.CurrentObject is not null;
Dialog.Instance.Parameters.Title = Content.CurrentObject is null ? "Add entry" : "Edit entry"; Dialog.Instance.Parameters.Title = (_currentlyEditing ? "Edit " : "Add ") + Content.Config.TableType.Name;
Dialog.Instance.Parameters.PreventScroll = true; Dialog.Instance.Parameters.PreventScroll = true;
Dialog.Instance.Parameters.Width = "500px"; Dialog.Instance.Parameters.Width = "500px";
Dialog.Instance.Parameters.PrimaryAction = "Save"; Dialog.Instance.Parameters.PrimaryAction = "Save";
_manager = Explorer.GetTableManager(Content.Config.PropertyName); _manager = Explorer.GetTableManager(Content.Config.PropertyName);
Content.CurrentObject ??= Activator.CreateInstance(Content.Config.TableType);
}
private TValue? GetPropertyValue<TValue>(PropertyConfig config) { //TODO: handle relational types
if (!config.DisplayValue) return default;
if (Content.CurrentObject is null) return default;
var value = config.Info.GetValue(Content.CurrentObject);
var newlyGenerated = false;
if (config.Info.PropertyType.IsDefaultValue(value) && config.Template is not null) {
value = config.Template.Invoke();
newlyGenerated = true;
}
if (value is null)
return default;
if (config.Info.PropertyType == typeof(TValue))
return (TValue)value;
if (typeof(TValue) == typeof(string)) {
if (!newlyGenerated)
return (TValue)(object)_manager!.DisplayProperty(Content.CurrentObject, config.Info, Content.Config);
return (TValue)(object)value.ToString()!;
}
return (TValue)Convert.ChangeType(value, typeof(TValue));
}
private void SetPropertyValue(PropertyConfig config, object? value, InputType senderType) {
object? result = null;
if (value is not null) {
switch (senderType) {
case InputType.Number:
result = Convert.ChangeType(value, config.Info.PropertyType);
break;
case InputType.Text:
result = Convert.ToString(value);
break;
case InputType.Switch:
result = Convert.ToBoolean(value);
break;
case InputType.Enum:
result = Enum.Parse(config.Info.PropertyType, (string)value);
break;
case InputType.Date:
if (config.Info.PropertyType == typeof(DateTime)) {
var newDate = (DateOnly)value;
var dateTime = GetPropertyValue<DateTime>(config);
result = new DateTime(newDate.Year, newDate.Month, newDate.Day, dateTime.Hour, dateTime.Minute, dateTime.Second, dateTime.Millisecond, dateTime.Microsecond);
}
else result = (DateOnly)value;
break;
case InputType.Time:
if (config.Info.PropertyType == typeof(DateTime)) {
var newTime = (TimeOnly)value;
var dateTime = GetPropertyValue<DateTime>(config);
result = new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, newTime.Hour, newTime.Minute, newTime.Second, newTime.Millisecond, newTime.Microsecond);
}
else result = (TimeOnly)value;
break;
default:
throw new ArgumentOutOfRangeException(nameof(senderType), senderType, null);
}
}
if (config.Parser is not null) {
result = config.Parser(result!.ToString()!);
}
config.Info.SetValue(Content.CurrentObject, result);
}
private enum InputType {
Number,
Switch,
Date,
Time,
Enum,
Text
} }
} }

View File

@@ -76,8 +76,7 @@
const elements = document.querySelectorAll(".col-sort-button"); const elements = document.querySelectorAll(".col-sort-button");
const style = new CSSStyleSheet(); const style = new CSSStyleSheet();
style.replaceSync(".control { background: none !important; }"); style.replaceSync(".control { background: none !important; }");
elements.forEach(e => e.shadowRoot.adoptedStyleSheets.push(style)); elements.forEach(e => e?.shadowRoot?.adoptedStyleSheets?.push(style));
console.log(elements);
} }
removeBg(); removeBg();
@@ -97,19 +96,19 @@
private ITableManager? _manager; private ITableManager? _manager;
private IEnumerable<object>? _currentlyDisplayedModels; private IEnumerable<object>? _currentlyDisplayedModels;
private int _currentPage = 0; private int _currentPage;
private int _totalPages; private int _totalPages;
private string? _searchTerm; private string? _searchTerm;
protected override void OnInitialized() { protected override void OnInitialized() {
_config = Explorer.GetTable(TableName); _config ??= Explorer.GetTable(TableName);
if (_config is null) { if (_config is null) {
Navigator.NavigateTo("/admin", true); Navigator.NavigateTo("/admin", true);
return; return;
} }
_manager = Explorer.GetTableManager(_config.PropertyName); _manager ??= Explorer.GetTableManager(_config.PropertyName);
_currentlyDisplayedModels = _manager!.LoadPage(_currentPage).ToArray(); _currentlyDisplayedModels = _manager!.LoadPage(_currentPage).ToArray();
_totalPages = _manager.TotalPages(); _totalPages = _manager.TotalPages();
} }
@@ -117,7 +116,10 @@
protected override async Task OnAfterRenderAsync(bool firstRender) { protected override async Task OnAfterRenderAsync(bool firstRender) {
try { try {
await Js.InvokeVoidAsync("removeBg"); await Js.InvokeVoidAsync("removeBg");
}catch (Exception) {} }
catch (Exception) {
// ignored
}
} }
private CancellationTokenSource _searchCancel = new(); private CancellationTokenSource _searchCancel = new();
@@ -128,36 +130,52 @@
_searchCancel = new(); _searchCancel = new();
await Task.Delay(500, _searchCancel.Token); await Task.Delay(500, _searchCancel.Token);
(_currentlyDisplayedModels, _totalPages) = _manager!.Search(_searchTerm); (var query, _totalPages) = _manager!.Search(_searchTerm);
_currentlyDisplayedModels = query.ToArray();
} }
private void ChangePage(int page) { private void Reload() {
if (page < 0 || page > _totalPages - 1) return;
_currentPage = page;
if (!string.IsNullOrEmpty(_searchTerm)) { if (!string.IsNullOrEmpty(_searchTerm)) {
(_currentlyDisplayedModels, _totalPages) = _manager!.Search(_searchTerm, page); (var query, _totalPages) = _manager!.Search(_searchTerm);
} _currentlyDisplayedModels = query.ToArray();
else {
_currentlyDisplayedModels = _manager!.LoadPage(page);
}
}
private async Task DeleteEntry(object element) { //TODO: display confirmation
var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?");
var result = await dialog.Result;
if (result.Cancelled) return;
await _manager!.DeleteItem(element);
if (!string.IsNullOrEmpty(_searchTerm)) {
(_currentlyDisplayedModels, _totalPages) = _manager!.Search(_searchTerm);
} }
else { else {
OnInitialized(); OnInitialized();
} }
} }
private void ChangePage(int page) {
if (page < 0 || page > _totalPages - 1) return;
_currentPage = page;
Reload();
}
private async Task DeleteEntry(object element) {
var dialog = await Dialogs.ShowConfirmationAsync("Do you really want to delete this entry?");
var result = await dialog.Result;
if (result.Cancelled) return;
await _manager!.DeleteItem(element);
Reload();
}
private async Task CreateOrEdit(object? element) { private async Task CreateOrEdit(object? element) {
var panel = await Dialogs.ShowPanelAsync<HopFrameEditor>(new EditorDialogData(_config!, element), new()); var panel = await Dialogs.ShowPanelAsync<HopFrameEditor>(new EditorDialogData(_config!, element), new());
var result = await panel.Result;
var data = result.Data as EditorDialogData;
if (result.Cancelled) {
if (data?.CurrentObject is not null)
await _manager!.RevertChanges(data.CurrentObject);
return;
}
if (element is null)
await _manager!.AddItem(data!.CurrentObject!);
else
await _manager!.EditItem(data!.CurrentObject!);
Reload();
} }
} }

View File

@@ -2,6 +2,7 @@ namespace HopFrame.Web.Helpers;
internal static class TypeExtensions { internal static class TypeExtensions {
public static bool IsNumeric(this Type o) { public static bool IsNumeric(this Type o) {
if (o.IsEnum) return false;
switch (Type.GetTypeCode(o)) { switch (Type.GetTypeCode(o)) {
case TypeCode.Byte: case TypeCode.Byte:
case TypeCode.SByte: case TypeCode.SByte:

View File

@@ -3,6 +3,6 @@ using HopFrame.Core.Config;
namespace HopFrame.Web.Models; namespace HopFrame.Web.Models;
public sealed class EditorDialogData(TableConfig config, object? current = null) { public sealed class EditorDialogData(TableConfig config, object? current = null) {
public object? CurrentObject { get; } = current; public object? CurrentObject { get; set; } = current;
public TableConfig Config { get; } = config; public TableConfig Config { get; } = config;
} }

View File

@@ -23,7 +23,8 @@ Welcome to your new Fluent Blazor app.
Id = Guid.CreateVersion7(), Id = Guid.CreateVersion7(),
FirstName = first, FirstName = first,
LastName = last, LastName = last,
Username = username Username = username,
Password = GenerateName(Random.Shared.Next(8, 16))
}; };
Context.Users.Add(user); Context.Users.Add(user);

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace HopFrame.Testing.Models; namespace HopFrame.Testing.Models;
@@ -15,5 +16,13 @@ public class Post {
[ForeignKey("author")] [ForeignKey("author")]
public User? Author { get; set; } public User? Author { get; set; }
public bool Published { get; set; } /*public bool Published { get; set; }
public DateTime CreatedAt { get; set; }
public DateOnly Created { get; set; }
public TimeOnly At { get; set; }*/
public ListSortDirection Type { get; set; }
} }

View File

@@ -24,7 +24,8 @@ builder.Services.AddHopFrame(options => {
options.AddDbContext<DatabaseContext>(context => { options.AddDbContext<DatabaseContext>(context => {
context.Table<User>(table => { context.Table<User>(table => {
table.Property(u => u.Password) table.Property(u => u.Password)
.List(false); .List(false)
.DisplayValue(false);
table.Property(u => u.FirstName) table.Property(u => u.FirstName)
.SetDisplayName("First Name"); .SetDisplayName("First Name");
@@ -33,20 +34,17 @@ builder.Services.AddHopFrame(options => {
.SetDisplayName("Last Name"); .SetDisplayName("Last Name");
table.Property(u => u.Id) table.Property(u => u.Id)
.Sortable(false); .Sortable(false)
.ValueTemplate(Guid.CreateVersion7);
}); });
/* context.Table<Post>()
.Property(p => p.Author)
.DisplayedProperty(u => u!.Username); */
context.Table<Post>() context.Table<Post>()
.Property(p => p.Author) .Property(p => p.Author)
.Format(user => $"{user?.FirstName} {user?.LastName}"); .Format(user => $"{user?.FirstName} {user?.LastName}");
context.Table<Post>() context.Table<Post>()
.Property(p => p.Id) .Property(p => p.Id)
.SetDisplayName("ID") .SetDisplayName("ID");
.Editable(true);
}); });
}); });