From 134e8d08a8270db6ce2c336754d08f53878c4eec Mon Sep 17 00:00:00 2001 From: Jason Dove Date: Sat, 13 Feb 2021 21:15:25 -0600 Subject: [PATCH] add metadata/poster refresh button to media cards --- .../MediaItems/AggregateMediaItemViewModel.cs | 1 + .../Commands/RefreshMediaItemPosterHandler.cs | 45 +++++++++++++++++++ .../Queries/GetAggregateMediaItemsHandler.cs | 1 + .../AggregateModels/MediaItemSummary.cs | 2 +- ErsatzTV.Core/Metadata/LocalMediaScanner.cs | 3 +- .../Data/Repositories/MediaItemRepository.cs | 9 +++- ErsatzTV/ErsatzTV.csproj | 2 +- ErsatzTV/Shared/MediaCard.razor | 29 +++++++++--- ErsatzTV/Shared/MediaItemsGrid.razor | 2 +- ErsatzTV/wwwroot/css/site.css | 15 ++++++- 10 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 ErsatzTV.Application/MediaItems/Commands/RefreshMediaItemPosterHandler.cs diff --git a/ErsatzTV.Application/MediaItems/AggregateMediaItemViewModel.cs b/ErsatzTV.Application/MediaItems/AggregateMediaItemViewModel.cs index 31d5a0ca3..8fed60516 100644 --- a/ErsatzTV.Application/MediaItems/AggregateMediaItemViewModel.cs +++ b/ErsatzTV.Application/MediaItems/AggregateMediaItemViewModel.cs @@ -1,6 +1,7 @@ namespace ErsatzTV.Application.MediaItems { public record AggregateMediaItemViewModel( + int MediaItemId, string Title, string Subtitle, string SortTitle, diff --git a/ErsatzTV.Application/MediaItems/Commands/RefreshMediaItemPosterHandler.cs b/ErsatzTV.Application/MediaItems/Commands/RefreshMediaItemPosterHandler.cs new file mode 100644 index 000000000..c0a6f3ace --- /dev/null +++ b/ErsatzTV.Application/MediaItems/Commands/RefreshMediaItemPosterHandler.cs @@ -0,0 +1,45 @@ +using System.Threading; +using System.Threading.Tasks; +using ErsatzTV.Core; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Metadata; +using ErsatzTV.Core.Interfaces.Repositories; +using LanguageExt; + +namespace ErsatzTV.Application.MediaItems.Commands +{ + public class + RefreshMediaItemPosterHandler : MediatR.IRequestHandler> + { + private readonly ILocalPosterProvider _localPosterProvider; + private readonly IMediaItemRepository _mediaItemRepository; + + public RefreshMediaItemPosterHandler( + IMediaItemRepository mediaItemRepository, + ILocalPosterProvider localPosterProvider) + { + _mediaItemRepository = mediaItemRepository; + _localPosterProvider = localPosterProvider; + } + + public Task> Handle( + RefreshMediaItemPoster request, + CancellationToken cancellationToken) => + Validate(request) + .MapT(RefreshPoster) + .Bind(v => v.ToEitherAsync()); + + private Task> Validate(RefreshMediaItemPoster request) => + MediaItemMustExist(request); + + private Task> MediaItemMustExist(RefreshMediaItemPoster request) => + _mediaItemRepository.Get(request.MediaItemId) + .Map( + maybeItem => maybeItem.ToValidation( + $"[MediaItem] {request.MediaItemId} does not exist.")); + + private Task RefreshPoster(MediaItem mediaItem) => + _localPosterProvider.RefreshPoster(mediaItem).ToUnit(); + } +} diff --git a/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs b/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs index 93ee700af..67dcfd792 100644 --- a/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs +++ b/ErsatzTV.Application/MediaItems/Queries/GetAggregateMediaItemsHandler.cs @@ -30,6 +30,7 @@ namespace ErsatzTV.Application.MediaItems.Queries var results = allItems .Map( s => new AggregateMediaItemViewModel( + s.MediaItemId, s.Title, s.Subtitle, s.SortTitle, diff --git a/ErsatzTV.Core/AggregateModels/MediaItemSummary.cs b/ErsatzTV.Core/AggregateModels/MediaItemSummary.cs index 90461143b..be009ab73 100644 --- a/ErsatzTV.Core/AggregateModels/MediaItemSummary.cs +++ b/ErsatzTV.Core/AggregateModels/MediaItemSummary.cs @@ -1,4 +1,4 @@ namespace ErsatzTV.Core.AggregateModels { - public record MediaItemSummary(string Title, string SortTitle, string Subtitle, string Poster); + public record MediaItemSummary(int MediaItemId, string Title, string SortTitle, string Subtitle, string Poster); } diff --git a/ErsatzTV.Core/Metadata/LocalMediaScanner.cs b/ErsatzTV.Core/Metadata/LocalMediaScanner.cs index 8d83df7f3..3841ade71 100644 --- a/ErsatzTV.Core/Metadata/LocalMediaScanner.cs +++ b/ErsatzTV.Core/Metadata/LocalMediaScanner.cs @@ -102,7 +102,8 @@ namespace ErsatzTV.Core.Metadata { DateTime lastWrite = File.GetLastWriteTimeUtc(mediaItem.Path); bool modified = lastWrite > mediaItem.LastWriteTime.IfNone(DateTime.MinValue); - return modified || mediaItem.Metadata == null || mediaItem.Metadata.MediaType != localMediaSource.MediaType; + return modified || mediaItem.Metadata == null || + mediaItem.Metadata.MediaType != localMediaSource.MediaType; }); modifiedPlayoutIds.AddRange(await _playoutRepository.GetPlayoutIdsForMediaItems(modifiedMediaItems)); foreach (MediaItem mediaItem in modifiedMediaItems) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs index 089659449..83c9c7a6f 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs @@ -24,7 +24,10 @@ namespace ErsatzTV.Infrastructure.Data.Repositories } public Task> Get(int id) => - _dbContext.MediaItems.SingleOrDefaultAsync(i => i.Id == id).Map(Optional); + _dbContext.MediaItems + .Include(i => i.Source) + .SingleOrDefaultAsync(i => i.Id == id) + .Map(Optional); public Task> GetAll() => _dbContext.MediaItems.ToListAsync(); @@ -46,6 +49,7 @@ namespace ErsatzTV.Infrastructure.Data.Repositories { MediaType.Movie => _dbContext.MediaItemSummaries.FromSqlRaw( @"SELECT + Id AS MediaItemId, Metadata_Title AS Title, Metadata_SortTitle AS SortTitle, substr(Metadata_Aired, 1, 4) AS Subtitle, @@ -59,10 +63,11 @@ LIMIT {0} OFFSET {1}", .ToListAsync(), MediaType.TvShow => _dbContext.MediaItemSummaries.FromSqlRaw( @"SELECT + min(Id) AS MediaItemId, Metadata_Title AS Title, Metadata_SortTitle AS SortTitle, count(*) || ' Episodes' AS Subtitle, - min(Poster) AS Poster + max(Poster) AS Poster FROM MediaItems WHERE Metadata_MediaType=1 GROUP BY Metadata_Title, Metadata_SortTitle ORDER BY Metadata_SortTitle diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index 1b97f7cad..4ebab80ad 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -20,7 +20,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/ErsatzTV/Shared/MediaCard.razor b/ErsatzTV/Shared/MediaCard.razor index 93706e081..44fea3a41 100644 --- a/ErsatzTV/Shared/MediaCard.razor +++ b/ErsatzTV/Shared/MediaCard.razor @@ -1,16 +1,17 @@ @using ErsatzTV.Application.MediaItems +@using ErsatzTV.Application.MediaItems.Commands +@using Unit = LanguageExt.Unit +@inject IMediator Mediator +
- - @if (!string.IsNullOrWhiteSpace(Data.Poster)) - { - - } - else + + @if (string.IsNullOrWhiteSpace(Data.Poster)) { @Placeholder(Data.SortTitle) } + @Data.Title @@ -25,10 +26,26 @@ [Parameter] public AggregateMediaItemViewModel Data { get; set; } + [Parameter] + public EventCallback DataRefreshed { get; set; } + private string Placeholder(string sortTitle) { string first = sortTitle.Substring(0, 1).ToUpperInvariant(); return int.TryParse(first, out _) ? "#" : first; } + private string PosterForItem() => string.IsNullOrWhiteSpace(Data.Poster) + ? "position: relative" + : $"position: relative; background-image: url(/posters/{Data.Poster}); background-size: cover"; + + private async Task RefreshMetadata() + { + // TODO: how should we refresh an entire television show? + await Mediator.Send(new RefreshMediaItemMetadata(Data.MediaItemId)); + await Mediator.Send(new RefreshMediaItemCollections(Data.MediaItemId)); + await Mediator.Send(new RefreshMediaItemPoster(Data.MediaItemId)); + await DataRefreshed.InvokeAsync(); + } + } \ No newline at end of file diff --git a/ErsatzTV/Shared/MediaItemsGrid.razor b/ErsatzTV/Shared/MediaItemsGrid.razor index fb9882c03..84a4ca52f 100644 --- a/ErsatzTV/Shared/MediaItemsGrid.razor +++ b/ErsatzTV/Shared/MediaItemsGrid.razor @@ -21,7 +21,7 @@ @foreach (AggregateMediaItemViewModel item in _data.DataPage) { - + } diff --git a/ErsatzTV/wwwroot/css/site.css b/ErsatzTV/wwwroot/css/site.css index b612390cd..d3f31315d 100644 --- a/ErsatzTV/wwwroot/css/site.css +++ b/ErsatzTV/wwwroot/css/site.css @@ -8,12 +8,16 @@ .media-card { display: flex; + filter: brightness(100%); flex-direction: column; height: 220px; justify-content: center; + transition: all 0.2s ease; width: 152px; } +.media-card:hover { filter: brightness(80%); } + .media-card-title { overflow: hidden; text-overflow: ellipsis; @@ -21,4 +25,13 @@ width: 100%; } -.media-card-poster-placeholder { font-weight: bold; } \ No newline at end of file +.media-card-poster-placeholder { font-weight: bold; } + +.media-card-menu { + bottom: 0; + display: none; + position: absolute; + right: 0; +} + +.media-card:hover .media-card-menu { display: block; } \ No newline at end of file