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.
287 lines
14 KiB
287 lines
14 KiB
@page "/media/music/artists/{ArtistId:int}" |
|
@using ErsatzTV.Extensions |
|
@using ErsatzTV.Application.Artists |
|
@using ErsatzTV.Application.MediaCards |
|
@using ErsatzTV.Application.MediaCollections |
|
@using ErsatzTV.Application.ProgramSchedules |
|
@using System.Globalization |
|
@implements IDisposable |
|
@inject IMediator _mediator |
|
@inject IDialogService _dialog |
|
@inject NavigationManager _navigationManager |
|
@inject ILogger<Artist> _logger |
|
@inject ISnackbar _snackbar |
|
|
|
<MudContainer MaxWidth="MaxWidth.False" Style="padding: 0" Class="fanart-container"> |
|
<div class="fanart-tint"></div> |
|
@if (!string.IsNullOrWhiteSpace(_artist.FanArt)) |
|
{ |
|
<img src="@($"/artwork/fanart/{_artist.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(_artist.Thumbnail)) |
|
{ |
|
<img class="mud-elevation-2 mr-6" |
|
style="border-radius: 4px; flex-shrink: 0; height: 220px; width: 220px" |
|
src="@($"/artwork/thumbnails/{_artist.Thumbnail}")" alt="artist thumbnail"/> |
|
} |
|
<div style="display: flex; flex-direction: column; height: 100%"> |
|
<MudText Typo="Typo.h2" Class="media-item-title">@_artist.Name</MudText> |
|
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_artist.Disambiguation</MudText> |
|
@if (!string.IsNullOrWhiteSpace(_artist.Biography)) |
|
{ |
|
<MudCard Elevation="2" Class="mb-6"> |
|
<MudCardContent Class="mx-3 my-3" Style="height: 100%"> |
|
<MudText Style="flex-grow: 1"> |
|
@if (_artist.Biography.Length > 400) |
|
{ |
|
@(_artist.Biography.Substring(0, 400) + "...") |
|
} |
|
else |
|
{ |
|
@_artist.Biography |
|
} |
|
</MudText> |
|
</MudCardContent> |
|
</MudCard> |
|
} |
|
<div> |
|
<MudButton Variant="Variant.Filled" |
|
Color="Color.Primary" |
|
StartIcon="@Icons.Material.Filled.Add" |
|
OnClick="@AddToCollection"> |
|
Add To Collection |
|
</MudButton> |
|
<MudButton Class="ml-3" |
|
Variant="Variant.Filled" |
|
Color="Color.Primary" |
|
StartIcon="@Icons.Material.Filled.Schedule" |
|
OnClick="@AddToSchedule"> |
|
Add To Schedule |
|
</MudButton> |
|
</div> |
|
</div> |
|
</div> |
|
<MudCard Class="mb-6"> |
|
<MudCardContent> |
|
@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 (_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 (_sortedStyles.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Styles: </MudText> |
|
<MudLink Href="@(@$"style:""{_sortedStyles.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedStyles.Head()</MudLink> |
|
@foreach (string style in _sortedStyles.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"style:""{style.ToLowerInvariant()}""".GetRelativeSearchQuery())">@style</MudLink> |
|
} |
|
</div> |
|
} |
|
@if (_sortedMoods.Any()) |
|
{ |
|
<div style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudText GutterBottom="true">Moods: </MudText> |
|
<MudLink Href="@(@$"mood:""{_sortedMoods.Head().ToLowerInvariant()}""".GetRelativeSearchQuery())">@_sortedMoods.Head()</MudLink> |
|
@foreach (string mood in _sortedMoods.Skip(1)) |
|
{ |
|
<MudText>, </MudText> |
|
<MudLink Href="@(@$"mood:""{mood.ToLowerInvariant()}""".GetRelativeSearchQuery())">@mood</MudLink> |
|
} |
|
</div> |
|
} |
|
</MudCardContent> |
|
</MudCard> |
|
</MudContainer> |
|
<MudContainer MaxWidth="MaxWidth.Large" Class="mt-8"> |
|
@foreach (MusicVideoCardViewModel musicVideo in _musicVideos.Cards) |
|
{ |
|
<MudCard Class="mb-6" Style="display: flex; flex-direction: column"> |
|
<div id="@($"music-video-{musicVideo.MusicVideoId}")" style="display: flex; flex-direction: row; scroll-margin-top: 85px"> |
|
@if (!string.IsNullOrWhiteSpace(musicVideo.Poster)) |
|
{ |
|
<MudPaper style="display: flex; flex-direction: column; position: relative"> |
|
<MudCardMedia Image="@($"/artwork/thumbnails/{musicVideo.Poster}")" Style="flex-grow: 1; height: 220px; width: 293px;"/> |
|
@if (musicVideo.State == 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 (musicVideo.State == MediaItemState.Unavailable) |
|
{ |
|
<div style="position: absolute; right: 10px; top: 8px;"> |
|
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Size="Size.Large"/> |
|
</div> |
|
} |
|
</MudPaper> |
|
} |
|
else |
|
{ |
|
<div style="display: flex; height: 220px; position: relative; width: 293px;"> |
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Animation="Animation.False"/> |
|
@if (musicVideo.State == 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 (musicVideo.State == MediaItemState.Unavailable) |
|
{ |
|
<div style="position: absolute; right: 10px; top: 8px;"> |
|
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Size="Size.Large"/> |
|
</div> |
|
} |
|
</div> |
|
} |
|
<MudCardContent Class="ml-3"> |
|
<div style="display: flex; flex-direction: column; height: 100%"> |
|
<MudText Typo="Typo.h4">@musicVideo.Title</MudText> |
|
@if (!string.IsNullOrWhiteSpace(musicVideo.Album)) |
|
{ |
|
<div style="display: flex; flex-direction: row"> |
|
<MudText GutterBottom="true">Album: </MudText> |
|
<MudLink Href="@(@$"album:""{musicVideo.Album}""".GetRelativeSearchQuery())">@musicVideo.Album</MudLink> |
|
</div> |
|
} |
|
<MudText Style="flex-grow: 1">@musicVideo.Plot</MudText> |
|
<div class="mt-6"> |
|
<MudButton Variant="Variant.Filled" |
|
Color="Color.Primary" |
|
StartIcon="@Icons.Material.Filled.Add" |
|
OnClick="@(_ => AddMusicVideoToCollection(musicVideo))"> |
|
Add To Collection |
|
</MudButton> |
|
</div> |
|
</div> |
|
</MudCardContent> |
|
</div> |
|
@if (musicVideo.State == MediaItemState.FileNotFound) |
|
{ |
|
<div class="ml-3 mt-3 mb-3" 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: </MudText> |
|
<MudText>@musicVideo.Path</MudText> |
|
</div> |
|
} |
|
else if (musicVideo.State == MediaItemState.Unavailable) |
|
{ |
|
<div class="ml-3 mt-3 mb-3" style="display: flex; flex-direction: row; flex-wrap: wrap"> |
|
<MudIcon Icon="@Icons.Material.Filled.Warning" Color="Color.Warning" Class="mr-2"/> |
|
<MudText>Unavailable: </MudText> |
|
<MudText>@musicVideo.LocalPath</MudText> |
|
</div> |
|
} |
|
</MudCard> |
|
} |
|
</MudContainer> |
|
|
|
@code { |
|
private readonly CancellationTokenSource _cts = new(); |
|
|
|
[Parameter] |
|
public int ArtistId { get; set; } |
|
|
|
private ArtistViewModel _artist; |
|
private List<CultureInfo> _sortedLanguages = new(); |
|
private List<string> _sortedGenres = new(); |
|
private List<string> _sortedStyles = new(); |
|
private List<string> _sortedMoods = new(); |
|
private MusicVideoCardResultsViewModel _musicVideos; |
|
|
|
public void Dispose() |
|
{ |
|
_cts.Cancel(); |
|
_cts.Dispose(); |
|
} |
|
|
|
protected override Task OnParametersSetAsync() => RefreshData(); |
|
|
|
private async Task RefreshData() |
|
{ |
|
await _mediator.Send(new GetArtistById(ArtistId), _cts.Token).IfSomeAsync(vm => |
|
{ |
|
_artist = vm; |
|
_sortedLanguages = _artist.Languages.OrderBy(ci => ci.EnglishName).ToList(); |
|
_sortedGenres = _artist.Genres.OrderBy(g => g).ToList(); |
|
_sortedStyles = _artist.Styles.OrderBy(s => s).ToList(); |
|
_sortedMoods = _artist.Moods.OrderBy(m => m).ToList(); |
|
}); |
|
|
|
_musicVideos = await _mediator.Send(new GetMusicVideoCards(ArtistId, 1, 100), _cts.Token); |
|
} |
|
|
|
private async Task AddToCollection() |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "artist" }, { "EntityName", _artist.Name } }; |
|
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 AddArtistToCollection(collection.Id, ArtistId), _cts.Token); |
|
_navigationManager.NavigateTo($"/media/collections/{collection.Id}"); |
|
} |
|
} |
|
|
|
private async Task AddToSchedule() |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "artist" }, { "EntityName", _artist.Name } }; |
|
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; |
|
|
|
IDialogReference dialog = _dialog.Show<AddToScheduleDialog>("Add To Schedule", parameters, options); |
|
DialogResult result = await dialog.Result; |
|
if (!result.Cancelled && result.Data is ProgramScheduleViewModel schedule) |
|
{ |
|
await _mediator.Send(new AddProgramScheduleItem(schedule.Id, StartType.Dynamic, null, PlayoutMode.One, ProgramScheduleItemCollectionType.Artist, null, null, null, ArtistId, PlaybackOrder.Shuffle, null, null, TailMode.None, null, GuideMode.Normal, null, null, null, null, null, null, null, null, null, null), _cts.Token); |
|
_navigationManager.NavigateTo($"/schedules/{schedule.Id}/items"); |
|
} |
|
} |
|
|
|
private async Task AddMusicVideoToCollection(MusicVideoCardViewModel musicVideo) |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "music video" }, { "EntityName", musicVideo.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 AddMusicVideoToCollection(collection.Id, musicVideo.MusicVideoId); |
|
Either<BaseError, Unit> addResult = await _mediator.Send(request, _cts.Token); |
|
addResult.Match( |
|
Left: error => |
|
{ |
|
_snackbar.Add($"Unexpected error adding music video to collection: {error.Value}"); |
|
_logger.LogError("Unexpected error adding music video to collection: {Error}", error.Value); |
|
}, |
|
Right: _ => _snackbar.Add($"Added {musicVideo.Title} to collection {collection.Name}", Severity.Success)); |
|
} |
|
} |
|
|
|
} |