mirror of https://github.com/ErsatzTV/ErsatzTV.git
49 changed files with 374 additions and 70 deletions
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
using ErsatzTV.Core; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Configuration; |
||||
|
||||
public record UpdatePaginationSettings(PaginationSettingsViewModel Settings) : IRequest<Either<BaseError, Unit>>; |
||||
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using System.Globalization; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Configuration; |
||||
|
||||
public class UpdatePaginationSettingsHandler : IRequestHandler<UpdatePaginationSettings, Either<BaseError, Unit>> |
||||
{ |
||||
private readonly IConfigElementRepository _configElementRepository; |
||||
|
||||
public UpdatePaginationSettingsHandler(IConfigElementRepository configElementRepository) => |
||||
_configElementRepository = configElementRepository; |
||||
|
||||
public async Task<Either<BaseError, Unit>> Handle( |
||||
UpdatePaginationSettings request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
int pageSize = PaginationOptions.NormalizePageSize(request.Settings.DefaultPageSize); |
||||
|
||||
await _configElementRepository.Upsert( |
||||
ConfigElementKey.PagesDefaultPageSize, |
||||
pageSize.ToString(CultureInfo.InvariantCulture), |
||||
cancellationToken); |
||||
|
||||
return Unit.Default; |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
namespace ErsatzTV.Application.Configuration; |
||||
|
||||
public class PaginationSettingsViewModel |
||||
{ |
||||
public int DefaultPageSize { get; set; } |
||||
} |
||||
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Application.Configuration; |
||||
|
||||
public record GetPaginationSettings : IRequest<PaginationSettingsViewModel>; |
||||
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
using ErsatzTV.Core; |
||||
using ErsatzTV.Core.Domain; |
||||
using ErsatzTV.Core.Interfaces.Repositories; |
||||
using LanguageExt; |
||||
|
||||
namespace ErsatzTV.Application.Configuration; |
||||
|
||||
public class GetPaginationSettingsHandler : IRequestHandler<GetPaginationSettings, PaginationSettingsViewModel> |
||||
{ |
||||
private readonly IConfigElementRepository _configElementRepository; |
||||
|
||||
public GetPaginationSettingsHandler(IConfigElementRepository configElementRepository) => |
||||
_configElementRepository = configElementRepository; |
||||
|
||||
public async Task<PaginationSettingsViewModel> Handle( |
||||
GetPaginationSettings request, |
||||
CancellationToken cancellationToken) |
||||
{ |
||||
Option<ConfigElement> maybeElement = await _configElementRepository.GetConfigElement( |
||||
ConfigElementKey.PagesDefaultPageSize, |
||||
cancellationToken); |
||||
|
||||
int defaultPageSize = PaginationOptions.NormalizePageSize( |
||||
maybeElement.Bind<int?>(element => int.TryParse(element.Value, out int value) ? value : null)); |
||||
|
||||
return new PaginationSettingsViewModel { DefaultPageSize = defaultPageSize }; |
||||
} |
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
using ErsatzTV.Core; |
||||
using FluentAssertions; |
||||
using NUnit.Framework; |
||||
|
||||
namespace ErsatzTV.Core.Tests; |
||||
|
||||
[TestFixture] |
||||
public class PaginationOptionsTests |
||||
{ |
||||
[Test] |
||||
public void NormalizePageSize_Should_Default_For_Null() |
||||
{ |
||||
PaginationOptions.NormalizePageSize(null).Should().Be(PaginationOptions.DefaultPageSize); |
||||
} |
||||
|
||||
[TestCase(0)] |
||||
[TestCase(-1)] |
||||
public void NormalizePageSize_Should_Default_For_NonPositive(int input) |
||||
{ |
||||
PaginationOptions.NormalizePageSize(input).Should().Be(PaginationOptions.DefaultPageSize); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePageSize_Should_Accept_Positive() |
||||
{ |
||||
PaginationOptions.NormalizePageSize(50).Should().Be(50); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePageSize_Should_Clamp_To_Max() |
||||
{ |
||||
PaginationOptions.NormalizePageSize(1000).Should().Be(PaginationOptions.MaxPageSize); |
||||
} |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
using System; |
||||
|
||||
namespace ErsatzTV.Core; |
||||
|
||||
public static class PaginationOptions |
||||
{ |
||||
public const int DefaultPageSize = 100; |
||||
public const int MaxPageSize = 500; |
||||
|
||||
public static int NormalizePageSize(int? requested) => |
||||
NormalizePageSize(requested, DefaultPageSize, MaxPageSize); |
||||
|
||||
public static int NormalizePageSize(int? requested, int defaultSize, int maxSize) |
||||
{ |
||||
if (requested is null || requested <= 0) |
||||
{ |
||||
return defaultSize; |
||||
} |
||||
|
||||
return Math.Clamp(requested.Value, 1, maxSize); |
||||
} |
||||
} |
||||
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
using ErsatzTV.Application.Configuration; |
||||
using ErsatzTV.Core; |
||||
using MediatR; |
||||
|
||||
namespace ErsatzTV.Extensions; |
||||
|
||||
public static class PaginationExtensions |
||||
{ |
||||
public static async Task<int> GetDefaultPageSize(this IMediator mediator, CancellationToken cancellationToken) |
||||
{ |
||||
PaginationSettingsViewModel settings = await mediator.Send(new GetPaginationSettings(), cancellationToken); |
||||
return PaginationOptions.NormalizePageSize(settings.DefaultPageSize); |
||||
} |
||||
} |
||||
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
@page "/settings/pagination" |
||||
@using ErsatzTV.Application.Configuration |
||||
@inject IMediator Mediator |
||||
@inject ISnackbar Snackbar |
||||
@inject ILogger<PaginationSettings> Logger |
||||
@implements IDisposable |
||||
|
||||
<MudForm Style="max-height: 100%"> |
||||
<MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center"> |
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@(_ => SavePaginationSettings())" Class="ml-6" StartIcon="@Icons.Material.Filled.Save">Save Settings</MudButton> |
||||
</MudPaper> |
||||
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto"> |
||||
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> |
||||
<MudText Typo="Typo.h5" Class="mb-2">Pagination</MudText> |
||||
<MudDivider Class="mb-6"/> |
||||
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> |
||||
<div class="d-flex"> |
||||
<MudText>Items Per Page</MudText> |
||||
</div> |
||||
<MudSelect @bind-Value="_settings.DefaultPageSize"> |
||||
<MudSelectItem Value="25">25</MudSelectItem> |
||||
<MudSelectItem Value="50">50</MudSelectItem> |
||||
<MudSelectItem Value="100">100</MudSelectItem> |
||||
<MudSelectItem Value="200">200</MudSelectItem> |
||||
<MudSelectItem Value="500">500</MudSelectItem> |
||||
</MudSelect> |
||||
</MudStack> |
||||
</MudContainer> |
||||
</div> |
||||
</MudForm> |
||||
|
||||
@code { |
||||
private CancellationTokenSource _cts; |
||||
private PaginationSettingsViewModel _settings = new(); |
||||
|
||||
public void Dispose() |
||||
{ |
||||
_cts?.Cancel(); |
||||
_cts?.Dispose(); |
||||
} |
||||
|
||||
protected override async Task OnParametersSetAsync() |
||||
{ |
||||
_cts?.Cancel(); |
||||
_cts?.Dispose(); |
||||
_cts = new CancellationTokenSource(); |
||||
var token = _cts.Token; |
||||
|
||||
try |
||||
{ |
||||
_settings = await Mediator.Send(new GetPaginationSettings(), token); |
||||
} |
||||
catch (OperationCanceledException) |
||||
{ |
||||
// do nothing |
||||
} |
||||
} |
||||
|
||||
private async Task SavePaginationSettings() |
||||
{ |
||||
Either<BaseError, Unit> result = await Mediator.Send(new UpdatePaginationSettings(_settings), _cts.Token); |
||||
result.Match( |
||||
Left: error => |
||||
{ |
||||
Snackbar.Add(error.Value, Severity.Error); |
||||
Logger.LogError("Unexpected error saving pagination settings: {Error}", error.Value); |
||||
}, |
||||
Right: _ => Snackbar.Add("Successfully saved pagination settings", Severity.Success)); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue