From e6c87304858ab02fb81efb16db8e31259d1ea41a Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sat, 20 Feb 2021 08:56:01 -0600 Subject: [PATCH] remove movies, shows, seasons, episodes from collections --- .../RemoveItemsFromSimpleMediaCollection.cs | 15 ++++ ...veItemsFromSimpleMediaCollectionHandler.cs | 74 ++++++++++++++++ ErsatzTV/Pages/MediaCollectionItems.razor | 85 ++++++++++++++++++- ErsatzTV/Shared/MainLayout.razor | 3 +- ErsatzTV/Shared/MediaCard.razor | 22 ++++- .../Shared/RemoveFromCollectionDialog.razor | 43 ++++++++++ ErsatzTV/wwwroot/css/site.css | 30 ++++--- 7 files changed, 250 insertions(+), 22 deletions(-) create mode 100644 ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs create mode 100644 ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs create mode 100644 ErsatzTV/Shared/RemoveFromCollectionDialog.razor diff --git a/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs b/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs new file mode 100644 index 000000000..a3ea0249d --- /dev/null +++ b/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using ErsatzTV.Core; +using LanguageExt; + +namespace ErsatzTV.Application.MediaCollections.Commands +{ + public record RemoveItemsFromSimpleMediaCollection + (int MediaCollectionId) : MediatR.IRequest> + { + public List MovieIds { get; set; } = new(); + public List TelevisionShowIds { get; set; } = new(); + public List TelevisionSeasonIds { get; set; } = new(); + public List TelevisionEpisodeIds { get; set; } = new(); + } +} diff --git a/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs new file mode 100644 index 000000000..d7342ebab --- /dev/null +++ b/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs @@ -0,0 +1,74 @@ +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; + +namespace ErsatzTV.Application.MediaCollections.Commands +{ + public class + RemoveItemsFromSimpleMediaCollectionHandler : MediatR.IRequestHandler< + RemoveItemsFromSimpleMediaCollection, + Either> + { + private readonly IMediaCollectionRepository _mediaCollectionRepository; + + public RemoveItemsFromSimpleMediaCollectionHandler( + IMediaCollectionRepository mediaCollectionRepository) => + _mediaCollectionRepository = mediaCollectionRepository; + + public Task> Handle( + RemoveItemsFromSimpleMediaCollection request, + CancellationToken cancellationToken) => + Validate(request) + .MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection)) + .Bind(v => v.ToEitherAsync()); + + private Task ApplyAddTelevisionEpisodeRequest( + RemoveItemsFromSimpleMediaCollection request, + SimpleMediaCollection collection) + { + var moviesToRemove = collection.Movies + .Filter(m => request.MovieIds.Contains(m.Id)) + .ToList(); + + moviesToRemove.ForEach(m => collection.Movies.Remove(m)); + + var showsToRemove = collection.TelevisionShows + .Filter(s => request.TelevisionShowIds.Contains(s.Id)) + .ToList(); + + showsToRemove.ForEach(s => collection.TelevisionShows.Remove(s)); + + var seasonsToRemove = collection.TelevisionSeasons + .Filter(s => request.TelevisionSeasonIds.Contains(s.Id)) + .ToList(); + + seasonsToRemove.ForEach(s => collection.TelevisionSeasons.Remove(s)); + + var episodesToRemove = collection.TelevisionEpisodes + .Filter(e => request.TelevisionEpisodeIds.Contains(e.Id)) + .ToList(); + + episodesToRemove.ForEach(e => collection.TelevisionEpisodes.Remove(e)); + + if (moviesToRemove.Any() || showsToRemove.Any() || seasonsToRemove.Any() || episodesToRemove.Any()) + { + return _mediaCollectionRepository.Update(collection).ToUnit(); + } + + return Task.FromResult(Unit.Default); + } + + private Task> Validate( + RemoveItemsFromSimpleMediaCollection request) => + SimpleMediaCollectionMustExist(request); + + private Task> SimpleMediaCollectionMustExist( + RemoveItemsFromSimpleMediaCollection updateSimpleMediaCollection) => + _mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId) + .Map(v => v.ToValidation("SimpleMediaCollection does not exist.")); + } +} diff --git a/ErsatzTV/Pages/MediaCollectionItems.razor b/ErsatzTV/Pages/MediaCollectionItems.razor index 695e0506c..006ab9b34 100644 --- a/ErsatzTV/Pages/MediaCollectionItems.razor +++ b/ErsatzTV/Pages/MediaCollectionItems.razor @@ -1,10 +1,12 @@ @page "/media/collections/{Id:int}" @using ErsatzTV.Application.MediaCards @using ErsatzTV.Application.MediaCards.Queries +@using ErsatzTV.Application.MediaCollections.Commands @inject NavigationManager NavigationManager @inject IMediator Mediator @inject ILogger Logger @inject ISnackbar Snackbar +@inject IDialogService Dialog
@_data.Name @@ -20,7 +22,9 @@ @foreach (MovieCardViewModel card in _data.MovieCards) { - + } } @@ -32,7 +36,9 @@ @foreach (TelevisionShowCardViewModel card in _data.ShowCards) { - + } } @@ -47,7 +53,8 @@ + Subtitle="@card.Title" + DeleteClicked="@RemoveSeasonFromCollection"/> } } @@ -64,7 +71,8 @@ Title="@card.ShowTitle" Subtitle="@card.Title" ContainerClass="media-card-episode-container mx-2" - CardClass="media-card-episode"/> + CardClass="media-card-episode" + DeleteClicked="@RemoveEpisodeFromCollection"/> } } @@ -88,4 +96,73 @@ error => NavigationManager.NavigateTo("404")); } + private async Task RemoveMovieFromCollection(MediaCardViewModel vm) + { + if (vm is MovieCardViewModel movie) + { + var request = new RemoveItemsFromSimpleMediaCollection(Id) + { + MovieIds = new List { movie.MovieId } + }; + + await RemoveItemsWithConfirmation("movie", $"{movie.Title} ({movie.Subtitle})", request); + } + } + + private async Task RemoveShowFromCollection(MediaCardViewModel vm) + { + if (vm is TelevisionShowCardViewModel show) + { + var request = new RemoveItemsFromSimpleMediaCollection(Id) + { + TelevisionShowIds = new List { show.TelevisionShowId } + }; + + await RemoveItemsWithConfirmation("show", $"{show.Title} ({show.Subtitle})", request); + } + } + + private async Task RemoveSeasonFromCollection(MediaCardViewModel vm) + { + if (vm is TelevisionSeasonCardViewModel season) + { + var request = new RemoveItemsFromSimpleMediaCollection(Id) + { + TelevisionSeasonIds = new List { season.TelevisionSeasonId } + }; + + await RemoveItemsWithConfirmation("season", $"{season.ShowTitle} - {season.Title}", request); + } + } + + private async Task RemoveEpisodeFromCollection(MediaCardViewModel vm) + { + if (vm is TelevisionEpisodeCardViewModel episode) + { + var request = new RemoveItemsFromSimpleMediaCollection(Id) + { + TelevisionEpisodeIds = new List { episode.EpisodeId } + }; + + await RemoveItemsWithConfirmation("episode", $"{episode.ShowTitle} - {episode.Title}", request); + } + } + + private async Task RemoveItemsWithConfirmation( + string entityType, + string entityName, + RemoveItemsFromSimpleMediaCollection request) + { + var parameters = new DialogParameters { { "EntityType", entityType }, { "EntityName", entityName } }; + var options = new DialogOptions { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall }; + + IDialogReference dialog = Dialog.Show("Remove From Collection", parameters, options); + DialogResult result = await dialog.Result; + if (!result.Cancelled) + { + await Mediator.Send(request); + await RefreshData(); + } + } + } \ No newline at end of file diff --git a/ErsatzTV/Shared/MainLayout.razor b/ErsatzTV/Shared/MainLayout.razor index 8cf5190f7..e0d55ba4a 100644 --- a/ErsatzTV/Shared/MainLayout.razor +++ b/ErsatzTV/Shared/MainLayout.razor @@ -67,7 +67,8 @@ Palette = new Palette { DrawerBackground = current.Palette.Background, - Background = current.Palette.BackgroundGrey + Background = current.Palette.BackgroundGrey, + Tertiary = Colors.Shades.White } }; } diff --git a/ErsatzTV/Shared/MediaCard.razor b/ErsatzTV/Shared/MediaCard.razor index 7d1dac30f..ac5a97799 100644 --- a/ErsatzTV/Shared/MediaCard.razor +++ b/ErsatzTV/Shared/MediaCard.razor @@ -5,16 +5,27 @@
@if (!string.IsNullOrWhiteSpace(Link)) { - - +
+ @if (string.IsNullOrWhiteSpace(Data.Poster)) { @GetPlaceholder(Data.SortTitle) } - - + +
+ + + @if (DeleteClicked.HasDelegate) + { + + } +
+
} else { @@ -61,6 +72,9 @@ [Parameter] public string CardClass { get; set; } + [Parameter] + public EventCallback DeleteClicked { get; set; } + private string GetPlaceholder(string sortTitle) { if (Placeholder != null) diff --git a/ErsatzTV/Shared/RemoveFromCollectionDialog.razor b/ErsatzTV/Shared/RemoveFromCollectionDialog.razor new file mode 100644 index 000000000..229bcf22c --- /dev/null +++ b/ErsatzTV/Shared/RemoveFromCollectionDialog.razor @@ -0,0 +1,43 @@ +@inject IMediator Mediator + + + + + + + + + Cancel + + Remove From Collection + + + + +@code { + + [CascadingParameter] + MudDialogInstance MudDialog { get; set; } + + [Parameter] + public string EntityType { get; set; } + + [Parameter] + public string EntityName { get; set; } + + [Parameter] + public string DetailText { get; set; } + + [Parameter] + public string DetailHighlight { get; set; } + + private string FormatText() => $"Are you sure you want to remove the {EntityType} {EntityName} from this collection?"; + + private void Submit() => MudDialog.Close(DialogResult.Ok(true)); + + private void Cancel() => MudDialog.Cancel(); + +} \ No newline at end of file diff --git a/ErsatzTV/wwwroot/css/site.css b/ErsatzTV/wwwroot/css/site.css index 768503083..58ddf3845 100644 --- a/ErsatzTV/wwwroot/css/site.css +++ b/ErsatzTV/wwwroot/css/site.css @@ -10,17 +10,7 @@ .media-card { display: flex; - filter: brightness(100%); - flex-direction: column; - height: 220px; - justify-content: center; - transition: all 0.2s ease; - width: 152px; -} - -.media-card { - display: flex; - filter: brightness(100%); + /*filter: brightness(100%);*/ flex-direction: column; height: 220px; justify-content: center; @@ -30,7 +20,7 @@ .media-card-episode { width: 392px; } -.media-card:hover { filter: brightness(75%); } +.media-card:hover { /*filter: brightness(75%);*/ } .media-card-title { overflow: hidden; @@ -48,4 +38,18 @@ right: 0; } -.media-card:hover .media-card-menu { display: block; } \ No newline at end of file +.media-card:hover .media-card-menu { display: block; } + +.media-card-overlay { + background: rgba(0, 0, 0, 0.4); + border-radius: 4px; + bottom: 0; + left: 0; + opacity: 0; + position: absolute; + right: 0; + top: 0; + transition: opacity 0.2s; +} + +.media-card-overlay:hover { opacity: 1; } \ No newline at end of file