From aff4fb0debf84e04d423736dbb7ff587b1b2ea04 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Sat, 5 Jul 2025 11:20:53 +0000 Subject: [PATCH] use new form layout for channel editor (#2112) --- CHANGELOG.md | 4 +- .../ErsatzTV.Application.csproj.DotSettings | 1 + .../Validators/GetMemberName.cs | 2 +- .../Validators/NumericValidation.cs | 2 +- .../Validators/StringValidation.cs | 2 +- ErsatzTV/Pages/ChannelEditor.razor | 384 +++++++++++------- .../ChannelEditViewModelValidator.cs | 9 + 7 files changed, 247 insertions(+), 157 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b3905f..7a6ba3b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -72,7 +72,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Remove some limits on multithreading that are no longer needed with latest ffmpeg - Mixed transcoding (software decode, hardware filters/encode) can now use multiple decode threads - Split main `Settings` page into multiple pages - - Update layout on all new settings pages to be less cramped and to work better on mobile +- Update form layout to be less cramped and to work better on mobile + - All new (split) settings pages + - Channel editor ### Fixed - Fix QSV acceleration in docker with older Intel devices diff --git a/ErsatzTV.Application/ErsatzTV.Application.csproj.DotSettings b/ErsatzTV.Application/ErsatzTV.Application.csproj.DotSettings index 8df6a2bf..d50a6f98 100644 --- a/ErsatzTV.Application/ErsatzTV.Application.csproj.DotSettings +++ b/ErsatzTV.Application/ErsatzTV.Application.csproj.DotSettings @@ -44,5 +44,6 @@ True True True + True True True \ No newline at end of file diff --git a/ErsatzTV.Application/Validators/GetMemberName.cs b/ErsatzTV.Application/Validators/GetMemberName.cs index 41fcd8d3..1a04c41c 100644 --- a/ErsatzTV.Application/Validators/GetMemberName.cs +++ b/ErsatzTV.Application/Validators/GetMemberName.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using System.Reflection; -namespace ErsatzTV; +namespace ErsatzTV.Application; public static partial class Validators { diff --git a/ErsatzTV.Application/Validators/NumericValidation.cs b/ErsatzTV.Application/Validators/NumericValidation.cs index 49e62454..c7f32665 100644 --- a/ErsatzTV.Application/Validators/NumericValidation.cs +++ b/ErsatzTV.Application/Validators/NumericValidation.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using ErsatzTV.Core; -namespace ErsatzTV; +namespace ErsatzTV.Application; public static partial class Validators { diff --git a/ErsatzTV.Application/Validators/StringValidation.cs b/ErsatzTV.Application/Validators/StringValidation.cs index 05a63a17..5c1753cf 100644 --- a/ErsatzTV.Application/Validators/StringValidation.cs +++ b/ErsatzTV.Application/Validators/StringValidation.cs @@ -1,7 +1,7 @@ using System.Linq.Expressions; using ErsatzTV.Core; -namespace ErsatzTV; +namespace ErsatzTV.Application; public static partial class Validators { diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index 09cd992c..0e3309c3 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -1,6 +1,5 @@ @page "/channels/{Id:int?}" @page "/channels/add" -@using System.Net @using ErsatzTV.Application.Artworks @using ErsatzTV.Application.Channels @using ErsatzTV.Application.FFmpegProfiles @@ -10,6 +9,8 @@ @using ErsatzTV.Application.Templates @using ErsatzTV.Application.Watermarks @using ErsatzTV.Core.Domain.Filler +@using ErsatzTV.Validators +@using FluentValidation.Results @using static Prelude @implements IDisposable @inject NavigationManager NavigationManager @@ -17,161 +18,243 @@ @inject ISnackbar Snackbar @inject IMediator Mediator - -
- - - - - - @(IsEdit ? "Edit Channel" : "Add Channel") - - - - - - - - - Active - Hidden - Inactive - - - Always - On Demand - - - MPEG-TS - MPEG-TS (Legacy) - HLS Direct - HLS Segmenter - HLS Segmenter V2 - - - @foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) - { - @profile.Name - } - - - Default - Custom - - @if (_model.StreamSelectorMode is ChannelStreamSelectorMode.Default) + + + @(IsEdit ? "Save Changes" : "Add Channel") + +
+ + @(IsEdit ? "Edit Channel" : "Add Channel") + + +
+ Number +
+ +
+ +
+ Name +
+ +
+ +
+ Group +
+ +
+ +
+ Categories +
+ +
+ +
+ Active Mode +
+ + Active + Hidden + Inactive + +
+ +
+ Progress Mode +
+ + Always + On Demand + +
+ +
+ Streaming Mode +
+ + MPEG-TS + MPEG-TS (Legacy) + HLS Direct + HLS Segmenter + HLS Segmenter V2 + +
+ +
+ FFmpeg Profile +
+ + @foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) { - - (none) - @foreach (LanguageCodeViewModel culture in _availableCultures) - { - @culture.EnglishName - } - - - - (none) - @foreach (LanguageCodeViewModel culture in _availableCultures) - { - @culture.EnglishName - } - - - None - Forced - Default - Any - + @profile.Name } - else - { - - (none) - @foreach (string selector in _streamSelectors) - { - @selector - } - - } - - None - Generate Subtitles - - - (none) - @foreach (string template in _musicVideoCreditsTemplates) + +
+ +
+ Stream Selector Mode +
+ + Default + Custom + +
+ @if (_model.StreamSelectorMode is ChannelStreamSelectorMode.Default) + { + +
+ Preferred Audio Language +
+ + (none) + @foreach (LanguageCodeViewModel culture in _availableCultures) { - @template + @culture.EnglishName } - - Default - With Progress - - - - - - - Upload Logo - - - - - + +
+ Preferred Audio Title +
+ +
+ +
+ Preferred Subtitle Language +
+ - (none) - @foreach (WatermarkViewModel watermark in _watermarks) + (none) + @foreach (LanguageCodeViewModel culture in _availableCultures) { - @watermark.Name + @culture.EnglishName } - - (none) - @foreach (FillerPresetViewModel fillerPreset in _fillerPresets) +
+ +
+ Subtitle Mode +
+ + None + Forced + Default + Any + +
+ } + else + { + +
+ Stream Selector +
+ + (none) + @foreach (string selector in _streamSelectors) { - @fillerPreset.Name + @selector } - - - - @(IsEdit ? "Save Changes" : "Add Channel") - - - - +
+ } + +
+ Music Video Credits Mode +
+ + None + Generate Subtitles + +
+ +
+ Music Video Credits Template +
+ + (none) + @foreach (string template in _musicVideoCreditsTemplates) + { + @template + } + +
+ +
+ Song Video Mode +
+ + Default + With Progress + +
+ +
+ Logo +
+
+ +
+ External Logo URL +
+ +
+ +
+ Logo Preview +
+ @if (!string.IsNullOrWhiteSpace(_model.Logo?.Path) || !string.IsNullOrWhiteSpace(_model.ExternalLogoUrl)) + { + + } +
+ +
+ Watermark +
+ + (none) + @foreach (WatermarkViewModel watermark in _watermarks) + { + @watermark.Name + } + +
+ +
+ Fallback Filler +
+ + (none) + @foreach (FillerPresetViewModel fillerPreset in _fillerPresets) + { + @fillerPreset.Name + } + +
+
- +
@code { private readonly CancellationTokenSource _cts = new(); @@ -180,8 +263,8 @@ public int? Id { get; set; } private readonly ChannelEditViewModel _model = new(); - private EditContext _editContext; - private ValidationMessageStore _messageStore; + private readonly ChannelEditViewModelValidator _validator = new(); + private MudForm _form; private List _ffmpegProfiles = []; private List _availableCultures = []; @@ -260,12 +343,6 @@ } } - protected override void OnInitialized() - { - _editContext = new EditContext(_model); - _messageStore = new ValidationMessageStore(_editContext); - } - private bool IsEdit => Id.HasValue; private async Task LoadFFmpegProfiles(CancellationToken cancellationToken) => @@ -286,8 +363,9 @@ private async Task HandleSubmitAsync() { - _messageStore.Clear(); - if (_editContext.Validate()) + await _form.Validate(); + ValidationResult result = await _validator.ValidateAsync(_model, _cts.Token); + if (result.IsValid) { Seq errorMessage = IsEdit ? (await Mediator.Send(_model.ToUpdate(), _cts.Token)).LeftToSeq() : (await Mediator.Send(_model.ToCreate(), _cts.Token)).LeftToSeq(); diff --git a/ErsatzTV/Validators/ChannelEditViewModelValidator.cs b/ErsatzTV/Validators/ChannelEditViewModelValidator.cs index 29e4c6cc..89dbba06 100644 --- a/ErsatzTV/Validators/ChannelEditViewModelValidator.cs +++ b/ErsatzTV/Validators/ChannelEditViewModelValidator.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.ViewModels; using FluentValidation; +using FluentValidation.Results; namespace ErsatzTV.Validators; @@ -24,4 +25,12 @@ public class ChannelEditViewModelValidator : AbstractValidator>> ValidateValue => async (model, propertyName) => + { + ValidationResult result = await ValidateAsync(ValidationContext.CreateWithOptions((ChannelEditViewModel)model, x => x.IncludeProperties(propertyName))); + if (result.IsValid) + return []; + return result.Errors.Select(e => e.ErrorMessage); + }; }