CRUD für Libraries erstellt

This commit is contained in:
BuildTools 2024-06-26 20:35:43 +02:00
parent 620d3794a6
commit 4fe401e360
8 changed files with 409 additions and 3 deletions

View File

@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17 # Visual Studio Version 17
VisualStudioVersion = 17.9.34728.123 VisualStudioVersion = 17.9.34728.123
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "songrequests_blazor", "songrequests_blazor\songrequests_blazor.csproj", "{9036E36D-4C0B-45FD-9DE5-2C171277B6B1}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "songrequests_blazor", "songrequests_blazor\songrequests_blazor.csproj", "{9036E36D-4C0B-45FD-9DE5-2C171277B6B1}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution

View File

@ -0,0 +1,51 @@
<div class="modal @modalClass" tabindex="-1" role="dialog" style="display:@modalDisplay; overflow-y: auto;">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@Title</h5>
</div>
<div class="modal-body">
@Body
</div>
<div class="modal-footer">
@Footer
</div>
</div>
</div>
</div>
@if (showBackdrop)
{
<div class="modal-backdrop fade show"></div>
}
@code {
[Parameter]
public RenderFragment? Title { get; set; }
[Parameter]
public RenderFragment? Body { get; set; }
[Parameter]
public RenderFragment? Footer { get; set; }
private string modalDisplay = "none;";
private string modalClass = "";
private bool showBackdrop = false;
public async Task ShowAsync()
{
modalDisplay = "block;";
await Task.Delay(100);//Delay allows bootstrap to perform nice fade animation
modalClass = "show";
showBackdrop = true;
}
public async Task CloseAsync()
{
modalDisplay = "none";
await Task.Delay(250);//Delay allows bootstrap to perform nice fade animation
modalClass = "";
showBackdrop = false;
}
}

View File

@ -0,0 +1,75 @@
@page "/options/libraries"
@using Microsoft.EntityFrameworkCore
@using songrequests_blazor.Data
@inject Microsoft.EntityFrameworkCore.IDbContextFactory<LocalDbContext> DataContextFactory
<h3>Bibliotheken</h3>
<a class="btn btn-primary mb-3" href="/options/libraries/edit">Bibliothek anlegen</a>
<ul class="list-group">
@foreach (var itm in Items ?? [])
{
<li class="list-group-item d-flex justify-content-between align-items-center">
@itm.Name
<div>
<a class="btn btn-primary" href="/options/libraries/edit/@itm.Id">Bearbeiten</a>
<button type="button" class="btn btn-secondary" @onclick="args => ShowDialog(itm)">Entfernen</button>
</div>
</li>
}
</ul>
<ModalComponent @ref="modal">
<Title>Löschen bestätigen</Title>
<Body>
Willst du die Bibliothek wirklich löschen?
</Body>
<Footer>
<button class="btn btn-danger" @onclick="RemoveItem">Ja</button>
<button class="btn btn-primary" @onclick="CloseDialog">Nein</button>
</Footer>
</ModalComponent>
@code {
List<Library>? Items { get; set; }
private ModalComponent modal = null!;
Library? ModalItem { get; set; }
protected override async Task OnInitializedAsync()
{
await ShowList();
}
private async Task ShowList()
{
using (LocalDbContext db = await DataContextFactory.CreateDbContextAsync())
{
Items = await db.Libraries.OrderBy(l => l.Name).ToListAsync();
}
}
private async Task ShowDialog(Library itm)
{
ModalItem = itm;
await modal.ShowAsync();
}
private async Task CloseDialog()
{
ModalItem = null;
await modal.CloseAsync();
}
private async Task RemoveItem()
{
if (ModalItem == null) return;
using (LocalDbContext db = await DataContextFactory.CreateDbContextAsync())
{
db.Libraries.Remove(ModalItem);
await db.SaveChangesAsync();
await ShowList();
}
await CloseDialog();
}
}

View File

