mirror of https://github.com/ErsatzTV/ErsatzTV.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
288 lines
14 KiB
288 lines
14 KiB
@page "/media/tv/shows/{ShowId:int}" |
|
@using System.Globalization |
|
@using ErsatzTV.Application.MediaCards |
|
@using ErsatzTV.Application.MediaCollections |
|
@using ErsatzTV.Application.ProgramSchedules |
|
@using ErsatzTV.Application.Television |
|
@using ErsatzTV.Extensions |
|
@implements IDisposable |
|
@inject IMediator Mediator |
|
@inject ILogger<TelevisionSeasonList> Logger |
|
@inject ISnackbar Snackbar |
|
@inject IDialogService Dialog |
|
@inject NavigationManager NavigationManager |
|
|
|
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container"> |
|
<div class="fanart-tint"></div> |
|
@if (!string.IsNullOrWhiteSpace(_show?.FanArt)) |
|
{ |
|
if (_show.FanArt.StartsWith("http://") || _show.FanArt.StartsWith("https://")) |
|
{ |
|
<img src="@_show.FanArt" alt="fan art"/> |
|
} |
|
else |
|
{ |
|
<img src="@($"artwork/fanart/{_show.FanArt}")" alt="fan art"/> |
|
} |
|
} |
|
</MudContainer> |
|
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 100px"> |
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Spacing="6"> |
|
<MudImage Src="@GetPosterUrl(_show.Poster)" Elevation="2" Class="rounded-lg" Style="max-height: 325px; margin-left: auto; margin-right: auto" /> |
|
<MudStack Row="false" Style="flex: 1"> |
|
<MudStack Row="false"> |
|
<MudHidden Invert="true" Breakpoint="Breakpoint.SmAndDown"> |
|
<MudText Typo="Typo.h4" Class="media-item-title">@_show?.Title</MudText> |
|
</MudHidden> |
|
<MudHidden Invert="true" Breakpoint="Breakpoint.MdAndUp"> |
|
<MudText Typo="Typo.h2" Class="media-item-title">@_show?.Title</MudText> |
|
</MudHidden> |
|
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_show?.Year</MudText> |
|
</MudStack> |
|
@if (!string.IsNullOrWhiteSpace(_show?.Plot)) |
|
{ |
|
<MudCard Elevation="2" Class="mb-6"> |
|
<MudCardContent Class="mx-3 my-3" Style="height: 100%"> |
|
<MudText Style="flex-grow: 1">@_show.Plot</MudText> |
|
</MudCardContent> |
|
</MudCard> |
|
} |
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Class="mb-6"> |
|
<MudButton Variant="Variant.Filled" |
|
Color="Color.Primary" |
|
StartIcon="@Icons.Material.Filled.Add" |
|
OnClick="@AddToCollection"> |
|
Add To Collection |
|
</MudButton> |
|
<MudButton Variant="Variant.Filled" |
|
Color="Color.Primary" |
|
StartIcon="@Icons.Material.Filled.PlaylistAdd" |
|
OnClick="@AddToPlaylist"> |
|
Add To Playlist |
|
</MudButton> |
|
<MudButton Variant="Variant.Filled" |
|
Color="Color.Primary" |
|
StartIcon="@Icons.Material.Filled.Schedule" |
|
OnClick="@AddToSchedule"> |
|
Add To Schedule |
|
</MudButton> |
|
</MudStack> |
|
</MudStack> |
|
</MudStack> |
|
<MudCard Class="mt-6 mb-6"> |
|
<MudCardContent> |
|
@if (_sortedContentRatings.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Content Ratings: </MudText> |
|
<MudLink Href="@(@$"content_rating:""{_sortedContentRatings.Head()}""".GetRelativeSearchQuery())">@_sortedContentRatings.Head()</MudLink> |
|
@foreach (string contentRating in _sortedContentRatings.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"content_rating:""{contentRating}""".GetRelativeSearchQuery())">@contentRating</MudLink> |
|
} |
|
</div> |
|
} |
|
@if (_sortedLanguages.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Languages: </MudText> |
|
<MudLink Href="@(@$"language:""{_sortedLanguages.Head().EnglishName.ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedLanguages.Head().EnglishName</MudLink> |
|
@foreach (CultureInfo language in _sortedLanguages.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"language:""{language.EnglishName.ToLowerInvariant()}""".GetRelativeSearchQuery())">@language.EnglishName</MudLink> |
|
} |
|
</div> |
|
} |
|
@if (_sortedStudios.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Studios: </MudText> |
|
<MudLink Href="@(@$"studio:""{_sortedStudios.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedStudios.Head()</MudLink> |
|
@foreach (string studio in _sortedStudios.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"studio:""{studio.ToLowerInvariant()}""".GetRelativeSearchQuery())">@studio</MudLink> |
|
} |
|
</div> |
|
} |
|
@if (_sortedNetworks.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Networks: </MudText> |
|
<MudLink Href="@(@$"network:""{_sortedNetworks.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedNetworks.Head()</MudLink> |
|
@foreach (string network in _sortedNetworks.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"network:""{network.ToLowerInvariant()}""".GetRelativeSearchQuery())">@network</MudLink> |
|
} |
|
</div> |
|
} |
|
@if (_sortedGenres.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Genres: </MudText> |
|
<MudLink Href="@(@$"genre:""{_sortedGenres.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedGenres.Head()</MudLink> |
|
@foreach (string genre in _sortedGenres.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"genre:""{genre.ToLowerInvariant()}""".GetRelativeSearchQuery())">@genre</MudLink> |
|
} |
|
</div> |
|
} |
|
@if (_sortedTags.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Tags: </MudText> |
|
<MudLink Href="@(@$"tag:""{_sortedTags.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedTags.Head()</MudLink> |
|
@foreach (string tag in _sortedTags.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"tag:""{tag.ToLowerInvariant()}""".GetRelativeSearchQuery())">@tag</MudLink> |
|
} |
|
</div> |
|
} |
|
</MudCardContent> |
|
</MudCard> |
|
</MudContainer> |
|
<MudContainer MaxWidth="MaxWidth.Large"> |
|
<MudText Class="mb-4">Seasons</MudText> |
|
<MudStack Row="true" Wrap="Wrap.Wrap"> |
|
@foreach (TelevisionSeasonCardViewModel card in _data.Cards) |
|
{ |
|
<MediaCard Data="@card" Placeholder="@card.Placeholder" |
|
Href="@($"media/tv/seasons/{card.TelevisionSeasonId}")" |
|
AddToCollectionClicked="@AddSeasonToCollection"/> |
|
} |
|
</MudStack> |
|
@if ((_show?.Actors?.Count ?? 0) > 0) |
|
{ |
|
<MudText Class="mb-4">Actors</MudText> |
|
<MudStack Row="true" Wrap="Wrap.Wrap"> |
|
@foreach (ActorCardViewModel actor in _show.Actors) |
|
{ |
|
<MediaCard Data="@actor" |
|
Href="@(@$"actor:""{actor.Name.ToLowerInvariant()}""".GetRelativeSearchQuery())" |
|
ArtworkKind="ArtworkKind.Thumbnail"/> |
|
} |
|
</MudStack> |
|
} |
|
</MudContainer> |
|
|
|
@code { |
|
private readonly CancellationTokenSource _cts = new(); |
|
|
|
[Parameter] |
|
public int ShowId { get; set; } |
|
|
|
private TelevisionShowViewModel _show; |
|
private List<string> _sortedContentRatings = []; |
|
private List<CultureInfo> _sortedLanguages = []; |
|
private List<string> _sortedStudios = []; |
|
private List<string> _sortedNetworks = []; |
|
private List<string> _sortedGenres = []; |
|
private List<string> _sortedTags = []; |
|
|
|
private static int PageSize => 100; |
|
private const int PageNumber = 1; |
|
|
|
private TelevisionSeasonCardResultsViewModel _data = new(0, new List<TelevisionSeasonCardViewModel>(), null); |
|
|
|
public void Dispose() |
|
{ |
|
_cts.Cancel(); |
|
_cts.Dispose(); |
|
} |
|
|
|
protected override Task OnParametersSetAsync() => RefreshData(); |
|
|
|
private async Task RefreshData() |
|
{ |
|
Option<TelevisionShowViewModel> maybeShow = await Mediator.Send(new GetTelevisionShowById(ShowId), _cts.Token); |
|
foreach (TelevisionShowViewModel show in maybeShow) |
|
{ |
|
_show = show; |
|
_sortedContentRatings = _show.ContentRatings.OrderBy(cr => cr).ToList(); |
|
_sortedLanguages = _show.Languages.OrderBy(ci => ci.EnglishName).ToList(); |
|
_sortedStudios = _show.Studios.OrderBy(s => s).ToList(); |
|
_sortedGenres = _show.Genres.OrderBy(g => g).ToList(); |
|
_sortedTags = _show.Tags.OrderBy(t => t).ToList(); |
|
_sortedNetworks = _show.Networks.OrderBy(n => n).ToList(); |
|
} |
|
|
|
_data = await Mediator.Send(new GetTelevisionSeasonCards(ShowId, PageNumber, PageSize), _cts.Token); |
|
} |
|
|
|
private async Task AddToCollection() |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "show" }, { "EntityName", _show.Title } }; |
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); |
|
DialogResult result = await dialog.Result; |
|
if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) |
|
{ |
|
await Mediator.Send(new AddShowToCollection(collection.Id, ShowId), _cts.Token); |
|
NavigationManager.NavigateTo($"media/collections/{collection.Id}"); |
|
} |
|
} |
|
|
|
private async Task AddToPlaylist() |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "show" }, { "EntityName", _show.Title } }; |
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<AddToPlaylistDialog>("Add To Playlist", parameters, options); |
|
DialogResult result = await dialog.Result; |
|
if (result is { Canceled: false, Data: PlaylistViewModel playlist }) |
|
{ |
|
await Mediator.Send(new AddShowToPlaylist(playlist.Id, ShowId), _cts.Token); |
|
NavigationManager.NavigateTo($"media/playlists/{playlist.Id}"); |
|
} |
|
} |
|
|
|
private async Task AddToSchedule() |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "show" }, { "EntityName", _show.Title } }; |
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
|
|
|
IDialogReference dialog = await Dialog.ShowAsync<AddToScheduleDialog>("Add To Schedule", parameters, options); |
|
DialogResult result = await dialog.Result; |
|
if (result is { Canceled: false, Data: ProgramScheduleViewModel schedule }) |
|
{ |
|
await Mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, null, PlayoutMode.One, ProgramScheduleItemCollectionType.TelevisionShow, null, null, null, ShowId, null, PlaybackOrder.Shuffle, FillWithGroupMode.None, MultipleMode.Count, null, null, TailMode.None, null, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token); |
|
NavigationManager.NavigateTo($"schedules/{schedule.Id}/items"); |
|
} |
|
} |
|
|
|
private async Task AddSeasonToCollection(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 = await Dialog.ShowAsync<AddToCollectionDialog>("Add To Collection", parameters, options); |
|
DialogResult result = await dialog.Result; |
|
if (result is { Canceled: false, Data: MediaCollectionViewModel collection }) |
|
{ |
|
var request = new AddSeasonToCollection(collection.Id, season.TelevisionSeasonId); |
|
Either<BaseError, Unit> addResult = await Mediator.Send(request, _cts.Token); |
|
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)); |
|
} |
|
} |
|
} |
|
|
|
private static string GetPosterUrl(string poster) |
|
{ |
|
return poster.StartsWith("http://") || poster.StartsWith("https://") ? poster : $"artwork/posters/{poster}"; |
|
} |
|
|
|
} |