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.
328 lines
17 KiB
328 lines
17 KiB
@page "/media/music/artists/{ArtistId:int}" |
|
@using System.Globalization |
|
@using ErsatzTV.Application.Artists |
|
@using ErsatzTV.Application.MediaCards |
|
@using ErsatzTV.Application.MediaCollections |
|
@using ErsatzTV.Application.ProgramSchedules |
|
@using ErsatzTV.Extensions |
|
@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"/> |
|
} |
|
else |
|
{ |
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Animation="Animation.False" /> |
|
} |
|
</MudContainer> |
|
<MudContainer MaxWidth="MaxWidth.Large" Style="margin-top: 100px" Class="z-10"> |
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" Spacing="6"> |
|
@if (!string.IsNullOrWhiteSpace(_artist.Thumbnail)) |
|
{ |
|
<MudImage Elevation="2" Src="@($"artwork/thumbnails/{_artist.Thumbnail}")" Class="rounded-lg z-10" Style="height: 220px; width: 220px; margin-left: auto; margin-right: auto;" /> |
|
} |
|
else |
|
{ |
|
<MudSkeleton SkeletonType="SkeletonType.Rectangle" Class="rounded-lg z-10" Animation="Animation.False" Height="220px" Width="220px" Style="margin-left: auto; margin-right: auto" /> |
|
} |
|
<div style="display: flex; flex-direction: column; height: 100%; width: 100%" class="z-10"> |
|
<MudStack Row="false"> |
|
<MudHidden Invert="true" Breakpoint="Breakpoint.SmAndDown"> |
|
<MudText Typo="Typo.h4" Class="media-item-title">@_artist?.Name</MudText> |
|
</MudHidden> |
|
<MudHidden Invert="true" Breakpoint="Breakpoint.MdAndUp"> |
|
<MudText Typo="Typo.h2" Class="media-item-title">@_artist?.Name</MudText> |
|
</MudHidden> |
|
<MudText Typo="Typo.subtitle1" Class="media-item-subtitle mb-6 mud-text-secondary">@_artist.Disambiguation</MudText> |
|
</MudStack> |
|
<MudCard Elevation="2" Class="mb-6"> |
|
<MudCardContent Class="mx-3 my-3" Style="height: 100%"> |
|
@if (!string.IsNullOrWhiteSpace(_artist.Biography)) |
|
{ |
|
<MudText Style="flex-grow: 1"> |
|
@if (_artist.Biography.Length > 400) |
|
{ |
|
@(_artist.Biography.Substring(0, 400) + "...") |
|
} |
|
else |
|
{ |
|
@_artist.Biography |
|
} |
|
</MudText> |
|
} |
|
else |
|
{ |
|
<div style="height: 130px; flex-grow: 1"> |
|
</div> |
|
} |
|
</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.Schedule" |
|
OnClick="@AddToSchedule"> |
|
Add To Schedule |
|
</MudButton> |
|
</MudStack> |
|
</div> |
|
</MudStack> |
|
<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"> |
|
<MudStack Row="false" Spacing="6"> |
|
@foreach (MusicVideoCardViewModel musicVideo in _musicVideos.Cards) |
|
{ |
|
<MudCard> |
|
<MudStack Row="true" Breakpoint="Breakpoint.SmAndDown" id="@($"music-video-{musicVideo.MusicVideoId}")"> |
|
@if (!string.IsNullOrWhiteSpace(musicVideo.Poster)) |
|
{ |
|
<div style="display: flex; flex-direction: column; position: relative"> |
|
<MudImage Src="@($"artwork/thumbnails/{musicVideo.Poster}")" Style="height: 220px; max-width: 265px; margin-left: auto; margin-right: auto;"/> |
|
@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> |
|
} |
|
else |
|
{ |
|
<MudHidden Invert="true" Breakpoint="Breakpoint.SmAndDown"> |
|
@if (musicVideo.State is not MediaItemState.Normal and not MediaItemState.RemoteOnly) |
|
{ |
|
<div style="display: flex; position: relative; height: 220px; width: 265px; "> |
|
<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> |
|
} |
|
</MudHidden> |
|
<MudHidden Invert="true" Breakpoint="Breakpoint.MdAndUp"> |
|
<div style="display: flex; position: relative; height: 220px; width: 265px; "> |
|
<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> |
|
</MudHidden> |
|
} |
|
<MudCardContent> |
|
<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> |
|
</MudStack> |
|
@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> |
|
} |
|
</MudStack> |
|
</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 = 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 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 = 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.Artist, null, null, null, ArtistId, 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 AddMusicVideoToCollection(MusicVideoCardViewModel musicVideo) |
|
{ |
|
var parameters = new DialogParameters { { "EntityType", "music video" }, { "EntityName", musicVideo.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 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)); |
|
} |
|
} |
|
|
|
} |