Almost nailed it

This commit is contained in:
tidusjar 2022-08-08 22:13:35 +01:00
commit 0a72c2ade9
15 changed files with 133 additions and 32 deletions

View file

@ -108,7 +108,7 @@ namespace Ombi.Core.Services
}); });
} }
return model.OrderByDescending(x => x.RequestDate); return model.OrderByDescending(x => x.ReleaseDate);
} }
} }
} }

View file

@ -21,6 +21,7 @@ namespace Ombi.Helpers
public const string LidarrRootFolders = nameof(LidarrRootFolders); public const string LidarrRootFolders = nameof(LidarrRootFolders);
public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles); public const string LidarrQualityProfiles = nameof(LidarrQualityProfiles);
public const string FanartTv = nameof(FanartTv); public const string FanartTv = nameof(FanartTv);
public const string TmdbImages = nameof(TmdbImages);
public const string UsersDropdown = nameof(UsersDropdown); public const string UsersDropdown = nameof(UsersDropdown);
} }
} }

View file

@ -46,5 +46,6 @@ namespace Ombi.Api.TheMovieDb
Task<List<Language>> GetLanguages(CancellationToken cancellationToken); Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken); Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, CancellationToken cancellationToken);
Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken); Task<List<MovieDbSearchResult>> AdvancedSearch(DiscoverModel model, int page, CancellationToken cancellationToken);
Task<TvImages> GetTvImages(string theMovieDbId, CancellationToken token);
} }
} }

View file

@ -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; }
}
}

View file

