Skip to content

Commit

Permalink
fix: Fixes default image for recently requested items. (#4767)
Browse files Browse the repository at this point in the history
  • Loading branch information
bernarden committed Oct 7, 2022
1 parent 33d6704 commit 2e6f35f
Show file tree
Hide file tree
Showing 2 changed files with 213 additions and 137 deletions.
@@ -1,92 +1,154 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from "@angular/core";
import {
Component,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
} from "@angular/core";
import { IRecentlyRequested, RequestType } from "../../interfaces";
import { ImageService } from "app/services";
import { Subject, takeUntil } from "rxjs";
import { DomSanitizer, SafeStyle } from "@angular/platform-browser";

@Component({
standalone: false,
selector: 'ombi-detailed-card',
templateUrl: './detailed-card.component.html',
styleUrls: ['./detailed-card.component.scss']
})
export class DetailedCardComponent implements OnInit, OnDestroy {
@Input() public request: IRecentlyRequested;
@Input() public isAdmin: boolean = false;
@Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
@Output() public onApprove: EventEmitter<void> = new EventEmitter<void>();

public RequestType = RequestType;
public loading: false;

private $imageSub = new Subject<void>();

public background: SafeStyle;

constructor(private imageService: ImageService, private sanitizer: DomSanitizer) { }

ngOnInit(): void {
if (!this.request.posterPath) {
this.loadImages();
} else {
this.request.posterPath = `https://image.tmdb.org/t/p/w300${this.request.posterPath}`;
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + this.request.background + ")");
}
}

public getStatus(request: IRecentlyRequested) {
if (request.available) {
return "Common.Available";
}
if (request.tvPartiallyAvailable) {
return "Common.PartiallyAvailable";
}
if (request.approved) {
return "Common.Approved";
} else {
return "Common.Pending";
}
}

public click() {
this.onClick.emit();
}

public approve() {
this.onApprove.emit();
}

public getClass(request: IRecentlyRequested) {
if (request.available || request.tvPartiallyAvailable) {
return "success";
}
if (request.approved) {
return "primary";
} else {
return "info";
}
}

public ngOnDestroy() {
this.$imageSub.next();
this.$imageSub.complete();
standalone: false,
selector: "ombi-detailed-card",
templateUrl: "./detailed-card.component.html",
styleUrls: ["./detailed-card.component.scss"],
})
export class DetailedCardComponent implements OnInit, OnDestroy {
@Input() public request: IRecentlyRequested;
@Input() public isAdmin: boolean = false;
@Output() public onClick: EventEmitter<void> = new EventEmitter<void>();
@Output() public onApprove: EventEmitter<void> = new EventEmitter<void>();

public RequestType = RequestType;
public loading: false;

private $imageSub = new Subject<void>();

public background: SafeStyle;

constructor(
private imageService: ImageService,
private sanitizer: DomSanitizer
) {}

ngOnInit(): void {
this.loadPosterPath();
this.loadBackgroundPath();
}

public getStatus(request: IRecentlyRequested) {
if (request.available) {
return "Common.Available";
}
if (request.tvPartiallyAvailable) {
return "Common.PartiallyAvailable";
}
if (request.approved) {
return "Common.Approved";
} else {
return "Common.Pending";
}
}

public click() {
this.onClick.emit();
}

public approve() {
this.onApprove.emit();
}

public getClass(request: IRecentlyRequested) {
if (request.available || request.tvPartiallyAvailable) {
return "success";
}
if (request.approved) {
return "primary";
} else {
return "info";
}
}

public ngOnDestroy() {
this.$imageSub.next();
this.$imageSub.complete();
}

private loadPosterPath() {
if (this.request.posterPath) {
this.setPosterPath(this.request.posterPath);
return;
}

switch (this.request.type) {
case RequestType.movie:
this.imageService
.getMoviePoster(this.request.mediaId)
.pipe(takeUntil(this.$imageSub))
.subscribe((x) => this.setPosterPath(x));
break;
case RequestType.tvShow:
this.imageService
.getTmdbTvPoster(Number(this.request.mediaId))
.pipe(takeUntil(this.$imageSub))
.subscribe((x) => this.setPosterPath(x));
break;
}
}

private loadImages() {
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.getMovieBackground(this.request.mediaId).pipe(takeUntil(this.$imageSub)).subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(" + 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}`);
this.imageService.getTmdbTvBackground(Number(this.request.mediaId)).pipe(takeUntil(this.$imageSub)).subscribe(x => {
this.background = this.sanitizer.bypassSecurityTrustStyle("linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(https://image.tmdb.org/t/p/w300" + x + ")");
});
break;
}
private setPosterPath(posterPath: string) {
if (!posterPath) {
this.request.posterPath = null;
} else {
this.request.posterPath = this.getImageUrl(posterPath);
}
}

}
private loadBackgroundPath() {
if (this.request.background) {
this.setBackgroundStyle(this.request.background);
return;
}

// Set background style while image path is loading.
this.setBackgroundStyle(null);
switch (this.request.type) {
case RequestType.movie:
this.imageService
.getMovieBackground(this.request.mediaId)
.pipe(takeUntil(this.$imageSub))
.subscribe((x) => this.setBackgroundStyle(x));
break;
case RequestType.tvShow:
this.imageService
.getTmdbTvBackground(Number(this.request.mediaId))
.pipe(takeUntil(this.$imageSub))
.subscribe((x) => this.setBackgroundStyle(x));
break;
}
}

private setBackgroundStyle(backgroundPath: string) {
if (backgroundPath) {
this.background = this.sanitizer.bypassSecurityTrustStyle(
`linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5)), url(${this.getImageUrl(
backgroundPath
)})`
);
} else {
this.background = "linear-gradient(rgba(0,0,0,.5), rgba(0,0,0,.5))";
}
}

private getImageUrl(path: string) {
if (new RegExp("^(http|https)://").test(path)) {
return path;
} else {
return `https://image.tmdb.org/t/p/w300${path}`;
}
}
}
124 changes: 69 additions & 55 deletions src/Ombi/ClientApp/src/app/components/image/image.component.ts
@@ -1,63 +1,77 @@
import { OmbiCommonModules } from "../modules";
import { ChangeDetectionStrategy, Component, ElementRef, Inject, Input, ViewEncapsulation } from "@angular/core";
import {
ChangeDetectionStrategy,
Component,
Inject,
Input,
ViewEncapsulation,
} from "@angular/core";
import { RequestType } from "../../interfaces";
import { APP_BASE_HREF } from "@angular/common";

