using System.Globalization; using ErsatzTV.Application.MediaCards; using ErsatzTV.Application.MediaCollections; using ErsatzTV.Core; using ErsatzTV.Shared; using LanguageExt.UnsafeValueAccess; using MediatR; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using MudBlazor; namespace ErsatzTV.Pages; public class MultiSelectBase : FragmentNavigationBase { private Option _recentlySelected; public MultiSelectBase() { _recentlySelected = None; SelectedItems = []; } [Inject] protected IDialogService Dialog { get; set; } [Inject] protected ISnackbar Snackbar { get; set; } [Inject] protected ILogger Logger { get; set; } [Inject] protected IMediator Mediator { get; set; } protected System.Collections.Generic.HashSet SelectedItems { get; } protected bool IsSelected(MediaCardViewModel card) => SelectedItems.Contains(card); protected bool IsSelectMode() => SelectedItems.Count != 0; protected string SelectionLabel() => $"{SelectedItems.Count} {(SelectedItems.Count == 1 ? "Item" : "Items")} Selected"; protected void ClearSelection() { SelectedItems.Clear(); _recentlySelected = None; } protected virtual Task RefreshData() => Task.CompletedTask; protected void SelectClicked( Func> getSortedItems, MediaCardViewModel card, MouseEventArgs e) { if (!SelectedItems.Remove(card)) { if (e.ShiftKey && _recentlySelected.IsSome) { List sorted = getSortedItems(); int start = sorted.IndexOf(_recentlySelected.ValueUnsafe()); int finish = sorted.IndexOf(card); if (start > finish) { (start, finish) = (finish, start); } for (int i = start; i < finish; i++) { SelectedItems.Add(sorted[i]); } } _recentlySelected = card; SelectedItems.Add(card); } } protected Task AddSelectionToCollection() => AddItemsToCollection( SelectedItems.OfType().Map(m => m.MovieId).ToList(), SelectedItems.OfType().Map(s => s.TelevisionShowId).ToList(), SelectedItems.OfType().Map(s => s.TelevisionSeasonId).ToList(), SelectedItems.OfType().Map(e => e.EpisodeId).ToList(), SelectedItems.OfType().Map(a => a.ArtistId).ToList(), SelectedItems.OfType().Map(mv => mv.MusicVideoId).ToList(), SelectedItems.OfType().Map(ov => ov.OtherVideoId).ToList(), SelectedItems.OfType().Map(s => s.SongId).ToList(), SelectedItems.OfType().Map(i => i.ImageId).ToList()); protected Task AddSelectionToPlaylist() => AddItemsToPlaylist( SelectedItems.OfType().Map(m => m.MovieId).ToList(), SelectedItems.OfType().Map(s => s.TelevisionShowId).ToList(), SelectedItems.OfType().Map(s => s.TelevisionSeasonId).ToList(), SelectedItems.OfType().Map(e => e.EpisodeId).ToList(), SelectedItems.OfType().Map(a => a.ArtistId).ToList(), SelectedItems.OfType().Map(mv => mv.MusicVideoId).ToList(), SelectedItems.OfType().Map(ov => ov.OtherVideoId).ToList(), SelectedItems.OfType().Map(s => s.SongId).ToList(), SelectedItems.OfType().Map(i => i.ImageId).ToList()); protected async Task AddItemsToCollection( List movieIds, List showIds, List seasonIds, List episodeIds, List artistIds, List musicVideoIds, List otherVideoIds, List songIds, List imageIds, string entityName = "selected items") { int count = movieIds.Count + showIds.Count + seasonIds.Count + episodeIds.Count + artistIds.Count + musicVideoIds.Count + otherVideoIds.Count + songIds.Count + imageIds.Count; var parameters = new DialogParameters { { "EntityType", count.ToString(CultureInfo.InvariantCulture) }, { "EntityName", entityName } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; IDialogReference dialog = await Dialog.ShowAsync("Add To Collection", parameters, options); DialogResult result = await dialog.Result; if (!result.Canceled && result.Data is MediaCollectionViewModel collection) { var request = new AddItemsToCollection( collection.Id, movieIds, showIds, seasonIds, episodeIds, artistIds, musicVideoIds, otherVideoIds, songIds, imageIds); Either addResult = await Mediator.Send(request, CancellationToken); addResult.Match( Left: error => { Snackbar.Add($"Unexpected error adding items to collection: {error.Value}"); Logger.LogError("Unexpected error adding items to collection: {Error}", error.Value); }, Right: _ => { Snackbar.Add( $"Added {count} items to collection {collection.Name}", Severity.Success); ClearSelection(); }); } } protected async Task RemoveSelectionFromCollection(int collectionId) { var parameters = new DialogParameters { { "EntityType", SelectedItems.Count.ToString(CultureInfo.InvariantCulture) }, { "EntityName", "selected items" } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; IDialogReference dialog = await Dialog.ShowAsync( "Remove From Collection", parameters, options); DialogResult result = await dialog.Result; if (!result.Canceled) { var itemIds = SelectedItems.Map(vm => vm.MediaItemId).ToList(); await Mediator.Send( new RemoveItemsFromCollection(collectionId) { MediaItemIds = itemIds }, CancellationToken); await RefreshData(); ClearSelection(); } } protected async Task AddItemsToPlaylist( List movieIds, List showIds, List seasonIds, List episodeIds, List artistIds, List musicVideoIds, List otherVideoIds, List songIds, List imageIds, string entityName = "selected items") { int count = movieIds.Count + showIds.Count + seasonIds.Count + episodeIds.Count + artistIds.Count + musicVideoIds.Count + otherVideoIds.Count + songIds.Count + imageIds.Count; var parameters = new DialogParameters { { "EntityType", count.ToString(CultureInfo.InvariantCulture) }, { "EntityName", entityName } }; var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; IDialogReference dialog = await Dialog.ShowAsync("Add To Playlist", parameters, options); DialogResult result = await dialog.Result; if (!result.Canceled && result.Data is PlaylistViewModel playlist) { var request = new AddItemsToPlaylist( playlist.Id, movieIds, showIds, seasonIds, episodeIds, artistIds, musicVideoIds, otherVideoIds, songIds, imageIds); Either addResult = await Mediator.Send(request, CancellationToken); addResult.Match( Left: error => { Snackbar.Add($"Unexpected error adding items to playlist: {error.Value}"); Logger.LogError("Unexpected error adding items to playlist: {Error}", error.Value); }, Right: _ => { Snackbar.Add( $"Added {count} items to playlist {playlist.Name}", Severity.Success); ClearSelection(); }); } } }