Implemented config editor. Fixed auto property

This commit is contained in:
Baumgartner Marcel 2023-10-31 10:48:20 +01:00
parent 93cfe7cd1a
commit 11dace2617
8 changed files with 245 additions and 99 deletions

View file

@ -24,13 +24,13 @@ public class PropBinder<T>
get => (int)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public long LongValue
{
get => (long)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public bool BoolValue
{
get => (bool)PropertyInfo.GetValue(DataObject)!;
@ -48,7 +48,7 @@ public class PropBinder<T>
get => (T)PropertyInfo.GetValue(DataObject)!;
set => PropertyInfo.SetValue(DataObject, value);
}
public double DoubleValue
{
get => (double)PropertyInfo.GetValue(DataObject)!;

View file

@ -21,7 +21,12 @@ public class ConfigService
var text = File.ReadAllText(Path);
Data = JsonConvert.DeserializeObject<ConfigV1>(text) ?? new();
text = JsonConvert.SerializeObject(Data, Formatting.Indented);
Save();
}
public void Save()
{
var text = JsonConvert.SerializeObject(Data, Formatting.Indented);
File.WriteAllText(Path, text);
}

View file

@ -133,6 +133,11 @@
Binder = new(Property, Data);
}
protected override void OnParametersSet()
{
Binder = new(Property, Data);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)

View file

@ -0,0 +1,22 @@
<div class="card">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/sys">
<i class="bx bx-sm bxs-dashboard me-2"></i> Overview
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/sys/settings">
<i class="bx bx-sm bx-cog me-2"></i> Settings
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; }
}

View file

@ -105,6 +105,17 @@
</span>
</a>
</div>
<div class="menu-item">
<a class="menu-link " href="/admin/sys">
<span class="menu-icon">
<i class="bx bx-sm bxs-component"></i>
</span>
<span class="menu-title">
System
</span>
</a>
</div>
}
</div>
</div>

View file

@ -1,95 +0,0 @@
@page "/admin/settings"
@using Moonlight.App.Services
@inject ConfigService ConfigService
@if (ModelToShow == null)
{
<h1>Nope</h1>
}
else
{
<div class="card">
<div class="card-header">
@{
string title;
if (Path.Length == 0)
title = "Configuration";
else
title = string.Join(
" > ",
Path.Select(x =>
x.EndsWith("Data") ? Formatter.ReplaceEnd(x, "Data", "") : x));
}
<h3 class="card-title">@(title)</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3 col-12">
@{
var props = ModelToShow
.GetType()
.GetProperties()
.Where(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.PropertyType.IsClass)
.ToArray();
}
@foreach (var prop in props)
{
<div class="d-flex flex-stack">
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
<a href="/admin/settings?section=@(Section + "/" + prop.Name)" class="fs-5 text-primary">@(prop.Name)</a>
</div>
</div>
}
</div>
<div class="col-md-9 col-12">
<AutoForm Model="ModelToShow" Columns="6" />
</div>
</div>
</div>
</div>
}
@code
{
[Parameter]
[SupplyParameterFromQuery]
public string? Section { get; set; } = "";
private object? ModelToShow = new();
private string[] Path = Array.Empty<string>();
protected override async Task OnParametersSetAsync()
{
if (Section != null && Section.StartsWith("/"))
Section = Section.TrimStart('/');
Path = Section != null ? Section.Split("/") : Array.Empty<string>();
ModelToShow = Resolve(ConfigService.Get(), Path, 0);
await InvokeAsync(StateHasChanged);
}
private object? Resolve(object model, string[] path, int index)
{
if (path.Length == 0)
return model;
if (path.Length == index)
return model;
var prop = model
.GetType()
.GetProperties()
.FirstOrDefault(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.PropertyType.IsClass && x.Name == path[index]);
if (prop == null)
return null;
return Resolve(prop.GetValue(model)!, path, index + 1);
}
}

View file

@ -0,0 +1,8 @@
@page "/admin/sys"
@using Moonlight.App.Extensions.Attributes
@using Moonlight.App.Models.Enums
@attribute [RequirePermission(Permission.AdminRoot)]
<AdminSysNavigation Index="0" />

View file