@ -0,0 +1,150 @@
@page "/options/libraries/edit"
@page "/options/libraries/edit/{id}"
@using songrequests_blazor.Data
@inject Microsoft.EntityFrameworkCore.IDbContextFactory<LocalDbContext> DataContextFactory
@inject NavigationManager nav;
<h3>Bibliothek bearbeiten</h3>
<EditForm Model="@libItem" OnValidSubmit="AddLibrary">
<CustomValidation @ref="customValidation" />
<DataAnnotationsValidator />
<ValidationSummary />
<div class="d-flex gap-3 mb-3">
<div class="border border-2 rounded p-3 w-100">
<div class="mb-3">
<label class="form-label" for="libName">Name der Bibliothek</label>
<input id="libName" name="libName" type="text" class="form-control" @bind="@libItem.Name" />
</div>
<div class="mb-3">
<label class="form-label" for="libType">Bibliothekstyp</label>
<select class="form-select" @bind="@libItem.Type">
@foreach (var tp in Enum.GetNames(typeof(Library.LibraryType)))
{
<option>@tp</option>
}
</select>
</div>
@if (libItem.Type == Library.LibraryType.Local)
{
<div class="mb-3">
<label class="form-label" for="libPaths">Pfade</label>
<div class="input-group">
<input class="form-control" type="text" id="libPaths" @bind="@InputAddPath" />
<button type="button" class="btn btn-primary" @onclick="args => AddPathToLibray(InputAddPath)">Hinzufügen</button>
</div>
@if (AddPathMessage != null)
{
<label class="form-label alert-danger">@AddPathMessage</label>
}
<ul class="list-group">
@if (libItem.ScanPaths != null && libItem.ScanPaths.Count > 0)
{
@foreach (var lp in libItem.ScanPaths ?? [])
{
<li class="list-group-item d-flex justify-content-between align-items-center">
@lp
<button type="button" class="btn btn-secondary" @onclick="args => RemovePathFromLibrary(lp)">Entfernen</button>
</li>
}
}
else
{
<li class="list-group-item text-secondary ">Keine Einträge</li>
}
</ul>
</div>
}
<button type="submit" class="btn btn-primary">Speichern</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Abbrechen</button>
</div>
</div>
</EditForm>
@code {
[Parameter]
public string? id { get; set; }
const string LISTURL = "/options/libraries";
Library libItem { get; set; } = null!;
string? InputAddPath { get; set; }
string? AddPathMessage { get; set; }
private CustomValidation? customValidation;
protected override async Task OnInitializedAsync()
{
if (id != null)
{
using (LocalDbContext db = await DataContextFactory.CreateDbContextAsync())
{
libItem = db.Libraries.Single(lib => lib.Id == Convert.ToInt32(id));
}
}
libItem ??= new();
}
private void AddPathToLibray(string path)
{
AddPathMessage = null;
if (!System.IO.Path.Exists(path))
{
AddPathMessage = "Der angegebene Pfad existiert nicht.";
return;
}
libItem.ScanPaths ??= new List<string>();
if (libItem.ScanPaths.Contains(path))
{
AddPathMessage = "Der Pfad ist bereits in der Liste enthalten.";
return;
}
libItem.ScanPaths.Add(path);
InputAddPath = null;
}
private void RemovePathFromLibrary(string path)
{
AddPathMessage = null;
libItem.ScanPaths?.Remove(path);
}
private bool CustomValidate()
{
customValidation?.ClearErrors();
var errors = new Dictionary<string, List<string>>();
if (libItem.Type == Library.LibraryType.Local && (libItem.ScanPaths == null || libItem.ScanPaths?.Count == 0))
{
errors.Add(nameof(libItem.Type), new() { "Type 'Local' is selected but no paths are specified." });
}
if (errors.Any())
{
customValidation?.DisplayErrors(errors);
return false;
}
else
{
return true;
}
}
private void Cancel()
{
nav.NavigateTo(LISTURL);
}
private async Task AddLibrary()
{
if (!CustomValidate()) return;
if (libItem.Type != Library.LibraryType.Local) libItem.ScanPaths = null;
using (LocalDbContext db = await DataContextFactory.CreateDbContextAsync())
{
db.Libraries.Add(libItem);
await db.SaveChangesAsync();
}
nav.NavigateTo(LISTURL);
}
}