@Component({
standalone: true,
selector: 'ombi-image',
imports: [...OmbiCommonModules],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './image.component.html',
})
export class ImageComponent {

@Input() public src: string;
@Input() public type: RequestType;

// Attributes from the parent
@Input() public class: string;
@Input() public id: string;
@Input() public alt: string;
@Input() public style: string;

public baseUrl: string = "";

public defaultTv = "/images/default_tv_poster.png";
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;
}
standalone: true,
selector: "ombi-image",
imports: [...OmbiCommonModules],
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "./image.component.html",
})
export class ImageComponent {
@Input() public src: string;
@Input() public type: RequestType;

// Attributes from the parent
@Input() public class: string;
@Input() public id: string;
@Input() public alt: string;
@Input() public style: string;

private baseUrl: string = "";

private defaultTv = "/images/default_tv_poster.png";
private defaultMovie = "/images/default_movie_poster.png";
private defaultMusic = "/images/default-music-placeholder.png";

private maxRetries = 1;
private retriesPerformed = 0;

constructor(@Inject(APP_BASE_HREF) private href: string) {
if (this.href.length > 1) {
this.baseUrl = this.href;
}
}

ngOnInit() {
if (!this.src) {
// Prevent unnecessary error handling when src is not specified.
this.src = this.getPlaceholderImage();
}
}

public onError(event: any) {
event.target.src = this.getPlaceholderImage();

if (!this.src || this.retriesPerformed === this.maxRetries) {
return;
}

// Retry the original image.
this.retriesPerformed++;
const timeout = setTimeout(() => {
clearTimeout(timeout);
event.target.src = this.src;
}, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000);
}

public onError(event: any) {
if (this.alreadyErrored) {
return;
}
// set to a placeholder
switch(this.type) {
case RequestType.movie:
event.target.src = this.baseUrl + this.defaultMovie;
break;
case RequestType.tvShow:
event.target.src = this.baseUrl + this.defaultTv;
break;
case RequestType.album:
event.target.src = this.baseUrl + this.defaultMusic;
break;
}

this.alreadyErrored = true;
// Retry the original image
const timeout = setTimeout(() => {
clearTimeout(timeout);
event.target.src = this.src;
}, Math.floor(Math.random() * (7000 - 1000 + 1)) + 1000);
private getPlaceholderImage() {
switch (this.type) {
case RequestType.movie:
return this.baseUrl + this.defaultMovie;
case RequestType.tvShow:
return this.baseUrl + this.defaultTv;
case RequestType.album:
return this.baseUrl + this.defaultMusic;
}
}
}
}

0 comments on commit 2e6f35f

Please sign in to comment.