Added basic export and import feature
This commit is contained in:
@@ -412,7 +412,7 @@
|
||||
_tokenSource.Dispose();
|
||||
}
|
||||
|
||||
private enum InputType {
|
||||
public enum InputType {
|
||||
Number,
|
||||
Switch,
|
||||
Date,
|
||||
|
||||
@@ -135,8 +135,26 @@
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<FluentToastProvider MaxToastCount="10" />
|
||||
|
||||
<InputFile style="display: none" @ref="FileInputElement" OnChange="OnInputFiles"></InputFile>
|
||||
|
||||
@inject IContextExplorer Explorer
|
||||
@inject NavigationManager Navigator
|
||||
@inject IJSRuntime Js
|
||||
@@ -254,7 +272,7 @@
|
||||
await Reload();
|
||||
}
|
||||
|
||||
private async Task Reload() {
|
||||
public async Task Reload() {
|
||||
_loading = true;
|
||||
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new ReloadEvent(this) {
|
||||
@@ -275,7 +293,7 @@
|
||||
_loading = false;
|
||||
}
|
||||
|
||||
private async Task ChangePage(int page) {
|
||||
public async Task ChangePage(int page) {
|
||||
var eventResult = await PluginOrchestrator.DispatchEvent(new PageChangeEvent(this) {
|
||||
CurrentPage = _currentPage,
|
||||
NewPage = page,
|
||||
@@ -383,4 +401,21 @@
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -55,5 +55,10 @@ public static class HopFrameConfiguratorExtensions {
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user