CRUD für Libraries erstellt
This commit is contained in:
parent
620d3794a6
commit
4fe401e360
@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.9.34728.123
|
||||
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
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
||||
51
songrequests_blazor/Components/Pages/ModalComponent.razor
Normal file
51
songrequests_blazor/Components/Pages/ModalComponent.razor
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
150
songrequests_blazor/Components/Pages/Settings/LibraryEdit.razor
Normal file
150
songrequests_blazor/Components/Pages/Settings/LibraryEdit.razor
Normal 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);
|
||||
}
|
||||
}
|
||||
75
songrequests_blazor/Components/Pages/Settings/Sessions.razor
Normal file
75
songrequests_blazor/Components/Pages/Settings/Sessions.razor
Normal 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();
|
||||
}
|
||||
}
|
||||
52
songrequests_blazor/CustomValidation.cs
Normal file
52
songrequests_blazor/CustomValidation.cs
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,10 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace songrequests_blazor.Data
|
||||
{
|
||||
public class Library
|
||||
{
|
||||
public int Id { get; set; }
|
||||
[Required(ErrorMessage = "Der Name fehlt")]
|
||||
public string Name { get; set; } = null!;
|
||||
public List<Song>? Songs { get; set; }
|
||||
public List<string>? ScanPaths { get; set; }
|
||||
|
||||
@ -20,6 +20,7 @@ builder.Services.Configure<Mailserver>(builder.Configuration.GetSection(nameof(M
|
||||
|
||||
// Database
|
||||
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));
|
||||
|
||||
// Authentication
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user