Files
ersatztv/ErsatzTV/Pages/CollectionItems.razor
T
Jason DoveandGitHub 398a3c041a detect /dev/dri/card* devices (#1698)
* remove unused nupkg

* cleanup obsolete properties

* detect /dev/dri/card devices
2024-05-02 10:17:38 -05:00

529 lines
20 KiB
Plaintext

@page "/media/collections/{Id:int}"
@using ErsatzTV.Application.MediaCards
@using ErsatzTV.Application.MediaCollections
@inherits MultiSelectBase<CollectionItems>
@inject NavigationManager NavigationManager
@inject IJSRuntime JsRuntime
<MudPaper Square="true" Style="display: flex; height: 64px; left: 240px; padding: 0; position: fixed; right: 0; z-index: 100;">
<div style="align-items: center; display: flex; flex-direction: row; margin-bottom: auto; margin-top: auto; width: 100%;" class="ml-6 mr-6">
@if (IsSelectMode())
{
<MudText Typo="Typo.h6" Color="Color.Primary">@SelectionLabel()</MudText>
<div style="margin-left: auto">
<MudButton Variant="Variant.Filled"
Color="Color.Error"
StartIcon="@Icons.Material.Filled.Remove"
OnClick="@(_ => RemoveSelectionFromCollection(Id))">
Remove From Collection
</MudButton>
<MudButton Class="ml-3"
Variant="Variant.Filled"
Color="Color.Secondary"
StartIcon="@Icons.Material.Filled.Check"
OnClick="@(_ => ClearSelection())">
Clear Selection
</MudButton>
</div>
}
else
{
<div style="align-items: center; display: flex; flex-direction: row;">
<MudText Typo="Typo.h4">@_data?.Name</MudText>
<MudIconButton Icon="@Icons.Material.Filled.Edit"
Href="@($"media/collections/{Id}/edit")"/>
</div>
@if (_data?.MovieCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#movies")">@_data.MovieCards.Count Movies</MudLink>
}
@if (_data?.ShowCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#shows")">@_data.ShowCards.Count Shows</MudLink>
}
@if (_data?.SeasonCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#seasons")">@_data.SeasonCards.Count Seasons</MudLink>
}
@if (_data?.EpisodeCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#episodes")">@_data.EpisodeCards.Count Episodes</MudLink>
}
@if (_data?.ArtistCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#artists")">@_data.ArtistCards.Count Artists</MudLink>
}
@if (_data?.MusicVideoCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#music_videos")">@_data.MusicVideoCards.Count Music Videos</MudLink>
}
@if (_data?.OtherVideoCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#other_videos")">@_data.OtherVideoCards.Count Other Videos</MudLink>
}
@if (_data?.SongCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#songs")">@_data.SongCards.Count Songs</MudLink>
}
@if (_data?.ImageCards.Count > 0)
{
<MudLink Class="ml-4" Href="@(NavigationManager.Uri.Split("#").Head() + "#images")">@_data.ImageCards.Count Images</MudLink>
}
@if (SupportsCustomOrdering())
{
<div style="margin-left: auto">
<MudSwitch T="bool"
Value="@(_data?.UseCustomPlaybackOrder == true)"
Color="Color.Primary"
ValueChanged="@OnUseCustomOrderChanged"
Label="Use Custom Playback Order"/>
</div>
}
}
</div>
</MudPaper>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="pt-8" Style="margin-top: 64px">
@if (_data?.MovieCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "movies" } })">
Movies
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid" UserAttributes="@(new Dictionary<string, object> { { "id", "sortable-collection" } })">
@foreach (MovieCardViewModel card in OrderMovies(_data.MovieCards))
{
<MediaCard Data="@card"
Href="@($"media/movies/{card.MovieId}")"
DeleteClicked="@RemoveMovieFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.ShowCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "shows" } })">
Television Shows
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionShowCardViewModel card in _data.ShowCards.OrderBy(m => m.SortTitle))
{
<MediaCard Data="@card"
Href="@($"media/tv/shows/{card.TelevisionShowId}")"
DeleteClicked="@RemoveShowFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.SeasonCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "seasons" } })">
Television Seasons
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionSeasonCardViewModel card in _data.SeasonCards.OrderBy(m => m.SortTitle))
{
<MediaCard Data="@card"
Href="@($"media/tv/seasons/{card.TelevisionSeasonId}")"
Title="@card.ShowTitle"
Subtitle="@card.Title"
DeleteClicked="@RemoveSeasonFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.EpisodeCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "episodes" } })">
Television Episodes
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionEpisodeCardViewModel card in _data.EpisodeCards.OrderBy(e => e.Aired))
{
<MediaCard Data="@card"
Href="@($"media/tv/seasons/{card.SeasonId}#episode-{card.EpisodeId}")"
Title="@card.ShowTitle"
Subtitle="@card.Title"
ContainerClass="media-card-episode-container mx-2"
CardClass="media-card-episode"
DeleteClicked="@(_ => RemoveEpisodeFromCollection(card))"
ArtworkKind="@ArtworkKind.Thumbnail"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.ArtistCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "artists" } })">
Artists
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (ArtistCardViewModel card in _data.ArtistCards.OrderBy(e => e.SortTitle))
{
<MediaCard Data="@card"
Href="@($"media/music/artists/{card.ArtistId}")"
ArtworkKind="ArtworkKind.Thumbnail"
DeleteClicked="@RemoveArtistFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.MusicVideoCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "music_videos" } })">
Music Videos
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (MusicVideoCardViewModel card in _data.MusicVideoCards.OrderBy(e => e.SortTitle))
{
<MediaCard Data="@card"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
DeleteClicked="@RemoveMusicVideoFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.OtherVideoCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "other_videos" } })">
Other Videos
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (OtherVideoCardViewModel card in _data.OtherVideoCards.OrderBy(e => e.SortTitle))
{
<MediaCard Data="@card"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
DeleteClicked="@RemoveOtherVideoFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.SongCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "songs" } })">
Songs
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (SongCardViewModel card in _data.SongCards.OrderBy(e => e.SortTitle))
{
<MediaCard Data="@card"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
DeleteClicked="@RemoveSongFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
@if (_data?.ImageCards.Count > 0)
{
<MudText GutterBottom="true"
Typo="Typo.h4"
Style="scroll-margin-top: 160px"
UserAttributes="@(new Dictionary<string, object> { { "id", "images" } })">
Images
</MudText>
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (ImageCardViewModel card in _data.ImageCards.OrderBy(e => e.SortTitle))
{
<MediaCard Data="@card"
Href=""
ArtworkKind="ArtworkKind.Thumbnail"
DeleteClicked="@RemoveImageFromCollection"
SelectColor="@Color.Error"
SelectClicked="@(e => SelectClicked(card, e))"
IsSelected="@IsSelected(card)"
IsSelectMode="@IsSelectMode()"/>
}
</MudContainer>
}
</MudContainer>
@code {
[Parameter]
public int Id { get; set; }
private CollectionCardResultsViewModel _data;
private bool SupportsCustomOrdering() =>
_data is not null && _data.MovieCards.Any() && !_data.ShowCards.Any() && !_data.SeasonCards.Any() && !_data.EpisodeCards.Any();
protected override async Task OnParametersSetAsync() => await RefreshData();
protected override async Task RefreshData()
{
Either<BaseError, CollectionCardResultsViewModel> maybeResult =
await Mediator.Send(new GetCollectionCards(Id), CancellationToken);
maybeResult.Match(
result => _data = result,
_ => NavigationManager.NavigateTo("404"));
}
private IOrderedEnumerable<MovieCardViewModel> OrderMovies(List<MovieCardViewModel> movies)
{
if (_data.UseCustomPlaybackOrder)
{
return movies.OrderBy(m => m.CustomIndex);
}
return movies.OrderBy(m => m.SortTitle);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
try
{
await JsRuntime.InvokeVoidAsync("sortableCollection", Id);
if (_data.UseCustomPlaybackOrder)
{
await JsRuntime.InvokeVoidAsync("enableSorting");
}
else
{
await JsRuntime.InvokeVoidAsync("disableSorting");
}
}
catch (Exception)
{
// ignored
}
await base.OnAfterRenderAsync(firstRender);
}
private void SelectClicked(MediaCardViewModel card, MouseEventArgs e)
{
List<MediaCardViewModel> GetSortedItems()
{
return _data.MovieCards.OrderBy(m => m.SortTitle)
.Append<MediaCardViewModel>(_data.ShowCards.OrderBy(s => s.SortTitle))
.Append(_data.SeasonCards.OrderBy(s => s.SortTitle))
.Append(_data.EpisodeCards.OrderBy(ep => ep.Aired))
.Append(_data.ArtistCards.OrderBy(a => a.SortTitle))
.Append(_data.MusicVideoCards.OrderBy(mv => mv.SortTitle))
.Append(_data.OtherVideoCards.OrderBy(ov => ov.SortTitle))
.Append(_data.SongCards.OrderBy(s => s.SortTitle))
.ToList();
}
SelectClicked(GetSortedItems, card, e);
}
private async Task RemoveMovieFromCollection(MediaCardViewModel vm)
{
if (vm is MovieCardViewModel movie)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [movie.MovieId]
};
await RemoveItemsWithConfirmation("movie", $"{movie.Title} ({movie.Subtitle})", request);
}
}
private async Task RemoveArtistFromCollection(MediaCardViewModel vm)
{
if (vm is ArtistCardViewModel artist)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [artist.ArtistId]
};
await RemoveItemsWithConfirmation(
"artist",
string.IsNullOrWhiteSpace(artist.Subtitle) ? artist.Title : $"{artist.Title} ({artist.Subtitle})",
request);
}
}
private async Task RemoveMusicVideoFromCollection(MediaCardViewModel vm)
{
if (vm is MusicVideoCardViewModel musicVideo)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [musicVideo.MusicVideoId]
};
await RemoveItemsWithConfirmation("music video", $"{musicVideo.Title} ({musicVideo.Subtitle})", request);
}
}
private async Task RemoveShowFromCollection(MediaCardViewModel vm)
{
if (vm is TelevisionShowCardViewModel show)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [show.TelevisionShowId]
};
await RemoveItemsWithConfirmation("show", $"{show.Title} ({show.Subtitle})", request);
}
}
private async Task RemoveSeasonFromCollection(MediaCardViewModel vm)
{
if (vm is TelevisionSeasonCardViewModel season)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [season.TelevisionSeasonId]
};
await RemoveItemsWithConfirmation("season", $"{season.ShowTitle} - {season.Title}", request);
}
}
private async Task RemoveEpisodeFromCollection(TelevisionEpisodeCardViewModel episode)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [episode.EpisodeId]
};
await RemoveItemsWithConfirmation("episode", $"{episode.ShowTitle} - {episode.Title}", request);
}
private async Task RemoveOtherVideoFromCollection(MediaCardViewModel vm)
{
if (vm is OtherVideoCardViewModel otherVideo)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [otherVideo.OtherVideoId]
};
await RemoveItemsWithConfirmation("other video", $"{otherVideo.Title}", request);
}
}
private async Task RemoveSongFromCollection(MediaCardViewModel vm)
{
if (vm is SongCardViewModel song)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [song.SongId]
};
await RemoveItemsWithConfirmation("song", $"{song.Title}", request);
}
}
private async Task RemoveImageFromCollection(MediaCardViewModel vm)
{
if (vm is ImageCardViewModel image)
{
var request = new RemoveItemsFromCollection(Id)
{
MediaItemIds = [image.ImageId]
};
await RemoveItemsWithConfirmation("image", $"{image.Title}", request);
}
}
private async Task RemoveItemsWithConfirmation(
string entityType,
string entityName,
RemoveItemsFromCollection request)
{
var parameters = new DialogParameters { { "EntityType", entityType }, { "EntityName", entityName } };
var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialog = await Dialog.ShowAsync<RemoveFromCollectionDialog>("Remove From Collection", parameters, options);
DialogResult result = await dialog.Result;
if (!result.Canceled)
{
await Mediator.Send(request, CancellationToken);
await RefreshData();
}
}
private async Task OnUseCustomOrderChanged()
{
_data.UseCustomPlaybackOrder = !_data.UseCustomPlaybackOrder;
var request = new UpdateCollection(Id, _data.Name) { UseCustomPlaybackOrder = _data.UseCustomPlaybackOrder };
await Mediator.Send(request, CancellationToken);
}
}