mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* move dark/light mode toggle to ui settings page * separate current culture (formatting) and ui culture (language) * add some more sample translations * update changelog * fix cancellation tokenpull/2810/head
23 changed files with 280 additions and 33 deletions
@ -0,0 +1,5 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Configuration; |
||||||
|
|
||||||
|
public record UpdateUiSettings(UiSettingsViewModel UiSettings) : IRequest<Either<BaseError, Unit>>; |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Configuration; |
||||||
|
|
||||||
|
public class UpdateUiSettingsHandler(IConfigElementRepository configElementRepository) |
||||||
|
: IRequestHandler<UpdateUiSettings, Either<BaseError, Unit>> |
||||||
|
{ |
||||||
|
public async Task<Either<BaseError, Unit>> Handle( |
||||||
|
UpdateUiSettings request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
return await ApplyUpdate(request.UiSettings, cancellationToken); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<Unit> ApplyUpdate(UiSettingsViewModel uiSettings, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
await configElementRepository.Upsert( |
||||||
|
ConfigElementKey.PagesIsDarkMode, |
||||||
|
uiSettings.IsDarkMode, |
||||||
|
cancellationToken); |
||||||
|
|
||||||
|
await configElementRepository.Upsert(ConfigElementKey.PagesLanguage, uiSettings.Language, cancellationToken); |
||||||
|
|
||||||
|
return Unit.Default; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,3 @@ |
|||||||
|
namespace ErsatzTV.Application.Configuration; |
||||||
|
|
||||||
|
public record GetUiSettings : IRequest<UiSettingsViewModel>; |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Configuration; |
||||||
|
|
||||||
|
public class GetUiSettingsHandler(IConfigElementRepository configElementRepository) |
||||||
|
: IRequestHandler<GetUiSettings, UiSettingsViewModel> |
||||||
|
{ |
||||||
|
public async Task<UiSettingsViewModel> Handle(GetUiSettings request, CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
Option<bool> pagesIsDarkMode = await configElementRepository.GetValue<bool>( |
||||||
|
ConfigElementKey.PagesIsDarkMode, |
||||||
|
cancellationToken); |
||||||
|
|
||||||
|
Option<string> pagesLanguage = await configElementRepository.GetValue<string>( |
||||||
|
ConfigElementKey.PagesLanguage, |
||||||
|
cancellationToken); |
||||||
|
|
||||||
|
return new UiSettingsViewModel |
||||||
|
{ |
||||||
|
IsDarkMode = await pagesIsDarkMode.IfNoneAsync(true), |
||||||
|
Language = await pagesLanguage.IfNoneAsync("en") |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,8 @@ |
|||||||
|
namespace ErsatzTV.Application.Configuration; |
||||||
|
|
||||||
|
public class UiSettingsViewModel |
||||||
|
{ |
||||||
|
public bool IsDarkMode { get; set; } |
||||||
|
|
||||||
|
public string Language { get; set; } |
||||||
|
} |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using Microsoft.AspNetCore.Localization; |
||||||
|
|
||||||
|
namespace ErsatzTV; |
||||||
|
|
||||||
|
public class DatabaseRequestCultureProvider(AcceptLanguageHeaderRequestCultureProvider defaultProvider) |
||||||
|
: RequestCultureProvider |
||||||
|
{ |
||||||
|
public override async Task<ProviderCultureResult> DetermineProviderCultureResult(HttpContext httpContext) |
||||||
|
{ |
||||||
|
var configElementRepository = httpContext.RequestServices.GetService<IConfigElementRepository>(); |
||||||
|
|
||||||
|
var defaultResult = await defaultProvider.DetermineProviderCultureResult(httpContext); |
||||||
|
string configuredLanguage = await configElementRepository.GetValue<string>( |
||||||
|
ConfigElementKey.PagesLanguage, |
||||||
|
httpContext.RequestAborted) |
||||||
|
.IfNoneAsync("en"); |
||||||
|
|
||||||
|
if (defaultResult != null) |
||||||
|
{ |
||||||
|
return new ProviderCultureResult(defaultResult.Cultures, [configuredLanguage]); |
||||||
|
} |
||||||
|
|
||||||
|
return new ProviderCultureResult(configuredLanguage); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
namespace ErsatzTV; |
||||||
|
|
||||||
|
public class Resources |
||||||
|
{ |
||||||
|
} |
||||||
@ -0,0 +1,35 @@ |
|||||||
|
<root> |
||||||
|
<resheader name="resmimetype"> |
||||||
|
<value>text/microsoft-resx</value> |
||||||
|
</resheader> |
||||||
|
<resheader name="version"> |
||||||
|
<value>1.3</value> |
||||||
|
</resheader> |
||||||
|
<resheader name="reader"> |
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
||||||
|
</resheader> |
||||||
|
<resheader name="writer"> |
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> |
||||||
|
</resheader> |
||||||
|
<data name="ButtonMainMenuChannels" xml:space="preserve"> |
||||||
|
<value>Canais</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuFFmpegProfiles" xml:space="preserve"> |
||||||
|
<value>Perfis FFmpeg</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuWatermarks" xml:space="preserve"> |
||||||
|
<value>Marcas d'água</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuMediaSources" xml:space="preserve"> |
||||||
|
<value>Fontes de mídia</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuMedia" xml:space="preserve"> |
||||||
|
<value>Mídias</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuLists" xml:space="preserve"> |
||||||
|
<value>Listas</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuSettings" xml:space="preserve"> |
||||||
|
<value>Definições</value> |
||||||
|
</data> |
||||||
|
</root> |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||||
|
<root> |
||||||
|
<data name="ButtonMainMenuChannels" xml:space="preserve"> |
||||||
|
<value>Channels</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuFFmpegProfiles" xml:space="preserve"> |
||||||
|
<value>FFmpeg Profiles</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuWatermarks" xml:space="preserve"> |
||||||
|
<value>Watermarks</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuMediaSources" xml:space="preserve"> |
||||||
|
<value>Media Sources</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuMedia" xml:space="preserve"> |
||||||
|
<value>Media</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuLists" xml:space="preserve"> |
||||||
|
<value>Lists</value> |
||||||
|
</data> |
||||||
|
<data name="ButtonMainMenuSettings" xml:space="preserve"> |
||||||
|
<value>Settings</value> |
||||||
|
</data> |
||||||
|
</root> |
||||||
@ -0,0 +1,84 @@ |
|||||||
|
@page "/settings/ui" |
||||||
|
@using ErsatzTV.Application.Configuration |
||||||
|
@using ErsatzTV.Core.Notifications |
||||||
|
@implements IDisposable |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject ISnackbar Snackbar |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
@inject ILogger<UISettings> Logger |
||||||
|
|
||||||
|
<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="@(_ => SaveUiSettings())" 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">UI</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>Theme</MudText> |
||||||
|
</div> |
||||||
|
<MudSelect @bind-Value="_uiSettings.IsDarkMode"> |
||||||
|
<MudSelectItem Value="@true">Dark</MudSelectItem> |
||||||
|
<MudSelectItem Value="@false">Light</MudSelectItem> |
||||||
|
</MudSelect> |
||||||
|
</MudStack> |
||||||
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> |
||||||
|
<div class="d-flex"> |
||||||
|
<MudText>Language</MudText> |
||||||
|
</div> |
||||||
|
<MudSelect @bind-Value="_uiSettings.Language"> |
||||||
|
<MudSelectItem Value="@("en-us")">English</MudSelectItem> |
||||||
|
<MudSelectItem Value="@("pt-br")">Português (Brasil)</MudSelectItem> |
||||||
|
</MudSelect> |
||||||
|
</MudStack> |
||||||
|
</MudContainer> |
||||||
|
</div> |
||||||
|
</MudForm> |
||||||
|
|
||||||
|
@code { |
||||||
|
private CancellationTokenSource _cts; |
||||||
|
|
||||||
|
private UiSettingsViewModel _uiSettings = 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 |
||||||
|
{ |
||||||
|
_uiSettings = await Mediator.Send(new GetUiSettings(), token); |
||||||
|
} |
||||||
|
catch (OperationCanceledException) |
||||||
|
{ |
||||||
|
// do nothing |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private async Task SaveUiSettings() |
||||||
|
{ |
||||||
|
Either<BaseError, Unit> result = await Mediator.Send(new UpdateUiSettings(_uiSettings), _cts.Token); |
||||||
|
|
||||||
|
foreach (var error in result.LeftToSeq()) |
||||||
|
{ |
||||||
|
Snackbar.Add(error.Value, Severity.Error); |
||||||
|
Logger.LogError("Unexpected error saving ui settings: {Error}", error.Value); |
||||||
|
} |
||||||
|
|
||||||
|
if (result.IsRight) |
||||||
|
{ |
||||||
|
NavigationManager.Refresh(true); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue