Browse Source

add smart collection editor to support renaming (#2253)

pull/2254/head
Jason Dove 1 year ago committed by GitHub
parent
commit
a728c5e31e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollection.cs
  3. 1
      ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs
  4. 3
      ErsatzTV.Application/MediaCollections/Queries/GetSmartCollectionById.cs
  5. 20
      ErsatzTV.Application/MediaCollections/Queries/GetSmartCollectionByIdHandler.cs
  6. 4
      ErsatzTV/Pages/CollectionEditor.razor
  7. 11
      ErsatzTV/Pages/Collections.razor
  8. 1
      ErsatzTV/Pages/Search.razor
  9. 71
      ErsatzTV/Pages/SmartCollectionEditor.razor
  10. 8
      ErsatzTV/ViewModels/SmartCollectionEditViewModel.cs

3
CHANGELOG.md

@ -44,6 +44,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -44,6 +44,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Add `Use Chapters As Media Items` option to filler preset
- This option allows scheduling individual chapters as filler
- The chapters are shuffled or otherwise sorted together just like normal filler would be
- Add smart collection edit page to allow renaming smart collections
- Previous edit link behavior (performing search using smart collection query) now uses magnifying glass icon
### Fixed
- Fix app startup with MySql/MariaDB
@ -62,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -62,6 +64,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Move search debug logging to its own log category; add `Searching Minimum Log Level` to `Settings` > `Logging`
- Classic schedules: always schedule the full `Duration` amount instead of stopping mid-duration
- This allows duration items to be scheduled beyond midnight
- e.g. fixed start time 22:00 with 4 hour duration will schedule until 02:00 instead of stopping at midnight
## [25.3.1] - 2025-07-24
### Fixed

2
ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollection.cs

@ -2,4 +2,4 @@ @@ -2,4 +2,4 @@
namespace ErsatzTV.Application.MediaCollections;
public record UpdateSmartCollection(int Id, string Query) : IRequest<Either<BaseError, Unit>>;
public record UpdateSmartCollection(int Id, string Name, string Query) : IRequest<Either<BaseError, Unit>>;

1
ErsatzTV.Application/MediaCollections/Commands/UpdateSmartCollectionHandler.cs

@ -46,6 +46,7 @@ public class UpdateSmartCollectionHandler : IRequestHandler<UpdateSmartCollectio @@ -46,6 +46,7 @@ public class UpdateSmartCollectionHandler : IRequestHandler<UpdateSmartCollectio
private async Task<Unit> ApplyUpdateRequest(TvContext dbContext, SmartCollection c, UpdateSmartCollection request)
{
c.Query = request.Query;
c.Name = request.Name;
// rebuild playouts
if (await dbContext.SaveChangesAsync() > 0)

3
ErsatzTV.Application/MediaCollections/Queries/GetSmartCollectionById.cs

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
namespace ErsatzTV.Application.MediaCollections;
public record GetSmartCollectionById(int Id) : IRequest<Option<SmartCollectionViewModel>>;

20
ErsatzTV.Application/MediaCollections/Queries/GetSmartCollectionByIdHandler.cs

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using static ErsatzTV.Application.MediaCollections.Mapper;
namespace ErsatzTV.Application.MediaCollections;
public class GetSmartCollectionByIdHandler(IDbContextFactory<TvContext> dbContextFactory) :
IRequestHandler<GetSmartCollectionById, Option<SmartCollectionViewModel>>
{
public async Task<Option<SmartCollectionViewModel>> Handle(
GetSmartCollectionById request,
CancellationToken cancellationToken)
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
return await dbContext.SmartCollections
.SelectOneAsync(c => c.Id, c => c.Id == request.Id)
.MapT(ProjectToViewModel);
}
}

4
ErsatzTV/Pages/CollectionEditor.razor

@ -9,7 +9,7 @@ @@ -9,7 +9,7 @@
<MudForm @ref="_form" @bind-IsValid="@_success" 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" Class="ml-6" OnClick="HandleSubmitAsync" StartIcon="@(IsEdit ? Icons.Material.Filled.Save : Icons.Material.Filled.Add)">@(IsEdit ? "Save Collection" : "Add Collection")</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Primary" Class="ml-6" OnClick="@HandleSubmitAsync" StartIcon="@(IsEdit ? Icons.Material.Filled.Save : Icons.Material.Filled.Add)">@(IsEdit ? "Save Collection" : "Add Collection")</MudButton>
</MudPaper>
<div class="d-flex flex-column" style="height: 100vh; overflow-x: auto">
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8">
@ -19,7 +19,7 @@ @@ -19,7 +19,7 @@
<div class="d-flex">
<MudText>Name</MudText>
</div>
<MudTextField @bind-Value="_model.Name" For="@(() => _model.Name)" Required="true" RequiredError="Schedule name is required!"/>
<MudTextField @bind-Value="_model.Name" For="@(() => _model.Name)" Required="true" RequiredError="Collection name is required!"/>
</MudStack>
</MudContainer>
</div>

11
ErsatzTV/Pages/Collections.razor

@ -114,7 +114,7 @@ @@ -114,7 +114,7 @@
<ColGroup>
<MudHidden Breakpoint="Breakpoint.Xs">
<col/>
<col style="width: 120px;"/>
<col style="width: 180px;"/>
</MudHidden>
</ColGroup>
<HeaderContent>
@ -125,12 +125,17 @@ @@ -125,12 +125,17 @@
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd>
<div style="align-items: center; display: flex;">
<MudTooltip Text="Edit Collection">
<MudTooltip Text="Edit Smart Collection">
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Href="@($"/media/smart-collections/{context.Id}/edit")">
</MudIconButton>
</MudTooltip>
<MudTooltip Text="Search Smart Collection">
<MudIconButton Icon="@Icons.Material.Filled.Search"
Href="@context.Query.GetRelativeSearchQuery()">
</MudIconButton>
</MudTooltip>
<MudTooltip Text="Delete Collection">
<MudTooltip Text="Delete Smart Collection">
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(_ => DeleteSmartCollection(context))">
</MudIconButton>

1
ErsatzTV/Pages/Search.razor

@ -950,6 +950,7 @@ @@ -950,6 +950,7 @@
{
var request = new UpdateSmartCollection(
collectionResult.Item1.Id,
collectionResult.Item1.Name,
_query);
Either<BaseError, Unit> updateResult = await Mediator.Send(request, CancellationToken);

71
ErsatzTV/Pages/SmartCollectionEditor.razor

@ -0,0 +1,71 @@ @@ -0,0 +1,71 @@
@page "/media/smart-collections/{Id:int}/edit"
@using ErsatzTV.Application.MediaCollections
@implements IDisposable
@inject NavigationManager NavigationManager
@inject ILogger<CollectionEditor> Logger
@inject ISnackbar Snackbar
@inject IMediator Mediator
<MudForm @ref="_form" @bind-IsValid="@_success" 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" Class="ml-6" OnClick="@HandleSubmitAsync" StartIcon="@Icons.Material.Filled.Save">
Save Smart Collection
</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">Smart Collection</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>Name</MudText>
</div>
<MudTextField @bind-Value="_model.Name" For="@(() => _model.Name)" Required="true" RequiredError="Smart collection name is required!"/>
</MudStack>
</MudContainer>
</div>
</MudForm>
@code {
private readonly CancellationTokenSource _cts = new();
[Parameter]
public int Id { get; set; }
private readonly SmartCollectionEditViewModel _model = new();
private MudForm _form;
private bool _success;
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
protected override async Task OnParametersSetAsync()
{
Option<SmartCollectionViewModel> maybeSmartCollection = await Mediator.Send(new GetSmartCollectionById(Id), _cts.Token);
foreach (var smartCollection in maybeSmartCollection)
{
_model.Id = smartCollection.Id;
_model.Name = smartCollection.Name;
_model.Query = smartCollection.Query;
}
}
private async Task HandleSubmitAsync()
{
await _form.Validate();
if (_success)
{
Either<BaseError, Unit> result = await Mediator.Send(new UpdateSmartCollection(Id, _model.Name, _model.Query), _cts.Token);
result.Match(
_ => NavigationManager.NavigateTo("media/collections"),
error =>
{
Snackbar.Add(error.Value, Severity.Error);
Logger.LogError("Error saving smart collection: {Error}", error.Value);
});
}
}
}

8
ErsatzTV/ViewModels/SmartCollectionEditViewModel.cs

@ -0,0 +1,8 @@ @@ -0,0 +1,8 @@
namespace ErsatzTV.ViewModels;
public class SmartCollectionEditViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Query { get; set; }
}
Loading…
Cancel
Save