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
|
# 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
|
||||||
|
|||||||
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
|
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; }
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user