@ -514,6 +514,14 @@ namespace Ombi.Api.TheMovieDb
return Api.Request<WatchProviders>(request, token); return Api.Request<WatchProviders>(request, token);
} }
public Task<TvImages> GetTvImages(string theMovieDbId, CancellationToken token)
{
var request = new Request($"tv/{theMovieDbId}/images", BaseUri, HttpMethod.Get);
request.AddQueryString("api_key", ApiToken);
return Api.Request<TvImages>(request, token);
}
private async Task AddDiscoverSettings(Request request) private async Task AddDiscoverSettings(Request request)
{ {
var settings = await Settings; var settings = await Settings;

View file

@ -46,10 +46,10 @@
</div> </div>
</div> --> </div> -->
<div class="detailed-container"> <div class="detailed-container" (click)="click()" >
<div class="row"> <div class="row">
<div class="col-5 posterColumn"> <div class="col-5 posterColumn">
<ombi-image [src]="request.posterPath" [type]="request.requestType" class="poster" alt="{{request.title}}"> <ombi-image [src]="request.posterPath" [type]="request.type" class="poster" alt="{{request.title}}">
</ombi-image> </ombi-image>
</div> </div>
<div class="col-7"> <div class="col-7">

View file

@ -10,10 +10,6 @@
background-color: $ombi-background-accent; background-color: $ombi-background-accent;
.top-spacing {
margin-top: 20px;
}
.overview { .overview {
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 4; -webkit-line-clamp: 4;
@ -22,12 +18,8 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.posterColumn {
width: 100%
}
::ng-deep .poster { ::ng-deep .poster {
cursor: pointer;
border-radius: 10px; border-radius: 10px;
opacity: 1; opacity: 1;
display: block; display: block;

View file

@ -4,7 +4,6 @@ import { Story, Meta, moduleMetadata } from '@storybook/angular';
import { IRecentlyRequested, RequestType } from '../../interfaces'; import { IRecentlyRequested, RequestType } from '../../interfaces';
import { DetailedCardComponent } from './detailed-card.component'; import { DetailedCardComponent } from './detailed-card.component';
import { TranslateModule } from "@ngx-translate/core"; 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 // More on default export: https://storybook.js.org/docs/angular/writing-stories/introduction#default-export
export default { export default {
@ -39,13 +38,12 @@ NewMovieRequest.args = {
available: false, available: false,
tvPartiallyAvailable: false, tvPartiallyAvailable: false,
requestDate: new Date(2022, 1, 1), requestDate: new Date(2022, 1, 1),
userName: 'John Doe', username: 'John Doe',
userId: '12345', userId: '12345',
requestType: RequestType.movie, type: RequestType.movie,
mediaId: '603', 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.', 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), releaseDate: new Date(2020, 1, 1),
posterPath: "https://assets.fanart.tv/fanart/movies/603/movieposter/the-matrix-52256ae1021be.jpg" posterPath: "https://assets.fanart.tv/fanart/movies/603/movieposter/the-matrix-52256ae1021be.jpg"
} as IRecentlyRequested, } as IRecentlyRequested,
is4kEnabled: false,
}; };

View file

@ -12,9 +12,8 @@ import { Subject, takeUntil } from "rxjs";
}) })
export class DetailedCardComponent implements OnInit, OnDestroy { export class DetailedCardComponent implements OnInit, OnDestroy {
@Input() public request: IRecentlyRequested; @Input() public request: IRecentlyRequested;
@Input() public is4kEnabled: boolean = false;
@Output() public onRequest: EventEmitter<boolean> = new EventEmitter<boolean>(); @Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
public RequestType = RequestType; public RequestType = RequestType;
public loading: false; public loading: false;
@ -25,18 +24,24 @@ import { Subject, takeUntil } from "rxjs";
ngOnInit(): void { ngOnInit(): void {
if (!this.request.posterPath) { if (!this.request.posterPath) {
switch (this.request.type) {
case RequestType.movie:
this.imageService.getMoviePoster(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => this.request.posterPath = x); 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) { public getStatus(request: IRecentlyRequested) {
if (request.available) { if (request.available) {
return "Common.Available"; return "Common.Available";
} }
if (request.tvPartiallyAvailable) {
return "Common.PartiallyAvailable";
}
if (request.approved) { if (request.approved) {
return "Common.Approved"; return "Common.Approved";
} else { } else {
@ -44,8 +49,12 @@ import { Subject, takeUntil } from "rxjs";
} }
} }
public click() {
this.onClick.emit();
}
public getClass(request: IRecentlyRequested) { public getClass(request: IRecentlyRequested) {
if (request.available) { if (request.available || request.tvPartiallyAvailable) {
return "success"; return "success";
} }
if (request.approved) { if (request.approved) {

View file

@ -28,6 +28,8 @@ import { APP_BASE_HREF } from "@angular/common";
private defaultMovie = "/images/default_movie_poster.png"; private defaultMovie = "/images/default_movie_poster.png";
private defaultMusic = "i/mages/default-music-placeholder.png"; private defaultMusic = "i/mages/default-music-placeholder.png";
private alreadyErrored = false;
constructor (@Inject(APP_BASE_HREF) public href: string) { constructor (@Inject(APP_BASE_HREF) public href: string) {
if (this.href.length > 1) { if (this.href.length > 1) {
this.baseUrl = this.href; this.baseUrl = this.href;
@ -35,6 +37,9 @@ import { APP_BASE_HREF } from "@angular/common";
} }
public onError(event: any) { public onError(event: any) {
if (this.alreadyErrored) {
return;
}
// set to a placeholder // set to a placeholder
switch(this.type) { switch(this.type) {
case RequestType.movie: case RequestType.movie:
@ -48,10 +53,11 @@ import { APP_BASE_HREF } from "@angular/common";
break; break;
} }
this.alreadyErrored = true;
// Retry the original image // Retry the original image
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
event.target.src = this.src;
clearTimeout(timeout); clearTimeout(timeout);
event.target.src = this.src;
}, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000); }, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000);
} }
} }

View file

@ -1,5 +1,5 @@
<p-carousel #carousel [numVisible]="4" [numScroll]="7" [page]="0" [value]="requests" [responsiveOptions]="responsiveOptions"> <p-carousel #carousel [numVisible]="10" [numScroll]="10" [page]="0" [value]="requests" [responsiveOptions]="responsiveOptions">
<ng-template let-result pTemplate="item"> <ng-template let-result pTemplate="item">
<ombi-detailed-card [request]="result"></ombi-detailed-card> <ombi-detailed-card [request]="result" (onClick)="navigate(result)"></ombi-detailed-card>
</ng-template> </ng-template>
</p-carousel> </p-carousel>

View file

@ -9,6 +9,7 @@ import { FeaturesFacade } from "../../../state/features/features.facade";
import { ResponsiveOptions } from "../carousel.options"; import { ResponsiveOptions } from "../carousel.options";
import { RequestServiceV2 } from "app/services/requestV2.service"; import { RequestServiceV2 } from "app/services/requestV2.service";
import { Subject, takeUntil } from "rxjs"; import { Subject, takeUntil } from "rxjs";
import { Router } from "@angular/router";
export enum DiscoverType { export enum DiscoverType {
Upcoming, Upcoming,
@ -41,7 +42,8 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
private $loadSub = new Subject<void>(); private $loadSub = new Subject<void>();
constructor(private requestService: RequestServiceV2, constructor(private requestService: RequestServiceV2,
private featureFacade: FeaturesFacade) { private featureFacade: FeaturesFacade,
private router: Router) {
Carousel.prototype.onTouchMove = () => {}, Carousel.prototype.onTouchMove = () => {},
this.responsiveOptions = ResponsiveOptions; this.responsiveOptions = ResponsiveOptions;
} }
@ -53,11 +55,25 @@ export class RecentlyRequestedListComponent implements OnInit, OnDestroy {
public ngOnInit() { public ngOnInit() {
this.loading(); 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.requestService.getRecentlyRequested().pipe(takeUntil(this.$loadSub)).subscribe(x => {
this.requests = x; this.requests = x;
this.finishLoading(); this.finishLoading();

View file

@ -2,7 +2,6 @@ import { RequestType } from "./IRequestModel";
export interface IRecentlyRequested { export interface IRecentlyRequested {
requestId: number; requestId: number;
requestType: RequestType;
userId: string; userId: string;
username: string; username: string;
available: boolean; available: boolean;
@ -13,6 +12,7 @@ export interface IRecentlyRequested {
releaseDate: Date; releaseDate: Date;
approved: boolean; approved: boolean;
mediaId: string; mediaId: string;
type: RequestType;
posterPath: string; posterPath: string;
} }

View file

@ -33,6 +33,10 @@ export class ImageService extends ServiceHelpers {
return this.http.get<string>(`${this.url}poster/tv/${tvdbid}`, { headers: this.headers }); return this.http.get<string>(`${this.url}poster/tv/${tvdbid}`, { headers: this.headers });
} }
public getTmdbTvPoster(tvdbid: number): Observable<string> {
return this.http.get<string>(`${this.url}poster/tv/tmdb/${tvdbid}`, { headers: this.headers });
}
public getMovieBackground(movieDbId: string): Observable<string> { public getMovieBackground(movieDbId: string): Observable<string> {
return this.http.get<string>(`${this.url}background/movie/${movieDbId}`, { headers: this.headers }); return this.http.get<string>(`${this.url}background/movie/${movieDbId}`, { headers: this.headers });
} }

View file

@ -1,9 +1,11 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Ombi.Api.FanartTv; using Ombi.Api.FanartTv;
using Ombi.Api.TheMovieDb;
using Ombi.Config; using Ombi.Config;
using Ombi.Core; using Ombi.Core;
using Ombi.Core.Engine.Interfaces; using Ombi.Core.Engine.Interfaces;
@ -17,11 +19,12 @@ namespace Ombi.Controllers.V1
[ApiController] [ApiController]
public class ImagesController : ControllerBase public class ImagesController : ControllerBase
{ {
public ImagesController(IFanartTvApi fanartTvApi, IApplicationConfigRepository config, public ImagesController(IFanartTvApi fanartTvApi, IMovieDbApi movieDbApi, IApplicationConfigRepository config,
IOptions<LandingPageBackground> options, ICacheService c, IImageService imageService, IOptions<LandingPageBackground> options, ICacheService c, IImageService imageService,
IMovieEngineV2 movieEngineV2, ITVSearchEngineV2 tVSearchEngineV2) IMovieEngineV2 movieEngineV2, ITVSearchEngineV2 tVSearchEngineV2)
{ {
FanartTvApi = fanartTvApi; FanartTvApi = fanartTvApi;
_movieDbApi = movieDbApi;
Config = config; Config = config;
Options = options.Value; Options = options.Value;
_cache = c; _cache = c;
@ -33,6 +36,8 @@ namespace Ombi.Controllers.V1
private IFanartTvApi FanartTvApi { get; } private IFanartTvApi FanartTvApi { get; }
private IApplicationConfigRepository Config { get; } private IApplicationConfigRepository Config { get; }
private LandingPageBackground Options { get; } private LandingPageBackground Options { get; }
private readonly IMovieDbApi _movieDbApi;
private readonly ICacheService _cache; private readonly ICacheService _cache;
private readonly IImageService _imageService; private readonly IImageService _imageService;
private readonly IMovieEngineV2 _movieEngineV2; private readonly IMovieEngineV2 _movieEngineV2;
@ -175,6 +180,23 @@ namespace Ombi.Controllers.V1
return string.Empty; return string.Empty;
} }
[HttpGet("poster/tv/tmdb/{tmdbId}")]
public async Task<string> 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}")] [HttpGet("background/movie/{movieDbId}")]
public async Task<string> GetMovieBackground(string movieDbId) public async Task<string> GetMovieBackground(string movieDbId)
{ {