mirror of https://github.com/ErsatzTV/ErsatzTV.git
Browse Source
* update trakt list items when re-adding existing list * add seasons to search indexpull/386/head
34 changed files with 3881 additions and 42 deletions
@ -1,6 +1,11 @@ |
|||||||
using System.Collections.Generic; |
using System.Collections.Generic; |
||||||
|
using ErsatzTV.Core.Search; |
||||||
|
using LanguageExt; |
||||||
|
|
||||||
namespace ErsatzTV.Application.MediaCards |
namespace ErsatzTV.Application.MediaCards |
||||||
{ |
{ |
||||||
public record TelevisionSeasonCardResultsViewModel(int Count, List<TelevisionSeasonCardViewModel> Cards); |
public record TelevisionSeasonCardResultsViewModel( |
||||||
|
int Count, |
||||||
|
List<TelevisionSeasonCardViewModel> Cards, |
||||||
|
Option<SearchPageMap> PageMap); |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,8 @@ |
|||||||
|
using ErsatzTV.Application.MediaCards; |
||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Search.Queries |
||||||
|
{ |
||||||
|
public record QuerySearchIndexSeasons |
||||||
|
(string Query, int PageNumber, int PageSize) : IRequest<TelevisionSeasonCardResultsViewModel>; |
||||||
|
} |
||||||
@ -0,0 +1,55 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Application.MediaCards; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using ErsatzTV.Core.Interfaces.Search; |
||||||
|
using ErsatzTV.Core.Search; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
using static ErsatzTV.Application.MediaCards.Mapper; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Search.Queries |
||||||
|
{ |
||||||
|
public class |
||||||
|
QuerySearchIndexSeasonsHandler : IRequestHandler<QuerySearchIndexSeasons, TelevisionSeasonCardResultsViewModel> |
||||||
|
{ |
||||||
|
private readonly IMediaSourceRepository _mediaSourceRepository; |
||||||
|
private readonly ISearchIndex _searchIndex; |
||||||
|
private readonly ITelevisionRepository _televisionRepository; |
||||||
|
|
||||||
|
public QuerySearchIndexSeasonsHandler( |
||||||
|
ISearchIndex searchIndex, |
||||||
|
ITelevisionRepository televisionRepository, |
||||||
|
IMediaSourceRepository mediaSourceRepository) |
||||||
|
{ |
||||||
|
_searchIndex = searchIndex; |
||||||
|
_televisionRepository = televisionRepository; |
||||||
|
_mediaSourceRepository = mediaSourceRepository; |
||||||
|
} |
||||||
|
|
||||||
|
public async Task<TelevisionSeasonCardResultsViewModel> Handle( |
||||||
|
QuerySearchIndexSeasons request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
SearchResult searchResult = await _searchIndex.Search( |
||||||
|
request.Query, |
||||||
|
(request.PageNumber - 1) * request.PageSize, |
||||||
|
request.PageSize); |
||||||
|
|
||||||
|
Option<JellyfinMediaSource> maybeJellyfin = await _mediaSourceRepository.GetAllJellyfin() |
||||||
|
.Map(list => list.HeadOrNone()); |
||||||
|
|
||||||
|
Option<EmbyMediaSource> maybeEmby = await _mediaSourceRepository.GetAllEmby() |
||||||
|
.Map(list => list.HeadOrNone()); |
||||||
|
|
||||||
|
List<TelevisionSeasonCardViewModel> items = await _televisionRepository |
||||||
|
.GetSeasonsForCards(searchResult.Items.Map(i => i.Id).ToList()) |
||||||
|
.Map(list => list.Map(s => ProjectToViewModel(s, maybeJellyfin, maybeEmby)).ToList()); |
||||||
|
|
||||||
|
return new TelevisionSeasonCardResultsViewModel(searchResult.TotalCount, items, searchResult.PageMap); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@ |
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations; |
||||||
|
|
||||||
|
namespace ErsatzTV.Infrastructure.Migrations |
||||||
|
{ |
||||||
|
public partial class Add_TraktListItemDeleteSetNull : Migration |
||||||
|
{ |
||||||
|
protected override void Up(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropForeignKey( |
||||||
|
name: "FK_TraktListItem_MediaItem_MediaItemId", |
||||||
|
table: "TraktListItem"); |
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey( |
||||||
|
name: "FK_TraktListItem_MediaItem_MediaItemId", |
||||||
|
table: "TraktListItem", |
||||||
|
column: "MediaItemId", |
||||||
|
principalTable: "MediaItem", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.SetNull); |
||||||
|
} |
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder) |
||||||
|
{ |
||||||
|
migrationBuilder.DropForeignKey( |
||||||
|
name: "FK_TraktListItem_MediaItem_MediaItemId", |
||||||
|
table: "TraktListItem"); |
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey( |
||||||
|
name: "FK_TraktListItem_MediaItem_MediaItemId", |
||||||
|
table: "TraktListItem", |
||||||
|
column: "MediaItemId", |
||||||
|
principalTable: "MediaItem", |
||||||
|
principalColumn: "Id", |
||||||
|
onDelete: ReferentialAction.Restrict); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,157 @@ |
|||||||
|
@page "/media/tv/seasons" |
||||||
|
@page "/media/tv/seasons/page/{PageNumber:int}" |
||||||
|
@using LanguageExt.UnsafeValueAccess |
||||||
|
@using ErsatzTV.Application.MediaCards |
||||||
|
@using ErsatzTV.Application.MediaCollections |
||||||
|
@using ErsatzTV.Application.MediaCollections.Commands |
||||||
|
@using ErsatzTV.Application.Search.Queries |
||||||
|
@using ErsatzTV.Extensions |
||||||
|
@using Unit = LanguageExt.Unit |
||||||
|
@inherits MultiSelectBase<TelevisionShowList> |
||||||
|
@inject NavigationManager _navigationManager |
||||||
|
@inject ChannelWriter<IBackgroundServiceRequest> _channel |
||||||
|
|
||||||
|
<MudPaper Square="true" Style="display: flex; height: 64px; left: 240px; padding: 0; position: fixed; right: 0; z-index: 100;"> |
||||||
|
<div style="display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%" class="ml-6 mr-6"> |
||||||
|
@if (IsSelectMode()) |
||||||
|
{ |
||||||
|
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText> |
||||||
|
<div style="margin-left: auto"> |
||||||
|
<MudButton Variant="Variant.Filled" |
||||||
|
Color="Color.Primary" |
||||||
|
StartIcon="@Icons.Material.Filled.Add" |
||||||
|
OnClick="@(_ => AddSelectionToCollection())"> |
||||||
|
Add To Collection |
||||||
|
</MudButton> |
||||||
|
<MudButton Class="ml-3" |
||||||
|
Variant="Variant.Filled" |
||||||
|
Color="Color.Secondary" |
||||||
|
StartIcon="@Icons.Material.Filled.Check" |
||||||
|
OnClick="@(_ => ClearSelection())"> |
||||||
|
Clear Selection |
||||||
|
</MudButton> |
||||||
|
</div> |
||||||
|
} |
||||||
|
else |
||||||
|
{ |
||||||
|
<MudText Style="margin-bottom: auto; margin-top: auto; width: 33%">@_query</MudText> |
||||||
|
<div style="max-width: 300px; width: 33%;"> |
||||||
|
<MudPaper Style="align-items: center; display: flex; justify-content: center;"> |
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.ChevronLeft" |
||||||
|
OnClick="@PrevPage" |
||||||
|
Disabled="@(PageNumber <= 1)"> |
||||||
|
</MudIconButton> |
||||||
|
<MudText Style="flex-grow: 1" |
||||||
|
Align="Align.Center"> |
||||||
|
@Math.Min((PageNumber - 1) * PageSize + 1, _data.Count)-@Math.Min(_data.Count, PageNumber * PageSize) of @_data.Count |
||||||
|
</MudText> |
||||||
|
<MudIconButton Icon="@Icons.Material.Outlined.ChevronRight" |
||||||
|
OnClick="@NextPage" Disabled="@(PageNumber * PageSize >= _data.Count)"> |
||||||
|
</MudIconButton> |
||||||
|
</MudPaper> |
||||||
|
</div> |
||||||
|
} |
||||||
|
</div> |
||||||
|
</MudPaper> |
||||||
|
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px"> |
||||||
|
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid"> |
||||||
|
<FragmentLetterAnchor TCard="TelevisionSeasonCardViewModel" Cards="@_data.Cards"> |
||||||
|
<MediaCard Data="@context" |
||||||
|
Link="@($"/media/tv/seasons/{context.TelevisionSeasonId}")" |
||||||
|
AddToCollectionClicked="@AddToCollection" |
||||||
|
SelectClicked="@(e => SelectClicked(context, e))" |
||||||
|
IsSelected="@IsSelected(context)" |
||||||
|
IsSelectMode="@IsSelectMode()"/> |
||||||
|
</FragmentLetterAnchor> |
||||||
|
</MudContainer> |
||||||
|
</MudContainer> |
||||||
|
@if (_data.PageMap.IsSome) |
||||||
|
{ |
||||||
|
<LetterBar PageMap="@_data.PageMap.ValueUnsafe()" |
||||||
|
BaseUri="/media/tv/seasons" |
||||||
|
Query="@_query"/> |
||||||
|
} |
||||||
|
|
||||||
|
@code { |
||||||
|
private static int PageSize => 100; |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int PageNumber { get; set; } |
||||||
|
|
||||||
|
private TelevisionSeasonCardResultsViewModel _data; |
||||||
|
private string _query; |
||||||
|
|
||||||
|
protected override Task OnParametersSetAsync() |
||||||
|
{ |
||||||
|
if (PageNumber == 0) |
||||||
|
{ |
||||||
|
PageNumber = 1; |
||||||
|
} |
||||||
|
|
||||||
|
_query = _navigationManager.Uri.GetSearchQuery(); |
||||||
|
return RefreshData(); |
||||||
|
} |
||||||
|
|
||||||
|
protected override async Task RefreshData() |
||||||
|
{ |
||||||
|
string searchQuery = string.IsNullOrWhiteSpace(_query) ? "type:season" : $"type:season AND ({_query})"; |
||||||
|
_data = await Mediator.Send(new QuerySearchIndexSeasons(searchQuery, PageNumber, PageSize)); |
||||||
|
} |
||||||
|
|
||||||
|
private void PrevPage() |
||||||
|
{ |
||||||
|
var uri = $"/media/tv/seasons/page/{PageNumber - 1}"; |
||||||
|
if (!string.IsNullOrWhiteSpace(_query)) |
||||||
|
{ |
||||||
|
(string key, string value) = _query.EncodeQuery(); |
||||||
|
uri = $"{uri}?{key}={value}"; |
||||||
|
} |
||||||
|
_navigationManager.NavigateTo(uri); |
||||||
|
} |
||||||
|
|
||||||
|
private void NextPage() |
||||||
|
{ |
||||||
|
var uri = $"/media/tv/seasons/page/{PageNumber + 1}"; |
||||||
|
if (!string.IsNullOrWhiteSpace(_query)) |
||||||
|
{ |
||||||
|
(string key, string value) = _query.EncodeQuery(); |
||||||
|
uri = $"{uri}?{key}={value}"; |
||||||
|
} |
||||||
|
_navigationManager.NavigateTo(uri); |
||||||
|
} |
||||||
|
|
||||||
|
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e) |
||||||
|
{ |
||||||
|
List<MediaCardViewModel> GetSortedItems() |
||||||
|
{ |
||||||
|
return _data.Cards.OrderBy(m => m.SortTitle).ToList<MediaCardViewModel>(); |
||||||
|
} |
||||||
|
|
||||||
|
SelectClicked(GetSortedItems, card, e); |
||||||
|
} |
||||||
|
|
||||||
|
private async Task AddToCollection(MediaCardViewModel card) |
||||||
|
{ |
||||||
|
if (card is TelevisionSeasonCardViewModel season) |
||||||
|
{ |
||||||
|
var parameters = new DialogParameters { { "EntityType", "season" }, { "EntityName", season.Title } }; |
||||||
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
||||||
|
|
||||||
|
IDialogReference dialog = Dialog.Show<AddToCollectionDialog>("Add To Collection", parameters, options); |
||||||
|
DialogResult result = await dialog.Result; |
||||||
|
if (!result.Cancelled && result.Data is MediaCollectionViewModel collection) |
||||||
|
{ |
||||||
|
var request = new AddSeasonToCollection(collection.Id, season.TelevisionSeasonId); |
||||||
|
Either<BaseError, Unit> addResult = await Mediator.Send(request); |
||||||
|
addResult.Match( |
||||||
|
Left: error => |
||||||
|
{ |
||||||
|
Snackbar.Add($"Unexpected error adding season to collection: {error.Value}"); |
||||||
|
Logger.LogError("Unexpected error adding season to collection: {Error}", error.Value); |
||||||
|
}, |
||||||
|
Right: _ => Snackbar.Add($"Added {season.Title} to collection {collection.Name}", Severity.Success)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue