tidusjar 3 months ago
parent 5a0c15dfbc
commit 55ffa68d9d

@ -1,43 +1,55 @@
<div class="small-middle-container"> <div class="small-middle-container">
<div class="section"> <div class="section">
<h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{'Discovery.Genres' | translate}}</h2> @defer {
<genre-button-select class="collapse show" id="genreCollapse"></genre-button-select> <h2 id="genreHeading" data-toggle="collapse" href="#genreCollapse" role="button">{{ 'Discovery.Genres' | translate }}</h2>
</div> <genre-button-select class="collapse show" id="genreCollapse"></genre-button-select>
<div class="section"> }
<h2>{{'Discovery.RecentlyRequestedTab' | translate}}</h2> </div>
<div> @defer {
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list> <div class="section">
</div> <h2>{{ 'Discovery.RecentlyRequestedTab' | translate }}</h2>
</div> <div>
<ombi-recently-list [id]="'recentlyRequested'"></ombi-recently-list>
</div>
</div>
}
<div class="section" [hidden]="!showSeasonal"> <div class="section" [hidden]="!showSeasonal">
<h2>{{'Discovery.SeasonalTab' | translate}}</h2> <h2>{{ 'Discovery.SeasonalTab' | translate }}</h2>
<div> <div>
<carousel-list [id]="'seasonal'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Seasonal" (movieCount)="setSeasonalMovieCount($event)"></carousel-list> <carousel-list
</div> [id]="'seasonal'"
</div> [isAdmin]="isAdmin"
[discoverType]="DiscoverType.Seasonal"
(movieCount)="setSeasonalMovieCount($event)"
></carousel-list>
</div>
</div>
<div class="section"> <div class="section">
<h2>{{'Discovery.PopularTab' | translate}}</h2> <h2>{{ 'Discovery.PopularTab' | translate }}</h2>
<div> <div>
<carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list> @defer {
</div> <carousel-list [id]="'popular'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Popular"></carousel-list>
}
</div>
</div>
</div> <div class="section">
<h2>{{ 'Discovery.TrendingTab' | translate }}</h2>
<div>
@defer {
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list>
}
</div>
</div>
<div class="section"> <div class="section">
<h2>{{'Discovery.TrendingTab' | translate}}</h2> <h2>{{ 'Discovery.UpcomingTab' | translate }}</h2>
<div > <div>
<carousel-list [id]="'trending'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Trending"></carousel-list> @defer {
</div> <carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
}
</div> </div>
</div>
<div class="section"> </div>
<h2>{{'Discovery.UpcomingTab' | translate}}</h2>
<div>
<carousel-list [id]="'upcoming'" [isAdmin]="isAdmin" [discoverType]="DiscoverType.Upcoming"></carousel-list>
</div>
</div>
</div>

@ -1,300 +1,417 @@
<div *ngIf="!movie" class="justify-content-md-center top-spacing loading-spinner"> <div *ngIf="!movie" class="justify-content-md-center top-spacing loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner> <mat-spinner [color]="'accent'"></mat-spinner>
</div> </div>
<div *ngIf="movie" class="main-content-container"> <div *ngIf="movie" class="main-content-container">
<top-banner
[background]="movie.background"
[available]="movie.available"
[title]="movie.title"
[releaseDate]="movie.releaseDate"
[tagline]="movie.tagline"
></top-banner>
<div class="social-icons-container">
<social-icons
[homepage]="movie.homepage"
[theMoviedbId]="movie.id"
[hasTrailer]="movie.videos?.results?.length > 0"
[imdbId]="movie.imdbId"
[twitter]="movie.externalIds?.twitterId"
[facebook]="movie.externalIds?.facebookId"
[instagram]="movie.externalIds?.instagramId"
[available]="movie.available"
[isAdmin]="isAdmin"
[canShowAdvanced]="showAdvanced && movieRequest"
[type]="requestType"
[has4KRequest]="movie.has4KRequest"
(openTrailer)="openDialog()"
(onAdvancedOptions)="openAdvancedOptions()"
(onReProcessRequest)="reProcessRequest(false)"
(onReProcess4KRequest)="reProcessRequest(true)"
>
</social-icons>
</div>
<section id="info-wrapper">
<div class="small-middle-container">
<div class="row justify-content-center justify-content-sm-start header-container">
<div class="details-poster-container">
<media-poster [posterPath]="movie.posterPath"></media-poster>
</div>
<top-banner [background]="movie.background" [available]="movie.available" [title]="movie.title" <!--Next to poster-->
[releaseDate]="movie.releaseDate" [tagline]="movie.tagline"></top-banner> <div class="details-button-container">
<div class="social-icons-container"> <div class="col-12 media-row">
<span *ngIf="movie.available || movie.available4K">
<a
id="viewOnPlexButton"
*ngIf="movie.plexUrl"
href="{{ movie.plexUrl }}"
mat-raised-button
target="_blank"
class="btn-spacing viewon-btn plex"
>
{{ 'Search.ViewOnPlex' | translate }}
<i class="far fa-play-circle fa-2x"></i>
</a>
<a
id="viewOnEmbyButton"
*ngIf="movie.embyUrl"
href="{{ movie.embyUrl }}"
mat-raised-button
target="_blank"
class="btn-spacing viewon-btn emby"
>
{{ 'Search.ViewOnEmby' | translate }}
<i class="far fa-play-circle fa-2x"></i>
</a>
<a
id="viewOnJellyfinButton"
*ngIf="movie.jellyfinUrl"
href="{{ movie.jellyfinUrl }}"
mat-raised-button
target="_blank"
class="btn-spacing viewon-btn jellyfin"
>
{{ 'Search.ViewOnJellyfin' | translate }}
<i class="far fa-play-circle fa-2x"></i>
</a>
</span>
<!-- Regular Movie Status -->
<button
mat-raised-button
class="btn-green btn-spacing"
id="availableBtn"
*ngIf="movie.available && !movie.plexUrl && !movie.embyUrl && !movie.jellyfinUrl"
>
{{ 'Common.Available' | translate }}
</button>
<span *ngIf="!movie.available">
<span *ngIf="movie.requested || movie.approved; then requestedBtn; else notRequestedBtn"></span>
<ng-template #requestedBtn>
<button
id="requestedBtn"
mat-raised-button
*ngIf="!hasRequest || (hasRequest && movieRequest && !movieRequest.denied)"
class="btn-spacing"
color="warn"
[disabled]
>
<i class="fas fa-check"></i>
{{ 'Common.Requested' | translate }}
</button>
</ng-template>
<ng-template #notRequestedBtn>
<button
*ngIf="!movie.requested"
id="requestBtn"
mat-raised-button
class="btn-spacing"
color="primary"
(click)="request(false)"
>
<i *ngIf="movie.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed" class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing" class="fas fa-check"></i>
{{ 'Common.Request' | translate }}
</button>
</ng-template>
</span>
<social-icons [homepage]="movie.homepage" [theMoviedbId]="movie.id" <!-- 4k Status -->
[hasTrailer]="movie.videos?.results?.length > 0" [imdbId]="movie.imdbId" <span *ngIf="is4KEnabled">
[twitter]="movie.externalIds?.twitterId" [facebook]="movie.externalIds?.facebookId" <span *permission="roleName4k">
[instagram]="movie.externalIds?.instagramId" [available]="movie.available" [isAdmin]="isAdmin" <button mat-raised-button class="btn-green btn-spacing" id="availableBtn4k" *ngIf="movie.available4K">
[canShowAdvanced]="showAdvanced && movieRequest" [type]="requestType" [has4KRequest]="movie.has4KRequest" {{ 'Common.Available4K' | translate }}
(openTrailer)="openDialog()" (onAdvancedOptions)="openAdvancedOptions()" </button>
(onReProcessRequest)="reProcessRequest(false)" (onReProcess4KRequest)="reProcessRequest(true)">
</social-icons>
</div> <span *ngIf="!movie.available4K">
<section id="info-wrapper"> <span *ngIf="movie.has4KRequest || movie.approved4K; then requestedBtn4K; else notRequestedBtn4K"></span>
<div class="small-middle-container"> <ng-template #requestedBtn4K>
<button
id="requestedBtn4K"
mat-raised-button
*ngIf="movieRequest && !movieRequest.denied4K"
class="btn-spacing"
color="warn"
[disabled]
>
<i class="fas fa-check"></i>
{{ 'Common.Requested4K' | translate }}
</button>
</ng-template>
<ng-template #notRequestedBtn4K>
<button
*ngIf="!movie.has4KRequest"
id="requestBtn4k"
mat-raised-button
class="btn-spacing"
color="primary"
(click)="request(true)"
>
<i *ngIf="movie.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed" class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing" class="fas fa-check"></i>
{{ 'Common.Request4K' | translate }}
</button>
</ng-template>
</span>
</span>
</span>
<div class="row justify-content-center justify-content-sm-start header-container"> <span *ngIf="movieRequest?.showSubscribe">
<div class="details-poster-container"> <button *ngIf="!movieRequest?.subscribed" (click)="notify()" id="notifyBtn" mat-raised-button class="btn-spacing">
<media-poster [posterPath]=movie.posterPath></media-poster> <i class="fas fa-bell"></i> {{ 'Requests.Notify' | translate }}
</div> </button>
<button *ngIf="movieRequest?.subscribed" (click)="unNotify()" id="unnotifyBtn" mat-raised-button class="btn-spacing">
<i class="fas fa-bell-slash"></i> {{ 'Requests.RemoveNotification' | translate }}
</button>
</span>
<!--Next to poster--> <span *ngIf="isAdmin && hasRequest">
<div class="details-button-container"> <button
<div class="col-12 media-row"> id="approveBtn"
<span *ngIf="movie.available || movie.available4K"> *ngIf="!movie.approved && movie.requested"
<a id="viewOnPlexButton" *ngIf="movie.plexUrl" href="{{movie.plexUrl}}" mat-raised-button (click)="approve(false)"
target="_blank" class="btn-spacing viewon-btn plex"> mat-raised-button
{{'Search.ViewOnPlex' | translate}} class="btn-spacing"
<i class="far fa-play-circle fa-2x"></i> color="accent"
</a> >
<a id="viewOnEmbyButton" *ngIf="movie.embyUrl" href="{{movie.embyUrl}}" mat-raised-button <i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }}
target="_blank" class="btn-spacing viewon-btn emby"> </button>
{{'Search.ViewOnEmby' | translate}} <button
<i class="far fa-play-circle fa-2x"></i> id="markAvailableBtn"
</a> *ngIf="!movie.available && movie.requested"
<a id="viewOnJellyfinButton" *ngIf="movie.jellyfinUrl" href="{{movie.jellyfinUrl}}" (click)="markAvailable(false)"
mat-raised-button target="_blank" class="btn-spacing viewon-btn jellyfin"> mat-raised-button
{{'Search.ViewOnJellyfin' | translate}} class="btn-spacing"
<i class="far fa-play-circle fa-2x"></i> color="accent"
</a> >
</span> <i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }}
<!-- Regular Movie Status --> </button>
<button mat-raised-button class="btn-green btn-spacing" id="availableBtn" <button
*ngIf="movie.available && !movie.plexUrl && !movie.embyUrl && !movie.jellyfinUrl"> {{ id="markUnavailableBtn"
'Common.Available' | translate }}</button> *ngIf="movie.available && movie.requested"
<span *ngIf="!movie.available"> (click)="markUnavailable(false)"
<span mat-raised-button
*ngIf="movie.requested || movie.approved; then requestedBtn else notRequestedBtn"></span> class="btn-spacing"
<ng-template #requestedBtn> color="accent"
<button id="requestedBtn" mat-raised-button >
*ngIf="!hasRequest || hasRequest && movieRequest && !movieRequest.denied" <i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }}
class="btn-spacing" color="warn" [disabled]> </button>
<i class="fas fa-check"></i>
{{ 'Common.Requested' | translate }}
</button>
</ng-template>
<ng-template #notRequestedBtn>
<button *ngIf="!movie.requested" id="requestBtn" mat-raised-button class="btn-spacing"
color="primary" (click)="request(false)">
<i *ngIf="movie.requestProcessing" class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed" class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing" class="fas fa-check"></i>
{{'Common.Request' | translate }}
</button>
</ng-template>
</span>
<!-- 4k Status --> <!-- 4k -->
<span *ngIf="is4KEnabled"> <span *ngIf="is4KEnabled">
<span *permission="roleName4k"> <span *permission="roleName4k">
<button mat-raised-button class="btn-green btn-spacing" id="availableBtn4k" <button
*ngIf="movie.available4K"> {{ id="approve4kBtn"
'Common.Available4K' | translate }} *ngIf="!movie.approved4K && movie.has4KRequest"
</button> (click)="approve(true)"
mat-raised-button
class="btn-spacing"
color="accent"
>
<i class="fas fa-plus"></i> {{ 'Common.Approve4K' | translate }}
</button>
<button
id="markAvailable4kBtn"
*ngIf="!movie.available4K && movie.has4KRequest"
(click)="markAvailable(true)"
mat-raised-button
class="btn-spacing"
color="accent"
>
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable4K' | translate }}
</button>
<button
id="markUnavailable4kBtn"
*ngIf="movie.available4K"
(click)="markUnavailable(true)"
mat-raised-button
class="btn-spacing"
color="accent"
>
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable4K' | translate }}
</button>
</span>
</span>
<span *ngIf="!movie.available4K"> <button
<span id="denyBtn"
*ngIf="movie.has4KRequest || movie.approved4K; then requestedBtn4K else notRequestedBtn4K"></span> *ngIf="!movieRequest.denied && movie.requested"
<ng-template #requestedBtn4K> mat-raised-button
<button id="requestedBtn4K" mat-raised-button class="btn-spacing"
*ngIf="movieRequest && !movieRequest.denied4K" class="btn-spacing" color="warn"
color="warn" [disabled]> (click)="deny(false)"
<i class="fas fa-check"></i> >
{{ 'Common.Requested4K' | translate }} <i class="fas fa-times"></i> {{ 'Requests.Deny' | translate }}
</button> </button>
</ng-template>
<ng-template #notRequestedBtn4K>
<button *ngIf="!movie.has4KRequest" id="requestBtn4k" mat-raised-button
class="btn-spacing" color="primary" (click)="request(true)">
<i *ngIf="movie.requestProcessing"
class="fas fa-circle-notch fa-spin fa-fw"></i>
<i *ngIf="!movie.requestProcessing && !movie.processed"
class="fas fa-plus"></i>
<i *ngIf="movie.processed && !movie.requestProcessing"
class="fas fa-check"></i>
{{'Common.Request4K' | translate }}
</button>
</ng-template>
</span>
</span>
</span>
<span *ngIf="movieRequest?.showSubscribe"> <button
<button *ngIf="!movieRequest?.subscribed" (click)="notify()" id="notifyBtn" id="deniedButton"
mat-raised-button class="btn-spacing"> <i class="fas fa-bell"></i> *ngIf="movieRequest && movieRequest.denied"
{{ 'Requests.Notify' | translate }}</button> [matTooltip]="movieRequest.deniedReason"
<button *ngIf="movieRequest?.subscribed" (click)="unNotify()" id="unnotifyBtn" mat-raised-button
mat-raised-button class="btn-spacing"> <i class="fas fa-bell-slash"></i> class="btn-spacing"
{{ 'Requests.RemoveNotification' | translate }}</button> color="warn"
</span> >
<i class="fas fa-times"></i> {{ 'MediaDetails.Denied' | translate }}
</button>
</span>
<span *ngIf="isAdmin && hasRequest"> <button id="reportIssueBtn" mat-raised-button class="btn-spacing" color="danger" (click)="issue()" *ngIf="issuesEnabled">
<button id="approveBtn" *ngIf="!movie.approved && movie.requested" (click)="approve(false)" <i class="fas fa-exclamation"></i> {{ 'Requests.ReportIssue' | translate }}
mat-raised-button class="btn-spacing" color="accent"> </button>
<i class="fas fa-plus"></i> {{ 'Common.Approve' | translate }} <button
</button> id="viewCollectionBtn"
<button id="markAvailableBtn" *ngIf="!movie.available && movie.requested" *ngIf="movie.belongsToCollection"
(click)="markAvailable(false)" mat-raised-button class="btn-spacing" color="accent"> [routerLink]="'/discover/collection/' + movie.belongsToCollection.id"
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable' | translate }} mat-raised-button
</button> class="btn-spacing"
<button id="markUnavailableBtn" *ngIf="movie.available && movie.requested" >
(click)="markUnavailable(false)" mat-raised-button class="btn-spacing" color="accent"> <i class="fas fa-list"></i> {{ 'MediaDetails.ViewCollection' | translate }}
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable' | translate }} </button>
</button> </div>
</div>
</div>
<!-- 4k --> <div class="row">
<span *ngIf="is4KEnabled"> <div class="col-12 col-md-2">
<span *permission="roleName4k"> <mat-card class="mat-elevation-z8">
<button id="approve4kBtn" *ngIf="!movie.approved4K && movie.has4KRequest" <mat-card-content>
(click)="approve(true)" mat-raised-button class="btn-spacing" color="accent"> <movie-information-panel
<i class="fas fa-plus"></i> {{ 'Common.Approve4K' | translate }} [movie]="movie"
</button> [request]="movieRequest"
<button id="markAvailable4kBtn" *ngIf="!movie.available4K && movie.has4KRequest" [advancedOptions]="showAdvanced"
(click)="markAvailable(true)" mat-raised-button class="btn-spacing" ></movie-information-panel>
color="accent"> </mat-card-content>
<i class="fas fa-plus"></i> {{ 'Requests.MarkAvailable4K' | translate }} </mat-card>
</button> </div>
<button id="markUnavailable4kBtn" *ngIf="movie.available4K"
(click)="markUnavailable(true)" mat-raised-button class="btn-spacing"
color="accent">
<i class="fas fa-minus"></i> {{ 'Requests.MarkUnavailable4K' | translate }}
</button>
</span>
</span>
<button id="denyBtn" *ngIf="!movieRequest.denied && movie.requested" mat-raised-button <div class="col-12 col-md-10">
class="btn-spacing" color="warn" (click)="deny(false)"> <div class="row">
<i class="fas fa-times"></i> {{'Requests.Deny' | translate }} <div class="col-12">
</button> <mat-card class="mat-elevation-z8 spacing-below">
<mat-card-content>
{{ movie.overview }}
</mat-card-content>
</mat-card>
</div>
</div>
<button id="deniedButton" *ngIf="movieRequest && movieRequest.denied" @defer{
[matTooltip]="movieRequest.deniedReason" mat-raised-button class="btn-spacing" <div class="row">
color="warn"> <div class="col-12">
<i class="fas fa-times"></i> {{'MediaDetails.Denied' | translate }} <cast-carousel [cast]="movie.credits.cast"></cast-carousel>
</button> </div>
</span> </div>
<button id="reportIssueBtn" mat-raised-button class="btn-spacing" color="danger" <div class="row">
(click)="issue()" *ngIf="issuesEnabled"> <div class="col-12">
<i class="fas fa-exclamation"></i> {{'Requests.ReportIssue' | translate }} <crew-carousel [crew]="movie.credits.crew"></crew-carousel>
</button> </div>
<button id="viewCollectionBtn" *ngIf="movie.belongsToCollection" </div>
[routerLink]="'/discover/collection/' + movie.belongsToCollection.id" mat-raised-button }
class="btn-spacing">
<i class="fas fa-list"></i> {{'MediaDetails.ViewCollection' | translate}}
</button>
</div>
</div>
</div>
<div class="row"> <!-- <div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
<div class="col-12 col-md-2">
<mat-card class="mat-elevation-z8">
<mat-card-content>
<movie-information-panel [movie]="movie" [request]="movieRequest"
[advancedOptions]="showAdvanced"></movie-information-panel>
</mat-card-content>
</mat-card>
</div>
<div class="col-12 col-md-10">
<div class="row">
<div class="col-12">
<mat-card class=" mat-elevation-z8 spacing-below">
<mat-card-content>
{{movie.overview}}
</mat-card-content>
</mat-card>
</div>
</div>
<div class="row">
<div class="col-12">
<cast-carousel [cast]="movie.credits.cast"></cast-carousel>
</div>
</div>
<div class="row">
<div class="col-12">
<crew-carousel [crew]="movie.credits.crew"></crew-carousel>
</div>
</div>
<!-- <div class="row card-spacer" *ngIf="movie.videos?.results?.length > 0">
<div class="col-md-6" *ngFor="let video of movie.videos?.results"> <div class="col-md-6" *ngFor="let video of movie.videos?.results">
<iframe width="100%" height="315px" [src]="'https://www.youtube.com/embed/' + video.key | safe" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe> <iframe width="100%" height="315px" [src]="'https://www.youtube.com/embed/' + video.key | safe" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
</div> </div>
</div> --> </div> -->
@defer {
<div class="row" *ngIf="movie.videos?.results?.length > 0">
<div class="col-12">
<mat-card class="mat-elevation-z8">
<mat-card-header>{{ 'MediaDetails.Trailers' | translate }}</mat-card-header>
<mat-card-content>
<p-carousel class="no-indicator" [numVisible]="2" [numScroll]="10" [page]="0" [value]="movie.videos?.results">
<ng-template let-result pTemplate="item">
<iframe
width="98%"
height="315px"
[src]="'https://www.youtube.com/embed/' + result.key | safe"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</ng-template>
</p-carousel>
</mat-card-content>
</mat-card>
</div>
</div>
}
<div class="row" *ngIf="movie.videos?.results?.length > 0"> <div class="row">
<div class="col-12"> <div class="col-12">
<mat-card class="mat-elevation-z8"> <div class="issuesPanel">
<mat-card-header>{{'MediaDetails.Trailers' | translate}}</mat-card-header> <issues-panel [providerId]="movie.imdbId" [isAdmin]="isAdmin"></issues-panel>
<mat-card-content> </div>
<p-carousel class="no-indicator" [numVisible]="2" [numScroll]="10" [page]="0"
[value]="movie.videos?.results">
<ng-template let-result pTemplate="item">
<iframe width="98%" height="315px"
[src]="'https://www.youtube.com/embed/' + result.key | safe"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen></iframe>
</ng-template>
</p-carousel>
</mat-card-content>
</mat-card>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="issuesPanel">
<issues-panel [providerId]="movie.imdbId" [isAdmin]="isAdmin"></issues-panel>
</div>
<mat-accordion class=" mat-elevation-z8 spacing-below "> @defer (on viewport) {
<mat-expansion-panel *ngIf="movie.recommendations?.results?.length> 0"> <mat-accordion class="mat-elevation-z8 spacing-below">
<mat-expansion-panel-header> <mat-expansion-panel *ngIf="movie.recommendations?.results?.length > 0">
<mat-panel-title> <mat-expansion-panel-header>
{{'MediaDetails.RecommendationsTitle' | translate}} <mat-panel-title>
</mat-panel-title> {{ 'MediaDetails.RecommendationsTitle' | translate }}
</mat-expansion-panel-header> </mat-panel-title>
</mat-expansion-panel-header>
<div class="row card-spacer "> <div class="row card-spacer">
<div class="col-md-2" *ngFor="let r of movie.recommendations?.results">
<div class="sidebar affixable affix-top preview-poster">
<div class="poster">
<a [routerLink]="'/details/movie/' + r.id">
<ombi-image
class="real grow"
matTooltip="{{ r.title }}"
src="https://image.tmdb.org/t/p/w300/{{ r.poster_path }}"
alt="Poster"
style="display: block"
>
</ombi-image>
</a>
</div>
</div>
</div>
</div>
</mat-expansion-panel>
<mat-expansion-panel *ngIf="movie.similar?.results?.length > 0">
<mat-expansion-panel-header>
<mat-panel-title>
{{ 'MediaDetails.SimilarTitle' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
<div class="col-md-2" *ngFor="let r of movie.recommendations?.results"> <div class="row card-spacer">
<div class="sidebar affixable affix-top preview-poster"> <div class="col-md-2" *ngFor="let r of movie.similar.results">
<div class="poster"> <div class="sidebar affixable affix-top preview-poster">
<a [routerLink]="'/details/movie/'+r.id"> <div class="poster">
<ombi-image class="real grow" matTooltip="{{r.title}}" <a [routerLink]="'/details/movie/' + r.id">
src="https://image.tmdb.org/t/p/w300/{{r.poster_path}}" <ombi-image
alt="Poster" style="display: block;"> </ombi-image> class="real grow"
</a> matTooltip="{{ r.title }}"
</div> src="https://image.tmdb.org/t/p/w300/{{ r.poster_path }}"
</div> alt="Poster"
</div> style="display: block"
</div> ></ombi-image>
</mat-expansion-panel> </a>
<mat-expansion-panel *ngIf="movie.similar?.results?.length > 0"> </div>
<mat-expansion-panel-header> </div>
<mat-panel-title> </div>
{{'MediaDetails.SimilarTitle' | translate}} </div>
</mat-panel-title> </mat-expansion-panel>
</mat-expansion-panel-header> </mat-accordion>
} @placeholder {
<div class="row card-spacer"> <div class="loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner>
<div class="col-md-2" *ngFor="let r of movie.similar.results"> </div>
<div class="sidebar affixable affix-top preview-poster"> }
<div class="poster "> </div>
<a [routerLink]="'/details/movie/'+r.id"> </div>
<ombi-image class="real grow" matTooltip="{{r.title}}" </div>
src="https://image.tmdb.org/t/p/w300/{{r.poster_path}}" </div>
alt="Poster" style="display: block;"></ombi-image> </div>
</a> <div class="bottom-page-gap"></div>
</div> </section>
</div>
</div>
</div>
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</div>
</div>
</div>
<div class="bottom-page-gap">
</div>
</section>
</div> </div>

@ -1,336 +1,376 @@
import { Component, OnInit, ViewEncapsulation } from "@angular/core"; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from "../../../services"; import { ImageService, SearchV2Service, RequestService, MessageService, RadarrService, SettingsStateService } from '../../../services';
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from '@angular/router';
import { DomSanitizer } from "@angular/platform-browser"; import { DomSanitizer } from '@angular/platform-browser';
import { ICrewViewModel, ISearchMovieResultV2 } from "../../../interfaces/ISearchMovieResultV2"; import { ICrewViewModel, ISearchMovieResultV2 } from '../../../interfaces/ISearchMovieResultV2';
import { MatDialog } from "@angular/material/dialog"; import { MatDialog } from '@angular/material/dialog';
import { YoutubeTrailerComponent } from "../shared/youtube-trailer.component"; import { YoutubeTrailerComponent } from '../shared/youtube-trailer.component';
import { AuthService } from "../../../auth/auth.service"; import { AuthService } from '../../../auth/auth.service';
import { IMovieRequests, RequestType, IAdvancedData } from "../../../interfaces"; import { IMovieRequests, RequestType, IAdvancedData } from '../../../interfaces';
import { DenyDialogComponent } from "../shared/deny-dialog/deny-dialog.component"; import { DenyDialogComponent } from '../shared/deny-dialog/deny-dialog.component';
import { NewIssueComponent } from "../shared/new-issue/new-issue.component"; import { NewIssueComponent } from '../shared/new-issue/new-issue.component';
import { TranslateService } from "@ngx-translate/core"; import { TranslateService } from '@ngx-translate/core';
import { MovieAdvancedOptionsComponent } from "./panels/movie-advanced-options/movie-advanced-options.component"; import { MovieAdvancedOptionsComponent } from './panels/movie-advanced-options/movie-advanced-options.component';
import { RequestServiceV2 } from "../../../services/requestV2.service"; import { RequestServiceV2 } from '../../../services/requestV2.service';
import { firstValueFrom, forkJoin } from "rxjs"; import { firstValueFrom, forkJoin } from 'rxjs';
import { AdminRequestDialogComponent } from "../../../shared/admin-request-dialog/admin-request-dialog.component"; import { AdminRequestDialogComponent } from '../../../shared/admin-request-dialog/admin-request-dialog.component';
import { FeaturesFacade } from "../../../state/features/features.facade"; import { FeaturesFacade } from '../../../state/features/features.facade';
@Component({ @Component({
templateUrl: "./movie-details.component.html", templateUrl: './movie-details.component.html',
styleUrls: ["../../media-details.component.scss"], styleUrls: ['../../media-details.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None,
}) })
export class MovieDetailsComponent implements OnInit{ export class MovieDetailsComponent implements OnInit {
public movie: ISearchMovieResultV2; public movie: ISearchMovieResultV2;
public hasRequest: boolean; public hasRequest: boolean;
public movieRequest: IMovieRequests; public movieRequest: IMovieRequests;
public isAdmin: boolean; public isAdmin: boolean;
public advancedOptions: IAdvancedData; public advancedOptions: IAdvancedData;
public showAdvanced: boolean; // Set on the UI public showAdvanced: boolean; // Set on the UI
public issuesEnabled: boolean; public issuesEnabled: boolean;
public roleName4k = "Request4KMovie"; public roleName4k = 'Request4KMovie';
public is4KEnabled = false; public is4KEnabled = false;
public requestType = RequestType.movie; public requestType = RequestType.movie;
private theMovidDbId: number; private theMovidDbId: number;
private imdbId: string; private imdbId: string;
private snapMovieId: string; private snapMovieId: string;
constructor(
private searchService: SearchV2Service,
private route: ActivatedRoute,
private router: Router,
private sanitizer: DomSanitizer,
private imageService: ImageService,
public dialog: MatDialog,
private requestService: RequestService,
private requestService2: RequestServiceV2,
private radarrService: RadarrService,
public messageService: MessageService,
private auth: AuthService,
private settingsState: SettingsStateService,
private translate: TranslateService,
private featureFacade: FeaturesFacade,
) {
this.snapMovieId = this.route.snapshot.params.movieDbId;
this.route.params.subscribe(async (params: any) => {
if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) {
if (params.movieDbId.startsWith('tt')) {
this.imdbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (this.imdbId !== this.snapMovieId) {
this.reloadComponent();
}
}
}
this.theMovidDbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (params.movieDbId !== this.snapMovieId) {
this.reloadComponent();
}
});
}
constructor(private searchService: SearchV2Service, private route: ActivatedRoute, private router: Router, reloadComponent() {
private sanitizer: DomSanitizer, private imageService: ImageService, let currentUrl = this.router.url;
public dialog: MatDialog, private requestService: RequestService, this.router.routeReuseStrategy.shouldReuseRoute = () => false;
private requestService2: RequestServiceV2, private radarrService: RadarrService, this.router.onSameUrlNavigation = 'reload';
public messageService: MessageService, private auth: AuthService, private settingsState: SettingsStateService, this.router.navigate([currentUrl]);
private translate: TranslateService, private featureFacade: FeaturesFacade) { }
this.snapMovieId = this.route.snapshot.params.movieDbId;
this.route.params.subscribe(async (params: any) => {
if (typeof params.movieDbId === 'string' || params.movieDbId instanceof String) {
if (params.movieDbId.startsWith("tt")) {
this.imdbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (this.imdbId !== this.snapMovieId) {
this.reloadComponent()
}
}
}
this.theMovidDbId = params.movieDbId;
// Check if we user navigated to another movie and if so reload the component
if (params.movieDbId !== this.snapMovieId) {
this.reloadComponent()
}
});
}
reloadComponent() { async ngOnInit() {
let currentUrl = this.router.url; this.is4KEnabled = this.featureFacade.is4kEnabled();
this.router.routeReuseStrategy.shouldReuseRoute = () => false; this.issuesEnabled = this.settingsState.getIssue();
this.router.onSameUrlNavigation = 'reload'; this.isAdmin = this.auth.hasRole('admin') || this.auth.hasRole('poweruser');
this.router.navigate([currentUrl]);
}
async ngOnInit() { if (this.isAdmin) {
this.is4KEnabled = this.featureFacade.is4kEnabled(); this.showAdvanced = await firstValueFrom(this.radarrService.isRadarrEnabled());
this.issuesEnabled = this.settingsState.getIssue(); }
this.isAdmin = this.auth.hasRole("admin") || this.auth.hasRole("poweruser");
if (this.isAdmin) { if (this.imdbId) {
this.showAdvanced = await firstValueFrom(this.radarrService.isRadarrEnabled()); this.searchService.getMovieByImdbId(this.imdbId).subscribe(async (x) => {
} this.movie = x;
this.checkPoster();
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew);
if (this.movie.requestId > 0) {
// Load up this request
this.hasRequest = true;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
}
this.loadBanner();
});
} else {
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async (x) => {
this.movie = x;
this.checkPoster();
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew);
if (this.movie.requestId > 0) {
// Load up this request
this.hasRequest = true;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.loadAdvancedInfo();
}
this.loadBanner();
});
}
}
if (this.imdbId) { public async request(is4K: boolean, userId?: string) {
this.searchService.getMovieByImdbId(this.imdbId).subscribe(async x => { if (!this.is4KEnabled) {
this.movie = x; is4K = false;
this.checkPoster(); }
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew); if (this.isAdmin) {
if (this.movie.requestId > 0) { const dialog = this.dialog.open(AdminRequestDialogComponent, {
// Load up this request width: '700px',
this.hasRequest = true; data: { type: RequestType.movie, id: this.movie.id, is4K: is4K },
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId); panelClass: 'modal-panel',
} });
this.loadBanner(); dialog.afterClosed().subscribe(async (result) => {
}); if (result) {
} else { const requestResult = await firstValueFrom(
this.searchService.getFullMovieDetails(this.theMovidDbId).subscribe(async x => { this.requestService.requestMovie({
this.movie = x; theMovieDbId: this.theMovidDbId,
this.checkPoster(); languageCode: this.translate.currentLang,
this.movie.credits.crew = this.orderCrew(this.movie.credits.crew); qualityPathOverride: result.radarrPathId,
if (this.movie.requestId > 0) { requestOnBehalf: result.username?.id,
// Load up this request rootFolderOverride: result.radarrFolderId,
this.hasRequest = true; is4KRequest: is4K,
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId); }),
this.loadAdvancedInfo(); );
} if (requestResult.result) {
this.loadBanner(); if (is4K) {
}); this.movie.has4KRequest = true;
} } else {
} this.movie.requested = true;
}
this.movie.requestId = requestResult.requestId;
this.messageService.send(this.translate.instant('Requests.RequestAddedSuccessfully', { title: this.movie.title }), 'Ok');
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
} else {
this.messageService.sendRequestEngineResultError(requestResult);
}
}
});
} else {
const result = await firstValueFrom(
this.requestService.requestMovie({
theMovieDbId: this.theMovidDbId,
languageCode: this.translate.currentLang,
requestOnBehalf: userId,
qualityPathOverride: undefined,
rootFolderOverride: undefined,
is4KRequest: is4K,
}),
);
if (result.result) {
if (is4K) {
this.movie.has4KRequest = true;
} else {
this.movie.requested = true;
}
this.movie.requestId = result.requestId;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.messageService.send(this.translate.instant('Requests.RequestAddedSuccessfully', { title: this.movie.title }), 'Ok');
} else {
this.messageService.sendRequestEngineResultError(result);
}
}
}
public async request(is4K: boolean, userId?: string) { public openDialog() {
if (!this.is4KEnabled) { this.dialog.open(YoutubeTrailerComponent, {
is4K = false; width: '560px',
} data: this.movie.videos.results[0].key,
if (this.isAdmin) { });
const dialog = this.dialog.open(AdminRequestDialogComponent, { width: "700px", data: { type: RequestType.movie, id: this.movie.id, is4K: is4K }, panelClass: 'modal-panel' }); }
dialog.afterClosed().subscribe(async (result) => {
if (result) {
const requestResult = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId,
languageCode: this.translate.currentLang,
qualityPathOverride: result.radarrPathId,
requestOnBehalf: result.username?.id,
rootFolderOverride: result.radarrFolderId,
is4KRequest: is4K }));
if (requestResult.result) {
if (is4K) {
this.movie.has4KRequest = true;
} else {
this.movie.requested = true;
}
this.movie.requestId = requestResult.requestId;
this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.movie.title }), "Ok");
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
} else {
this.messageService.sendRequestEngineResultError(requestResult);
}
}
});
} else {
const result = await firstValueFrom(this.requestService.requestMovie({ theMovieDbId: this.theMovidDbId, languageCode: this.translate.currentLang, requestOnBehalf: userId, qualityPathOverride: undefined, rootFolderOverride: undefined, is4KRequest: is4K }));
if (result.result) {
if (is4K) {
this.movie.has4KRequest = true;
} else {
this.movie.requested = true;
}
this.movie.requestId = result.requestId;
this.movieRequest = await this.requestService.getMovieRequest(this.movie.requestId);
this.messageService.send(this.translate.instant("Requests.RequestAddedSuccessfully", { title: this.movie.title }), "Ok");
} else {
this.messageService.sendRequestEngineResultError(result);
}
}
}
public openDialog() { public async deny() {
this.dialog.open(YoutubeTrailerComponent, { const dialogRef = this.dialog.open(DenyDialogComponent, {
width: '560px', width: '250px',
data: this.movie.videos.results[0].key data: { requestId: this.movieRequest.id, requestType: RequestType.movie },
}); });
}
public async deny() { dialogRef.afterClosed().subscribe((result) => {
const dialogRef = this.dialog.open(DenyDialogComponent, { this.movieRequest.denied = result.denied;
width: '250px', this.movieRequest.deniedReason = result.reason;
data: { requestId: this.movieRequest.id, requestType: RequestType.movie } });
}); }
dialogRef.afterClosed().subscribe(result => { public async issue() {
this.movieRequest.denied = result.denied; let provider = this.movie.id.toString();
this.movieRequest.deniedReason = result.reason; if (this.movie.imdbId) {
}); provider = this.movie.imdbId;
} }
const dialogRef = this.dialog.open(NewIssueComponent, {
width: '500px',
data: {
requestId: this.movieRequest ? this.movieRequest.id : null,
requestType: RequestType.movie,
providerId: provider,
title: this.movie.title,
posterPath: this.movie.posterPath,
},
});
}
public async issue() { public async approve(is4K: boolean) {
let provider = this.movie.id.toString(); const result = await firstValueFrom(this.requestService.approveMovie({ id: this.movieRequest.id, is4K }));
if (this.movie.imdbId) { if (result.result) {
provider = this.movie.imdbId; if (is4K) {
} this.movie.approved4K = true;
const dialogRef = this.dialog.open(NewIssueComponent, { } else {
width: '500px', this.movie.approved = true;
data: { requestId: this.movieRequest ? this.movieRequest.id : null, requestType: RequestType.movie, providerId: provider, title: this.movie.title, posterPath: this.movie.posterPath } }
}); this.messageService.send(this.translate.instant('Requests.SuccessfullyApproved'), 'Ok');
} } else {
this.messageService.sendRequestEngineResultError(result);
}
}
public async approve(is4K: boolean) { public async markAvailable(is4K: boolean) {
const result = await firstValueFrom(this.requestService.approveMovie({ id: this.movieRequest.id, is4K })); const result = await firstValueFrom(this.requestService.markMovieAvailable({ id: this.movieRequest.id, is4K }));
if (result.result) { if (result.result) {
if (is4K) { if (is4K) {
this.movie.approved4K = true; this.movie.available4K = true;
} else { } else {
this.movie.approved = true; this.movie.available = true;
} }
this.messageService.send(this.translate.instant("Requests.SuccessfullyApproved"), "Ok"); this.messageService.send(this.translate.instant('Requests.NowAvailable'), 'Ok');
} else { } else {
this.messageService.sendRequestEngineResultError(result); this.messageService.sendRequestEngineResultError(result);
} }
} }
public async markAvailable(is4K: boolean) { public async markUnavailable(is4K: boolean) {
const result = await firstValueFrom(this.requestService.markMovieAvailable({ id: this.movieRequest.id, is4K })) const result = await firstValueFrom(this.requestService.markMovieUnavailable({ id: this.movieRequest.id, is4K }));
if (result.result) { if (result.result) {
if (is4K) { if (is4K) {
this.movie.available4K = true; this.movie.available4K = false;
} else { } else {
this.movie.available = true; this.movie.available = false;
} }
this.messageService.send(this.translate.instant("Requests.NowAvailable"), "Ok"); this.messageService.send(this.translate.instant('Requests.NowUnavailable'), 'Ok');
} else { } else {
this.messageService.sendRequestEngineResultError(result); this.messageService.sendRequestEngineResultError(result);
} }
} }
public setAdvancedOptions(data: IAdvancedData) {
this.advancedOptions = data;
if (data.rootFolderId) {
this.movieRequest.qualityOverrideTitle = data.profiles.filter((x) => x.id == data.profileId)[0].name;
}
if (data.profileId) {
this.movieRequest.rootPathOverrideTitle = data.rootFolders.filter((x) => x.id == data.rootFolderId)[0].path;
}
}
public async markUnavailable(is4K: boolean) { public async openAdvancedOptions() {
const result = await firstValueFrom(this.requestService.markMovieUnavailable({ id: this.movieRequest.id, is4K })); const dialog = this.dialog.open(MovieAdvancedOptionsComponent, {
if (result.result) { width: '700px',
if (is4K) { data: <IAdvancedData>{ movieRequest: this.movieRequest },
this.movie.available4K = false; panelClass: 'modal-panel',
} else { });
this.movie.available = false; await dialog.afterClosed().subscribe(async (result) => {
} if (result) {
this.messageService.send(this.translate.instant("Requests.NowUnavailable"), "Ok"); result.rootFolder = result.rootFolders.filter((f) => f.id === +result.rootFolderId)[0];
} else { result.profile = result.profiles.filter((f) => f.id === +result.profileId)[0];
this.messageService.sendRequestEngineResultError(result); await this.requestService2
} .updateMovieAdvancedOptions({
} qualityOverride: result.profileId,
rootPathOverride: result.rootFolderId,
languageProfile: 0,
requestId: this.movieRequest.id,
})
.toPromise();
this.setAdvancedOptions(result);
}
});
}
public setAdvancedOptions(data: IAdvancedData) { public reProcessRequest(is4K: boolean) {
this.advancedOptions = data; this.requestService2.reprocessRequest(this.movieRequest.id, RequestType.movie, is4K).subscribe((result) => {
if (data.rootFolderId) { if (result.result) {
this.movieRequest.qualityOverrideTitle = data.profiles.filter(x => x.id == data.profileId)[0].name; this.messageService.send(result.message ? result.message : this.translate.instant('Requests.SuccessfullyReprocessed'), 'Ok');
} } else {
if (data.profileId) { this.messageService.sendRequestEngineResultError(result);
this.movieRequest.rootPathOverrideTitle = data.rootFolders.filter(x => x.id == data.rootFolderId)[0].path; }
} });
} }
public async openAdvancedOptions() { public notify() {
const dialog = this.dialog.open(MovieAdvancedOptionsComponent, { width: "700px", data: <IAdvancedData>{ movieRequest: this.movieRequest }, panelClass: 'modal-panel' }) this.requestService.subscribeToMovie(this.movieRequest.id).subscribe((result) => {
await dialog.afterClosed().subscribe(async result => { if (result) {
if (result) { this.movie.subscribed = true;
result.rootFolder = result.rootFolders.filter(f => f.id === +result.rootFolderId)[0]; this.messageService.send(this.translate.instant('Requests.SuccessfulNotify', { title: this.movie.title }), 'Ok');
result.profile = result.profiles.filter(f => f.id === +result.profileId)[0]; } else {
await this.requestService2.updateMovieAdvancedOptions({ qualityOverride: result.profileId, rootPathOverride: result.rootFolderId, languageProfile: 0, requestId: this.movieRequest.id }).toPromise(); this.messageService.send(this.translate.instant('Requests.CouldntNotify', { title: this.movie.title }), 'Ok');
this.setAdvancedOptions(result); }
} });
}); }
}
public reProcessRequest(is4K: boolean) { public unNotify() {
this.requestService2.reprocessRequest(this.movieRequest.id, RequestType.movie, is4K).subscribe(result => { this.requestService.unSubscribeToMovie(this.movieRequest.id).subscribe((result) => {
if (result.result) { if (result) {
this.messageService.send(result.message ? result.message : this.translate.instant("Requests.SuccessfullyReprocessed"), "Ok"); this.movie.subscribed = false;
} else { this.messageService.send(this.translate.instant('Requests.SuccessfulUnNotify', { title: this.movie.title }), 'Ok');
this.messageService.sendRequestEngineResultError(result); } else {
} this.messageService.send(this.translate.instant('Requests.CouldntNotify', { title: this.movie.title }), 'Ok');
}); }
} });
}
public notify() { private loadBanner() {
this.requestService.subscribeToMovie(this.movieRequest.id).subscribe(result => { this.imageService.getMovieBanner(this.theMovidDbId.toString()).subscribe((x) => {
if (result) { if (!this.movie.backdropPath) {
this.movie.subscribed = true; this.movie.background = this.sanitizer.bypassSecurityTrustStyle('url(' + x + ')');
this.messageService.send(this.translate.instant("Requests.SuccessfulNotify", {title: this.movie.title}), "Ok"); } else {
} else { this.movie.background = this.sanitizer.bypassSecurityTrustStyle(
this.messageService.send(this.translate.instant("Requests.CouldntNotify", {title: this.movie.title}), "Ok"); 'url(https://image.tmdb.org/t/p/original/' + this.movie.backdropPath + ')',
} );
}); }
} });
}
private checkPoster() {
if (this.movie.posterPath == null) {
this.movie.posterPath = '../../../images/default_movie_poster.png';
} else {
this.movie.posterPath = 'https://image.tmdb.org/t/p/w300/' + this.movie.posterPath;
}
}
private loadAdvancedInfo() {
const profile = this.radarrService.getQualityProfilesFromSettings();
const folders = this.radarrService.getRootFoldersFromSettings();
public unNotify() { forkJoin([profile, folders]).subscribe((x) => {
this.requestService.unSubscribeToMovie(this.movieRequest.id).subscribe(result => { const radarrProfiles = x[0] ?? [];
if (result) { const radarrRootFolders = x[1] ?? [];
this.movie.subscribed = false;
this.messageService.send(this.translate.instant("Requests.SuccessfulUnNotify", {title: this.movie.title}), "Ok");
} else {
this.messageService.send(this.translate.instant("Requests.CouldntNotify", {title: this.movie.title}), "Ok");
}
});
}
private loadBanner() { const profile = radarrProfiles.filter((p) => {
this.imageService.getMovieBanner(this.theMovidDbId.toString()).subscribe(x => { return p.id === this.movieRequest.qualityOverride;
if (!this.movie.backdropPath) { });
this.movie.background = this.sanitizer.bypassSecurityTrustStyle if (profile.length > 0) {
("url(" + x + ")"); this.movieRequest.qualityOverrideTitle = profile[0].name;
} else { }
this.movie.background = this.sanitizer.bypassSecurityTrustStyle
("url(https://image.tmdb.org/t/p/original/" + this.movie.backdropPath + ")");
}
});
}
private checkPoster() {
if (this.movie.posterPath == null) {
this.movie.posterPath = "../../../images/default_movie_poster.png";
}
else {
this.movie.posterPath = "https://image.tmdb.org/t/p/w300/" + this.movie.posterPath
};
}
private loadAdvancedInfo() {
const profile = this.radarrService.getQualityProfilesFromSettings();
const folders = this.radarrService.getRootFoldersFromSettings();
forkJoin([profile, folders]).subscribe(x => { const path = radarrRootFolders.filter((folder) => {
const radarrProfiles = x[0]; return folder.id === this.movieRequest.rootPathOverride;
const radarrRootFolders = x[1]; });
if (path.length > 0) {
this.movieRequest.rootPathOverrideTitle = path[0].path;
}
});
}
const profile = radarrProfiles.filter((p) => { private orderCrew(crew: ICrewViewModel[]): ICrewViewModel[] {
return p.id === this.movieRequest.qualityOverride; return crew.sort((a, b) => {
}); if (a.job === 'Director') {
if (profile.length > 0) { return -1;
this.movieRequest.qualityOverrideTitle = profile[0].name; } else if (b.job === 'Director') {
} return 1;
} else {
const path = radarrRootFolders.filter((folder) => { return 0;
return folder.id === this.movieRequest.rootPathOverride; }
}); });
if (path.length > 0) { }
this.movieRequest.rootPathOverrideTitle = path[0].path;
}
});
}
private orderCrew(crew: ICrewViewModel[]): ICrewViewModel[] {
return crew.sort((a, b) => {
if (a.job === "Director") {
return -1;
} else if (b.job === "Director") {
return 1;
} else {
return 0;
}
});
}
} }

@ -1,171 +1,240 @@
<div *ngIf="!tv" class="justify-content-md-center top-spacing loading-spinner"> <div *ngIf="!tv" class="justify-content-md-center top-spacing loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner> <mat-spinner [color]="'accent'"></mat-spinner>
</div> </div>
<div *ngIf="tv" class="main-content-container"> <div *ngIf="tv" class="main-content-container">
<div *ngIf="tv.id === 0; else main"> <div *ngIf="tv.id === 0; else main">
<div class="small-middle-container no-info"> <div class="small-middle-container no-info">
<h1><i class="far fa-frown-o" aria-hidden="true"></i></h1> <h1><i class="far fa-frown-o" aria-hidden="true"></i></h1>
<h3> {{ 'MediaDetails.NotEnoughInfo' | translate }}</h3> <h3>{{ 'MediaDetails.NotEnoughInfo' | translate }}</h3>
</div> </div>
</div> </div>
<ng-template #main>
<ng-template #main> <div>
<top-banner
<div> [background]="tv.background"
[available]="tv.available"
<top-banner [background]="tv.background" [available]="tv.available" [title]="tv.title" [title]="tv.title"
[releaseDate]="tv.firstAired" [tagline]="tv.tagline"></top-banner> [releaseDate]="tv.firstAired"
<div class="social-icons-container"> [tagline]="tv.tagline"
<social-icons ></top-banner>
[homepage]="tv.homepage" <div class="social-icons-container">
[theMoviedbId]="tv.id" <social-icons
[hasTrailer]="tv.trailer" [homepage]="tv.homepage"
[twitter]="tv.externalIds?.twitterId" [theMoviedbId]="tv.id"
[facebook]="tv.externalIds?.facebookId" [hasTrailer]="tv.trailer"
[instagram]="tv.externalIds?.instagramId" [twitter]="tv.externalIds?.twitterId"
(openTrailer)="openDialog()" [facebook]="tv.externalIds?.facebookId"
[imdbId]="tv.imdbId" [instagram]="tv.externalIds?.instagramId"
[isAdmin]="isAdmin" (openTrailer)="openDialog()"
[canShowAdvanced]="showAdvanced && showRequest" [imdbId]="tv.imdbId"
[type]="requestType" [isAdmin]="isAdmin"
(onAdvancedOptions)="openAdvancedOptions()" [canShowAdvanced]="showAdvanced && showRequest"
> [type]="requestType"
</social-icons> (onAdvancedOptions)="openAdvancedOptions()"
</div> >
</social-icons>
<section id="info-wrapper"> </div>
<div class="small-middle-container">
<div class="row justify-content-center justify-content-sm-start header-container"> <section id="info-wrapper">
<div class="details-poster-container"> <div class="small-middle-container">
<media-poster [posterPath]=tv.images.original></media-poster> <div class="row justify-content-center justify-content-sm-start header-container">
</div> <div class="details-poster-container">
<!--Next to poster--> <media-poster [posterPath]="tv.images.original"></media-poster>
<div class="details-button-container"> </div>
<div class="col-12 media-row"> <!--Next to poster-->
<ng-container *ngIf="tv.fullyAvailable || tv.partlyAvailable"> <div class="details-button-container">
<a id="viewOnPlexButton" *ngIf="tv.plexUrl" href="{{tv.plexUrl}}" mat-raised-button target="_blank" class="btn-spacing viewon-btn plex"> <div class="col-12 media-row">
{{'Search.ViewOnPlex' | translate}} <ng-container *ngIf="tv.fullyAvailable || tv.partlyAvailable">
<i class="far fa-play-circle fa-2x"></i> <a
</a> id="viewOnPlexButton"
<a id="viewOnEmbyButton" *ngIf="tv.embyUrl" href="{{tv.embyUrl}}" mat-raised-button target="_blank" class="btn-spacing viewon-btn emby"> *ngIf="tv.plexUrl"
{{'Search.ViewOnEmby' | translate}} href="{{ tv.plexUrl }}"
<i class="far fa-play-circle fa-2x"></i> mat-raised-button
</a> target="_blank"
<a id="viewOnJellyfinButton" *ngIf="tv.jellyfinUrl" href="{{tv.jellyfinUrl}}" mat-raised-button target="_blank" class="btn-spacing viewon-btn jellyfin"> class="btn-spacing viewon-btn plex"
{{'Search.ViewOnJellyfin' | translate}} >
<i class="far fa-play-circle fa-2x"></i> {{ 'Search.ViewOnPlex' | translate }}
</a> <i class="far fa-play-circle fa-2x"></i>
</ng-container> </a>
<button *ngIf="(!tv.fullyAvailable || (tv.fullyAvailable && tv.partlyAvailable)) && !allEpisodesRequestedOrAvailable()" mat-raised-button id="requestBtn" class="btn-spacing" color="primary" <a
(click)="request()"><i class="fas fa-plus"></i> id="viewOnEmbyButton"
{{ 'Common.Request' | translate }}</button> *ngIf="tv.embyUrl"
href="{{ tv.embyUrl }}"
<button *ngIf="!tv.denied && allEpisodesRequestedOrAvailable()" mat-raised-button class="btn-spacing" color="warn" [disabled]> mat-raised-button
<i class="fas fa-check"></i> target="_blank"
{{ 'Common.Requested' | translate }}</button> class="btn-spacing viewon-btn emby"
>
<button *ngIf="tv.fullyAvailable && !tv.partlyAvailable" id="availableBtn" mat-raised-button class="btn-spacing" color="accent" {{ 'Search.ViewOnEmby' | translate }}
[disabled]> <i class="far fa-play-circle fa-2x"></i>
<i class="fas fa-check"></i> {{'Common.Available' | translate }}</button> </a>
<a
<button *ngIf="tv.partlyAvailable && !tv.fullyAvailable" id="partiallyAvailableBtn" mat-raised-button id="viewOnJellyfinButton"
class="btn-spacing" color="accent" [disabled]> *ngIf="tv.jellyfinUrl"
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button> href="{{ tv.jellyfinUrl }}"
mat-raised-button
<!-- There are unaired episodes--> target="_blank"
<button *ngIf="tv.partlyAvailable && tv.fullyAvailable" id="partiallyAvailableBtn" mat-raised-button class="btn-spacing viewon-btn jellyfin"
class="btn-spacing" color="accent" [disabled]> >
<i class="fas fa-check"></i> {{'Common.PartiallyAvailable' | translate }}</button> {{ 'Search.ViewOnJellyfin' | translate }}
<!-- end unaired episodes--> <i class="far fa-play-circle fa-2x"></i>
</a>
<button id="deniedButton" *ngIf="tv.denied" [matTooltip]="tv.deniedReason" mat-raised-button class="btn-spacing" color="warn"> </ng-container>
<i class="fas fa-times"></i> {{'Common.Denied' | translate }} <button
</button> *ngIf="(!tv.fullyAvailable || (tv.fullyAvailable && tv.partlyAvailable)) && !allEpisodesRequestedOrAvailable()"
mat-raised-button
<button mat-raised-button class="btn-spacing" color="danger" id="reportIssueBtn" *ngIf="issuesEnabled" (click)="issue()"> id="requestBtn"
<i class="fas fa-exclamation"></i> {{ class="btn-spacing"
'Requests.ReportIssue' | translate }}</button> color="primary"
(click)="request()"
</div> >
</div> <i class="fas fa-plus"></i> {{ 'Common.Request' | translate }}
</div> </button>
<div class="row"> <button
<div class="col-12 col-md-2"> *ngIf="!tv.denied && allEpisodesRequestedOrAvailable()"
<mat-card class="mat-elevation-z8 spacing-below"> mat-raised-button
<mat-card-content> class="btn-spacing"
<tv-information-panel [tv]="tv" [request]="showRequest" color="warn"
[advancedOptions]="showAdvanced"></tv-information-panel> [disabled]
</mat-card-content> >
</mat-card> <i class="fas fa-check"></i>
</div> {{ 'Common.Requested' | translate }}
<div class="col-12 col-md-10"> </button>
<div class="row">
<div class="col-12"> <button
<mat-card class="mat-elevation-z8 spacing-below"> *ngIf="tv.fullyAvailable && !tv.partlyAvailable"
<mat-card-content> id="availableBtn"
{{tv.overview}} mat-raised-button
</mat-card-content> class="btn-spacing"
</mat-card> color="accent"
</div> [disabled]
<div class="col-12"> >
<cast-carousel [cast]="tv.cast"></cast-carousel> <i class="fas fa-check"></i> {{ 'Common.Available' | translate }}
</div> </button>
</div>
</div> <button
</div> *ngIf="tv.partlyAvailable && !tv.fullyAvailable"
id="partiallyAvailableBtn"
mat-raised-button
<div class="row"> class="btn-spacing"
<div class="col-12 col-md-2"> color="accent"
[disabled]
<!--Just some space yo--> >
<i class="fas fa-check"></i> {{ 'Common.PartiallyAvailable' | translate }}
</div> </button>
<div class="col-12 col-md-10"> <!-- There are unaired episodes-->
<tv-request-grid id="requests-grid" [tvRequest]="tvRequest" [isAdmin]="isAdmin" [tv]="tv"></tv-request-grid> <button
</div> *ngIf="tv.partlyAvailable && tv.fullyAvailable"
id="partiallyAvailableBtn"
<div class="col-12 col-md-2"> mat-raised-button
class="btn-spacing"
<!--Just some space yo--> color="accent"
[disabled]
</div> >
<div class="col-12 col-md-10"> <i class="fas fa-check"></i> {{ 'Common.PartiallyAvailable' | translate }}
<div class="issuesPanel"> </button>
<issues-panel [providerId]="tv.id" [isAdmin]="isAdmin"></issues-panel> <!-- end unaired episodes-->
</div>
<mat-accordion id="requests-panel"> <button
<mat-expansion-panel> id="deniedButton"
<mat-expansion-panel-header> *ngIf="tv.denied"
<mat-panel-title> [matTooltip]="tv.deniedReason"
{{'Requests.Title' | translate}} mat-raised-button
</mat-panel-title> class="btn-spacing"
</mat-expansion-panel-header> color="warn"
<tv-requests-panel [tvRequest]="tvRequest" [isAdmin]="isAdmin" [manageOwnRequests]="manageOwnRequests"></tv-requests-panel> >
</mat-expansion-panel> <i class="fas fa-times"></i> {{ 'Common.Denied' | translate }}
</button>
</mat-accordion>
<button
</div> mat-raised-button
class="btn-spacing"
color="danger"
</div> id="reportIssueBtn"
*ngIf="issuesEnabled"
(click)="issue()"
>
</div> <i class="fas fa-exclamation"></i> {{ 'Requests.ReportIssue' | translate }}
</button>
</div>
</div>
</div>
<div class="bottom-page-gap"> <div class="row">
</div> <div class="col-12 col-md-2">
</section> <mat-card class="mat-elevation-z8 spacing-below">
<mat-card-content>
</div> <tv-information-panel [tv]="tv" [request]="showRequest" [advancedOptions]="showAdvanced"></tv-information-panel>
</ng-template> </mat-card-content>
</mat-card>
</div>
<div class="col-12 col-md-10">
<div class="row">
<div class="col-12">
<mat-card class="mat-elevation-z8 spacing-below">
<mat-card-content>
{{ tv.overview }}
</mat-card-content>
</mat-card>
</div>
<div class="col-12">
<cast-carousel [cast]="tv.cast"></cast-carousel>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-md-2">
<!--Just some space yo-->
</div>
<div class="col-12 col-md-10">
@defer {
<tv-request-grid id="requests-grid" [tvRequest]="tvRequest" [isAdmin]="isAdmin" [tv]="tv"></tv-request-grid>
} @placeholder {
<div class="loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner>
</div>
}
</div>
<div class="col-12 col-md-2">
<!--Just some space yo-->
</div>
<div class="col-12 col-md-10">
<div class="issuesPanel">
<issues-panel [providerId]="tv.id" [isAdmin]="isAdmin"></issues-panel>
</div>
<mat-accordion id="requests-panel">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
{{ 'Requests.Title' | translate }}
</mat-panel-title>
</mat-expansion-panel-header>
@defer (on viewport) {
<tv-requests-panel
[tvRequest]="tvRequest"
[isAdmin]="isAdmin"
[manageOwnRequests]="manageOwnRequests"
></tv-requests-panel>
} @placeholder {
<div class="loading-spinner">
<mat-spinner [color]="'accent'"></mat-spinner>
</div>
}
</mat-expansion-panel>
</mat-accordion>
</div>
</div>
</div>
<div class="bottom-page-gap"></div>
</section>
</div>
</ng-template>
</div> </div>

@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Ombi.Api.Radarr; using Ombi.Api.Radarr;
using Ombi.Api.Radarr.Models; using Ombi.Api.Radarr.Models;
using Ombi.Api.Radarr.Models.V3;
using Ombi.Attributes; using Ombi.Attributes;
using Ombi.Core.Settings; using Ombi.Core.Settings;
using Ombi.Helpers; using Ombi.Helpers;
@ -77,7 +78,7 @@ namespace Ombi.Controllers.V1.External
{ {
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri)); return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
} }
return null; return Ok(new List<RadarrV3QualityProfile>());
} }
/// <summary> /// <summary>
@ -94,7 +95,7 @@ namespace Ombi.Controllers.V1.External
{ {
return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri)); return Ok(await _radarrV3Api.GetProfiles(settings.ApiKey, settings.FullUri));
} }
return null; return Ok(new List<RadarrV3QualityProfile>());
} }
/// <summary> /// <summary>

Loading…
Cancel
Save