mirror of https://github.com/ErsatzTV/ErsatzTV.git
23 changed files with 401 additions and 66 deletions
@ -0,0 +1,7 @@ |
|||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaCards.Queries |
||||||
|
{ |
||||||
|
public record GetTelevisionEpisodeCards |
||||||
|
(int TelevisionSeasonId, int PageNumber, int PageSize) : IRequest<TelevisionEpisodeCardResultsViewModel>; |
||||||
|
} |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
using static ErsatzTV.Application.MediaCards.Mapper; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaCards.Queries |
||||||
|
{ |
||||||
|
public class |
||||||
|
GetTelevisionEpisodeCardsHandler : IRequestHandler<GetTelevisionEpisodeCards, |
||||||
|
TelevisionEpisodeCardResultsViewModel> |
||||||
|
{ |
||||||
|
private readonly ITelevisionRepository _televisionRepository; |
||||||
|
|
||||||
|
public GetTelevisionEpisodeCardsHandler(ITelevisionRepository televisionRepository) => |
||||||
|
_televisionRepository = televisionRepository; |
||||||
|
|
||||||
|
public async Task<TelevisionEpisodeCardResultsViewModel> Handle( |
||||||
|
GetTelevisionEpisodeCards request, |
||||||
|
CancellationToken cancellationToken) |
||||||
|
{ |
||||||
|
int count = await _televisionRepository.GetEpisodeCount(request.TelevisionSeasonId); |
||||||
|
|
||||||
|
List<TelevisionEpisodeCardViewModel> results = await _televisionRepository |
||||||
|
.GetPagedEpisodes(request.TelevisionSeasonId, request.PageNumber, request.PageSize) |
||||||
|
.Map(list => list.Map(ProjectToViewModel).ToList()); |
||||||
|
|
||||||
|
return new TelevisionEpisodeCardResultsViewModel(count, results); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
using System.Collections.Generic; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaCards |
||||||
|
{ |
||||||
|
public record TelevisionEpisodeCardResultsViewModel(int Count, List<TelevisionEpisodeCardViewModel> Cards); |
||||||
|
} |
||||||
@ -1,11 +1,17 @@ |
|||||||
namespace ErsatzTV.Application.MediaCards |
namespace ErsatzTV.Application.MediaCards |
||||||
{ |
{ |
||||||
public record TelevisionEpisodeCardViewModel |
public record TelevisionEpisodeCardViewModel |
||||||
(string Title, string Subtitle, string SortTitle, string Poster) : MediaCardViewModel( |
( |
||||||
Title, |
int EpisodeId, |
||||||
Subtitle, |
string Title, |
||||||
SortTitle, |
string Subtitle, |
||||||
Poster) |
string SortTitle, |
||||||
|
string Poster, |
||||||
|
string Placeholder) : MediaCardViewModel( |
||||||
|
Title, |
||||||
|
Subtitle, |
||||||
|
SortTitle, |
||||||
|
Poster) |
||||||
{ |
{ |
||||||
} |
} |
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,8 @@ |
|||||||
|
using ErsatzTV.Core; |
||||||
|
using LanguageExt; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaCollections.Commands |
||||||
|
{ |
||||||
|
public record AddTelevisionSeasonToSimpleMediaCollection |
||||||
|
(int MediaCollectionId, int TelevisionSeasonId) : MediatR.IRequest<Either<BaseError, Unit>>; |
||||||
|
} |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
using System.Linq; |
||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core; |
||||||
|
using ErsatzTV.Core.Domain; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.MediaCollections.Commands |
||||||
|
{ |
||||||
|
public class |
||||||
|
AddTelevisionSeasonToSimpleMediaCollectionHandler : MediatR.IRequestHandler< |
||||||
|
AddTelevisionSeasonToSimpleMediaCollection, |
||||||
|
Either<BaseError, Unit>> |
||||||
|
{ |
||||||
|
private readonly IMediaCollectionRepository _mediaCollectionRepository; |
||||||
|
private readonly ITelevisionRepository _televisionRepository; |
||||||
|
|
||||||
|
public AddTelevisionSeasonToSimpleMediaCollectionHandler( |
||||||
|
IMediaCollectionRepository mediaCollectionRepository, |
||||||
|
ITelevisionRepository televisionRepository) |
||||||
|
{ |
||||||
|
_mediaCollectionRepository = mediaCollectionRepository; |
||||||
|
_televisionRepository = televisionRepository; |
||||||
|
} |
||||||
|
|
||||||
|
public Task<Either<BaseError, Unit>> Handle( |
||||||
|
AddTelevisionSeasonToSimpleMediaCollection request, |
||||||
|
CancellationToken cancellationToken) => |
||||||
|
Validate(request) |
||||||
|
.MapT(ApplyAddTelevisionSeasonRequest) |
||||||
|
.Bind(v => v.ToEitherAsync()); |
||||||
|
|
||||||
|
private async Task<Unit> ApplyAddTelevisionSeasonRequest(RequestParameters parameters) |
||||||
|
{ |
||||||
|
if (parameters.Collection.TelevisionSeasons.All(s => s.Id != parameters.SeasonToAdd.Id)) |
||||||
|
{ |
||||||
|
parameters.Collection.TelevisionSeasons.Add(parameters.SeasonToAdd); |
||||||
|
await _mediaCollectionRepository.Update(parameters.Collection); |
||||||
|
} |
||||||
|
|
||||||
|
return Unit.Default; |
||||||
|
} |
||||||
|
|
||||||
|
private async Task<Validation<BaseError, RequestParameters>> |
||||||
|
Validate(AddTelevisionSeasonToSimpleMediaCollection request) => |
||||||
|
(await SimpleMediaCollectionMustExist(request), await ValidateSeason(request)) |
||||||
|
.Apply( |
||||||
|
(simpleMediaCollectionToUpdate, season) => |
||||||
|
new RequestParameters(simpleMediaCollectionToUpdate, season)); |
||||||
|
|
||||||
|
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist( |
||||||
|
AddTelevisionSeasonToSimpleMediaCollection updateSimpleMediaCollection) => |
||||||
|
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId) |
||||||
|
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist.")); |
||||||
|
|
||||||
|
private Task<Validation<BaseError, TelevisionSeason>> ValidateSeason( |
||||||
|
AddTelevisionSeasonToSimpleMediaCollection request) => |
||||||
|
LoadTelevisionSeason(request) |
||||||
|
.Map(v => v.ToValidation<BaseError>("TelevisionSeason does not exist")); |
||||||
|
|
||||||
|
private Task<Option<TelevisionSeason>> LoadTelevisionSeason( |
||||||
|
AddTelevisionSeasonToSimpleMediaCollection request) => |
||||||
|
_televisionRepository.GetSeason(request.TelevisionSeasonId); |
||||||
|
|
||||||
|
private record RequestParameters(SimpleMediaCollection Collection, TelevisionSeason SeasonToAdd); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Television.Queries |
||||||
|
{ |
||||||
|
public record GetTelevisionSeasonById(int SeasonId) : IRequest<Option<TelevisionSeasonViewModel>>; |
||||||
|
} |
||||||
@ -0,0 +1,24 @@ |
|||||||
|
using System.Threading; |
||||||
|
using System.Threading.Tasks; |
||||||
|
using ErsatzTV.Core.Interfaces.Repositories; |
||||||
|
using LanguageExt; |
||||||
|
using MediatR; |
||||||
|
using static ErsatzTV.Application.Television.Mapper; |
||||||
|
|
||||||
|
namespace ErsatzTV.Application.Television.Queries |
||||||
|
{ |
||||||
|
public class |
||||||
|
GetTelevisionSeasonByIdHandler : IRequestHandler<GetTelevisionSeasonById, Option<TelevisionSeasonViewModel>> |
||||||
|
{ |
||||||
|
private readonly ITelevisionRepository _televisionRepository; |
||||||
|
|
||||||
|
public GetTelevisionSeasonByIdHandler(ITelevisionRepository televisionRepository) => |
||||||
|
_televisionRepository = televisionRepository; |
||||||
|
|
||||||
|
public Task<Option<TelevisionSeasonViewModel>> Handle( |
||||||
|
GetTelevisionSeasonById request, |
||||||
|
CancellationToken cancellationToken) => |
||||||
|
_televisionRepository.GetSeason(request.SeasonId) |
||||||
|
.MapT(ProjectToViewModel); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,4 @@ |
|||||||
|
namespace ErsatzTV.Application.Television |
||||||
|
{ |
||||||
|
public record TelevisionSeasonViewModel(int ShowId, string Title, string Year, string Plot, string Poster); |
||||||
|
} |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
@page "/media/tv/seasons/{SeasonId:int}" |
||||||
|
@using ErsatzTV.Application.Television |
||||||
|
@using ErsatzTV.Application.Television.Queries |
||||||
|
@using ErsatzTV.Application.MediaCards |
||||||
|
@using ErsatzTV.Application.MediaCards.Queries |
||||||
|
@using ErsatzTV.Application.MediaCollections |
||||||
|
@using ErsatzTV.Application.MediaCollections.Commands |
||||||
|
@inject IMediator Mediator |
||||||
|
@inject IDialogService Dialog |
||||||
|
@inject NavigationManager NavigationManager |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large"> |
||||||
|
<MudBreadcrumbs Items="_breadcrumbs" Class="mb-6"></MudBreadcrumbs> |
||||||
|
<MudCard Class="mb-6"> |
||||||
|
<div style="display: flex; flex-direction: row;"> |
||||||
|
<MudPaper Style="flex-shrink: 0;"> |
||||||
|
<MudCardMedia Image="@($"/posters/{_season.Poster}")" Style="height: 440px; width: 304px;"/> |
||||||
|
</MudPaper> |
||||||
|
<MudCardContent> |
||||||
|
<div style="display: flex; flex-direction: column; height: 100%"> |
||||||
|
<MudText Typo="Typo.h3">@_season.Title</MudText> |
||||||
|
<MudText Typo="Typo.subtitle1" Class="mb-6 mud-text-secondary">@_season.Year</MudText> |
||||||
|
<MudText Style="flex-grow: 1">@_season.Plot</MudText> |
||||||
|
<div> |
||||||
|
<MudButton Variant="Variant.Filled" |
||||||
|
Color="Color.Primary" |
||||||
|
StartIcon="@Icons.Material.Filled.Add" |
||||||
|
OnClick="@AddToCollection"> |
||||||
|
Add To Collection |
||||||
|
</MudButton> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</MudCardContent> |
||||||
|
</div> |
||||||
|
</MudCard> |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid"> |
||||||
|
@foreach (TelevisionEpisodeCardViewModel card in _data.Cards) |
||||||
|
{ |
||||||
|
<MediaCard Data="@card" |
||||||
|
Placeholder="@card.Placeholder" |
||||||
|
Link="@($"/media/tv/episodes/{card.EpisodeId}")" |
||||||
|
DataRefreshed="@(() => RefreshData())" |
||||||
|
ContainerClass="media-card-episode-container mx-2" |
||||||
|
CardClass="media-card-episode"/> |
||||||
|
} |
||||||
|
</MudContainer> |
||||||
|
|
||||||
|
@code { |
||||||
|
|
||||||
|
[Parameter] |
||||||
|
public int SeasonId { get; set; } |
||||||
|
|
||||||
|
private TelevisionSeasonViewModel _season; |
||||||
|
|
||||||
|
private int _pageSize => 100; |
||||||
|
private readonly int _pageNumber = 1; |
||||||
|
|
||||||
|
private TelevisionEpisodeCardResultsViewModel _data; |
||||||
|
|
||||||
|
private List<BreadcrumbItem> _breadcrumbs; |
||||||
|
|
||||||
|
protected override Task OnParametersSetAsync() => RefreshData(); |
||||||
|
|
||||||
|
private async Task RefreshData() |
||||||
|
{ |
||||||
|
await Mediator.Send(new GetTelevisionSeasonById(SeasonId)) |
||||||
|
.IfSomeAsync(vm => _season = vm); |
||||||
|
|
||||||
|
_data = await Mediator.Send(new GetTelevisionEpisodeCards(SeasonId, _pageNumber, _pageSize)); |
||||||
|
|
||||||
|
_breadcrumbs = new List<BreadcrumbItem> |
||||||
|
{ |
||||||
|
new("TV Shows", "/media/tv/shows"), |
||||||
|
new($"{_season.Title} ({_season.Year})", $"/media/tv/shows/{_season.ShowId}"), |
||||||
|
new(_season.Plot, null, true) |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private async Task AddToCollection() |
||||||
|
{ |
||||||
|
var parameters = new DialogParameters { { "EntityType", "season" }, { "EntityName", $"{_season.Title} - {_season.Plot}" } }; |
||||||
|
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) |
||||||
|
{ |
||||||
|
await Mediator.Send(new AddTelevisionSeasonToSimpleMediaCollection(collection.Id, SeasonId)); |
||||||
|
NavigationManager.NavigateTo($"/media/collections/{collection.Id}"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue