Browse Source

add television seasons to collections

pull/26/head
Jason Dove 6 years ago
parent
commit
3280b22299
  1. 10
      ErsatzTV.Application/MediaCards/Mapper.cs
  2. 7
      ErsatzTV.Application/MediaCards/Queries/GetTelevisionEpisodeCards.cs
  3. 34
      ErsatzTV.Application/MediaCards/Queries/GetTelevisionEpisodeCardsHandler.cs
  4. 6
      ErsatzTV.Application/MediaCards/TelevisionEpisodeCardResultsViewModel.cs
  5. 16
      ErsatzTV.Application/MediaCards/TelevisionEpisodeCardViewModel.cs
  6. 2
      ErsatzTV.Application/MediaCards/TelevisionSeasonCardViewModel.cs
  7. 8
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollection.cs
  8. 68
      ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollectionHandler.cs
  9. 8
      ErsatzTV.Application/Television/Mapper.cs
  10. 7
      ErsatzTV.Application/Television/Queries/GetTelevisionSeasonById.cs
  11. 24
      ErsatzTV.Application/Television/Queries/GetTelevisionSeasonByIdHandler.cs
  12. 4
      ErsatzTV.Application/Television/TelevisionSeasonViewModel.cs
  13. 3
      ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs
  14. 1
      ErsatzTV.Core/Metadata/LocalFolderScanner.cs
  15. 2
      ErsatzTV.Core/Metadata/LocalMetadataProvider.cs
  16. 18
      ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs
  17. 2
      ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs
  18. 68
      ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs
  19. 15
      ErsatzTV/Pages/MediaCollectionItems.razor
  20. 95
      ErsatzTV/Pages/TelevisionEpisodes.razor
  21. 21
      ErsatzTV/Pages/TelevisionSeasons.razor
  22. 34
      ErsatzTV/Shared/MediaCard.razor
  23. 14
      ErsatzTV/wwwroot/css/site.css

10
ErsatzTV.Application/MediaCards/Mapper.cs

@ -15,7 +15,9 @@ namespace ErsatzTV.Application.MediaCards @@ -15,7 +15,9 @@ namespace ErsatzTV.Application.MediaCards
internal static TelevisionSeasonCardViewModel ProjectToViewModel(TelevisionSeason televisionSeason) =>
new(
televisionSeason.TelevisionShow.Metadata.Title,
televisionSeason.Id,
televisionSeason.Number,
GetSeasonName(televisionSeason.Number),
string.Empty,
GetSeasonName(televisionSeason.Number),
@ -25,10 +27,12 @@ namespace ErsatzTV.Application.MediaCards @@ -25,10 +27,12 @@ namespace ErsatzTV.Application.MediaCards
internal static TelevisionEpisodeCardViewModel ProjectToViewModel(
TelevisionEpisodeMediaItem televisionEpisode) =>
new(
televisionEpisode.Id,
televisionEpisode.Metadata.Title,
string.Empty,
televisionEpisode.Metadata.SortTitle,
televisionEpisode.Poster);
$"Episode {televisionEpisode.Metadata.Episode}",
televisionEpisode.Metadata.Episode.ToString(),
televisionEpisode.Poster,
televisionEpisode.Metadata.Episode.ToString());
internal static MovieCardViewModel ProjectToViewModel(MovieMediaItem movie) =>
new(

7
ErsatzTV.Application/MediaCards/Queries/GetTelevisionEpisodeCards.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using MediatR;
namespace ErsatzTV.Application.MediaCards.Queries
{
public record GetTelevisionEpisodeCards
(int TelevisionSeasonId, int PageNumber, int PageSize) : IRequest<TelevisionEpisodeCardResultsViewModel>;
}

34
ErsatzTV.Application/MediaCards/Queries/GetTelevisionEpisodeCardsHandler.cs

@ -0,0 +1,34 @@ @@ -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);
}
}
}

6
ErsatzTV.Application/MediaCards/TelevisionEpisodeCardResultsViewModel.cs

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
using System.Collections.Generic;
namespace ErsatzTV.Application.MediaCards
{
public record TelevisionEpisodeCardResultsViewModel(int Count, List<TelevisionEpisodeCardViewModel> Cards);
}

16
ErsatzTV.Application/MediaCards/TelevisionEpisodeCardViewModel.cs

