From 0a72c2ade99b7fb15741e4d553ad3b103aea9588 Mon Sep 17 00:00:00 2001 From: tidusjar Date: Mon, 8 Aug 2022 22:13:35 +0100 Subject: [PATCH] Almost nailed it --- .../Services/RecentlyRequestedService.cs | 2 +- src/Ombi.Helpers/CacheKeys.cs | 1 + src/Ombi.TheMovieDbApi/IMovieDbApi.cs | 1 + src/Ombi.TheMovieDbApi/Models/TvImages.cs | 44 +++++++++++++++++++ src/Ombi.TheMovieDbApi/TheMovieDbApi.cs | 8 ++++ .../detailed-card.component.html | 4 +- .../detailed-card.component.scss | 10 +---- .../detailed-card.component.stories.ts | 6 +-- .../detailed-card/detailed-card.component.ts | 25 +++++++---- .../app/components/image/image.component.ts | 8 +++- .../recently-requested-list.component.html | 4 +- .../recently-requested-list.component.ts | 22 ++++++++-- .../src/app/interfaces/IRecentlyRequested.ts | 2 +- .../src/app/services/image.service.ts | 4 ++ src/Ombi/Controllers/V1/ImagesController.cs | 24 +++++++++- 15 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 src/Ombi.TheMovieDbApi/Models/TvImages.cs diff --git a/src/Ombi.Core/Services/RecentlyRequestedService.cs b/src/Ombi.Core/Services/RecentlyRequestedService.cs index b31272899..b8ef838bc 100644 --- a/src/Ombi.Core/Services/RecentlyRequestedService.cs +++ b/src/Ombi.Core/Services/RecentlyRequestedService.cs @@ -108,7 +108,7 @@ namespace Ombi.Core.Services }); } - return model.OrderByDescending(x => x.RequestDate); + return model.OrderByDescending(x => x.ReleaseDate); } } } diff --git a/src/Ombi.Helpers/CacheKeys.cs b/src/Ombi.Helpers/CacheKeys.cs index 89faead8a..06e406d64 100644 --- a/src/Ombi.Helpers/CacheKeys.cs +++ b/src/Ombi.Helpers/CacheKeys.cs @@ -21,6 +21,7 @@ namespace Ombi.Helpers public const string LidarrRootFolders = nameof(LidarrRootFolders); public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles); public const string FanartTv = nameof(FanartTv); + public const string TmdbImages = nameof(TmdbImages); public const string UsersDropdown = nameof(UsersDropdown); } } diff --git a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs index 9b277c091..44dea2656 100644 --- a/src/Ombi.TheMovieDbApi/IMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/IMovieDbApi.cs @@ -46,5 +46,6 @@ namespace Ombi.Api.TheMovieDb Task> GetLanguages(CancellationToken cancellationToken); Task> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken); Task> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken); + Task GetTvImages(string theMovieDbId, CancellationToken token); } } diff --git a/src/Ombi.TheMovieDbApi/Models/TvImages.cs b/src/Ombi.TheMovieDbApi/Models/TvImages.cs new file mode 100644 index 000000000..2a235d10d --- /dev/null +++ b/src/Ombi.TheMovieDbApi/Models/TvImages.cs @@ -0,0 +1,44 @@ +namespace Ombi.Api.TheMovieDb.Models +{ + public class TvImages + { + public Backdrop[] backdrops { get; set; } + public int id { get; set; } + public Logo[] logos { get; set; } + public Poster[] posters { get; set; } + } + + public class Backdrop + { + public float aspect_ratio { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public string file_path { get; set; } + public float vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + public class Logo + { + public float aspect_ratio { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public string file_path { get; set; } + public float vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + + public class Poster + { + public float aspect_ratio { get; set; } + public int height { get; set; } + public string iso_639_1 { get; set; } + public string file_path { get; set; } + public float vote_average { get; set; } + public int vote_count { get; set; } + public int width { get; set; } + } + +} diff --git a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs index 055265701..0db03fef3 100644 --- a/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs +++ b/src/Ombi.TheMovieDbApi/TheMovieDbApi.cs @@ -514,6 +514,14 @@ namespace Ombi.Api.TheMovieDb return Api.Request(request, token); } + public Task GetTvImages(string theMovieDbId, CancellationToken token) + { + var request = new Request($"tv/{theMovieDbId}/images", BaseUri, HttpMethod.Get); + request.AddQueryString("api_key", ApiToken); + + return Api.Request(request, token); + } + private async Task AddDiscoverSettings(Request request) { var settings = await Settings; diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html index 2df8224c4..a830d803c 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.html @@ -46,10 +46,10 @@ --> -
+
- +
diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss index 3712a715d..edc9f0707 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.scss @@ -10,10 +10,6 @@ background-color: $ombi-background-accent; - .top-spacing { - margin-top: 20px; - } - .overview { display: -webkit-box; -webkit-line-clamp: 4; @@ -22,12 +18,8 @@ text-overflow: ellipsis; } - .posterColumn { - width: 100% - } - - ::ng-deep .poster { + cursor: pointer; border-radius: 10px; opacity: 1; display: block; diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts index 96ea683e6..b71756723 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.stories.ts @@ -4,7 +4,6 @@ import { Story, Meta, moduleMetadata } from '@storybook/angular'; import { IRecentlyRequested, RequestType } from '../../interfaces'; import { DetailedCardComponent } from './detailed-card.component'; import { TranslateModule } from "@ngx-translate/core"; -import { ImageService } from '../../services/image.service'; // More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export export default { @@ -39,13 +38,12 @@ NewMovieRequest.args = { available: false, tvPartiallyAvailable: false, requestDate: new Date(2022, 1, 1), - userName: 'John Doe', + username: 'John Doe', userId: '12345', - requestType: RequestType.movie, + type: RequestType.movie, mediaId: '603', overview: 'The Matrix is a movie about a group of people who are forced to fight against a powerful computer system that controls them.', releaseDate: new Date(2020, 1, 1), posterPath: "https://assets.fanart.tv/fanart/movies/603/movieposter/the-matrix-52256ae1021be.jpg" } as IRecentlyRequested, - is4kEnabled: false, }; \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts index 2f552f4c6..873dc5a9b 100644 --- a/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts +++ b/src/Ombi/ClientApp/src/app/components/detailed-card/detailed-card.component.ts @@ -12,9 +12,8 @@ import { Subject, takeUntil } from "rxjs"; }) export class DetailedCardComponent implements OnInit, OnDestroy { @Input() public request: IRecentlyRequested; - @Input() public is4kEnabled: boolean = false; - @Output() public onRequest: EventEmitter = new EventEmitter(); + @Output() public onClick: EventEmitter = new EventEmitter(); public RequestType = RequestType; public loading: false; @@ -25,18 +24,24 @@ import { Subject, takeUntil } from "rxjs"; ngOnInit(): void { if (!this.request.posterPath) { - this.imageService.getMoviePoster(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = x); + switch (this.request.type) { + case RequestType.movie: + this.imageService.getMoviePoster(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = x); + break; + case RequestType.tvShow: + this.imageService.getTmdbTvPoster(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = `https://image.tmdb.org/t/p/w300${x}`); + break; + } } } - public submitRequest(is4k: boolean) { - this.onRequest.emit(is4k); - } - public getStatus(request: IRecentlyRequested) { if (request.available) { return "Common.Available"; } + if (request.tvPartiallyAvailable) { + return "Common.PartiallyAvailable"; + } if (request.approved) { return "Common.Approved"; } else { @@ -44,8 +49,12 @@ import { Subject, takeUntil } from "rxjs"; } } + public click() { + this.onClick.emit(); + } + public getClass(request: IRecentlyRequested) { - if (request.available) { + if (request.available || request.tvPartiallyAvailable) { return "success"; } if (request.approved) { diff --git a/src/Ombi/ClientApp/src/app/components/image/image.component.ts b/src/Ombi/ClientApp/src/app/components/image/image.component.ts index 57099016a..1257d150b 100644 --- a/src/Ombi/ClientApp/src/app/components/image/image.component.ts +++ b/src/Ombi/ClientApp/src/app/components/image/image.component.ts @@ -28,6 +28,8 @@ import { APP_BASE_HREF } from "@angular/common"; private defaultMovie = "/images/default_movie_poster.png"; private defaultMusic = "i/mages/default-music-placeholder.png"; + private alreadyErrored = false; + constructor (@Inject(APP_BASE_HREF) public href: string) { if (this.href.length > 1) { this.baseUrl = this.href; @@ -35,6 +37,9 @@ import { APP_BASE_HREF } from "@angular/common"; } public onError(event: any) { + if (this.alreadyErrored) { + return; + } // set to a placeholder switch(this.type) { case RequestType.movie: @@ -48,10 +53,11 @@ import { APP_BASE_HREF } from "@angular/common"; break; } + this.alreadyErrored = true; // Retry the original image const timeout = setTimeout(() => { - event.target.src = this.src; clearTimeout(timeout); + event.target.src = this.src; }, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000); } } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html index d395e15d6..2119489af 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html +++ b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.html @@ -1,5 +1,5 @@ - + - + \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts index 10b475292..866284cb2 100644 --- a/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts +++ b/src/Ombi/ClientApp/src/app/discover/components/recently-requested-list/recently-requested-list.component.ts @@ -9,6 +9,7 @@ import { FeaturesFacade } from "../../../state/features/features.facade"; import { ResponsiveOptions } from "../carousel.options"; import { RequestServiceV2 } from "app/services/requestV2.service"; import { Subject, takeUntil } from "rxjs"; +import { Router } from "@angular/router"; export enum DiscoverType { Upcoming, @@ -41,7 +42,8 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy { private $loadSub = new Subject(); constructor(private requestService: RequestServiceV2, - private featureFacade: FeaturesFacade) { + private featureFacade: FeaturesFacade, + private router: Router) { Carousel.prototype.onTouchMove = () => {}, this.responsiveOptions = ResponsiveOptions; } @@ -53,11 +55,25 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy { public ngOnInit() { this.loading(); - this.loadData(false); + this.loadData(); } + public navigate(request: IRecentlyRequested) { + this.router.navigate([this.generateDetailsLink(request), request.mediaId]); + } - private loadData(clearExisting: boolean = true) { + private generateDetailsLink(request: IRecentlyRequested): string { + switch (request.type) { + case RequestType.movie: + return `/details/movie/`; + case RequestType.tvShow: + return `/details/tv/`; + case RequestType.album: //Actually artist + return `/details/artist/`; + } + } + + private loadData() { this.requestService.getRecentlyRequested().pipe(takeUntil(this.$loadSub)).subscribe(x => { this.requests = x; this.finishLoading(); diff --git a/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts b/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts index 52fee4427..420c8c6a7 100644 --- a/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts +++ b/src/Ombi/ClientApp/src/app/interfaces/IRecentlyRequested.ts @@ -2,7 +2,6 @@ import { RequestType } from "./IRequestModel"; export interface IRecentlyRequested { requestId: number; - requestType: RequestType; userId: string; username: string; available: boolean; @@ -13,6 +12,7 @@ export interface IRecentlyRequested { releaseDate: Date; approved: boolean; mediaId: string; + type: RequestType; posterPath: string; } \ No newline at end of file diff --git a/src/Ombi/ClientApp/src/app/services/image.service.ts b/src/Ombi/ClientApp/src/app/services/image.service.ts index 348d56f5b..1b85fb15c 100644 --- a/src/Ombi/ClientApp/src/app/services/image.service.ts +++ b/src/Ombi/ClientApp/src/app/services/image.service.ts @@ -33,6 +33,10 @@ export class ImageService extends ServiceHelpers { return this.http.get(`${this.url}poster/tv/${tvdbid}`, { headers: this.headers }); } + public getTmdbTvPoster(tvdbid: number): Observable { + return this.http.get(`${this.url}poster/tv/tmdb/${tvdbid}`, { headers: this.headers }); + } + public getMovieBackground(movieDbId: string): Observable { return this.http.get(`${this.url}background/movie/${movieDbId}`, { headers: this.headers }); } diff --git a/src/Ombi/Controllers/V1/ImagesController.cs b/src/Ombi/Controllers/V1/ImagesController.cs index 0b8a6ce4c..b3b19b70f 100644 --- a/src/Ombi/Controllers/V1/ImagesController.cs +++ b/src/Ombi/Controllers/V1/ImagesController.cs @@ -1,9 +1,11 @@ using System; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Ombi.Api.FanartTv; +using Ombi.Api.TheMovieDb; using Ombi.Config; using Ombi.Core; using Ombi.Core.Engine.Interfaces; @@ -17,11 +19,12 @@ namespace Ombi.Controllers.V1 [ApiController] public class ImagesController : ControllerBase { - public ImagesController(IFanartTvApi fanartTvApi, IApplicationConfigRepository config, + public ImagesController(IFanartTvApi fanartTvApi, IMovieDbApi movieDbApi, IApplicationConfigRepository config, IOptions options, ICacheService c, IImageService imageService, IMovieEngineV2 movieEngineV2, ITVSearchEngineV2 tVSearchEngineV2) { FanartTvApi = fanartTvApi; + _movieDbApi = movieDbApi; Config = config; Options = options.Value; _cache = c; @@ -33,6 +36,8 @@ namespace Ombi.Controllers.V1 private IFanartTvApi FanartTvApi { get; } private IApplicationConfigRepository Config { get; } private LandingPageBackground Options { get; } + + private readonly IMovieDbApi _movieDbApi; private readonly ICacheService _cache; private readonly IImageService _imageService; private readonly IMovieEngineV2 _movieEngineV2; @@ -175,6 +180,23 @@ namespace Ombi.Controllers.V1 return string.Empty; } + [HttpGet("poster/tv/tmdb/{tmdbId}")] + public async Task GetTmdbTvPoster(string tmdbId) + { + var images = await _cache.GetOrAddAsync($"{CacheKeys.TmdbImages}tv{tmdbId}", () => _movieDbApi.GetTvImages(tmdbId, HttpContext.RequestAborted), DateTimeOffset.Now.AddDays(1)); + + if (images?.posters?.Any() ?? false) + { + return images.posters.Select(x => x.file_path).FirstOrDefault(); + } + + if (images?.backdrops?.Any() ?? false) + { + return images.backdrops.Select(x => x.file_path).FirstOrDefault(); + } + return string.Empty; + } + [HttpGet("background/movie/{movieDbId}")] public async Task GetMovieBackground(string movieDbId) {