Files
ersatztv/ErsatzTV/Pages/Movie.razor
T

287 lines
13 KiB
Plaintext

@page "/media/movies/{MovieId:int}"
@using ErsatzTV.Extensions
@using ErsatzTV.Application.Movies
@using System.Globalization
@using ErsatzTV.Application.MediaCollections
@using ErsatzTV.Application.MediaItems
@using ErsatzTV.Application.MediaCards
@implements IDisposable
@inject IMediator _mediator
@inject IDialogService _dialog
@inject NavigationManager _navigationManager
@inject ILogger<Movie> _logger
@inject ISnackbar _snackbar
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container">
<div class="fanart-tint"></div>
@if (!string.IsNullOrWhiteSpace(_movie.FanArt))
{
if (_movie.FanArt.StartsWith("http://") || _movie.FanArt.StartsWith("https://"))
{
<img src="@_movie.FanArt" alt="fan art"/>
}
else
{
<img src="@($"artwork/fanart/{_movie.FanArt}")" alt="fan art"/>
}
}
</MudContainer>
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 200px">
<div style="display: flex; flex-direction: row;" class="mb-6">
@if (!string.IsNullOrWhiteSpace(_movie.Poster))
{
<div class="mr-6" style="display: flex; flex-direction: column; max-height: 440px; position: relative">
@if (_movie.Poster.StartsWith("http://") || _movie.Poster.StartsWith("https://"))
{
<img class="mud-elevation-2"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@_movie.Poster" alt="movie poster"/>
}
else
{
<img class="mud-elevation-2"
style="border-radius: 4px; flex-shrink: 0; max-height: 440px;"
src="@($"artwork/posters/{_movie.Poster}")" alt="movie poster"/>
}
@if (_movie.MediaItemState == MediaItemState.FileNotFound)
{
<div style="position: absolute; right: 10px; top: 8px;">
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Error" Size="Size.Large"/>
</div>
}
else if (_movie.MediaItemState == MediaItemState.Unavailable)
{
<div style="position: absolute; right: 10px; top: 8px;">
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Size="Size.Large"/>
</div>
}
</div>
}
<div style="display: flex; flex-direction: column; height: 100%">
<MudText Typo="Typo.h2" Class="media-item-title">@_movie.Title</MudText>
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_movie.Year</MudText>
@if (!string.IsNullOrWhiteSpace(_movie.Plot))
{
<MudCard Elevation="2" Class="mb-6">
<MudCardContent Class="mx-3 my-3" Style="height: 100%">
<MudText Style="flex-grow: 1">@_movie.Plot</MudText>
</MudCardContent>
</MudCard>
}
<div>
<MudButton Class="mb-6"
Variant="Variant.Filled"
Color="Color.Primary"
StartIcon="@Icons.Material.Filled.Add"
OnClick="@AddToCollection">
Add To Collection
</MudButton>
</div>
<div>
<MudButton Variant="Variant.Filled"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Info"
OnClick="@ShowInfo">
Show Media Info
</MudButton>
</div>
</div>
</div>
@if (_movie.MediaItemState == MediaItemState.FileNotFound)
{
<MudCard Class="mb-6">
<MudCardContent>
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Error" Class="mr-2"/>
<MudText>File Not Found:&nbsp;</MudText>
<MudText>@_movie.Path</MudText>
</div>
</MudCardContent>
</MudCard>
}
else if (_movie.MediaItemState == MediaItemState.Unavailable)
{
<MudCard Class="mb-6">
<MudCardContent>
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Class="mr-2"/>
<MudText>Unavailable:&nbsp;</MudText>
<MudText>@_movie.LocalPath</MudText>
</div>
</MudCardContent>
</MudCard>
}
<MudCard Class="mb-6">
<MudCardContent>
@if (_sortedContentRatings.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Content Ratings:&nbsp;</MudText>
<MudLink Href="@(@$"content_rating:""{_sortedContentRatings.Head()}""".GetRelativeSearchQuery())">@_sortedContentRatings.Head()</MudLink>
@foreach (string contentRating in _sortedContentRatings.Skip(1))
{
<MudText>,&nbsp;</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:&nbsp;</MudText>
<MudLink Href="@(@$"language:""{_sortedLanguages.Head().EnglishName.ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedLanguages.Head().EnglishName</MudLink>
@foreach (CultureInfo language in _sortedLanguages.Skip(1))
{
<MudText>,&nbsp;</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:&nbsp;</MudText>
<MudLink Href="@(@$"studio:""{_sortedStudios.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedStudios.Head()</MudLink>
@foreach (string studio in _sortedStudios.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@(@$"studio:""{studio.ToLowerInvariant()}""".GetRelativeSearchQuery())">@studio</MudLink>
}
</div>
}
@if (_sortedDirectors.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Directors:&nbsp;</MudText>
<MudLink Href="@(@$"director:""{_sortedDirectors.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedDirectors.Head()</MudLink>
@foreach (string director in _sortedDirectors.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@(@$"director:""{director.ToLowerInvariant()}""".GetRelativeSearchQuery())">@director</MudLink>
}
</div>
}
@if (_sortedWriters.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Writers:&nbsp;</MudText>
<MudLink Href="@(@$"writer:""{_sortedWriters.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedWriters.Head()</MudLink>
@foreach (string writer in _sortedWriters.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@(@$"writer:""{writer.ToLowerInvariant()}""".GetRelativeSearchQuery())">@writer</MudLink>
}
</div>
}
@if (_sortedGenres.Any())
{
<div style="display: flex; flex-direction: row; flex-wrap: wrap">
<MudText GutterBottom="true">Genres:&nbsp;</MudText>
<MudLink Href="@(@$"genre:""{_sortedGenres.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedGenres.Head()</MudLink>
@foreach (string genre in _sortedGenres.Skip(1))
{
<MudText>,&nbsp;</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:&nbsp;</MudText>
<MudLink Href="@(@$"tag:""{_sortedTags.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedTags.Head()</MudLink>
@foreach (string tag in _sortedTags.Skip(1))
{
<MudText>,&nbsp;</MudText>
<MudLink Href="@(@$"tag:""{tag.ToLowerInvariant()}""".GetRelativeSearchQuery())">@tag</MudLink>
}
</div>
}
</MudCardContent>
</MudCard>
</MudContainer>
@if (_movie.Actors.Any())
{
<MudContainer MaxWidth="MaxWidth.Large">
<MudText Class="mb-4">Actors</MudText>
</MudContainer>
<MudContainer MaxWidth="MaxWidth.Large" Class="media-card-grid">
@foreach (ActorCardViewModel actor in _movie.Actors)
{
<MediaCard Data="@actor"
Link="@(@$"actor:""{actor.Name.ToLowerInvariant()}""".GetRelativeSearchQuery())"
ArtworkKind="ArtworkKind.Thumbnail"/>
}
</MudContainer>
}
@code {
private readonly CancellationTokenSource _cts = new();
[Parameter]
public int MovieId { get; set; }
private MovieViewModel _movie;
private List<string> _sortedContentRatings = new();
private List<CultureInfo> _sortedLanguages = new();
private List<string> _sortedDirectors = new();
private List<string> _sortedWriters = new();
private List<string> _sortedStudios = new();
private List<string> _sortedGenres = new();
private List<string> _sortedTags = new();
public void Dispose()
{
_cts.Cancel();
_cts.Dispose();
}
protected override Task OnParametersSetAsync() => RefreshData();
private async Task RefreshData() =>
await _mediator.Send(new GetMovieById(MovieId), _cts.Token).IfSomeAsync(vm =>
{
_movie = vm;
_sortedContentRatings = _movie.ContentRatings.OrderBy(cr => cr).ToList();
_sortedLanguages = _movie.Languages.OrderBy(ci => ci.EnglishName).ToList();
_sortedStudios = _movie.Studios.OrderBy(s => s).ToList();
_sortedGenres = _movie.Genres.OrderBy(g => g).ToList();
_sortedTags = _movie.Tags.OrderBy(t => t).ToList();
_sortedDirectors = _movie.Directors.OrderBy(d => d).ToList();
_sortedWriters = _movie.Writers.OrderBy(w => w).ToList();
});
private async Task AddToCollection()
{
var parameters = new DialogParameters { { "EntityType", "movie" }, { "EntityName", _movie.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.Canceled && result.Data is MediaCollectionViewModel collection)
{
await _mediator.Send(new AddMovieToCollection(collection.Id, MovieId), _cts.Token);
_navigationManager.NavigateTo($"media/collections/{collection.Id}");
}
}
private async Task ShowInfo()
{
Either<BaseError, MediaItemInfo> maybeInfo = await _mediator.Send(new GetMediaItemInfo(MovieId));
foreach (BaseError error in maybeInfo.LeftToSeq())
{
_snackbar.Add("Unexpected error loading media info");
_logger.LogError("Unexpected error loading media info: {Error}", error.Value);
}
foreach (MediaItemInfo info in maybeInfo.RightToSeq())
{
var parameters = new DialogParameters { { "MediaItemInfo", info } };
var options = new DialogOptions { CloseButton = true, CloseOnEscapeKey = true, MaxWidth = MaxWidth.Medium, FullWidth = true };
IDialogReference dialog = await _dialog.ShowAsync<MediaItemInfoDialog>(_movie.Title, parameters, options);
DialogResult _ = await dialog.Result;
}
}
}