Added basic export and import feature
This commit is contained in:
37
.idea/.idea.HopFrame/.idea/workspace.xml
generated
37
.idea/.idea.HopFrame/.idea/workspace.xml
generated
@@ -12,17 +12,15 @@
|
|||||||
</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.Web/Plugins/Internal/ExporterPlugin.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/Config/DbContextConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/DbContextConfig.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/Config/HopFrameConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/HopFrameConfig.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/Config/TableConfig.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Core/Config/TableConfig.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/Dialogs/HopFrameEditor.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Dialogs/HopFrameEditor.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/Components/Pages/HopFrameTablePage.razor" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Components/Pages/HopFrameTablePage.razor" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/HopFrameConfiguratorExtensions.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/HopFrameConfiguratorExtensions.cs" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/HopFramePlugin.cs" beforeDir="false" afterPath="$PROJECT_DIR$/src/HopFrame.Web/Plugins/HopFramePlugin.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" />
|
||||||
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Tests.Core/Config/TableConfiguratorTests.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Tests.Core/Config/TableConfiguratorTests.cs" afterDir="false" />
|
|
||||||
<change beforePath="$PROJECT_DIR$/tests/HopFrame.Tests.Core/Services/DisplayPropertyTests.cs" beforeDir="false" afterPath="$PROJECT_DIR$/tests/HopFrame.Tests.Core/Services/DisplayPropertyTests.cs" afterDir="false" />
|
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@@ -42,7 +40,7 @@
|
|||||||
<component name="Git.Settings">
|
<component name="Git.Settings">
|
||||||
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
<option name="RECENT_BRANCH_BY_REPOSITORY">
|
||||||
<map>
|
<map>
|
||||||
<entry key="$PROJECT_DIR$" value="dev" />
|
<entry key="$PROJECT_DIR$" value="feature/virtual-properties" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
@@ -75,6 +73,7 @@
|
|||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/24dd1164ba47541cb1d3eb011e638e16953dbea3ae3f4dc208c3bbf3e96298a/ServiceCollectionServiceExtensions.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/24dd1164ba47541cb1d3eb011e638e16953dbea3ae3f4dc208c3bbf3e96298a/ServiceCollectionServiceExtensions.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/26c9a2fb5243863babc926e4be763daf4128d4f97c4a769cdce1e2e3e5c532/FluentButton.razor.cs" root0="SKIP_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/26c9a2fb5243863babc926e4be763daf4128d4f97c4a769cdce1e2e3e5c532/FluentButton.razor.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2751d5afefca5424bfc4b21347f581372f7a739c0ae4df661ea557fcb97ef20/EnumExtensions.cs" root0="SKIP_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2751d5afefca5424bfc4b21347f581372f7a739c0ae4df661ea557fcb97ef20/EnumExtensions.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/2a4d2ce4c06ab596b3676c5cf06066b4391ec7dd93cdf8f0334b69dc1a9de/TextReader.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/439c4ee753b23e743cc14119593bc889751f9eb0b38997577d8e4c47c4fed/ToCollection.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/439c4ee753b23e743cc14119593bc889751f9eb0b38997577d8e4c47c4fed/ToCollection.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4c41a7d338749915157d56585365d1693fbad6be8231d3d583b1cf10d16896d9/FluentIcon.razor.cs" root0="SKIP_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4c41a7d338749915157d56585365d1693fbad6be8231d3d583b1cf10d16896d9/FluentIcon.razor.cs" root0="SKIP_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4ee221fd7e91e9a4c14ff82aae2ee938edecde35a934133e991aba56aa9499/Icon.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/4ee221fd7e91e9a4c14ff82aae2ee938edecde35a934133e991aba56aa9499/Icon.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
@@ -94,6 +93,7 @@
|
|||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bfff78ecaa39c818519fc918bb2d4bbdca6ad93d7170f5cf325f67ccd0b97d43/BooleanAsserts.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/bfff78ecaa39c818519fc918bb2d4bbdca6ad93d7170f5cf325f67ccd0b97d43/BooleanAsserts.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d0165cb640e16fb3b8fe6932c042fc2917cd7f2770ff123cf7b9d11b5bfc6/Task.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d0165cb640e16fb3b8fe6932c042fc2917cd7f2770ff123cf7b9d11b5bfc6/Task.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d04a416cac8afac0341a8be0e859b230f2eae64924298eef48c317ba35916/RenderTreeBuilder.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d04a416cac8afac0341a8be0e859b230f2eae64924298eef48c317ba35916/RenderTreeBuilder.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d1287462d4ec4078c61b8e92a0952fb7de3e7e877d279e390a4c136a6365126/Stream.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d39923abb31e6a6e7a9e8173e217da584c54925ce63e568126a2b89b9ab/DefaultRazorComponentsServiceOptionsConfiguration.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/d39923abb31e6a6e7a9e8173e217da584c54925ce63e568126a2b89b9ab/DefaultRazorComponentsServiceOptionsConfiguration.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/d858ddb35a8e36df5573b7612542f9ad50f426b8ab43818587d1ac65fab14829/DatabaseGeneratedAttribute.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/dac3553c90d47a746e7e7f02faecb1a5e581090/Components_AppBar_FluentAppBarItem_razor.g.cs" root0="FORCE_HIGHLIGHTING" />
|
<setting file="file://$APPLICATION_CONFIG_DIR$/resharper-host/SourcesCache/dac3553c90d47a746e7e7f02faecb1a5e581090/Components_AppBar_FluentAppBarItem_razor.g.cs" root0="FORCE_HIGHLIGHTING" />
|
||||||
@@ -127,14 +127,14 @@
|
|||||||
"keyToString": {
|
"keyToString": {
|
||||||
".NET Launch Settings Profile.HopFrame.Testing.Api: https.executor": "Run",
|
".NET Launch Settings Profile.HopFrame.Testing.Api: https.executor": "Run",
|
||||||
".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": "Debug",
|
||||||
".NET Project.HopFrame.Testing.executor": "Run",
|
".NET Project.HopFrame.Testing.executor": "Run",
|
||||||
"72b118b0-a6fc-4561-acdf-74f0b454dbb8.executor": "Debug",
|
"72b118b0-a6fc-4561-acdf-74f0b454dbb8.executor": "Debug",
|
||||||
"RunOnceActivity.ShowReadmeOnStart": "true",
|
"RunOnceActivity.ShowReadmeOnStart": "true",
|
||||||
"RunOnceActivity.git.unshallow": "true",
|
"RunOnceActivity.git.unshallow": "true",
|
||||||
"b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug",
|
"b5f11219-dfc4-47a1-b02c-90ab603034fb.executor": "Debug",
|
||||||
"dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug",
|
"dcdf1689-dc07-47e4-8824-2e60a4fbf301.executor": "Debug",
|
||||||
"git-widget-placeholder": "!32 on feature/virtual-properties",
|
"git-widget-placeholder": "!33 on feature/exporters",
|
||||||
"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",
|
||||||
@@ -254,7 +254,8 @@
|
|||||||
<workItem from="1739352479748" duration="3047000" />
|
<workItem from="1739352479748" duration="3047000" />
|
||||||
<workItem from="1739369355001" duration="1751000" />
|
<workItem from="1739369355001" duration="1751000" />
|
||||||
<workItem from="1739461452173" duration="5533000" />
|
<workItem from="1739461452173" duration="5533000" />
|
||||||
<workItem from="1739550750776" duration="3388000" />
|
<workItem from="1739550750776" duration="3613000" />
|
||||||
|
<workItem from="1739617785048" duration="5712000" />
|
||||||
</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" />
|
||||||
@@ -584,7 +585,15 @@
|
|||||||
<option name="project" value="LOCAL" />
|
<option name="project" value="LOCAL" />
|
||||||
<updated>1738775556256</updated>
|
<updated>1738775556256</updated>
|
||||||
</task>
|
</task>
|
||||||
<option name="localTasksCounter" value="42" />
|
<task id="LOCAL-00042" summary="Added fully virtual properties">
|
||||||
|
<option name="closed" value="true" />
|
||||||
|
<created>1739554261551</created>
|
||||||
|
<option name="number" value="00042" />
|
||||||
|
<option name="presentableId" value="LOCAL-00042" />
|
||||||
|
<option name="project" value="LOCAL" />
|
||||||
|
<updated>1739554261551</updated>
|
||||||
|
</task>
|
||||||
|
<option name="localTasksCounter" value="43" />
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
<component name="TypeScriptGeneratedFilesManager">
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
@@ -635,7 +644,6 @@
|
|||||||
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
|
<component name="UnityProjectConfiguration" hasMinimizedUI="false" />
|
||||||
<component name="VcsManagerConfiguration">
|
<component name="VcsManagerConfiguration">
|
||||||
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
<option name="CLEAR_INITIAL_COMMIT_MESSAGE" value="true" />
|
||||||
<MESSAGE value="Created tests for the core module" />
|
|
||||||
<MESSAGE value="Added more tests" />
|
<MESSAGE value="Added more tests" />
|
||||||
<MESSAGE value="Added web module tests" />
|
<MESSAGE value="Added web module tests" />
|
||||||
<MESSAGE value="Tested login functionality" />
|
<MESSAGE value="Tested login functionality" />
|
||||||
@@ -660,6 +668,7 @@
|
|||||||
<MESSAGE value="Added plugin buttons" />
|
<MESSAGE value="Added plugin buttons" />
|
||||||
<MESSAGE value="Added default button removal feature" />
|
<MESSAGE value="Added default button removal feature" />
|
||||||
<MESSAGE value="Added custom search functionality" />
|
<MESSAGE value="Added custom search functionality" />
|
||||||
<option name="LAST_COMMIT_MESSAGE" value="Added custom search functionality" />
|
<MESSAGE value="Added fully virtual properties" />
|
||||||
|
<option name="LAST_COMMIT_MESSAGE" value="Added fully virtual properties" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -10,6 +10,7 @@ public interface ITableManager {
|
|||||||
public Task DeleteItem(object item);
|
public Task DeleteItem(object item);
|
||||||
public Task EditItem(object item);
|
public Task EditItem(object item);
|
||||||
public Task AddItem(object item);
|
public Task AddItem(object item);
|
||||||
|
public Task AddAll(IEnumerable<object> items);
|
||||||
public Task RevertChanges(object item);
|
public Task RevertChanges(object item);
|
||||||
|
|
||||||
public Task<string> DisplayProperty(object? item, PropertyConfig prop, object? value = null, object? enumerableValue = null);
|
public Task<string> DisplayProperty(object? item, PropertyConfig prop, object? value = null, object? enumerableValue = null);
|
||||||
|
|||||||
@@ -49,6 +49,12 @@ internal sealed class TableManager<TModel>(DbContext context, TableConfig config
|
|||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddAll(IEnumerable<object> items) {
|
||||||
|
var table = context.Set<TModel>();
|
||||||
|
await table.AddRangeAsync(items.Cast<TModel>());
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task RevertChanges(object item) {
|
public async Task RevertChanges(object item) {
|
||||||
var entry = context.Entry((TModel)item);
|
var entry = context.Entry((TModel)item);
|
||||||
await entry.ReloadAsync();
|
await entry.ReloadAsync();
|
||||||
|
|||||||
@@ -412,7 +412,7 @@
|
|||||||
_tokenSource.Dispose();
|
_tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum InputType {
|
public enum InputType {
|
||||||
Number,
|
Number,
|
||||||
Switch,
|
Switch,
|
||||||
Date,
|
Date,
|
||||||
|
|||||||
@@ -135,8 +135,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
removeBg();
|
removeBg();
|
||||||
|
|
||||||
|
window.downloadFileFromStream = async (fileName, contentStreamReference) => {
|
||||||
|
const arrayBuffer = await contentStreamReference.arrayBuffer();
|
||||||
|
const blob = new Blob([arrayBuffer]);
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const anchorElement = document.createElement('a');
|
||||||
|
anchorElement.href = url;
|
||||||
|
anchorElement.download = fileName ?? '';
|
||||||
|
anchorElement.click();
|
||||||
|
anchorElement.remove();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.triggerClick = (elt) => elt.click();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<FluentToastProvider MaxToastCount="10" />
|
||||||
|
|
||||||
|
<InputFile style="display: none" @ref="FileInputElement" OnChange="OnInputFiles"></InputFile>
|
||||||
|
|
||||||
@inject IContextExplorer Explorer
|
@inject IContextExplorer Explorer
|
||||||
@inject NavigationManager Navigator
|
@inject NavigationManager Navigator
|
||||||
@inject IJSRuntime Js
|
@inject IJSRuntime Js
|
||||||
@@ -254,7 +272,7 @@
|
|||||||
await Reload();
|
await Reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Reload() {
|
public async Task Reload() {
|
||||||
_loading = true;
|
_loading = true;
|
||||||
|
|
||||||
var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this) {
|
var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this) {
|
||||||
@@ -275,7 +293,7 @@
|
|||||||
_loading = false;
|
_loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ChangePage(int page) {
|
public async Task ChangePage(int page) {
|
||||||
var eventResult = await PluginOrchestrator.DispatchEvent(new PageChangeEvent(this) {
|
var eventResult = await PluginOrchestrator.DispatchEvent(new PageChangeEvent(this) {
|
||||||
CurrentPage = _currentPage,
|
CurrentPage = _currentPage,
|
||||||
NewPage = page,
|
NewPage = page,
|
||||||
@@ -383,4 +401,21 @@
|
|||||||
|
|
||||||
return display;
|
return display;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public InputFile? FileInputElement;
|
||||||
|
public Func<IEnumerable<IBrowserFile>, Task>? OnFileUpload;
|
||||||
|
private async Task OnInputFiles(InputFileChangeEventArgs e) {
|
||||||
|
if (OnFileUpload is null) return;
|
||||||
|
|
||||||
|
if (e.FileCount == 1) {
|
||||||
|
await OnFileUpload.Invoke([e.File]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await OnFileUpload.Invoke(e.GetMultipleFiles());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestRender() {
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -56,4 +56,9 @@ public static class HopFrameConfiguratorExtensions {
|
|||||||
return configurator;
|
return configurator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static HopFrameConfigurator AddExporters(this HopFrameConfigurator configurator) {
|
||||||
|
configurator.AddPlugin<ExporterPlugin>();
|
||||||
|
return configurator;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,34 @@
|
|||||||
namespace HopFrame.Web.Plugins;
|
using HopFrame.Web.Components.Pages;
|
||||||
|
using Microsoft.AspNetCore.Components.Forms;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
public abstract class HopFramePlugin;
|
namespace HopFrame.Web.Plugins;
|
||||||
|
|
||||||
|
public abstract class HopFramePlugin {
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a file using a helper js function
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileName">The name of the file</param>
|
||||||
|
/// <param name="data">The content of the file</param>
|
||||||
|
/// <param name="runtime">The js reference for invoking the function (Injectable)</param>
|
||||||
|
protected async Task DownloadFile(string fileName, byte[] data, IJSRuntime runtime) {
|
||||||
|
using var stream = new DotNetStreamReference(new MemoryStream(data));
|
||||||
|
|
||||||
|
await runtime.InvokeVoidAsync("downloadFileFromStream", fileName, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Task<IBrowserFile> UploadFile(HopFrameTablePage targetPage, IJSRuntime runtime) {
|
||||||
|
var result = new TaskCompletionSource<IBrowserFile>();
|
||||||
|
|
||||||
|
targetPage.OnFileUpload = files => {
|
||||||
|
result.SetResult(files.First());
|
||||||
|
targetPage.OnFileUpload = null;
|
||||||
|
return Task.CompletedTask;
|
||||||
|
};
|
||||||
|
|
||||||
|
runtime.InvokeVoidAsync("triggerClick", targetPage.FileInputElement!.Element);
|
||||||
|
return result.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
112
src/HopFrame.Web/Plugins/Internal/ExporterPlugin.cs
Normal file
112
src/HopFrame.Web/Plugins/Internal/ExporterPlugin.cs
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
using System.Text;
|
||||||
|
using HopFrame.Core.Config;
|
||||||
|
using HopFrame.Core.Services;
|
||||||
|
using HopFrame.Web.Components.Pages;
|
||||||
|
using HopFrame.Web.Plugins.Annotations;
|
||||||
|
using HopFrame.Web.Plugins.Events;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace HopFrame.Web.Plugins.Internal;
|
||||||
|
|
||||||
|
internal sealed class ExporterPlugin(IContextExplorer explorer, IJSRuntime runtime, IToastService toasts, IServiceProvider provider) : HopFramePlugin {
|
||||||
|
|
||||||
|
public const char Separator = ';';
|
||||||
|
|
||||||
|
[EventHandler]
|
||||||
|
public void OnInit(TableInitializedEvent e) {
|
||||||
|
e.AddPageButton("Export", () => Export(e.Table), icon: new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.ArrowUpload());
|
||||||
|
e.AddPageButton("Import", () => Import(e.Table, e.Sender), icon: new Microsoft.FluentUI.AspNetCore.Components.Icons.Regular.Size20.ArrowDownload());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Export(TableConfig table) {
|
||||||
|
var manager = explorer.GetTableManager(table.PropertyName);
|
||||||
|
if (manager is null) {
|
||||||
|
toasts.ShowError("Data could not be exported!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = await manager
|
||||||
|
.LoadPage(0, int.MaxValue)
|
||||||
|
.ToArrayAsync();
|
||||||
|
|
||||||
|
var properties = table.Properties.Where(prop => prop.List).OrderBy(prop => prop.Order).ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
var csv = new StringBuilder(string.Join(Separator, properties.Select(prop => prop.Name)) + '\n');
|
||||||
|
foreach (var entry in data) {
|
||||||
|
var row = new List<string>();
|
||||||
|
|
||||||
|
foreach (var property in properties) {
|
||||||
|
var value = await manager.DisplayProperty(entry, property);
|
||||||
|
row.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
csv.Append(string.Join(Separator, row) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = csv.ToString();
|
||||||
|
await DownloadFile($"{table.DisplayName}.csv", Encoding.UTF8.GetBytes(result), runtime);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Import(TableConfig table, HopFrameTablePage target) {
|
||||||
|
var file = await UploadFile(target, runtime);
|
||||||
|
|
||||||
|
var stream = file.OpenReadStream();
|
||||||
|
var reader = new StreamReader(stream);
|
||||||
|
|
||||||
|
var properties = table.Properties.Where(prop => prop.List).OrderBy(prop => prop.Order).ToArray();
|
||||||
|
var data = await reader.ReadToEndAsync();
|
||||||
|
var rows = data.Split('\n');
|
||||||
|
|
||||||
|
reader.Dispose();
|
||||||
|
await stream.DisposeAsync();
|
||||||
|
|
||||||
|
var headerProps = rows.First().Split(Separator);
|
||||||
|
if (!headerProps.Any(h => properties.Any(prop => prop.Name == h))) {
|
||||||
|
toasts.ShowError("Table header in csv is not valid!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var elements = new List<object>();
|
||||||
|
for (int rowIndex = 1; rowIndex < rows.Length; rowIndex++) {
|
||||||
|
var row = rows[rowIndex];
|
||||||
|
if (string.IsNullOrWhiteSpace(row)) continue;
|
||||||
|
|
||||||
|
var element = Activator.CreateInstance(table.TableType)!;
|
||||||
|
|
||||||
|
var rowValues = row.Split(Separator);
|
||||||
|
for (int i = 0; i < headerProps.Length; i++) {
|
||||||
|
var property = properties.FirstOrDefault(prop => prop.Name == headerProps[i]);
|
||||||
|
if (property is null) continue;
|
||||||
|
if (property.IsEnumerable) continue;
|
||||||
|
|
||||||
|
object value = rowValues[i];
|
||||||
|
|
||||||
|
if (property.Info.PropertyType == typeof(Guid)) {
|
||||||
|
var success = Guid.TryParse((string)value, out var guid);
|
||||||
|
if (success) value = guid;
|
||||||
|
else toasts.ShowError($"'{value}' is not a valid guid");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.Parser is not null) {
|
||||||
|
value = await property.Parser(value.ToString()!, provider);
|
||||||
|
}
|
||||||
|
property.SetValue(element, value, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
var manager = explorer.GetTableManager(table.PropertyName);
|
||||||
|
if (manager is null) {
|
||||||
|
toasts.ShowError("Data could not be exported!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await manager.AddAll(elements);
|
||||||
|
await target.Reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -85,6 +85,8 @@ builder.Services.AddHopFrame(options => {
|
|||||||
options.AddCustomView("Counter", "/counter")
|
options.AddCustomView("Counter", "/counter")
|
||||||
.SetDescription("A custom view")
|
.SetDescription("A custom view")
|
||||||
.SetPolicy("counter.view");
|
.SetPolicy("counter.view");
|
||||||
|
|
||||||
|
options.AddExporters();
|
||||||
});
|
});
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
Reference in New Issue
Block a user