View File

@ -0,0 +1,75 @@
@page "/options/sessions"
@using Microsoft.EntityFrameworkCore
@using songrequests_blazor.Data
@inject Microsoft.EntityFrameworkCore.IDbContextFactory<LocalDbContext> DataContextFactory
<h3>Sessions</h3>
<a class="btn btn-primary mb-3" href="/options/sessions/edit">Session anlegen</a>
<ul class="list-group">
@foreach (var itm in Items ?? [])
{
<li class="list-group-item d-flex justify-content-between align-items-center">
Erstellt am @itm.Created
<div>
<a class="btn btn-primary" href="/options/libraries/edit/@itm.Id">Bearbeiten</a>
<button type="button" class="btn btn-secondary" @onclick="args => ShowDialog(itm)">Entfernen</button>
</div>
</li>
}
</ul>
<ModalComponent @ref="modal">
<Title>Löschen bestätigen</Title>
<Body>
Willst du die Session wirklich löschen?
</Body>
<Footer>
<button class="btn btn-danger" @onclick="RemoveItem">Ja</button>
<button class="btn btn-primary" @onclick="CloseDialog">Nein</button>
</Footer>
</ModalComponent>
@code {
List<Session>? Items { get; set; }
private ModalComponent modal = null!;
Session? ModalItem { get; set; }
protected override async Task OnInitializedAsync()
{
await ShowList();
}
private async Task ShowList()
{
using (LocalDbContext db = await DataContextFactory.CreateDbContextAsync())
{
Items = await db.Sessions.OrderBy(l => l.Created).ToListAsync();
}
}
private async Task ShowDialog(Session itm)
{
ModalItem = itm;
await modal.ShowAsync();
}
private async Task CloseDialog()
{
ModalItem = null;
await modal.CloseAsync();
}
private async Task RemoveItem()
{
if (ModalItem == null) return;
using (LocalDbContext db = await DataContextFactory.CreateDbContextAsync())
{
db.Sessions.Remove(ModalItem);
await db.SaveChangesAsync();
await ShowList();
}
await CloseDialog();
}
}

View File

@ -0,0 +1,52 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
namespace songrequests_blazor
{
public class CustomValidation : ComponentBase
{
private ValidationMessageStore? messageStore;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
protected override void OnInitialized()
{
if (CurrentEditContext is null)
{
throw new InvalidOperationException(
$"{nameof(CustomValidation)} requires a cascading " +
$"parameter of type {nameof(EditContext)}. " +
$"For example, you can use {nameof(CustomValidation)} " +
$"inside an {nameof(EditForm)}.");
}
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) =>
messageStore?.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore?.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
if (CurrentEditContext is not null)
{
foreach (var err in errors)
{
messageStore?.Add(CurrentEditContext.Field(err.Key), err.Value);
}
CurrentEditContext.NotifyValidationStateChanged();
}
}
public void ClearErrors()
{
messageStore?.Clear();
CurrentEditContext?.NotifyValidationStateChanged();
}
}
}

View File

@ -1,10 +1,12 @@
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace songrequests_blazor.Data namespace songrequests_blazor.Data
{ {
public class Library public class Library
{ {
public int Id { get; set; } public int Id { get; set; }
[Required(ErrorMessage = "Der Name fehlt")]
public string Name { get; set; } = null!; public string Name { get; set; } = null!;
public List<Song>? Songs { get; set; } public List<Song>? Songs { get; set; }
public List<string>? ScanPaths { get; set; } public List<string>? ScanPaths { get; set; }

View File

@ -20,6 +20,7 @@ builder.Services.Configure<Mailserver>(builder.Configuration.GetSection(nameof(M
// Database // Database
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContextFactory<LocalDbContext>(options => options.UseSqlite(connectionString));
builder.Services.AddDbContext<LocalDbContext>(options => options.UseSqlite(connectionString)); builder.Services.AddDbContext<LocalDbContext>(options => options.UseSqlite(connectionString));
// Authentication // Authentication