Browse Source

use new form layout for local library editor (#2129)

pull/2130/head
Jason Dove 1 month ago committed by GitHub
parent
commit
28a65e74bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 2
      ErsatzTV/Pages/ChannelEditor.razor
  3. 157
      ErsatzTV/Pages/LocalLibraryEditor.razor
  4. 9
      ErsatzTV/Validators/LocalLibraryEditViewModelValidator.cs
  5. 13
      ErsatzTV/Validators/LocalLibraryPathEditViewModelValidator.cs

1
CHANGELOG.md

@ -78,6 +78,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- FFmpeg Profile editor - FFmpeg Profile editor
- Schedule editor - Schedule editor
- Watermark editor - Watermark editor
- Local library editor
### Fixed ### Fixed
- Fix QSV acceleration in docker with older Intel devices - Fix QSV acceleration in docker with older Intel devices

2
ErsatzTV/Pages/ChannelEditor.razor

@ -24,7 +24,7 @@
</MudPaper> </MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto"> <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudText Typo="Typo.h5" Class="mb-2">@(IsEdit ? "Edit Channel" : "Add Channel")</MudText> <MudText Typo="Typo.h5" Class="mb-2">Channel</MudText>
<MudDivider Class="mb-6"/> <MudDivider Class="mb-6"/>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5"> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"> <div class="d-flex">

157
ErsatzTV/Pages/LocalLibraryEditor.razor

@ -9,68 +9,79 @@
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8"> <MudForm Model="@_model" @bind-IsValid="@_success" Style="max-height: 100%">
<div style="max-width: 400px;"> <MudPaper Square="true" Style="display: flex; height: 64px; min-height: 64px; width: 100%; z-index: 100; align-items: center">
<MudText Typo="Typo.h4" Class="mb-4">@(IsEdit ? "Edit Local Library" : "Add Local Library")</MudText> <MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-8" OnClick="SaveChangesAsync" StartIcon="@(IsEdit ? Icons.Material.Filled.Save : Icons.Material.Filled.Add)">@(IsEdit ? "Save Local Library" : "Add Local Library")</MudButton>
</MudPaper>
<EditForm EditContext="_editContext" OnSubmit="@SaveChangesAsync"> <div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<FluentValidationValidator/> <MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
<MudCard> <MudText Typo="Typo.h5" Class="mb-2">Local Library</MudText>
<MudCardContent> <MudDivider Class="mb-6"/>
<MudTextField Class="mt-3" Label="Name" @bind-Value="_model.Name" For="@(() => _model.Name)"/> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<MudSelect Disabled="IsEdit" Label="Media Kind" @bind-Value="_model.MediaKind" For="@(() => _model.MediaKind)"> <div class="d-flex">
@foreach (LibraryMediaKind mediaKind in Enum.GetValues<LibraryMediaKind>()) <MudText>Name</MudText>
{
<MudSelectItem Value="@mediaKind">@mediaKind</MudSelectItem>
}
</MudSelect>
<MudTextField Class="mt-3" Label="Path" @bind-Value="_newPath.Path" For="@(() => _newPath.Path)"/>
</MudCardContent>
<MudCardActions>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddLibraryPath())" Class="ml-2">
Add Path
</MudButton>
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Class="mr-2 ml-auto">
Save Changes
</MudButton>
</MudCardActions>
</MudCard>
</EditForm>
</div>
<MudTable Hover="true" Items="_model.Paths" Dense="true" Class="mt-6">
<ToolBarContent>
<MudText Typo="Typo.h6">Library Paths</MudText>
</ToolBarContent>
<ColGroup>
<col/>
<col style="width: 120px;"/>
</ColGroup>
<HeaderContent>
<MudTh>Path</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Path">@context.Path</MudTd>
<MudTd>
<div style="align-items: center; display: flex;">
<MudTooltip Text="Move Library Path">
<MudIconButton Icon="@Icons.Material.Filled.DriveFileMove"
Disabled="@(_model.Id == 0 || context.Id == 0 || Locker.IsLibraryLocked(_model.Id))"
OnClick="@(() => MoveLibraryPath(context))">
</MudIconButton>
</MudTooltip>
<MudTooltip Text="Delete Library Path">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(() => DeleteLibraryPath(context))">
</MudIconButton>
</MudTooltip>
</div> </div>
</MudTd> <MudTextField @bind-Value="_model.Name" For="@(() => _model.Name)" Required="true" RequiredError="Local library name is required!"/>
</RowTemplate> </MudStack>
</MudTable> <MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
</MudContainer> <div class="d-flex">
<MudText>Media Kind</MudText>
</div>
<MudSelect Disabled="IsEdit" @bind-Value="_model.MediaKind" For="@(() => _model.MediaKind)">
@foreach (LibraryMediaKind mediaKind in Enum.GetValues<LibraryMediaKind>())
{
<MudSelectItem Value="@mediaKind">@mediaKind</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex">
<MudText>Path</MudText>
</div>
<MudTextField @bind-Value="_newPath.Path" For="@(() => _newPath.Path)"/>
</MudStack>
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="form-field-stack gap-md-8 mb-5">
<div class="d-flex"></div>
<MudButton Variant="Variant.Filled" Color="Color.Secondary" OnClick="@(_ => AddLibraryPath())" StartIcon="@Icons.Material.Filled.Add">
Add Path
</MudButton>
</MudStack>
<MudTable Hover="true" Items="_model.Paths" Class="mt-6">
<ToolBarContent>
<MudText Typo="Typo.h6">Library Paths</MudText>
</ToolBarContent>
<ColGroup>
<MudHidden Breakpoint="Breakpoint.Xs">
<col/>
<col style="width: 120px;"/>
</MudHidden>
</ColGroup>
<HeaderContent>
<MudTh>Path</MudTh>
<MudTh/>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Path</MudTd>
<MudTd>
<div style="align-items: center; display: flex;">
<MudTooltip Text="Move Library Path">
<MudIconButton Icon="@Icons.Material.Filled.DriveFileMove"
Disabled="@(_model.Id == 0 || context.Id == 0 || Locker.IsLibraryLocked(_model.Id))"
OnClick="@(() => MoveLibraryPath(context))">
</MudIconButton>
</MudTooltip>
<MudTooltip Text="Delete Library Path">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(() => DeleteLibraryPath(context))">
</MudIconButton>
</MudTooltip>
</div>
</MudTd>
</RowTemplate>
</MudTable>
</MudContainer>
</div>
</MudForm>
@code { @code {
private readonly CancellationTokenSource _cts = new(); private readonly CancellationTokenSource _cts = new();
@ -80,8 +91,7 @@
private readonly LocalLibraryEditViewModel _model = new(); private readonly LocalLibraryEditViewModel _model = new();
private readonly LocalLibraryPathEditViewModel _newPath = new(); private readonly LocalLibraryPathEditViewModel _newPath = new();
private EditContext _editContext; private bool _success;
private ValidationMessageStore _messageStore;
private bool IsEdit => Id != 0; private bool IsEdit => Id != 0;
@ -117,9 +127,6 @@
protected override void OnInitialized() protected override void OnInitialized()
{ {
Locker.OnLibraryChanged += LockChanged; Locker.OnLibraryChanged += LockChanged;
_editContext = new EditContext(_model);
_messageStore = new ValidationMessageStore(_editContext);
} }
private void LockChanged(object sender, EventArgs e) => private void LockChanged(object sender, EventArgs e) =>
@ -143,7 +150,7 @@
IDialogReference dialog = await Dialog.ShowAsync<MoveLocalLibraryPathDialog>("Move Local Library Path", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<MoveLocalLibraryPathDialog>("Move Local Library Path", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled && result.Data is LocalLibraryViewModel library) if (result is { Canceled: false, Data: LocalLibraryViewModel library })
{ {
var request = new MoveLocalLibraryPath(libraryPath.Id, library.Id); var request = new MoveLocalLibraryPath(libraryPath.Id, library.Id);
Either<BaseError, Unit> moveResult = await Mediator.Send(request, _cts.Token); Either<BaseError, Unit> moveResult = await Mediator.Send(request, _cts.Token);
@ -177,7 +184,7 @@
IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Library Path", parameters, options); IDialogReference dialog = await Dialog.ShowAsync<DeleteDialog>("Delete Library Path", parameters, options);
DialogResult result = await dialog.Result; DialogResult result = await dialog.Result;
if (!result.Canceled) if (result is { Canceled: false })
{ {
_model.HasChanges = true; _model.HasChanges = true;
_model.Paths.Remove(libraryPath); _model.Paths.Remove(libraryPath);
@ -186,6 +193,17 @@
private void AddLibraryPath() private void AddLibraryPath()
{ {
if (string.IsNullOrWhiteSpace(_newPath.Path))
{
return;
}
if (!Directory.Exists(_newPath.Path))
{
Snackbar.Add("Path must exist on filesystem", Severity.Error);
return;
}
if (!string.IsNullOrWhiteSpace(_newPath.Path) && _model.Paths.All(p => NormalizePath(p.Path) != NormalizePath(_newPath.Path))) if (!string.IsNullOrWhiteSpace(_newPath.Path) && _model.Paths.All(p => NormalizePath(p.Path) != NormalizePath(_newPath.Path)))
{ {
_model.HasChanges = true; _model.HasChanges = true;
@ -201,8 +219,7 @@
private async Task SaveChangesAsync() private async Task SaveChangesAsync()
{ {
_messageStore.Clear(); if (_success)
if (_editContext.Validate())
{ {
Either<BaseError, LocalLibraryViewModel> result = IsEdit Either<BaseError, LocalLibraryViewModel> result = IsEdit
? await Mediator.Send( ? await Mediator.Send(

9
ErsatzTV/Validators/LocalLibraryEditViewModelValidator.cs

@ -1,9 +0,0 @@
using ErsatzTV.ViewModels;
using FluentValidation;
namespace ErsatzTV.Validators;
public class LocalLibraryEditViewModelValidator : AbstractValidator<LocalLibraryEditViewModel>
{
public LocalLibraryEditViewModelValidator() => RuleFor(c => c.Name).NotEmpty();
}

13
ErsatzTV/Validators/LocalLibraryPathEditViewModelValidator.cs

@ -1,13 +0,0 @@
using ErsatzTV.ViewModels;
using FluentValidation;
namespace ErsatzTV.Validators;
public class LocalLibraryPathEditViewModelValidator : AbstractValidator<LocalLibraryPathEditViewModel>
{
public LocalLibraryPathEditViewModelValidator()
{
RuleFor(x => x.Path).NotEmpty();
RuleFor(x => x.Path).Must(Directory.Exists).WithMessage("Path must exist on filesystem");
}
}
Loading…
Cancel
Save