@ -1,11 +1,17 @@ @@ -1,11 +1,17 @@
namespace ErsatzTV.Application.MediaCards
{
public record TelevisionEpisodeCardViewModel
(string Title, string Subtitle, string SortTitle, string Poster) : MediaCardViewModel(
Title,
Subtitle,
SortTitle,
Poster)
(
int EpisodeId,
string Title,
string Subtitle,
string SortTitle,
string Poster,
string Placeholder) : MediaCardViewModel(
Title,
Subtitle,
SortTitle,
Poster)
{
}
}

2
ErsatzTV.Application/MediaCards/TelevisionSeasonCardViewModel.cs

@ -2,7 +2,9 @@ @@ -2,7 +2,9 @@
{
public record TelevisionSeasonCardViewModel
(
string ShowTitle,
int TelevisionSeasonId,
int TelevisionSeasonNumber,
string Title,
string Subtitle,
string SortTitle,

8
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollection.cs

@ -0,0 +1,8 @@ @@ -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>>;
}

68
ErsatzTV.Application/MediaCollections/Commands/AddTelevisionSeasonToSimpleMediaCollectionHandler.cs

@ -0,0 +1,68 @@ @@ -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);
}
}

8
ErsatzTV.Application/Television/Mapper.cs

@ -6,5 +6,13 @@ namespace ErsatzTV.Application.Television @@ -6,5 +6,13 @@ namespace ErsatzTV.Application.Television
{
internal static TelevisionShowViewModel ProjectToViewModel(TelevisionShow show) =>
new(show.Metadata.Title, show.Metadata.Year?.ToString(), show.Metadata.Plot, show.Poster);
internal static TelevisionSeasonViewModel ProjectToViewModel(TelevisionSeason season) =>
new(
season.TelevisionShowId,
season.TelevisionShow.Metadata.Title,
season.TelevisionShow.Metadata.Year?.ToString(),
$"Season {season.Number}",
season.Poster);
}
}

7
ErsatzTV.Application/Television/Queries/GetTelevisionSeasonById.cs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
using LanguageExt;
using MediatR;
namespace ErsatzTV.Application.Television.Queries
{
public record GetTelevisionSeasonById(int SeasonId) : IRequest<Option<TelevisionSeasonViewModel>>;
}

24
ErsatzTV.Application/Television/Queries/GetTelevisionSeasonByIdHandler.cs

@ -0,0 +1,24 @@ @@ -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);
}
}

4
ErsatzTV.Application/Television/TelevisionSeasonViewModel.cs

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
namespace ErsatzTV.Application.Television
{
public record TelevisionSeasonViewModel(int ShowId, string Title, string Year, string Plot, string Poster);
}

3
ErsatzTV.Core/Interfaces/Repositories/ITelevisionRepository.cs

@ -13,8 +13,11 @@ namespace ErsatzTV.Core.Interfaces.Repositories @@ -13,8 +13,11 @@ namespace ErsatzTV.Core.Interfaces.Repositories
Task<Option<TelevisionShow>> GetShow(int televisionShowId);
Task<int> GetShowCount();
Task<List<TelevisionShow>> GetPagedShows(int pageNumber, int pageSize);
Task<Option<TelevisionSeason>> GetSeason(int televisionSeasonId);
Task<int> GetSeasonCount(int televisionShowId);
Task<List<TelevisionSeason>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize);
Task<int> GetEpisodeCount(int televisionSeasonId);
Task<List<TelevisionEpisodeMediaItem>> GetPagedEpisodes(int televisionSeasonId, int pageNumber, int pageSize);
Task<Option<TelevisionShow>> GetShowByPath(int mediaSourceId, string path);
Task<Option<TelevisionShow>> GetShowByMetadata(TelevisionShowMetadata metadata);

1
ErsatzTV.Core/Metadata/LocalFolderScanner.cs

@ -90,6 +90,7 @@ namespace ErsatzTV.Core.Metadata @@ -90,6 +90,7 @@ namespace ErsatzTV.Core.Metadata
hash =>
{
show.Poster = hash;
show.PosterLastWriteTime = _localFileSystem.GetLastWriteTime(posterPath);
return update(show);
},
error =>

2
ErsatzTV.Core/Metadata/LocalMetadataProvider.cs

@ -220,7 +220,7 @@ namespace ErsatzTV.Core.Metadata @@ -220,7 +220,7 @@ namespace ErsatzTV.Core.Metadata
{
Source = MetadataSource.Sidecar,
LastWriteTime = File.GetLastWriteTimeUtc(nfoFileName),
Title = nfo.ShowTitle,
Title = nfo.Title,
Aired = GetAired(nfo.Aired),
Episode = nfo.Episode,
Season = nfo.Season,

18
ErsatzTV.Core/Metadata/TelevisionFolderScanner.cs

@ -158,7 +158,8 @@ namespace ErsatzTV.Core.Metadata @@ -158,7 +158,8 @@ namespace ErsatzTV.Core.Metadata
async nfoFile =>
{
if (show.Metadata == null || show.Metadata.Source == MetadataSource.Fallback ||
show.Metadata.LastWriteTime < _localFileSystem.GetLastWriteTime(nfoFile))
(show.Metadata.LastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(nfoFile))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
await _localMetadataProvider.RefreshSidecarMetadata(show, nfoFile);
@ -190,7 +191,8 @@ namespace ErsatzTV.Core.Metadata @@ -190,7 +191,8 @@ namespace ErsatzTV.Core.Metadata
async nfoFile =>
{
if (episode.Metadata == null || episode.Metadata.Source == MetadataSource.Fallback ||
episode.Metadata.LastWriteTime < _localFileSystem.GetLastWriteTime(nfoFile))
(episode.Metadata.LastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(nfoFile))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Sidecar Metadata", nfoFile);
await _localMetadataProvider.RefreshSidecarMetadata(episode, nfoFile);
@ -223,7 +225,8 @@ namespace ErsatzTV.Core.Metadata @@ -223,7 +225,8 @@ namespace ErsatzTV.Core.Metadata
async posterFile =>
{
if (string.IsNullOrWhiteSpace(show.Poster) ||
show.PosterLastWriteTime < _localFileSystem.GetLastWriteTime(posterFile))
(show.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Poster", posterFile);
Either<BaseError, string> maybePoster = await SavePosterToDisk(posterFile, 440);
@ -231,6 +234,7 @@ namespace ErsatzTV.Core.Metadata @@ -231,6 +234,7 @@ namespace ErsatzTV.Core.Metadata
poster =>
{
show.Poster = poster;
show.PosterLastWriteTime = _localFileSystem.GetLastWriteTime(posterFile);
return _televisionRepository.Update(show);
},
error =>
@ -260,10 +264,11 @@ namespace ErsatzTV.Core.Metadata @@ -260,10 +264,11 @@ namespace ErsatzTV.Core.Metadata
async posterFile =>
{
if (string.IsNullOrWhiteSpace(season.Poster) ||
season.PosterLastWriteTime < _localFileSystem.GetLastWriteTime(posterFile))
(season.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Poster", posterFile);
await SavePosterToDisk(season, posterFile, _televisionRepository.Update);
await SavePosterToDisk(season, posterFile, _televisionRepository.Update, 440);
}
});
@ -284,7 +289,8 @@ namespace ErsatzTV.Core.Metadata @@ -284,7 +289,8 @@ namespace ErsatzTV.Core.Metadata
async posterFile =>
{
if (string.IsNullOrWhiteSpace(episode.Poster) ||
episode.PosterLastWriteTime < _localFileSystem.GetLastWriteTime(posterFile))
(episode.PosterLastWriteTime ?? DateTime.MinValue) <
_localFileSystem.GetLastWriteTime(posterFile))
{
_logger.LogDebug("Refreshing {Attribute} from {Path}", "Thumbnail", posterFile);
await SavePosterToDisk(episode, posterFile, _televisionRepository.Update);

2
ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs

@ -51,6 +51,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -51,6 +51,8 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Include(s => s.TelevisionShows)
.ThenInclude(s => s.Metadata)
.Include(s => s.TelevisionSeasons)
.ThenInclude(s => s.TelevisionShow)
.ThenInclude(s => s.Metadata)
.Include(s => s.TelevisionEpisodes)
.ThenInclude(s => s.Metadata)
.SingleOrDefaultAsync(c => c.Id == id)

68
ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs

@ -46,7 +46,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -46,7 +46,6 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
public Task<int> GetShowCount() =>
_dbContext.TelevisionShows
.AsNoTracking()
.GroupBy(s => new { s.Metadata.Title, s.Metadata.Year })
.CountAsync();
public Task<List<TelevisionShow>> GetPagedShows(int pageNumber, int pageSize) =>
@ -58,42 +57,49 @@ namespace ErsatzTV.Infrastructure.Data.Repositories @@ -58,42 +57,49 @@ namespace ErsatzTV.Infrastructure.Data.Repositories
.Take(pageSize)
.ToListAsync();
public async Task<int> GetSeasonCount(int televisionShowId)
{
Option<TelevisionShowMetadata> metadata = await _dbContext.TelevisionShowMetadata
public Task<Option<TelevisionSeason>> GetSeason(int televisionSeasonId) =>
_dbContext.TelevisionSeasons
.AsNoTracking()
.Where(m => m.TelevisionShowId == televisionShowId)
.SingleOrDefaultAsync()
.Include(s => s.TelevisionShow)
.ThenInclude(s => s.Metadata)
.SingleOrDefaultAsync(s => s.Id == televisionSeasonId)
.Map(Optional);
return await metadata.Match(
m => _dbContext.TelevisionShows
.AsNoTracking()
.Where(s => s.Metadata.Title == m.Title && s.Metadata.Year == m.Year)
.SelectMany(s => s.Seasons)
.CountAsync(),
Task.FromResult(0));
}
public Task<int> GetSeasonCount(int televisionShowId) =>
_dbContext.TelevisionSeasons
.AsNoTracking()
.Where(s => s.TelevisionShowId == televisionShowId)
.CountAsync();
public async Task<List<TelevisionSeason>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize)
{
Option<TelevisionShowMetadata> metadata = await _dbContext.TelevisionShowMetadata
public Task<List<TelevisionSeason>> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize) =>
_dbContext.TelevisionSeasons
.AsNoTracking()
.Where(m => m.TelevisionShowId == televisionShowId)
.SingleOrDefaultAsync()
.Map(Optional);
.Where(s => s.TelevisionShowId == televisionShowId)
.Include(s => s.TelevisionShow)
.ThenInclude(s => s.Metadata)
.OrderBy(s => s.Number)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
return await metadata.Match(
m => _dbContext.TelevisionShows
.AsNoTracking()
.Where(s => s.Metadata.Title == m.Title && s.Metadata.Year == m.Year)
.SelectMany(s => s.Seasons)
.OrderBy(s => s.Number)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync(),
Task.FromResult(new List<TelevisionSeason>()));
}
public Task<int> GetEpisodeCount(int televisionSeasonId) =>
_dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Where(e => e.SeasonId == televisionSeasonId)
.CountAsync();
public Task<List<TelevisionEpisodeMediaItem>> GetPagedEpisodes(
int televisionSeasonId,
int pageNumber,
int pageSize) =>
_dbContext.TelevisionEpisodeMediaItems
.AsNoTracking()
.Include(e => e.Metadata)
.Where(e => e.SeasonId == televisionSeasonId)
.OrderBy(s => s.Metadata.Episode)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
public async Task<Option<TelevisionShow>> GetShowByPath(int mediaSourceId, string path)
{

15
ErsatzTV/Pages/MediaCollectionItems.razor

@ -37,6 +37,21 @@ @@ -37,6 +37,21 @@
</MudContainer>
}
@if (_data.SeasonCards.Any())
{
<MudText GutterBottom="true" Typo="Typo.h4">Television Seasons</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards)
{
<MediaCard Data="@card"
Link="@($"/media/tv/Seasons/{card.TelevisionSeasonId}")"
Title="@card.ShowTitle"
Subtitle="@card.Title"/>
}
</MudContainer>
}
@* <MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid"> *@
@* @foreach (var card in _data.Cards) *@
@* { *@

95
ErsatzTV/Pages/TelevisionEpisodes.razor

@ -0,0 +1,95 @@ @@ -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}");
}
}
}

21
ErsatzTV/Pages/TelevisionSeasons.razor

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
@page "/media/tv/shows/{ShowId:int}"
@using static LanguageExt.Prelude
@using ErsatzTV.Application.Television
@using ErsatzTV.Application.Television.Queries
@using ErsatzTV.Application.MediaCards
@ -11,6 +10,7 @@ @@ -11,6 +10,7 @@
@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;">
@ -28,9 +28,6 @@ @@ -28,9 +28,6 @@
OnClick="@AddToCollection">
Add To Collection
</MudButton>
@* <MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" Disabled="true"> *@
@* Add To Schedule *@
@* </MudButton> *@
</div>
</div>
</MudCardContent>
@ -41,7 +38,9 @@ @@ -41,7 +38,9 @@
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
@foreach (TelevisionSeasonCardViewModel card in _data.Cards)
{
<MediaCard Data="@card" CustomPlaceholder="@SeasonPlaceholder" Link="@($"/media/tv/shows/{ShowId}/seasons/{card.TelevisionSeasonId}")" DataRefreshed="@(() => RefreshData())"/>
<MediaCard Data="@card" Placeholder="@card.Placeholder"
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
DataRefreshed="@(() => RefreshData())"/>
}
</MudContainer>
@ -57,6 +56,8 @@ @@ -57,6 +56,8 @@
private TelevisionSeasonCardResultsViewModel _data;
private List<BreadcrumbItem> _breadcrumbs;
protected override Task OnParametersSetAsync() => RefreshData();
private async Task RefreshData()
@ -65,11 +66,13 @@ @@ -65,11 +66,13 @@
.IfSomeAsync(vm => _show = vm);
_data = await Mediator.Send(new GetTelevisionSeasonCards(ShowId, _pageNumber, _pageSize));
}
private string SeasonPlaceholder(string sortTitle) =>
Optional(_data.Cards.SingleOrDefault(d => d.SortTitle == sortTitle))
.Match(vm => vm.Placeholder, () => sortTitle.Substring(0, 1).ToUpperInvariant());
_breadcrumbs = new List<BreadcrumbItem>
{
new("TV Shows", "/media/tv/shows"),
new($"{_show.Title} ({_show.Year})", null, true)
};
}
private async Task AddToCollection()
{

34
ErsatzTV/Shared/MediaCard.razor

@ -2,15 +2,15 @@ @@ -2,15 +2,15 @@
@using Unit = LanguageExt.Unit
@inject IMediator Mediator
<div class="media-card-container mx-3 pb-3">
<div class="@((ContainerClass ?? "media-card-container mr-6") + " pb-3")">
@if (!string.IsNullOrWhiteSpace(Link))
{
<MudPaper>
<MudButton Link="@Link" Class="media-card" Style="@PosterForItem()">
<MudButton Link="@Link" Class="@($"media-card {CardClass}")" Style="@PosterForItem()">
@if (string.IsNullOrWhiteSpace(Data.Poster))
{
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-disabled">
@Placeholder(Data.SortTitle)
@GetPlaceholder(Data.SortTitle)
</MudText>
}
</MudButton>
@ -18,20 +18,20 @@ @@ -18,20 +18,20 @@
}
else
{
<MudPaper Class="media-card" Style="@PosterForItem()">
<MudPaper Class="@($"media-card {CardClass}")" Style="@PosterForItem()">
@if (string.IsNullOrWhiteSpace(Data.Poster))
{
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-disabled">
@Placeholder(Data.SortTitle)
@GetPlaceholder(Data.SortTitle)
</MudText>
}
</MudPaper>
}
<MudText Align="Align.Center" Class="media-card-title" UserAttributes="@(new Dictionary<string, object> { { "title", Data.Title } })">
@Data.Title
@(Title ?? Data.Title)
</MudText>
<MudText Typo="Typo.body2" Align="Align.Center" Class="mud-text-secondary">
@Data.Subtitle
@(Subtitle ?? Data.Subtitle)
</MudText>
</div>
@ -47,13 +47,25 @@ @@ -47,13 +47,25 @@
public EventCallback<Unit> DataRefreshed { get; set; }
[Parameter]
public Func<string, string> CustomPlaceholder { get; set; }
public string Placeholder { get; set; }
private string Placeholder(string sortTitle)
[Parameter]
public string Title { get; set; }
[Parameter]
public string Subtitle { get; set; }
[Parameter]
public string ContainerClass { get; set; }
[Parameter]
public string CardClass { get; set; }
private string GetPlaceholder(string sortTitle)
{
if (CustomPlaceholder != null)
if (Placeholder != null)
{
return CustomPlaceholder(sortTitle);
return Placeholder;
}
string first = sortTitle.Substring(0, 1).ToUpperInvariant();

14
ErsatzTV/wwwroot/css/site.css

@ -6,6 +6,18 @@ @@ -6,6 +6,18 @@
.media-card-container { width: 152px; }
.media-card-episode-container { width: 291px; }
.media-card {
display: flex;
filter: brightness(100%);
flex-direction: column;
height: 220px;
justify-content: center;
transition: all 0.2s ease;
width: 152px;
}
.media-card {
display: flex;
filter: brightness(100%);
@ -16,6 +28,8 @@ @@ -16,6 +28,8 @@
width: 152px;
}
.media-card-episode { width: 291px; }
.media-card:hover { filter: brightness(75%); }
.media-card-title {

Loading…
Cancel
Save