diff --git a/songrequests_blazor.sln b/songrequests_blazor.sln
index 1a29aaa..6df0166 100644
--- a/songrequests_blazor.sln
+++ b/songrequests_blazor.sln
@@ -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
diff --git a/songrequests_blazor/Components/Pages/ModalComponent.razor b/songrequests_blazor/Components/Pages/ModalComponent.razor
new file mode 100644
index 0000000..258a377
--- /dev/null
+++ b/songrequests_blazor/Components/Pages/ModalComponent.razor
@@ -0,0 +1,51 @@
+
+
+@if (showBackdrop)
+{
+
+}
+
+@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;
+ }
+}
\ No newline at end of file
diff --git a/songrequests_blazor/Components/Pages/Settings/Libraries.razor b/songrequests_blazor/Components/Pages/Settings/Libraries.razor
new file mode 100644
index 0000000..5fd89d5
--- /dev/null
+++ b/songrequests_blazor/Components/Pages/Settings/Libraries.razor
@@ -0,0 +1,75 @@
+@page "/options/libraries"
+@using Microsoft.EntityFrameworkCore
+@using songrequests_blazor.Data
+@inject Microsoft.EntityFrameworkCore.IDbContextFactory DataContextFactory
+Bibliotheken
+
+Bibliothek anlegen
+
+
+ @foreach (var itm in Items ?? [])
+ {
+ -
+ @itm.Name
+
+
+ }
+
+
+
+ Löschen bestätigen
+
+ Willst du die Bibliothek wirklich löschen?
+
+
+
+
+@code {
+
+ List? 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();
+ }
+}
\ No newline at end of file
diff --git a/songrequests_blazor/Components/Pages/Settings/LibraryEdit.razor b/songrequests_blazor/Components/Pages/Settings/LibraryEdit.razor
new file mode 100644
index 0000000..41aace0
--- /dev/null
+++ b/songrequests_blazor/Components/Pages/Settings/LibraryEdit.razor
@@ -0,0 +1,150 @@
+@page "/options/libraries/edit"
+@page "/options/libraries/edit/{id}"
+@using songrequests_blazor.Data
+@inject Microsoft.EntityFrameworkCore.IDbContextFactory DataContextFactory
+@inject NavigationManager nav;
+Bibliothek bearbeiten
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ @if (libItem.Type == Library.LibraryType.Local)
+ {
+
+
+
+
+
+
+ @if (AddPathMessage != null)
+ {
+
+ }
+
+ @if (libItem.ScanPaths != null && libItem.ScanPaths.Count > 0)
+ {
+ @foreach (var lp in libItem.ScanPaths ?? [])
+ {
+ -
+ @lp
+
+
+ }
+ }
+ else
+ {
+ - Keine Einträge
+ }
+
+
+ }
+
+
+
+
+
+
+
+@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();
+ 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>();
+
+ 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);
+ }
+}
diff --git a/songrequests_blazor/Components/Pages/Settings/Sessions.razor b/songrequests_blazor/Components/Pages/Settings/Sessions.razor
new file mode 100644
index 0000000..3bc25e1
--- /dev/null
+++ b/songrequests_blazor/Components/Pages/Settings/Sessions.razor
@@ -0,0 +1,75 @@
+@page "/options/sessions"
+@using Microsoft.EntityFrameworkCore
+@using songrequests_blazor.Data
+@inject Microsoft.EntityFrameworkCore.IDbContextFactory DataContextFactory
+Sessions
+
+Session anlegen
+
+
+ @foreach (var itm in Items ?? [])
+ {
+ -
+ Erstellt am @itm.Created
+
+
+ }
+
+
+
+ Löschen bestätigen
+
+ Willst du die Session wirklich löschen?
+
+
+
+
+@code {
+
+ List? 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();
+ }
+}
\ No newline at end of file
diff --git a/songrequests_blazor/CustomValidation.cs b/songrequests_blazor/CustomValidation.cs
new file mode 100644
index 0000000..9327e32
--- /dev/null
+++ b/songrequests_blazor/CustomValidation.cs
@@ -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> 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();
+ }
+
+ }
+}
diff --git a/songrequests_blazor/Data/Library.cs b/songrequests_blazor/Data/Library.cs
index 8595830..2a9cb13 100644
--- a/songrequests_blazor/Data/Library.cs
+++ b/songrequests_blazor/Data/Library.cs
@@ -1,13 +1,15 @@
-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? Songs { get; set; }
- public List? ScanPaths { get; set; }
+ public List? ScanPaths { get; set; }
public LibraryType Type { get; set; }
[Column("Sessions_ID")]
diff --git a/songrequests_blazor/Program.cs b/songrequests_blazor/Program.cs
index c05b6be..2dd8ced 100644
--- a/songrequests_blazor/Program.cs
+++ b/songrequests_blazor/Program.cs
@@ -20,6 +20,7 @@ builder.Services.Configure(builder.Configuration.GetSection(nameof(M
// Database
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
+builder.Services.AddDbContextFactory(options => options.UseSqlite(connectionString));
builder.Services.AddDbContext(options => options.UseSqlite(connectionString));
// Authentication