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 LidarrQualityProfiles = nameof(LidarrQualityProfiles);
public const string FanartTv = nameof(FanartTv);
public const string TmdbImages = nameof(TmdbImages);
public const string UsersDropdown = nameof(UsersDropdown);
}
}

View file

@ -46,5 +46,6 @@ namespace Ombi.Api.TheMovieDb
Task<List<Language>> GetLanguages(CancellationToken cancellationToken);
Task<List<WatchProvidersResults>> SearchWatchProviders(string media, string searchTerm, 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);
}
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)
{
var settings = await Settings;

View file

@ -46,10 +46,10 @@
</div>
</div> -->
<div class="detailed-container">
<div class="detailed-container" (click)="click()" >
<div class="row">
<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>
</div>
<div class="col-7">

View file

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

View file

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

View file

@ -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<boolean> = new EventEmitter<boolean>();
@Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
public RequestType = RequestType;
public loading: false;
@ -25,18 +24,24 @@ import { Subject, takeUntil } from "rxjs";
ngOnInit(): void {
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);
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) {

View file

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

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">
<ombi-detailed-card [request]="result"></ombi-detailed-card>
<ombi-detailed-card [request]="result" (onClick)="navigate(result)"></ombi-detailed-card>
</ng-template>
</p-carousel>

View file

@ -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<void>();
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();

View file

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

View file

@ -33,6 +33,10 @@ export class ImageService extends ServiceHelpers {
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> {
return this.http.get<string>(`${this.url}background/movie/${movieDbId}`, { headers: this.headers });
}

View file

@ -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<LandingPageBackground> 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<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}")]
public async Task<string> GetMovieBackground(string movieDbId)
{