Browse Source

remove movies, shows, seasons, episodes from collections

pull/26/head
Jason Dove 6 years ago
parent
commit
e6c8730485
  1. 15
      ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs
  2. 74
      ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs
  3. 85
      ErsatzTV/Pages/MediaCollectionItems.razor
  4. 3
      ErsatzTV/Shared/MainLayout.razor
  5. 22
      ErsatzTV/Shared/MediaCard.razor
  6. 43
      ErsatzTV/Shared/RemoveFromCollectionDialog.razor
  7. 30
      ErsatzTV/wwwroot/css/site.css

15
ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollection.cs

@ -0,0 +1,15 @@ @@ -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<Either<BaseError, Unit>>
{
public List<int> MovieIds { get; set; } = new();
public List<int> TelevisionShowIds { get; set; } = new();
public List<int> TelevisionSeasonIds { get; set; } = new();
public List<int> TelevisionEpisodeIds { get; set; } = new();
}
}

74
ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromSimpleMediaCollectionHandler.cs

@ -0,0 +1,74 @@ @@ -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<BaseError, Unit>>
{
private readonly IMediaCollectionRepository _mediaCollectionRepository;
public RemoveItemsFromSimpleMediaCollectionHandler(
IMediaCollectionRepository mediaCollectionRepository) =>
_mediaCollectionRepository = mediaCollectionRepository;
public Task<Either<BaseError, Unit>> Handle(
RemoveItemsFromSimpleMediaCollection request,
CancellationToken cancellationToken) =>
Validate(request)
.MapT(collection => ApplyAddTelevisionEpisodeRequest(request, collection))
.Bind(v => v.ToEitherAsync());
private Task<Unit> 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<Validation<BaseError, SimpleMediaCollection>> Validate(
RemoveItemsFromSimpleMediaCollection request) =>
SimpleMediaCollectionMustExist(request);
private Task<Validation<BaseError, SimpleMediaCollection>> SimpleMediaCollectionMustExist(
RemoveItemsFromSimpleMediaCollection updateSimpleMediaCollection) =>
_mediaCollectionRepository.GetSimpleMediaCollectionWithItems(updateSimpleMediaCollection.MediaCollectionId)
.Map(v => v.ToValidation<BaseError>("SimpleMediaCollection does not exist."));
}
}

85
ErsatzTV/Pages/MediaCollectionItems.razor

@ -1,10 +1,12 @@ @@ -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<MediaCollectionItems> Logger
@inject ISnackbar Snackbar
@inject IDialogService Dialog
<div class="mb-6" style="display: flex; flex-direction: row;">
<MudText GutterBottom="true" Typo="Typo.h2">@_data.Name</MudText>
@ -20,7 +22,9 @@ @@ -20,7 +22,9 @@
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (MovieCardViewModel card in _data.MovieCards)
{
<MediaCard Data="@card" Link="@($"/media/movies/{card.MovieId}")"/>
<MediaCard Data="@card"
Link="@($"/media/movies/{card.MovieId}")"
DeleteClicked="@RemoveMovieFromCollection"/>
}
</MudContainer>
}
@ -32,7 +36,9 @@ @@ -32,7 +36,9 @@
<MudContainer MaxWidth="MaxWidth.False" Class="media-card-grid">
@foreach (TelevisionShowCardViewModel card in _data.ShowCards)
{
<MediaCard Data="@card" Link="@($"/media/tv/shows/{card.TelevisionShowId}")"/>
<MediaCard Data="@card"
Link="@($"/media/tv/shows/{card.TelevisionShowId}")"
DeleteClicked="@RemoveShowFromCollection"/>
}
</MudContainer>
}
@ -47,7 +53,8 @@ @@ -47,7 +53,8 @@
<MediaCard Data="@card"
Link="@($"/media/tv/seasons/{card.TelevisionSeasonId}")"
Title="@card.ShowTitle"
Subtitle="@card.Title"/>
Subtitle="@card.Title"
DeleteClicked="@RemoveSeasonFromCollection"/>
}
</MudContainer>
}
@ -64,7 +71,8 @@ @@ -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"/>
}
</MudContainer>
}
@ -88,4 +96,73 @@ @@ -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<int> { 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<int> { 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<int> { 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<int> { 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<RemoveFromCollectionDialog>("Remove From Collection", parameters, options);
DialogResult result = await dialog.Result;
if (!result.Cancelled)
{
await Mediator.Send(request);
await RefreshData();
}
}
}

3
ErsatzTV/Shared/MainLayout.razor

@ -67,7 +67,8 @@ @@ -67,7 +67,8 @@
Palette = new Palette
{
DrawerBackground = current.Palette.Background,
Background = current.Palette.BackgroundGrey
Background = current.Palette.BackgroundGrey,
Tertiary = Colors.Shades.White
}
};
}

22
ErsatzTV/Shared/MediaCard.razor

@ -5,16 +5,27 @@ @@ -5,16 +5,27 @@
<div class="@((ContainerClass ?? "media-card-container mr-6") + " pb-3")">
@if (!string.IsNullOrWhiteSpace(Link))
{
<MudPaper>
<MudButton Link="@Link" Class="@($"media-card {CardClass}")" Style="@PosterForItem()">
<div style="position: relative">
<MudPaper Class="@($"media-card {CardClass}")" Style="@PosterForItem()">
@if (string.IsNullOrWhiteSpace(Data.Poster))
{
<MudText Align="Align.Center" Typo="Typo.h1" Class="media-card-poster-placeholder mud-text-disabled">
@GetPlaceholder(Data.SortTitle)
</MudText>
}
</MudButton>
</MudPaper>
</MudPaper>
<div class="media-card-overlay" style="">
<MudButton Link="@Link" Style="height: 100%; width: 100%">
</MudButton>
@if (DeleteClicked.HasDelegate)
{
<MudIconButton Color="Color.Tertiary"
Icon="@Icons.Material.Filled.Delete"
Style="bottom: 0; position: absolute; right: 0;"
OnClick="@(() => DeleteClicked.InvokeAsync(Data))"/>
}
</div>
</div>
}
else
{
@ -61,6 +72,9 @@ @@ -61,6 +72,9 @@
[Parameter]
public string CardClass { get; set; }
[Parameter]
public EventCallback<MediaCardViewModel> DeleteClicked { get; set; }
private string GetPlaceholder(string sortTitle)
{
if (Placeholder != null)

43
ErsatzTV/Shared/RemoveFromCollectionDialog.razor

@ -0,0 +1,43 @@ @@ -0,0 +1,43 @@
@inject IMediator Mediator
<MudDialog>
<DialogContent>
<MudContainer Class="mb-6">
<MudHighlighter Class="mud-primary-text"
Style="background-color: transparent; font-weight: bold"
Text="@FormatText()"
HighlightedText="@EntityName"/>
</MudContainer>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Error" Variant="Variant.Filled" OnClick="Submit">
Remove From Collection
</MudButton>
</DialogActions>
</MudDialog>
@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();
}

30
ErsatzTV/wwwroot/css/site.css

@ -10,17 +10,7 @@ @@ -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 @@ @@ -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 @@ @@ -48,4 +38,18 @@
right: 0;
}
.media-card:hover .media-card-menu { display: block; }
.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; }
Loading…
Cancel
Save