@ -0,0 +1,190 @@
@page "/admin/sys/settings"
@using Moonlight.App.Services
@using System.Reflection
@using Moonlight.App.Extensions.Attributes
@using Moonlight.App.Models.Enums
@attribute [RequirePermission(Permission.AdminRoot)]
@inject ConfigService ConfigService
@inject ToastService ToastService
<AdminSysNavigation Index="1" />
@if (ModelToShow == null)
{
<NotFoundAlert />
}
else
{
<div class="card mt-5">
<div class="card-header">
@{
string title;
if (Path.Length == 0)
title = "Configuration";
else
{
title = "Configuration - " + string.Join(" - ", Path);
}
}
<h3 class="card-title">@(title)</h3>
<div class="card-toolbar">
<WButton OnClick="Reload" CssClasses="btn btn-icon btn-warning me-3">
<i class="bx bx-sm bx-revision"></i>
</WButton>
<WButton OnClick="Save" CssClasses="btn btn-icon btn-success">
<i class="bx bx-sm bx-save"></i>
</WButton>
</div>
</div>
</div>
<Tooltip>
Changes to these settings are live applied. The save button only make the changes persistently saved to disk
</Tooltip>
<div class="row mt-5">
<div class="col-md-3 col-12 mb-5">
<div class="card card-body">
@{
var props = ModelToShow
.GetType()
.GetProperties()
.Where(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.PropertyType.IsClass)
.ToArray();
}
@foreach (var prop in props)
{
<div class="d-flex flex-stack">
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
<a href="/admin/sys/settings?section=@(Section + "/" + prop.Name)" class="fs-4 text-primary">@(prop.Name)</a>
</div>
</div>
}
@if (Path.Length != 0)
{
<div class="d-flex flex-stack">
<div class="d-flex align-items-center flex-row-fluid flex-wrap">
<a href="/admin/sys/@(GetBackPath())" class="fs-4 text-primary">Back</a>
</div>
</div>
}
</div>
</div>
<div class="col-md-9 col-12">
<div class="card card-body">
<div class="row">
<LazyLoader @ref="LazyLoader" Load="Load">
@foreach (var prop in Properties)
{
<div class="col-md-6 col-12">
<CascadingValue Name="Property" Value="prop">
<CascadingValue Name="Data" Value="ModelToShow">
@{
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
}
@ComponentHelper.FromType(typeToCreate)
</CascadingValue>
</CascadingValue>
</div>
}
</LazyLoader>
</div>
</div>
</div>
</div>
}
@code
{
[Parameter]
[SupplyParameterFromQuery]
public string? Section { get; set; } = "";
private object? ModelToShow;
private PropertyInfo[] Properties = Array.Empty<PropertyInfo>();
private string[] Path = Array.Empty<string>();
private LazyLoader? LazyLoader;
protected override async Task OnParametersSetAsync()
{
if (Section != null && Section.StartsWith("/"))
Section = Section.TrimStart('/');
Path = Section != null ? Section.Split("/") : Array.Empty<string>();
ModelToShow = Resolve(ConfigService.Get(), Path, 0);
if (ModelToShow != null)
{
Properties = ModelToShow
.GetType()
.GetProperties()
.Where(x => !x.PropertyType.Assembly.FullName!.Contains("Moonlight"))
.ToArray();
}
else
{
Properties = Array.Empty<PropertyInfo>();
}
await InvokeAsync(StateHasChanged);
if (LazyLoader != null)
await LazyLoader.Reload();
}
private string GetBackPath()
{
if (Path.Length == 1)
return "settings";
else
{
var path = string.Join('/', Path.Take(Path.Length - 1)).TrimEnd('/');
return $"settings?section={path}";
}
}
private object? Resolve(object model, string[] path, int index)
{
if (path.Length == 0)
return model;
if (path.Length == index)
return model;
var prop = model
.GetType()
.GetProperties()
.FirstOrDefault(x => x.PropertyType.Assembly.FullName!.Contains("Moonlight") && x.Name == path[index]);
if (prop == null)
return null;
return Resolve(prop.GetValue(model)!, path, index + 1);
}
private Task Load(LazyLoader arg)
{
return Task.CompletedTask;
}
private async Task Save()
{
ConfigService.Save();
await ToastService.Success("Successfully saved config to disk");
}
private async Task Reload()
{
ConfigService.Reload();
await ToastService.Info("Reloaded configuration from disk");
}
}