Added entry saving support
This commit is contained in:
64
.idea/.idea.HopFrame/.idea/workspace.xml
generated
64
.idea/.idea.HopFrame/.idea/workspace.xml
generated
@@ -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[{
|
||||||
"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" />
|
||||||
@@ -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>
|
||||||
@@ -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;
|
||||||
@@ -57,5 +70,10 @@ public class PropertyConfig<TProp>(PropertyConfig config) {
|
|||||||
config.Creatable = creatable;
|
config.Creatable = creatable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PropertyConfig<TProp> DisplayValue(bool display) {
|
||||||
|
config.DisplayValue = display;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user